# OpenClaw Twitter Curator: Technical Deep Dive

## Executive summary

The Twitter Curator became reliable without changing the selected model or abandoning the ChatGPT/Codex subscription.

The material change was the **agent runtime**:

- **Before:** `openai/gpt-5.6-terra` executed through the bundled Codex app-server harness (`agentRuntime.id: "codex"`).
- **Now:** the same model executes through OpenClaw’s embedded agent loop (`agentRuntime.id: "openclaw"`).
- **Authentication stayed subscription-backed:** the dedicated agent reads the existing `openai` OAuth profile through the default agent’s credential store.
- **LiteLLM and OpenRouter are not on the working request path.**

The failures were therefore not evidence that the subscription, model, Discord integration, or curator scripts were broken. The strongest evidence isolates the failure to the extra app-server execution/completion boundary used by the old route.

> **Epistemic status:** the failing boundary is strongly isolated by controlled comparison, but the precise upstream defect inside the app-server integration was not independently proven.

---

## 1. Keep the layers separate

Several independent choices were previously being treated as though they were one setting:

| Layer | Selected value | Responsibility |
|---|---|---|
| Provider | `openai` | Model catalog, auth selection, and request transport |
| Model | `gpt-5.6-terra` | Reasoning and text generation |
| Authentication | OpenAI OAuth profile | ChatGPT/Codex subscription entitlement |
| Agent runtime | `codex` or `openclaw` | Owns the agent turn and tool lifecycle |
| Scheduler | OpenClaw cron | Starts isolated scheduled runs |
| Delivery | Discord message tool | Publishes the briefing |

The key lesson is:

> **OAuth answers “who pays and which entitlement is used?” Runtime selection answers “which software owns the agent loop?”**

Using a Codex OAuth profile does **not** require the Codex app-server harness. OpenClaw 2026.7.1 can use its embedded runtime while routing an `openai/*` model through its internal Codex-auth transport.

---

## 2. The old execution path

The shared default agent selected `openai/gpt-5.6-terra`, whose effective runtime policy was `codex`.

```mermaid
flowchart LR
    C[Cron scheduler] --> O[OpenClaw cron runner]
    O --> H[Codex app-server harness]
    H --> A[OpenAI OAuth]
    A --> M[GPT-5.6 Terra]
    M --> H
    H --> R[Streamed event and completion reconciliation]
    R --> O
    O --> T[Exec / process / message tools]
    T --> D[Discord]

    classDef risk fill:#4a1f2a,stroke:#ff6b78,color:#fff;
    class H,R risk;
```

### What the app-server boundary added

OpenClaw prepared the cron turn, but a separate harness owned the Codex turn lifecycle. The integration then had to:

1. start or attach to an app-server session;
2. forward the prepared prompt and runtime context;
3. consume streamed events;
4. coordinate tool execution and continuation;
5. detect the final assistant result;
6. convert that result into the shape expected by the cron runner;
7. report completion, delivery, and heartbeat status.

This architecture can work for ordinary turns. The Twitter Curator was a more demanding case:

- large injected workspace context;
- editorial-policy loading;
- Python execution;
- possible process polling;
- structured JSON parsing;
- ranking up to ten items;
- Discord message-tool delivery;
- acknowledgement command;
- a run duration commonly exceeding one minute.

Each extra tool continuation increases the importance of correct session and completion-state reconciliation.

---

## 3. Observed symptoms

The recurring errors were variants of:

- `no JSON output`
- `no JSON output returned`
- `check-heartbeat returned no JSON`
- `check produced no JSON output`
- `empty result from heartbeat check`

These messages describe what the **cron runner received**, not necessarily what the model or curator script had done internally.

### Why this distinction matters

Some failed runs had already produced useful work or reached later workflow stages. That pattern is inconsistent with a simple authentication rejection at request start.

```mermaid
sequenceDiagram
    participant Cron as Cron runner
    participant Harness as Codex app-server harness
    participant Model as GPT-5.6 Terra
    participant Tools as Curator tools

    Cron->>Harness: Start prepared agent turn
    Harness->>Model: Prompt + context
    Model->>Tools: Read policy / run heartbeat check
    Tools-->>Model: Data or tool result
    Model->>Tools: Optional Discord send / acknowledgement
    Tools-->>Model: Success
    Note over Harness,Cron: Fragile final-event reconciliation boundary
    Harness--xCron: Missing or unusable final result
    Cron->>Cron: Report “no JSON output” / empty result
```

### Components ruled out by evidence

| Suspected component | Evidence against it being the primary cause |
|---|---|
| OpenAI OAuth | The OAuth profile was valid and later completed both a smoke test and the real cron run. |
| GPT-5.6 Terra | The same model succeeded after only the runtime changed. |
| Twitter curator scripts | The scripts returned usable batches through the working OpenRouter route and the new OAuth route. |
| Discord | The message tool successfully published the final briefing. |
| Cron scheduling | The job consistently started at the expected times. |
| LiteLLM | It was neither required nor used by the final working request path. |

---

## 4. Why the OpenRouter workaround worked

The temporary route was:

```text
litellm/openai/gpt-5.6-terra
```

