Deep analysis of 6 articles on modern agent harness architecture, by Aparna Dhinakaran (Arize) and Mike Piccolo (iii). Convergence, context management, swarms, sandboxes, and the battle for trajectory data.
"A harness is not designed for humans to build agents. A harness is designed for the agent to accomplish almost any task."
The modern agent harness was born bottom-up, from coding agents solving real tasks. Cursor, Claude Code, Windsurf, and Codex independently arrived at strikingly similar architectures. A harness is not a framework: it ships as a working agent. The iteration loop, context management, tool registry, and safety layer are already configured and running. There is no assembly step.
The harness closes the open loop of an LLM. Without a harness, the model is a one-shot text generator: it responds and stops. A model inside a harness can read a file, edit it, run tests, see errors, read the log, fix the code, and run tests again. Act → Observe → Adjust, a closed feedback loop.
| Dimension | Framework | Harness |
|---|---|---|
| Purpose | A tool for a human to assemble an agent | A ready-made environment for an agent to perform tasks |
| Starting state | Requires configuration and assembly | Works "out of the box" |
| Architecture | Human defines the state graph | Fixed architecture, validated by convergence |
| Loop | Open-loop: call model → response | Closed-loop: Act → Observe → Adjust |
| Examples | LangGraph, CrewAI, AutoGen, OpenAI Agents SDK | Claude Code, Hermes, OpenClaw, Pi, Letta, Cursor |
| Component replacement | Entire stack — monolith; replacement = fork | Components are isolated; replacement = swap worker/plugin |
| # | Component | Description | Analogy |
|---|---|---|---|
| 1 | Outer Iteration Loop | Infinite loop: model thinks → calls tools → gets results → thinks again. Provider-agnostic (Chat Completions, Anthropic Messages, Bedrock). | CPU instruction cycle |
| 2 | Context Management | Active context window management: file caps, tool result budgets, pagination, pressure estimation, compaction. | Virtual memory manager |
| 3 | Skills / Tools | Tool registry, separated from exposure. The model discovers available tools and composes them on its own. | syscall table |
| 4 | SubAgent Management | Spawn child agents with isolated sessions, tool allowlists, and context inheritance. | fork() + exec() |
| 5 | Built-in Skills | Pre-packaged skills: file operations, shell, search, web requests. | libc / standard library |
| 6 | Session Persistence | Session persistence: SQLite + FTS5, append-only JSONL, git-backed MemFS. Recovery after restart. | Filesystem journal |
| 7 | System Prompt Assembly | Multi-level system prompt assembly: stable (SOUL.md) → contextual (AGENTS.md) → volatile (memory, timestamps). | Linker / symbol resolution |
| 8 | Lifecycle Hooks | Pre/post tool call hooks: plugin lifecycle (in-process) + filesystem gateway (out-of-process shell/Python). | BPF / eBPF hooks |
| 9 | Permission & Safety | Permission hierarchy: read-only → workspace-write → full-access. Fail-closed semantics. Approval gate. Recursion depth cap. | seccomp / sandbox |
These nine components are not a wishlist. They are what every successful harness converged on independently. That convergence is the signal. Cursor, Claude Code, Windsurf, and Codex did not coordinate. They solved the same problem, and arrived at the same answer.
"These nine components are not a wishlist. They are what every successful harness converged on independently. That convergence is the signal."
"Every agent harness runs into the same limit: the context window is too small for everything the model might want to remember."
Context management is where the real engineering lives. It's not about "what fits in the prompt." It's about a multi-layered system: file read caps, tool result budgets, offset/limit pagination, compaction triggers, deduplication, and memory persistence. The key design question: how much context management happens inside the harness, and how much does the model do itself.
All four analyzed harnesses (Pi, OpenClaw, Claude Code, Letta) make different choices, but converge on the same patterns. This is the most striking finding of the comparative analysis, not the differences, but the degree of agreement.
| Dimension | Pi | OpenClaw | Claude Code | Letta |
|---|---|---|---|---|
| File read line cap | 2,000 | 2,000 | 2,000 (default) | 2,000 |
| File read byte cap | 50KB | 50KB | 256KB gate + 25K tok | 10MB stat reject |
| Line char truncation | — | — | 2,000 chars | 2,000 chars |
| Tool result budget | — | 16K chars / 30% window | 50K chars/tool, 200K/msg | 30K bash, 10K grep |
| Oversized tool result | — | — | 2KB preview + disk persist | — |
| Bootstrap file caps | — | 12K/file, 60K total | — | — |
| Compaction trigger | context − 16,384 tok | history > 50% window | context − 13,000 tok | server-side + 25 msg |
| Compaction method | LLM head-tail (+~20K tok tail) | Multi-pass staged LLM + chunk drop | 9-section LLM + 5-file re-attach | Server LLM + reflection subagents |
| Token estimation | — | — | — | 4 bytes/token heuristic |
| Tool result cache TTL | — | 5 minutes | — | — |
| Sub-agent context | Fresh process, in-memory | Fresh isolated (fork mode exists) | Blank conv (fork path) | 7 built-in types, isolated |
| Deduplication | — | — | ✓ idempotent reads | — |
"50 years of computing taught us that the best memory management is the kind the program never thinks about. Registers, cache lines, page tables, swap. Each layer managed by the system, each invisible to the layer above."
The central design question: how much context management does the harness do, and how much does the model do. The winners (Claude Code, Hermes) delegate decisions to the model: what to keep, which files are important, when to compact. The harness provides scaffolding; the model works within it. But this is a tradeoff: more model autonomy = more tokens spent on meta-reasoning.
"The goal is not to show the model everything. It is to give it the right working set at the right time and allow it to dynamically make decisions to manage its own context."
Compaction policy determines whether a long-running agent remains coherent or gradually degrades. Four harnesses demonstrate a spectrum of approaches, from simple head-tail split (Pi) to multi-stage staged summarization with reflection subagents (Letta).
Pi established the baseline pattern: LLM summarization, head-tail split (truncating old messages), token-based trigger. OpenClaw added a second layer: bootstrap caps + tool result budgets + multi-pass staged summarization. Claude Code introduced pre-query optimization (tool results to disk, 2KB previews) and a 9-section structured compaction prompt with re-attachment of up to 5 recently read files. Letta moved compaction server-side and added reflection subagents with a budget of 16,000 tokens.
| Parameter | Pi | OpenClaw | Claude Code | Letta |
|---|---|---|---|---|
| Trigger | token > window − 16,384 | history > 50% window | token > window − 13,000 (~167K for 200K-model) | server-side + 25 messages |
| Tokens retained | ~20,000 tail | multi-stage: remnants per pass | 9-section summary + up to 5 files | reflection summary (budget: 16,000 tok) |
| Summarizer model | Same LLM | Same LLM (staged) | Same LLM (9-section prompt) | Same LLM + reflection subagent |
| Tool call safety | Pre-compaction flush | Pre-compaction flush | Pre-compaction flush + file re-attach | Server-side state preservation |
| Post-compact recovery | Seed: summary + tail | Seed: summary + bootstrap + cache | Seed: summary + 5 recent files | Seed: summary + MemFS + reflection |
| Fallback | — | Chunk drop | Oldest 20% API-group drop | — |
| Budget model | Fixed reserve | Window share (50%) | Fixed buffer (13K) | Step-count (25) + server-side |
A critical safety pattern: before compaction, all pending tool calls must be completed (flush). You cannot safely summarize a session while a tool call is in flight. This is another example of convergent design, an engineering necessity, not a choice.
Hermes implements a unique approach. Instead of repeatedly rewriting one transcript, on each compaction:
Summary budget: ~20% of compressed content, with a floor of 2,000 tokens and a ceiling of 12,000 tokens. The result, a lineage chain instead of one repeatedly rewritten transcript. This is unique relative to other architectures.
"If a long conversation compresses multiple times, you get a lineage chain instead of one repeatedly rewritten transcript. This is pretty unique relative to other harness architectures we've reviewed."
No harness solves the problem of long-lived subagents whose context grows independently of the parent. When a subagent runs for hours, its own compaction policy must be independent of the parent's. This is an open problem: coordinated compaction in the agent tree.
Sub-agents are the fourth component of the harness model. Patterns of spawning, isolation, and context inheritance differ, but the foundational principles are convergent.
| Pattern | Pi | OpenClaw | Claude Code | Letta | Hermes |
|---|---|---|---|---|---|
| Spawn (fresh) | ✓ new process | ✓ fresh session + session key | ✓ blank conversation | ✓ 7 built-in subagent types | ✓ delegate_task |
| Fork (inheritance) | — | ✓ fork mode (copies transcript) | ✓ fork path exists | ✓ | — |
| Tool restrictions | Restricted set | Per-subagent allowlists | Restricted set | Per-type allowlists | Dangerous commands → deny in delegated context |
| Workspace inheritance | Isolated | Isolated (fork = copy) | Isolated | Isolated + MemFS | Under parent call path |
| Lifetime | Until task complete | Durable (session key, run ID, registry) | Until task complete | Persistent via MemFS | Within session |
| Steering | Block → return | Steer, interrupt, kill, cascade | Block → return | Block → return | Task ID + structured return |
Letta stands out with the richest set of built-in sub-agent types (7). Each type has isolated context, a specific tool allowlist, and a token budget. The reflection subagent is the most interesting: it runs every 25 messages, analyzes history, and updates the agent's persistent memory in MemFS.
Claude Code offers two paths for sub-agents: blank conversation (clean spawn) and fork (copying the parent transcript). The fork path is critical for tasks where the sub-agent needs the parent's context, for example, continuing a refactoring in the same codebase.
Hermes implements delegate_task with task ID and structured returns. Recursion is capped (configurable depth). But steering is block → return. Durable child-run control (run IDs, external steering, cleanup after parent exits) is an explicitly identified gap and "the most natural next architectural step."
In the delegation model, the parent. In the swarm model, the runtime. This distinction determines everything: can the sub-agent outlive the parent, can it be addressed externally, who is responsible for cleanup. OpenClaw is the only harness that answers this question in favor of the runtime.
"One thing has become clear: swarm management is the next real systems problem in AI. Not single agents. Not one-off tool calls. Managing swarms of long-running agents."
Delegation is not swarm management. Most frameworks can spawn sub-agents. That is the beginning of the problem, not its end. The real question: what happens after the child agent is created? Where does it live? Who owns it? Can it be addressed? Can it be steered? Can it complete after the parent has gone?
OpenClaw is the best example of swarm management in the ecosystem. Its sub-agents become gateway sessions with durable session keys, run IDs, lifecycle records, parent-child lineage, cleanup policy, and push-based completion.
| Dimension | Delegation | Orchestration / Swarm Management |
|---|---|---|
| Question | How does one agent split work? | How does the runtime own many agents over time? |
| Model | Parent → Child, block → return | Identity, addressing, steering, recovery |
| Completion | Return value | Routing problem (push-based event) |
| Ownership | Parent owns child | Runtime owns all agents |
| Lifecycle | Tied to parent | Independent (session key, run ID) |
| Steering | Spawn only | Steer, interrupt, kill, cascade |
| Cleanup | GC on parent exit | Sweeper: reconciliation state machine |
| Queues | None | Strict ordering per session, parallelism across sessions |
| Roles | None | Orchestrator vs leaf, enforced at tool level |
| Implementation | Most harnesses | OpenClaw (gold standard) |
"They treat completion as a return value. In a swarm manager, completion is a routing problem."
agent:<targetAgentId>:subagent:<uuid>, unique address for each agenttask_completion event → routing → requester session"Every swarm manager eventually becomes a cleanup system."
Hermes has excellent delegation (delegate_task with task ID), but lacks durable child-run control: run IDs, external steering, independent lifecycle management, and cleanup semantics that survive parent termination. This is "the most natural next architectural step", explicitly identified in article 4.
"Hermes is one of the best open-source harnesses in the ecosystem right now."
Hermes implements all 9 components of the harness model, and adds three additional subsystems: messaging gateway, profile system, and cron as a first-class subsystem. The details are in how the components are implemented, and in what lies beyond this framework.
| Component | Hermes Implementation | Rating |
|---|---|---|
| 1. Outer Iteration Loop | Provider abstraction via transport adapters: Chat Completions, Anthropic Messages, Codex, Bedrock | ✓ Strongest in open source |
| 2. Context Management | Full compression path with lineage chains. session_search for focused recall. | ✓ Unique lineage approach |
| 3. Skills / Tools | Central registry at import + exposed toolset, scoped by platform/scenario | ✓ Registration and exposure separated |
| 4. SubAgent Management | delegate_task with task ID, structured returns, recursion cap | ✓ Strong delegation |
| 5. Built-in Skills | File operations, shell, search, web | ✓ |
| 6. Session Persistence | SQLite + FTS5 + WAL journaling (with fallback). Compression lineage. | ✓ Advanced |
| 7. System Prompt Assembly | 3-tier composition: stable, context, volatile. Explicit in code. | ✓ Transparent tiering |
| 8. Lifecycle Hooks | 2 surfaces: plugin lifecycle (in-process) + filesystem gateway (out-of-process shell/Python) | ✓ Two trust models |
| 9. Permission & Safety | Hierarchy: read-only → workspace-write → full-access. Dangerous commands → deny in delegated contexts. | ✓ |
| + Additional Subsystems | ||
| Messaging Gateway | Telegram, Discord, Slack, WhatsApp and others | ✓ Multi-platform |
| Profile System | Isolated agent roots: "Two profiles on one machine = two different agents" | ✓ Unique |
| Cron | First-class: durable, gated permissions, isolated per profile | ✓ Unique |
| ▸ Gap Analysis | ||
| Swarm Orchestration | GAP: No durable child-run control (run IDs, external steering, cleanup after parent). The most natural next step. | |
| Sub-agent Fork | GAP: No fork mode for parent transcript inheritance (unlike OpenClaw, Claude Code, Letta). | |
"Hermes already has excellent execution quality and a strong systems foundation for an open harness. It is probably one of the best open model harnesses in the ecosystem."
"A harness is not a thing you install. A harness is a set of jobs your system has to do for an agent to run durably, safely and observably."
Mike Piccolo argues: the traditional harness form is wrong. It's not one thing, it's 10-12 different things bundled together. Every long-running agent team eventually rewrites their harness from scratch because frameworks ship it as a monolith. iii's bet: workers on a shared bus, each replaceable, each independently versioned.
The model: 15 jobs (tasks a production harness must perform) → 11 workers (independent processes) on a WebSocket bus. Each worker registers functions and triggers. Every layer is reachable via 1-2 function IDs on the bus.
| # | Job | Worker | Function ID |
|---|---|---|---|
| 1 | Accept turn request + persist | harness-meta | harness::accept_turn |
| 2 | Resolve credentials | auth-credentials | auth::get_token |
| 3 | Look up model capabilities | models-catalog | models::get_capabilities |
| 4 | Drive per-turn state machine | turn-orchestrator | turn::orchestrate |
| 5 | Load/serve skill bodies | harness-meta | directory::skills::get |
| 6 | Assemble system prompt | harness-meta | prompt::assemble |
| 7 | Stream tokens to client | provider-* | provider::stream |
| 8 | Check tool calls vs policy | policy-engine | policy::check_permissions |
| 9 | Pause + route approval | approval-gate | approval::resolve |
| 10 | Track LLM spend | llm-budget | budget::record |
| 11 | Run hooks before/after tools | hook-fanout | hook::fanout |
| 12 | Persist session (branching tree) | session | session::persist |
| 13 | Compact session history | context-compaction | context::compact |
| 14 | Emit event stream for UI | harness-meta | events::emit |
| 15 | Carry OTel trace | harness-meta | trace::propagate |
The turn finite state machine has been collapsed from 11 to 7 states in the production version:
provisioning → assistant_streaming → function_execute → function_awaiting_approval → steering_check → (loop back or finishSession)
The policy engine is a replaceable worker. Any can be swapped without changing the rest of the system:
policy::check_permissions"That's the property the composition gives you: a major internal rewrite of one worker is a self-contained change because every neighbour talks to it through bus-level function ids."
policy::check_permissions; fail-closed → gate_unavailableiii.session.id, iii.message.id, iii.function.id"You don't adopt the iii harness. You install the workers you want, write the ones you need, and end up with a harness shaped exactly like your system."
"The harness is the application. The sandbox is the server."
The harness-to-sandbox relationship is the same as application-to-server. The harness reasons and calls tools; the sandbox provides isolated execution. They can be swapped independently. Sandbox evolution proceeds in two phases:
| Dimension | Evals (Phase 1) | Long-Running (Phase 2) | Swarms (Future) |
|---|---|---|---|
| Lifecycle | Disposable: spun up, thrown away | Persistent: hours of execution | Fleet: hundreds of parallel runs |
| State | No persistent state | Filesystem state preserved | Distributed state |
| Artifacts | Evaluation results only | Trajectory + local data | Trajectory + data + lineage |
| Restartability | Impossible (no state) | From trajectory + filesystem | From trajectory (Claude Code, Cursor already can) |
| Scale | Dozens of runs | Hundreds of runs | Thousands of runs across providers |
| Who owns state | Lab (eval framework) | Split: lab / enterprise | Critical market question |
| Examples | Daytona, E2B (disposable mode) | Stripe Minions, Browserbase | OpenClaw swarm + sandbox fleet |
Two things make a harness restartable:
Anthropic frames this as "decoupling the brain from the hands." Brain = harness. Hands = sandbox. The session log lives outside both.
The key divide: labs (AI providers) want to control the trajectory because it's data for model improvement. Enterprise customers cannot allow labs connectivity to production infrastructure. Hence the tension: where does the swarm control layer live, in the customer's infrastructure or inside the labs?
"The sandbox becomes cattle."
"The ones who own the swarm management layer and the trajectory data will define this market."
The agent harness market is defined by three battle lines:
Where does the control layer live? If at labs (Anthropic, OpenAI), they control the trajectory, model, observability, and upgrade path. If at the enterprise customer, the customer controls data, security, and compliance. This is a fundamental tension: enterprises cannot give labs access to production, but labs need data for improvement.
Who manages the agent swarm? OpenClaw shows that swarm management is a system inside the runtime. But who will manage cross-runtime swarms? Agents distributed across different harness instances, different organizations, different clouds? This is still an open problem.
Trajectory is the most valuable artifact. It makes work reproducible, auditable, and improvable. Control over trajectory = leverage in the future AI market. Current state: Claude Code and Cursor can restart from their own trajectories, but not from others'. There is no standard trajectory format.
"Control of the swarm and control of the trajectory state are the battle lines."
Just as with the 9 harness components, the market is moving toward convergence on several patterns: trajectory as a standard artifact, push-based completion for swarms, policy engine as a replaceable component, and sandbox as cattle (disposable infrastructure). The differences are in who controls these components.
"The next generation of tough technical problems in artificial intelligence are AI engineering systems problems."
| Feature | Pi | OpenClaw | Claude Code | Letta | Hermes | iii |
|---|---|---|---|---|---|---|
| Outer while-tool loop | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| File read caps (lines/bytes) | ✓ | ✓ | ✓ | ✓ | ✓ | — |
| Offset/limit pagination | ✓ | ✓ | ✓ | ✓ | — | — |
| Tool result size caps | ✓ | ✓ | ✓ | ✓ | ✓ | — |
| LLM-powered compaction | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Token-threshold trigger | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Context usage estimation | ✓ | ✓ | ✓ | ✓ | ✓ | — |
| Sub-agent isolation | ✓ | ✓ | ✓ | ✓ | ✓ | — |
| System prompt assembly | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Permission/safety layer | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Session persistence | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Lifecycle hooks | — | ✓ | — | — | ✓ | ✓ |
| Built-in pre-packaged skills | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Sub-agent spawning | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Tool-call/result boundary safety | ✓ | ✓ | ✓ | — | ✓ | — |
| Forked sub-agent transcripts | — | ✓ | ✓ | ✓ | — | — |
| Pre-compaction state flush | — | ✓ | ✓ | ✓ | — | — |
| Deduplication of idempotent reads | — | — | ✓ | — | — | — |
| Remote-tunable feature flags | — | — | ✓ | — | — | — |
| Multi-provider model abstraction | — | — | — | — | ✓ | ✓ |
| Messaging gateway | — | ✓ | — | — | ✓ | — |
| Cron / first-class scheduling | — | — | — | — | ✓ | — |
| Profile/workspace isolation | — | — | — | — | ✓ | — |
| OpenTelemetry tracing | — | — | — | — | — | ✓ |
| Worker-level replaceability | — | — | — | — | — | ✓ |
| Durable child-run control (swarm) | — | ✓ | — | — | — | — |
| Push-based completion routing | — | ✓ | — | — | — | — |
| Parameter | Value | System |
|---|---|---|
| File read line cap | 2,000 lines | Pi, OpenClaw, Claude Code, Letta |
| File read byte cap | 50 KB | Pi, OpenClaw |
| File read byte gate (pre-open) | 256 KB | Claude Code |
| Post-read token gate | 25,000 tok | Claude Code |
| File read stat reject | 10 MB | Letta |
| Line character truncation | 2,000 chars | Claude Code, Letta |
| Bootstrap file char cap | 12,000 chars/file | OpenClaw |
| Bootstrap total char cap | 60,000 chars | OpenClaw |
| Bootstrap split ratio | 75% head / 25% tail | OpenClaw |
| Tool result char budget | 16,000 chars / 30% window | OpenClaw |
| Tool result (bash/subagent) | 30,000 chars | Letta |
| Tool result (grep) | 10,000 chars | Letta |
| Per-tool cap | 50,000 chars | Claude Code |
| Per-message aggregate cap | 200,000 chars | Claude Code |
| Oversized tool result preview | 2 KB | Claude Code |
| Tool result cap (Alyx) | 10,000 tok | Alyx (Arize) |
| Reflection subagent budget | 16,000 tok | Letta |
| Policy engine timeout | 5 seconds | iii |
| Parameter | Value | System |
|---|---|---|
| Compaction reserve tokens | 16,384 | Pi |
| Keep recent tokens | ~20,000 | Pi |
| Compaction trigger (history share) | 50% window | OpenClaw |
| Compaction trigger (buffer) | 13,000 tok | Claude Code |
| Effective compaction (200K model) | ~167K tok | Claude Code |
| Post-compaction file re-attach | up to 5 files | Claude Code |
| Compaction prompt sections | 9 sections | Claude Code |
| Fallback head-drop | oldest 20% API groups | Claude Code |
| Compression summary ratio | ~20% | Hermes |
| Compression summary floor | 2,000 tok | Hermes |
| Compression summary ceiling | 12,000 tok | Hermes |
| Compression trigger (Alyx) | 50,000 tok | Alyx (Arize) |
| Reflection trigger | 25 user messages | Letta |
| Token estimation heuristic | 4 bytes/token | Letta, Alyx |
| Tool result cache TTL | 5 minutes | OpenClaw |
| Parameter | Value | System |
|---|---|---|
| Harness components | 9 | Dhinakaran Model |
| Harness jobs | 15 (13-15) | Piccolo / iii Model |
| iii production workers | 11 | iii |
| iii FSM states (current) | 7 (from 11) | iii |
| Prompt tiers | 3 (stable, context, volatile) | Hermes |
| Policy outcomes | 3 (allow, deny, needs_approval) | iii |
| Hook surfaces | 2 (plugin + filesystem) | Hermes |
| Letta subagent types | 7 built-in | Letta |
| Run modes | 3 (plan, ask, agent) | iii |
| OTel span tags | 3 (session, message, function) | iii |
| Latency saved per function call | ~500ms | iii |
| Sandbox deployment scale | 100s-1000s runs | Eval industry |
| Restart artifacts | 2 (trajectory + local data) | General |
Authors: Aparna Dhinakaran (Arize, articles 1, 2, 3, 4, 6), Mike Piccolo (iii, article 5).
Harnesses: Pi (pi-mono / earendil-works), OpenClaw, Claude Code (Anthropic), Letta (Letta Code), Hermes (Nous Research), iii (iii.dev).
Sandboxes: Daytona, E2B, Stripe Minions (Steve Kaliski), Browserbase (Kyle Jeong).
Tools: GrowthBook (feature flags Claude Code), Arize (observability, Alyx).
Contrast: LangChain, LangGraph, CrewAI, AutoGen, OpenAI Agents SDK, OPA, Cedar.
Products: Cursor, Windsurf, Codex.