What the Codex Harness Actually Takes Off Your Plate
The Agents API isn't an agent. It's the launch mechanism for the execution loop OpenAI already runs internally—the same orchestration layer that powers Codex-powered tasks and ChatGPT agent workflows. When you call it, you're not writing a loop; you're licensing one that has been battle-tested on OpenAI's own production agents. That inversion of ownership is the entire point, and it changes what "building an agent" means.
Three concrete responsibilities get absorbed the moment you adopt it.
First, long-lived session state with pause/resume. The harness holds event history, turn boundaries, and session continuity server-side. When you pause for human approval, the turn suspends without you serializing anything. Resume isn't a fresh start—it's a continuation with the prior context intact. On a self-hosted framework like LangGraph, you'd be building that persistence layer with Postgres or Redis, managing checkpoints, and writing the logic to restore a session after a multi-hour human delay.
Second, automatic context compaction tuned to Codex's prompt patterns. OpenAI's summarization and window-optimization policies have been shaped by how their internal coding agents actually consume context. You don't design a pruning strategy or build a vector-recall system over session history. The harness decides what to keep, what to summarize, and what to drop. That's powerful, and as we'll see, it's also the most dangerous black box in the system.
Third, subagent delegation orchestration. Primary agents break tasks down and hand work to specialized subagents. The harness manages the delegation depth, the turn-taking, and the return of results to the parent loop. You're not writing the recursive delegation logic; you're defining the task boundaries and letting the runtime manage the tree.
The mechanics are bidirectional event streaming over JSON-RPC patterns. Tool execution results, status changes, and HITL approval requests flow through the harness as events. You're not polling for the agent to finish; you're listening to the stream.
flowchart TD
A[Primary Agent] --> B[Harness Event Stream]
B --> C[Subagent Delegation]
B --> D[External Tool Call via MCP]
B --> E[Parallel Tool Calling]
E --> F{Context Threshold Reached?}
F -->|Yes| G[Compaction Trigger]
F -->|No| B
G --> B
B --> H[Pause for HITL Approval]
H --> I[Human Responds]
I --> J[Resume Turn]
J --> K[Return to Parent Loop]
D --> E
C --> E
The loop is theirs. The hard part—state, window optimization, delegation—is theirs. Your job shifts to defining the task, wiring the tools, and managing the boundary conditions. That's a much smaller surface area, which is exactly why it's attractive.
The Sandbox Is Not the Session: Two Separate State Spaces
This is the most dangerous boundary in the whole system, and it's easy to miss because the marketing glosses over it. The harness holds conversational memory, event history, and session continuity in OpenAI's cloud. The sandbox holds the file system, running processes, and environment variables somewhere else entirely—E2B, Modal, Cloudflare, or your own VPC. These are two distinct state spaces with independent lifecycles.
That creates a specific failure mode I'd watch for immediately. The harness session stays active, but the sandbox recycles. Ephemeral containers have TTL limits by design—they're cheap and disposable, so they get torn down. When that happens, the agent's conversational memory still thinks it's in a working environment. It issues a shell command against a path that no longer exists. The process it assumes is running is gone. The environment variables it needs were never populated on the fresh container.
The agent isn't confused. It's blind. The harness has no awareness of sandbox lifecycle events unless you surface them. The error surfaces as a cryptic shell failure, and the agent may retry, hallucinate a recovery path, or spiral into a loop trying to fix an environment it can't see.
Contrast this with LangGraph. In that world, the state store and the execution environment are both yours. They share a lifecycle at the design level: you create the container, you initialize the state, you define the persistence schema, and you tear it all down together. The mismatch between conversational memory and compute state is impossible by construction because you own both sides. With the Agents API, you own neither—you're renting them from two different vendors, and the seams between them are your responsibility.
stateDiagram-v2
[*] --> HarnessActive
[*] --> SandboxAlive
HarnessActive: Harness Session Active
SandboxAlive: Sandbox Container Alive
SandboxAlive --> SandboxRecycled: TTL expiry or timeout
SandboxRecycled: Sandbox Container Destroyed
HarnessActive --> SandboxCommand: Agent issues shell command
SandboxCommand --> ErrorState: Path not found / process missing
ErrorState: Shell Error: command fails
ErrorState --> Reconstitution: Agent triggers recovery
Reconstitution: Reconstitution Script Resets Env
Reconstitution --> Workspace: Fresh Workspace with expected state
Workspace --> FreshPath: Agent continues with fresh paths
Workspace --> StaleHandle: Agent holds stale absolute path
StaleHandle: Stale File Handle / Bad Path Reference
StaleHandle --> ErrorState: Command fails again on missing path
The mitigation is lifecycle synchronization. Your sandbox TTL must match the agent session TTL, and you need automated reconstitution scripts that can rebuild the expected workspace state on a freshly spun-up container. When the sandbox recycles mid-session, the script runs, restores the working directory, reinstalls dependencies, repopulates env vars, and the agent can continue. But if you're holding stale absolute paths in memory or in tool call results, even a perfect reconstitution won't save you—the agent will keep pointing at old locations. The ground truth for paths needs to live outside the harness context, which is exactly what the next section addresses.
Compaction Is a Feature and a Liability: Semantic Drift in Practice
The harness's automatic context compaction is genuinely impressive. It keeps token counts bounded across long-running sessions, maintains continuity, and prevents the window exhaustion that kills most self-hosted agent loops. It's the thing you're paying for when you use the managed harness instead of building your own summarization pipeline.
But here's the uncomfortable truth: compaction is lossy by design. When the summarizer kicks in, it doesn't preserve every detail. It preserves the narrative. Exact identifiers—UUIDs, regex patterns, specific configuration parameters, intermediate API schema shapes, environment variable values—are exactly the kind of precise, low-entropy data that gets generalized away. The summarizer keeps the story about what you did; it drops the specific values that made it work.
This isn't a bug. It's the optimization. Compaction exists to reduce token count, and exact identifiers are the most expensive tokens per bit of information. Skipping them is how you keep a 200-turn session within a bounded window.
That means semantic drift is inevitable. Later in the session, after compaction events, the agent operates on a generalized memory of what happened. If a downstream turn depends on the exact regex pattern from turn 47, and that pattern was summarized into "the validation regex we set up earlier," the agent will hallucinate the pattern. It will invent something plausible and proceed. The error isn't visible at that moment—it compounds silently into downstream outputs.
The practice I'd reach for is an external ground-truth store. Postgres or Redis holds canonical IDs, environment configurations, and any critical parameter that must survive context loss. The store isn't the agent's memory; it's the source of truth the agent can consult. Register explicit lookup tools on the agent—not vague "search database" tools, but precise, purpose-built retrieval functions: get_environment_config, fetch_session_parameters, resolve_canonical_id. When a turn needs an exact value, the agent calls the tool rather than relying on its summarized context.
This is the pattern I recommend before deploying anything beyond a short demo. The moment your agent runs more than a handful of turns, or delegates work to subagents, or processes jobs that span hours, you need a canonical store outside the harness. The cost is a few extra tool calls per turn; the benefit is that a compaction event stops being a potentially catastrophic memory wipe.
Without this, the failure mode is pervasive. A subagent runs a refactor based on the wrong function signature threshold. A later turn validates a transaction against a UUID the summarizer dropped, and the check silently returns false. The agent doesn't fail loudly—it fails confidently, with a plausible generalization doing the work of an actual value. And because the errors are downstream and compounding, you can't debug them from the event log alone. You'll see the agent confidently using the wrong parameter and have no idea where the substitution happened. The ground-truth store makes the failure visible and recoverable. The event log will show the lookup call and its return value, so you can trace exactly what the agent knew at each step.
Subagent Delegation Needs Hard Depth Limits or You'll Burn Your Budget
The harness gives you multi-agent orchestration for free—primary agents break down tasks, delegate to subagents, and collect results. That's genuinely valuable. But free orchestration is also free recursion, and recursion without a termination condition is a budget incinerator.
The failure mode is straightforward: Subagent A needs clarification from Subagent B. Subagent B needs context from Subagent A. Each turn generates tokens, each token costs money, and neither agent is structurally capable of recognizing that the conversation has become circular. The harness doesn't know what "done" means for your task—it just faithfully executes the delegation graph you've implicitly defined. If you haven't set boundaries, the graph can grow without limit.
In CrewAI or AutoGen, this loop pattern is at least visible in your own code. You write the agent definitions, you see the task lists, you can trace the delegation chain in a debugger. The runtime is yours. With the Agents API, the delegation tree lives behind the API boundary. You can't inspect it mid-flight, you can't step through it, and you won't know it's looping until the invoice arrives.
So demand explicit circuit breakers before you deploy anything real. Hard ceiling of 2 for maximum subagent delegation depth. Hard ceiling of 15 tool calls per turn. Hard per-session token cap that triggers an abort, not just a warning. These aren't suggestions—they're your only defense against a subtask that spirals into a token-drain loop you can't observe in real time.
| Circuit Breaker Limit | OpenAI Agents API (managed, opaque) | LangGraph (self-hosted, inspectable) | SWE-agent/OpenHands (local, container-bound) |
|---|---|---|---|
| Max delegation depth | Must enforce via API config | Enforce in state machine graph | N/A—single-agent loop |
| Max tool calls per turn | Must enforce via API config | Enforce in node transitions | Enforce in shell interaction loop |
| Per-session token cap | Enforce via session budget param | Enforce via custom checkpoint logic | Enforce via container resource limits |
| Runtime observability | Event log—post-hoc inspection | Live graph state inspection | Direct container/process monitoring |
| Failure visibility | Invoice arrives after the damage | Immediate—debugger trace shows loop | Immediate—process log shows runaway |
| Default safety posture | None until you configure it | Built into graph topology | Bounded by container lifecycle |
The constraint isn't that the harness can't do this—it's that you have to remember to configure it. The default is unlimited, and the harness won't warn you. When you set depth to 2, you're also signaling what kind of task decomposition you expect: one level of specialization, one level of return, done. That's a real design decision, not just a safety valve.
I'd reach for the ceiling even on short demo runs. The discipline of setting limits forces you to think about task boundaries before you deploy, and it makes runaway loops fail loudly instead of silently.
Idempotency and Retries: Where Partial Tool Failures Become Duplicate Side Effects
The harness supports native parallel tool calling. That's a productivity win—multiple independent calls fly concurrently instead of serializing turns. But parallelism multiplies a failure mode that's invisible in serial execution: partial completion. When three mutating calls are in flight simultaneously and one hits a network drop or a downstream 5xx, the harness retries. The retry logic is sound from the harness's perspective. It has no idea whether your payment endpoint already committed the transaction before the connection dropped, or whether the database write landed but the ack never made it back.
The harness can't know. It only knows the call failed. Its retry path is a blind re-attempt.
That's the collision. Say your agent calls three tools in parallel: charge a customer, write a provisioning record, send a notification. The charge endpoint times out. The harness retries the charge. Meanwhile, the original request actually reached your payment processor and succeeded—only the response was lost. You now have two charges for one order. The harness will faithfully report the second charge as the successful one, and your downstream logic will treat the first as if it never happened because the harness never saw the ack.
This is entirely on you. Every mutating REST or MCP tool needs two things before it touches production: an idempotency key and a strict execution timeout. The key goes into the request header or body so your tool can deduplicate on retry. The timeout—5 to 15 seconds tops—ensures a hanging tool doesn't block the harness execution step until the gateway timeout fires, which in a parallel batch can hold up the entire turn.
Without these, the failure signature is insidious. The harness doesn't crash. It retries, succeeds on the second attempt, and marks the turn complete. Your system has a duplicate transaction, but the agent's event log looks clean. You'll discover the problem days later when reconciling invoices or checking the database for duplicate rows.
The pattern I'd enforce is mechanical: every mutating tool registers a fresh idempotency key at call time, derived from the session ID plus a unique operation ID. Inside the tool, the first check is a lookup: does a record exist with this key? If yes, return the stored result instead of executing again. Combined with the timeout, this means the harness's retry path becomes idempotent by contract, even though the harness itself doesn't know or care about your deduplication logic. It's defensive work, but it's the difference between an agent that occasionally double-charges and one that doesn't.
Pause/Resume and the Human-in-the-Loop Time Bomb
HITL approval is one of the best features of the harness. The agent streams an approval request as an event, the turn suspends, and you don't have to serialize anything. The session state stays server-side, ready to resume when the human responds. That's dramatically simpler than building your own pause/checkpoint mechanism.
But the pause is a time bomb, and the fuse is the sandbox lifecycle.
When the harness pauses, it holds conversational memory, event history, and turn boundaries. The sandbox, however, is an ephemeral compute environment with its own TTL. It doesn't pause. It keeps running its clock, and eventually it gets recycled—especially if the human takes hours or days to respond. By the time approval arrives, the container the agent was operating in is gone. Temp auth tokens have expired. Temporary files are vanished. Background processes are dead.
Resume isn't just restoring context. It's restoring an environment. The harness will faithfully restore the compacted conversational context, but it has no idea whether the sandbox is still alive. When the agent's first tool call fires after a three-day pause—using an auth token that expired on day one—it fails. Not with a clear message, but with a cryptic authentication error that gives the agent no indication that the root cause is environment staleness, not bad credentials.
This is where the pause feature exposes its seam. LangGraph, because you own the persistence schema, lets you decide what freezes and what doesn't. You can freeze the conversation graph while deliberately re-provisioning compute on resume. You control the lifecycle contract between state and environment. The Agents API gives you a pause button but not a provision button—the environment is managed by whatever sandbox backend you've chosen, and its lifecycle isn't synchronized to the human's response time.
sequenceDiagram
participant Agent as Agent Loop
participant Harness as Codex Harness
participant Human as Human Reviewer
participant Sandbox as Execution Sandbox
participant Tool as External Tool API
Agent->>Harness: Emit HITL approval request
Harness->>Human: Stream approval event
Harness->>Harness: Pause turn, hold session state
Human->>Human: Delays 3 days before responding
Sandbox->>Sandbox: TTL expiry - container recycled
Human->>Harness: Respond with approval
Harness->>Agent: Resume turn with compacted context
Agent->>Tool: Call tool with expired auth token
Tool-->>Agent: Error: auth token invalid
Agent->>Harness: Tool call failed
Harness->>Sandbox: Attempt command execution
Sandbox-->>Harness: Error: environment not found
Agent->>Harness: Trigger reconstitution script
Harness->>Sandbox: Re-inject fresh workspace + new token
Sandbox-->>Agent: Environment ready with fresh credentials
Agent->>Tool: Retry tool call with new token
Tool-->>Agent: Success
Agent->>Harness: Turn continues, task proceeds
The mitigation is a reconstitution script, and I'd build it before relying on HITL at all. The script runs on resume, checks whether the workspace state matches what the agent expects, and re-injects anything that's missing: the working directory, dependencies, environment variables, fresh auth tokens. The agent's first real tool call after resume should be a self-check that validates environment health, not a mutating operation that assumes the world is as it was three days ago.
If you don't do this, the failure mode is a cascade. Agent resumes confidently, hits a dead token, fails, tries to recover, realizes the environment is gone, and flails. Or worse, it succeeds against a partially reconstituted environment and produces output based on stale state. One human approval, three days, and a simple pause-resume becomes an environment reconstruction project you didn't plan for.
Where the Managed Abstraction Leaks: Debugging a Black Box
The compaction engine is the crown jewel of the harness, and it's also the most opaque component in the stack. When you use LangGraph, you have a Postgres table of checkpoints. You can query it, inspect exactly what state was preserved at each step, and verify that the regex pattern you care about survived the transition. You can diff two checkpoints and see precisely what got pruned. That's the foundation of deterministic debugging. With the Agents API, you get a summary. The harness decides what to keep and what to drop, and you can't see inside its judgment calls.
This matters more than it might seem. When a self-hosted agent produces a wrong output, you can trace the exact state mutation that caused it. With the harness, you're debugging in the dark. The event log will show the agent using a wrong value, but not where the value got substituted. You'll see the compaction event in the stream, but not its contents. You'll know something was lost, but not what. The debugging path becomes: infer the loss from the downstream symptom, then build workarounds that don't depend on the compacted context existing.
That's a real cost, and I'd weigh it honestly. The operational savings are substantial—you're not building checkpointing databases, streaming pipelines, or compaction heuristics. The harness genuinely absorbs months of infrastructure work. But you're trading that for a loss of inspectability that will bite during production debugging, especially in the first few weeks when you're still learning how the compacted context behaves under load.
My honest read is that the trade-off is worth it for most teams, but only if you're disciplined about the external ground-truth store. The black box becomes manageable when you stop treating the harness context as authoritative. You design your system so that any value that matters lives in a store you can inspect, and the harness context is merely the agent's working memory—a cache that can be invalidated without breaking the system. If you build with that assumption from the start, the opacity of compaction becomes a non-issue. You don't need to see inside the black box because you've designed your system to survive losing it.
Production Projects You Can Build on the Harness Today
The harness earns its keep when you build agents that actually need managed session state and long-horizon orchestration. Three patterns come to mind, each one mapping directly to the failure modes above.
A context-safe migration agent is the clearest fit. Imagine an agent that refactors a large repository across multiple turns—renaming modules, updating imports, adjusting config values. The whole point is that the work spans dozens of steps, and the compaction engine will absolutely fire. Without precautions, the agent will lose the exact path to a config file and hallucinate a replacement. The fix is to register a Postgres database as an MCP resource, exposing lookup tools for canonical file paths, config values, and the target schema. The agent pulls ground truth from the store instead of trusting its compacted memory. The sandbox runs self-hosted with a reconstitution script that replays the expected workspace on every recycle, so a fresh container comes up with the exact state the agent expects. The watch item is the interaction between compaction and your lookup tools: compaction will strip regex patterns and UUIDs, so the lookup tool must be the one returning them, and your reconstitution script must re-apply environment variables before any resume path fires.
A provisioning agent with hard delegation limits is the second pattern. Give it a natural-language request to create VMs, buckets, and databases, and let it delegate each resource type to a specialized subagent. The harness handles the orchestration, but you set max delegation depth to 2 and enforce it through the API config. Every mutating REST tool carries an idempotency header generated from the session ID plus a unique operation ID, and every tool has a strict 5-second timeout. The sandbox runs in E2B for isolated micro-VM execution, and you wire in an external budget tracker that counts tokens per session and aborts when it hits the ceiling. The failure to watch is a 5xx error mid-parallel-call causing duplicate resource creation—the idempotency key and timeout are non-negotiable on every mutating call.
A review agent with human-in-the-loop approvals is the third. The agent proposes changes, streams approval requests as events to a UI, and pauses. The human takes their time. When they respond, a webhook consumer sends the decision back through the harness. The problem is the pause. The sandbox recycles, auth tokens expire, and the resume path hits a stale credential. Solve it with an external token store—a secrets manager holding the credentials—plus a sandbox lifecycle manager that re-provisions the workspace on resume, and a refresh job that re-validates credentials before the agent's first tool call fires. The watch item is sandbox staleness: if the human waits too long, the resume path must not assume the world is as it was when the pause happened.
Resources
Updated 2026-09-09 by Mehran Mozaffari.
Related posts
15 September 2026
Borrowing the User's Browser: How BrowserSkill Solves Agent Auth Without Leaking Secrets
12 September 2026
Runbooks for the Reasoning Engine: How Markdown Skills Actually Change Agent Behavior
12 September 2026
Tracing the Limits: Where Microsoft Foundry's Agent Governance Actually Holds
10 September 2026
life-recorder: Owning the Ambient Capture Pipeline With an iPhone and a Mac
10 September 2026
Unbundling the Hype: How Prompt-to-3D, MCP, and Collaborative Generative Workflows Actually Fit Together
8 September 2026
Character Locking and Timestamp Directing: A Systems View of ChatGPT Image 2.5 + Seedance 2.5
