Harness Architecture¶
HarnessAgent is a thin wrapper around ReActAgent that packages the engineering capabilities long-running agents need — workspace-driven persona, long-term memory, subagent orchestration, sandbox isolation, skill composition, plan mode — into a single builder.
A bare ReActAgent only handles “one request → reason → tool → reply”. Harness answers a different set of questions: how does the next turn pick up where the last left off, how does context stay bounded, how do users stay isolated, how do dangerous actions get reviewed, how do reusable capabilities accumulate.
Installation, dependency, and an end-to-end “first
HarnessAgent” walkthrough live in Quickstart. This page is architecture only.
Core working principle¶
Three things to keep in mind:
1. Capabilities layer onto the reasoning loop, not into it. Workspace injection, compaction, subagents, sandbox, Plan Mode — each hooks into key moments of the ReAct loop. The core algorithm is untouched; Harness only adds.
2. Capabilities don’t depend on each other; they share three objects. Each capability does one job and is unaware of the others. They cooperate through:
RuntimeContext— who is speaking in this call:sessionId,userId, plus arbitrary extras. Not persisted.The workspace — who reads and writes which files. Where they physically land (local disk, sandbox, KV store) is a configuration choice.
Session— how runtime state is restored across calls.
3. Built-ins run in a fixed order; your middleware runs first.
Harness wires its built-in middleware in a fixed order at build time. Anything you add via .middleware(...) runs before Harness’s built-ins.
Core components¶
Each capability answers one problem; opt in on the builder.
Capability |
What it solves |
Builder hook |
Detail |
|---|---|---|---|
Workspace-driven persona |
Persona, knowledge, subagent specs, skills, MCP allowlist all live as files |
|
|
Session persistence |
Same |
on by default; override with |
|
Two-layer long-term memory |
Facts in long conversations sediment into |
|
|
Conversation compaction |
History bounded; force-retry on real overflow |
|
|
Large tool-result offloading |
>80K-char results moved to disk + placeholder |
|
|
Subagent orchestration |
Delegate to children, sync or background, with auto push-back |
|
|
Pluggable filesystem |
Local + shell / shared store / sandbox without code changes |
|
|
Sandbox isolation |
Files and commands isolated; cross-call recovery; multi-replica |
|
|
Plan Mode |
Read-only think-first phase with HITL exit |
|
|
Skill composition |
Skills from Git / Nacos / MySQL / classpath / workspace |
|
|
MCP integration & tool allowlist |
Declarative MCP servers + allow/deny per tool |
|
How state flows¶
Three layers exist; the framework moves data between them automatically.
In-call state —
AgentState(conversation context, permission rules, Plan Mode state, tool state) plusRuntimeContext(sessionId,userId, sandbox handle, extras).Cross-call state — auto-saved at the end of every
call()and auto-loaded on the next: the runtime snapshot underagents/<agentId>/context/<sessionId>/, the never-compacted full conversation log undersessions/<sessionId>.log.jsonl, subtask records, and sandbox metadata.Long-term memory — accumulated across sessions:
memory/YYYY-MM-DD.mdis append-only, periodically merged intoMEMORY.mdby a throttled background job;MEMORY.mdis injected into the system prompt every reasoning step.
Three invariants worth remembering:
The system prompt is rebuilt every reasoning step, so edits to
AGENTS.mdorMEMORY.mdtake effect immediately — no restart.Compaction, memory distillation, and background maintenance are throttled; they don’t run every turn.
AgentStateis persisted by core’sReActAgent+Session. Harness no longer adds its own persistence hook.
Adding your own middleware¶
To insert custom behaviour without bypassing Harness’s plumbing:
Use
.middleware(...)— your middleware runs before all Harness built-ins.Read
RuntimeContextfrom the agent for the current call’s identity (userId/sessionId).For workspace I/O, go through
harnessAgent.getWorkspaceManager()— it routes correctly under sandbox or remote-store modes.java.nio.Fileswrites to the host disk and will land in the wrong place outside local mode.