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-terraexecuted 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
openaiOAuth 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.
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:
- start or attach to an app-server session;
- forward the prepared prompt and runtime context;
- consume streamed events;
- coordinate tool execution and continuation;
- detect the final assistant result;
- convert that result into the shape expected by the cron runner;
- 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 outputno JSON output returnedcheck-heartbeat returned no JSONcheck produced no JSON outputempty 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.
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:
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.
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.
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
{
"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:
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:
- exact entry in
agents.list[].models["provider/model"].agentRuntime; - exact entry in
agents.defaults.models["provider/model"].agentRuntime; - exact provider model definition;
- provider wildcard policy;
- provider-wide policy;
- automatic runtime claim or OpenClaw compatibility fallback.
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-agentresolves Terra toopenclaw;default-agentstill resolves Terra tocodex;- 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:
/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:
- the secondary agent requests an
openaiauth profile; - its own store does not need a copied refresh token;
- OpenClaw reads the matching profile from the default-agent store;
- the freshest usable access token is adopted for the request;
- refresh material is not duplicated across agent stores.
The verification output showed:
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
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:
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.
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
- provider and model;
- effective
agentRuntime.id; - auth mode and selected profile source;
- session key and owning agent;
- tool-call result, not just the model’s narration;
- delivery result;
- fallback usage;
- final runner status and summary.
Preferred rollout sequence
- validate configuration;
- verify agent/default routing;
- verify auth visibility for the target agent;
- run a harmless fixed-response smoke test;
- inspect session runtime attribution;
- switch the real cron;
- perform one manual real run;
- observe several scheduled runs;
- retain an explicit rollback command.
12. Rollback
The emergency rollback restores the known working paid route without deleting the dedicated agent:
{
"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
- Provider, model, auth and runtime are separate architectural decisions.
- A successful provider workaround does not prove the original model was bad; it may only bypass a failing harness.
- “No output” errors often describe the observer’s state, not the actual amount of work completed downstream.
- Long-running tool agents stress lifecycle boundaries more than short chat turns.
- Prefer one component to own the complete tool/result state machine when reliability matters.
- Use per-agent runtime overrides to constrain blast radius.
- Disable automatic paid fallbacks when cost predictability is more important than silent continuity.
- 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.