Blog
Published on

Anatomy of a Long-Running Agent: Session, Harness, and Sandbox

Authors
  • avatar
    Name
    Jared Chung
    Twitter

Introduction

An agent that works for hours cannot depend on one process, one context window, or one temporary container staying alive. Processes crash. Context must be compacted. Workspaces become stale. Models and orchestration strategies change.

The durable part of the system is not the running agent loop. It is the record that allows work to be understood, resumed, and audited.

Managed agent systems described by OpenAI and Anthropic separate durable sessions from the harness and execution environment. Those attributed examples lead to a vendor-neutral architecture you can test locally.

Runtime model

SESSIONevents + artifactsHARNESScontext + policySANDBOXtools + filesDURABLE LEDGERReceive goal
Runtime controls · 1/5
Session is handling “Receive goal”. Append the user goal to durable history.
Separate the three agent lifecycles. Advance the work across durable state, orchestration, and replaceable compute.

Three Components, Three Lifecycles

A useful decomposition is:

                    selects context and actions
Durable session  <------------------------------>  Harness
      |                                                   |
      | records events                                    | invokes tools
      v                                                   v
Artifact store                                      Sandbox(es)
                                                     files/processes

Session

The session is an append-only history of inputs, model outputs, tool requests, tool results, approvals, state transitions, and artifact references. It should survive process and sandbox loss.

Harness

The harness runs the control loop. It chooses which session events enter the model context, exposes tools, handles compaction, applies policy, routes work to subagents, and records new events. Harness logic should be replaceable because its assumptions can age as models improve.

Sandbox

The sandbox is where tools read files, execute processes, and create artifacts. It may be a local workspace, container, virtual machine, or remote environment. Treat it as replaceable unless the workload explicitly requires persistent machine state.

Putting all three into one container is convenient for a prototype. It also couples the durable record to the component most likely to be restarted or discarded.

Make the Session Authoritative

Use an append-only event schema with stable ordering and explicit relationships.

from dataclasses import dataclass
from datetime import datetime
from typing import Any, Literal


@dataclass(frozen=True)
class SessionEvent:
    event_id: int
    session_id: str
    event_type: Literal[
        "user_input",
        "model_output",
        "tool_request",
        "tool_result",
        "approval",
        "artifact",
        "checkpoint",
    ]
    created_at: datetime
    payload: dict[str, Any]
    parent_event_id: int | None = None

The model's context is a projection of this record. It may include the latest turns, a summary of older work, selected tool results, and current task state. The projection can be rebuilt; the event history should not be overwritten by the summary.

def build_context(events, token_budget):
    current_goal = latest_event(events, "user_input")
    checkpoint = latest_event(events, "checkpoint")
    recent = take_recent_events(events, token_budget * 0.6)
    evidence = select_relevant_artifacts(events, token_budget * 0.2)

    return [current_goal, checkpoint, *evidence, *recent]

Store large files and command outputs as artifacts, then keep hashes, metadata, and references in the session. This avoids forcing every byte into the context window while preserving evidence.

OpenAI's beta Agents API exposes this separation directly. The Python example below creates a durable session with no execution environment, allowing the application to handle required tool actions itself. Keep beta-specific code isolated behind an adapter.

import os

from openai import OpenAI


client = OpenAI()

session = client.beta.agents.sessions.create(
    agent={
        "name": "repository-reviewer",
        "model": os.environ["OPENAI_AGENT_MODEL"],
        "instructions": (
            "Inspect the supplied repository evidence, identify correctness risks, "
            "and cite the file or test behind every finding."
        ),
        "multi_agent": {
            "enabled": False,
            "max_concurrent_subagents": 1,
        },
    },
    environment={"type": "none"},
    input="Review the parser change and report any untested edge cases.",
    metadata={"workflow": "parser-review-v1"},
)

print(session.id, session.status)

A session may later require the application to perform a function call. Submit the result as a new session event with an idempotency key so a client retry does not create duplicate input events:

client.beta.agents.sessions.events.create(
    session_id=session.id,
    events=[
        {
            "type": "agent.session.input.message",
            "input": [
                {
                    "role": "user",
                    "content": [
                        {
                            "type": "input_text",
                            "text": "The test artifact is available at artifact://parser-tests.",
                        }
                    ],
                }
            ],
        }
    ],
    idempotency_key="parser-review-evidence-v1",
)

This SDK surface manages OpenAI's session representation. Your application still owns its acceptance criteria, tool-side effects, data policy, and any state outside the managed session.

Treat the Sandbox as Replaceable

Suppose an agent changes a repository, runs tests, and then loses its container. Recovery needs more than the conversation:

  • a known repository revision or snapshot;
  • declared modified artifacts;
  • environment and dependency metadata;
  • the last verified checkpoint;
  • incomplete operations marked visibly.

A replacement sandbox should restore only declared state. Hidden state—an untracked file, a background process, or a package installed manually—should cause a visible recovery failure instead of silently changing behavior.

checkpoint:
  repository_revision: 8b7d2f1
  patch_artifact: artifacts/step-04.patch
  runtime_image: agent-python-v1
  completed_steps:
    - reproduce_failure
    - add_regression_test
  next_step: implement_fix
  verification:
    command: uv run pytest tests/test_parser.py -q
    status: failing_as_expected

This structure also supports multiple sandboxes. A research subagent may use a network-enabled environment while an implementation agent works in a restricted repository workspace. The harness chooses where a tool runs; the session records what happened.

Recover Without Repeating Unsafe Work

Session recovery and side-effect recovery are different. The session may show that an agent requested a ticket creation but never received the result. It cannot prove whether the external service committed the ticket before the connection failed.

Classify operations:

OperationRecovery approach
Read-only lookupRepeat when safe
Local deterministic computationRecompute or restore cached artifact
File mutation in version controlInspect repository state and patch
External idempotent writeRetry with the same operation key
External write with queryable statusReconcile before retrying
Irreversible unknown writeStop for review

Bind approvals to a specific payload or operation hash. Resuming a session must not turn approval for one action into approval for a broader request.

def approval_matches(approval, operation):
    return (
        approval.operation_id == operation.id
        and approval.payload_hash == operation.payload_hash
        and approval.expires_at > now()
    )

Exactly-once effects cannot be promised across arbitrary external systems. The architecture should make unknown outcomes explicit and route them to reconciliation or human review.

Test Failure and Replacement

A local architecture demo should inject failure rather than only show a successful run:

  1. Start a multi-step repository task.
  2. Persist every event outside the sandbox.
  3. Terminate the harness after a tool request.
  4. Replace the sandbox with a clean environment.
  5. Restore declared artifacts and rebuild context.
  6. Resume from the last verified checkpoint.
  7. Confirm that completed steps were not repeated.
  8. Repeat with an external outcome deliberately left unknown.

Record the event log, artifact hashes, model and harness versions, and expected transition at each point. A test should fail if an undeclared artifact is required for recovery.

Managed runtimes can remove substantial orchestration work, but they do not define your application contract, tool semantics, data boundaries, or evaluation criteria. Compare managed and self-hosted options on durability, environment placement, observability, policy, cost, and operational ownership.

Conclusion

Long-running agents become easier to reason about when session, harness, and sandbox are allowed to change on different schedules. Preserve an authoritative event history, make context a rebuildable view, declare artifacts, and assume execution environments will be replaced.

The resulting system can survive more than a long conversation. It can recover, change models and harnesses, use multiple environments, and still explain what happened.

References