That route behaved like a conventional OpenAI-compatible model provider. OpenClaw owned the embedded tool loop and received normal provider responses through LiteLLM. It avoided the Codex app-server harness, so it also avoided the fragile completion boundary.

```mermaid
flowchart LR
    C[Cron] --> O[OpenClaw embedded loop]
    O --> L[LiteLLM]
    L --> OR[OpenRouter]
    OR --> M[GPT-5.6 Terra]
    M --> O
    O --> D[Discord + acknowledgement]
```

This proved that the curator workflow itself was viable, but it introduced usage-based OpenRouter billing and quickly depleted credits.

The workaround was diagnostically valuable because it changed the runtime/provider transport while keeping the high-level task nearly identical.

---

## 5. The final architecture

A dedicated curator agent now overrides only the runtime for the exact OpenAI model.

```mermaid
flowchart LR
    C[Cron scheduler] --> TA[Dedicated curator agent]
    TA --> E[OpenClaw embedded runtime]
    E --> OA[Internal Codex-auth transport]
    OA --> P[OpenAI OAuth profile]
    P --> M[GPT-5.6 Terra]
    M --> E
    E --> X[Exec and process tools]
    E --> MSG[Discord message tool]
    MSG --> ACK[Tweet acknowledgement]
    E --> RES[Final cron result]

    B[Codex app-server harness]:::bypassed
    E -. bypasses .-> B

    classDef good fill:#13372c,stroke:#55d68b,color:#fff;
    classDef bypassed fill:#202733,stroke:#667085,color:#9aa4b2,stroke-dasharray:5 5;
    class E,OA,MSG,RES good;
```

### Effective per-agent configuration

```json
{
  "agents": {
    "list": [
      {
        "id": "default-agent",
        "default": true
      },
      {
        "id": "curator-agent",
        "workspace": "/path/to/shared-workspace",
        "agentDir": "/path/to/agent-state/curator",
        "model": {
          "primary": "openai/gpt-5.6-terra",
          "fallbacks": []
        },
        "thinkingDefault": "medium",
        "models": {
          "openai/gpt-5.6-terra": {
            "agentRuntime": {
              "id": "openclaw"
            }
          }
        }
      }
    ]
  }
}
```

The cron job is pinned separately:

```text
agentId: curator-agent
model: openai/gpt-5.6-terra
thinking: medium
fallbacks: []
```

---

## 6. Why the per-agent override wins

OpenClaw resolves runtime policy after resolving the provider and model.

For this deployment, the important precedence is:

1. exact entry in `agents.list[].models["provider/model"].agentRuntime`;
2. exact entry in `agents.defaults.models["provider/model"].agentRuntime`;
3. exact provider model definition;
4. provider wildcard policy;
5. provider-wide policy;
6. automatic runtime claim or OpenClaw compatibility fallback.

```mermaid
flowchart TD
    A[Resolve provider + model] --> B{Per-agent exact runtime?}
    B -- Yes --> C[Use curator-agent policy: openclaw]
    B -- No --> D{Default exact runtime?}
    D -- Yes --> E[Use global policy: codex]
    D -- No --> F{Provider/model policy?}
    F -- Yes --> G[Use configured provider policy]
    F -- No --> H[Auto claim or OpenClaw fallback]
```

Therefore:

- `curator-agent` resolves Terra to `openclaw`;
- `default-agent` still resolves Terra to `codex`;
- the HN job, heartbeat, primary Discord agent, and subagents remain unchanged.

This is safer than changing the provider or global model policy.

---

## 7. OAuth inheritance

Agents have separate state directories. The dedicated agent uses:

```text
/path/to/agent-state/curator
```

The OAuth refresh material remains in the real default-agent store. Because that agent remains the configured default, OpenClaw supports **read-through OAuth inheritance**:

1. the secondary agent requests an `openai` auth profile;
2. its own store does not need a copied refresh token;
3. OpenClaw reads the matching profile from the default-agent store;
4. the freshest usable access token is adopted for the request;
5. refresh material is not duplicated across agent stores.

The verification output showed:

```text
openai effective=profiles:/path/to/default-agent/auth-store
profiles=1 (oauth=1)
```

This is why it was important to keep an explicit default-agent entry with `default: true`. If the new agent had accidentally become the default, the expected OAuth read-through behavior could have changed.

---

## 8. Successful run sequence

```mermaid
sequenceDiagram
    participant Scheduler as OpenClaw cron
    participant Agent as curator-agent
    participant Runtime as OpenClaw embedded runtime
    participant OAuth as OpenAI OAuth profile
    participant Terra as GPT-5.6 Terra
    participant Tools as Exec / process
    participant Discord as Discord message tool

    Scheduler->>Agent: Start isolated cron run
    Agent->>Runtime: Resolve openai/gpt-5.6-terra
    Runtime->>Runtime: Per-agent policy selects openclaw
    Runtime->>OAuth: Resolve subscription credential
    OAuth-->>Runtime: Valid auth-profile token
    Runtime->>Terra: Prompt + workspace context
    Terra->>Tools: Read GOAL.md and run check-heartbeat
    Tools-->>Terra: Candidate tweet batch
    Terra->>Terra: Filter, rank and compose briefing
    Terra->>Discord: Send one complete message
    Discord-->>Terra: Delivery succeeded
    Terra->>Tools: Acknowledge delivered tweet IDs
    Tools-->>Terra: Acknowledgement succeeded
    Terra-->>Runtime: HEARTBEAT_OK
    Runtime-->>Scheduler: status=ok, delivered=true
```

