AI Development

How Does the OpenAI Agents API Run Long-Lived AI Agents? Context, Tools, Subagents, and JSON State (2026)

About 16 min read

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_id to 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_call only means it was dispatched. Watch agent.session.subagent.created and 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:

  1. Create one session and write session_id into your conversation. Tighten the tool table and Schema once.
  2. Subscribe, then send later input. Open the stream before agent.session.input.message, or early events vanish.
  3. Write back on required_actions. Reuse the same turn_id / call_id. Do not retry on a new session.
  4. On disconnect, list items. Streams do not replay. Use order=asc and match the order ID and any open call.
  5. 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_id line up.
  • Use JSONPath extract to pull order_id, turn_id, and a subagent agent_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

Changelog: First published