How Does the OpenAI Agents API Run Long-Lived AI Agents? Context, Tools, Subagents, and JSON State (2026)
The 10 September public beta was not “another chat.” OpenAI shipped the harness that already ran Codex for days: the Session continues, context is compacted, each subagent keeps its own window. This piece is only about surviving a long job: which JSON you store, what OpenAI compresses, which items you reread after a drop.
Before you read
| Object | What it does | What you store |
|---|---|---|
| Session | Durable instance across hours and windows | session_id |
| Turn | Async work started by one input | turn_id / terminal events |
| Items | Saved messages and tool calls | Replay after a drop |
| Compact | Auto-compress near the window limit | Keep key fields yourself |
| Subagent | Own context, parallel work | Concurrency cap |
An idle session is not a successful turn. Read turn events and items, not the last sentence.
A long job lives in a Session, not in a longer prompt
The Agents API’s answer is concrete: create a durable session and send later input to the same session_id. Near the context limit, the harness auto-compacts earlier work. Independent subtasks can go to a subagent. You do not rebuild the full chat log.
- Continue: bind
session_idto your conversation. Do not open a second session for the same case. - Disconnect: streams do not replay. Retrieve the session and its items. Do not assume the client still has the last hop.
- Money and IDs: compact drops detail. Keep order IDs, amounts, and approvals in your own JSON.
How Session meets tool JSON: OpenAI Agents API walkthrough. Who should own the loop: Agents API vs Responses API. If Agent and Function Calling are still one word, start with AI Agent complete guide.
The Session continues; a Turn runs async
Create once: model, tools, environment, multi_agent. The official create is POST /v1/agents/sessions with OpenAI-Beta: agents=v1. Hosted sandbox is openai_hosted:
{
"agent": {
"model": "gpt-6-astra",
"instructions": "Look up orders. Confirm before any refund. Delegate independent lookups to subagents.",
"tools": [
{
"type": "function",
"name": "lookup_order",
"parameters": {
"type": "object",
"properties": {
"order_id": { "type": "string", "pattern": "^[A-Z]-\\d{4}$" }
},
"required": ["order_id"],
"additionalProperties": false
}
},
{ "type": "web_search" }
],
"multi_agent": { "enabled": true, "max_concurrent_subagents": 3 }
},
"environment": { "type": "openai_hosted", "workspace_directory": "/workspace" },
"input": [
{
"role": "user",
"content": [{ "type": "input_text", "text": "Look up A-1042 and keep the case open." }]
}
]
}
Send another agent.session.input.message on the same session. Idle starts a new turn; active steers the current one. Cancel with agent.session.input.cancel — the session and saved work stay:
{
"events": [
{
"type": "agent.session.input.message",
"input": [
{
"role": "user",
"content": [
{ "type": "input_text", "text": "Warehouse marked A-1042 packed. Continue." }
]
}
]
}
]
}
Read the outcome, not “is it idle.” Official events are agent.session.turn.completed, failed, cancelled. A completed turn does not mean every tool succeeded. When a function result is needed, inspect required_actions.
The tool chain spans hours; the write-back is still JSON
On a long job the model still only fills arguments. When your function is due, the harness stops; you return a result keyed by turn_id / call_id. Four hours later the shape is the same:
{
"type": "tool_result",
"turn_id": "turn_4",
"call_id": "call_3c91",
"success": true,
"output": "{\"order_id\":\"A-1042\",\"status\":\"packed\"}"
}
Functions need you. MCP and web_search can be run by the harness. Subagents inherit MCP and web search; they do not inherit function tools. Side effects on your orders stay on the main agent.
The gate is still Schema. A wide object is worse on a long job: after compact, the model is likelier to invent an ID. MCP is another tool table; the boundary is MCP Tool Schema vs Function Calling.
Compact shrinks the window, not your ledger
Official wording: as a session nears its limit, the Agents API auto-compacts earlier context so work can span windows. You do not write that compressor. This is not the Responses context_management / /responses/compact layer.
- Agents API: the session compresses itself. Retrieve items and check the order ID is still there.
- Responses: you enable compact, or call the compact endpoint. Pass the encrypted compaction item through; do not prune it by hand.
- Both: compression drops detail. Amounts, confirms, and releases stay in the host.
If order_id lives only in the chat, you handed the ledger to a summary. Schema as a gate: Why AI needs JSON Schema.
Subagents: parallel, with separate context
With multi_agent.enabled, the harness supplies create, message, wait, and interrupt. You do not declare those tools. Coordination shows up as items:
{
"data": [
{
"type": "function_call",
"turn_id": "turn_4",
"call_id": "call_3c91",
"name": "lookup_order",
"arguments": "{\"order_id\":\"A-1042\"}"
},
{
"type": "create_subagent_call",
"agent_id": "agent_main",
"task": "Compare A-1042 packing notes with the invoice."
}
]
}
- Own context: each subagent has one. The main agent closes with create / message / wait / interrupt.
- Cap concurrency: default is 6, excluding the coordinator. Pin a smaller number in production.
- A done item ≠ a finished subtask: a completed
create_subagent_callonly means it was dispatched. Watchagent.session.subagent.createdand that subagent’s own items.
A handoff should be auditable JSON, not “please give this to billing.” How to capture it as Schema: JSON Schema and agent workflows.
Hold your own state; do not ask compact to remember it
OpenAI keeps session config, the conversation, and saved work. You still need a business object you can rebuild after a drop:
{
"session_id": "sess_8f21",
"order_id": "A-1042",
"last_turn_id": "turn_4",
"last_call_id": "call_3c91",
"awaiting": "tool_result",
"compact_checkpoint": "after_turn_3"
}
That object is not a Session, not an SDK session, not a Responses conversation, and not a sandbox. Deleting one does not clear the others. Data residency is United States only for now; a self-hosted sandbox does not make the API ZDR.
Five steps to finish a cross-window job
Do not reverse-engineer a demo video. Walk the objects:
- Create one session and write
session_idinto your conversation. Tighten the tool table and Schema once. - Subscribe, then send later input. Open the stream before
agent.session.input.message, or early events vanish. - Write back on required_actions. Reuse the same
turn_id/call_id. Do not retry on a new session. - On disconnect, list items. Streams do not replay. Use
order=ascand match the order ID and any open call. - After compact, audit the ledger again. If the summary lost
A-1042, the next hop will invent one.
All five sit on objects. Skip one and you are betting the window is not full yet.
Review a long job by spreading two JSON trails
Most failures are mismatched objects, not a dumber model.
- format the session items next to your local state. Confirm
session_id/call_idline up. - Use JSONPath extract to pull
order_id,turn_id, and a subagentagent_id. - JSON Diff items before and after compact. If the ID drifted, look at compression before the model.
- List the main agent’s items and a subagent’s items separately. Mixed on one page, coordination attaches to the wrong actor.
- Do not paste orders, sessions, or intranet MCP URLs into a public formatter. JSONSort runs in the browser locally.
If both trails rebuild, you continued the job. Otherwise you guessed it again.
Common traps
- Treating idle as success. Read
turn.completed/failed/cancelled, then the tool results. - Waiting for the stream to replay after a drop. Official docs: it does not. Retrieve the session and items.
- Leaving the order ID only in the chat. After compact, the summary may drop
A-1042. - Enabling multi_agent without a cap. The default of 6 still fans out tokens and side effects.
- Sending your function to a subagent. Subagents do not support function tools. Lookups stay on the main agent.
- Treating Session and Conversation as one resource. Deleting one does not clear the other.
FAQ
Is a long run just a longer prompt?
No. It is a durable session, automatic compact, and the business JSON you keep. A longer system prompt does not lift the window limit.
Can I mix Agents compact with Responses compact?
Do not. An Agents session compresses itself. Responses uses context_management or /responses/compact. Two resources, two next-window inputs.
How do I recover after a disconnect?
Load the session and items with the stored session_id. The stream will not replay missed events. Align last_turn_id and any open call_id, then write back or send the next message.
Can a subagent call my lookup_order?
No. Official docs: subagents do not support function tools. They inherit MCP, web search, and the environment’s files / commands. Order lookups stay on the main agent.
Does a self-hosted sandbox give me Zero Data Retention?
No. Official docs: the Agents API currently supports US data residency only and does not support ZDR. A self-hosted sandbox does not change that.
Conclusion
A long run is not a smarter model. It is a durable Session, an automatic compact, and JSON state you hold. Tool arguments, turn events, and subagent items are objects on that chain.
Write down whether you can rebuild A-1042 after a drop. If yes, the job is alive. Models change; session_id and Schema should settle first.
Further reading
- OpenAI Agents API, complete walkthrough How Session, tool calling, and Schema connect
- Agents API vs Responses API Who should run the loop
- What is an AI Agent? Complete guide Runtime and the JSON contract
- Why AI needs JSON Schema Schema / Function Calling / Structured Output
- JSON Schema and agent workflows Capture tools and state as Schema
- JSONSort local toolbox Format, validate, Diff
- All posts More JSON and AI notes
Changelog: First published