The same component now owns both the model continuation and the interpretation of tool results. There is no second harness required to reconstruct a final completion from an external streamed event lifecycle.

---

## 9. Evidence from the verified run

### Smoke test

| Field | Observed value | Meaning |
|---|---|---|
| Response | `OAUTH_RUNTIME_OK` | Model completed the requested turn |
| Provider | `openai` | Not routed through LiteLLM |
| Model | `gpt-5.6-terra` | Intended subscription model |
| Harness | `openclaw` | Embedded runtime selected |
| Runner | `embedded` | No CLI/app-server execution backend |
| Auth mode | `auth-profile` | OAuth profile used |
| Fallback | `false` | No paid fallback |

### Real curator run

| Field | Observed value |
|---|---|
| Status | `ok` |
| Summary | `HEARTBEAT_OK` |
| Provider | `openai` |
| Model | `gpt-5.6-terra` |
| Agent | `curator-agent` |
| Runtime | `openclaw` |
| Delivery | `delivered: true` |
| Fallback used | `false` |
| Session key prefix | `agent:<curator-agent>:cron:` |

The Discord screenshot showed one complete briefing with four selected tweets. No acknowledgement-failure warning appeared, which is consistent with a successful acknowledgement path.

---

## 10. Why the final design is safer

### Failure isolation

The Twitter job can change runtime policy without affecting:

- interactive Discord conversations;
- the HN curator;
- the six-hour heartbeat;
- ordinary subagents;
- other OpenAI models.

### Cost isolation

`fallbacks: []` is explicit. If subscription auth or the embedded route fails, the job fails visibly instead of silently consuming OpenRouter credits.

### State isolation

The dedicated agent has its own session and agent state while sharing the established workspace:

```text
Shared:
  /path/to/shared-workspace
  ├── AGENTS.md
  ├── Twitter curator skill
  ├── GOAL.md
  └── curator scripts/data

Separate:
  /path/to/agent-state/curator/
  ├── agent state
  └── sessions
```

This preserves the workflow’s inputs without mixing its run history with the default agent.

---

## 11. A reusable diagnostic method

When a scheduled agent fails, inspect each layer independently.

```mermaid
flowchart TD
    S[Scheduled job failed] --> A{Did authentication start?}
    A -- No --> A1[Inspect auth profile, expiry and entitlement]
    A -- Yes --> M{Did the model produce a turn?}
    M -- No --> M1[Inspect provider/model errors and limits]
    M -- Yes --> T{Did tools execute?}
    T -- No --> T1[Inspect tool schema, permissions and runtime]
    T -- Yes --> D{Did external delivery succeed?}
    D -- No --> D1[Inspect channel target and message-tool result]
    D -- Yes --> R{Did the runner receive a final result?}
    R -- No --> R1[Inspect harness/bridge completion lifecycle]
    R -- Yes --> OK[Inspect task-specific validation or acknowledgement]
```

### Minimum evidence to collect

1. provider and model;
2. effective `agentRuntime.id`;
3. auth mode and selected profile source;
4. session key and owning agent;
5. tool-call result, not just the model’s narration;
6. delivery result;
7. fallback usage;
8. final runner status and summary.

### Preferred rollout sequence

1. validate configuration;
2. verify agent/default routing;
3. verify auth visibility for the target agent;
4. run a harmless fixed-response smoke test;
5. inspect session runtime attribution;
6. switch the real cron;
7. perform one manual real run;
8. observe several scheduled runs;
9. retain an explicit rollback command.

---

## 12. Rollback

The emergency rollback restores the known working paid route without deleting the dedicated agent:

```json
{
  "job": "<curator-job>",
  "agentId": "<default-agent>",
  "model": "<paid-fallback-provider>/gpt-5.6-terra",
  "thinking": "medium",
  "fallbacks": []
}
```

Leaving the dormant agent state intact makes rollback fast and avoids destructive cleanup during an incident.

---

## 13. Durable lessons

1. **Provider, model, auth and runtime are separate architectural decisions.**
2. **A successful provider workaround does not prove the original model was bad; it may only bypass a failing harness.**
3. **“No output” errors often describe the observer’s state, not the actual amount of work completed downstream.**
4. **Long-running tool agents stress lifecycle boundaries more than short chat turns.**
5. **Prefer one component to own the complete tool/result state machine when reliability matters.**
6. **Use per-agent runtime overrides to constrain blast radius.**
7. **Disable automatic paid fallbacks when cost predictability is more important than silent continuity.**
8. **Require runtime attribution in verification logs; checking only the model name is insufficient.**

The final design works because it keeps the desired subscription and model while removing the unnecessary execution bridge that correlated with the failures.
