Architecture
The harness is the layer you use when explicit message delivery is not enough and you want a reusable agent loop on top of the runtime. It does not replace the runtime. It uses the runtime as its execution substrate and adds a structured way to turn model calls, tool calls, and handoffs into a coherent workflow.
The pieces matter in relation to one another.
Task gives ordinary orchestration
code a home above the runtime. The lower-level
Agent is the harness agent type you
bind to a runtime and address by AgentId. It binds addressed runs to one
descriptor, one lifecycle, and one runner. The local
agentlane.harness.agents.DefaultAgent
provides the smaller high-level run(...), run_stream(...), and
run_events(...) surface. The
agentlane.harness.shims
package provides the mutating extension seam for instructions, tools,
conversation history, and per-run context shaping. The
Runner starts a
RunLoop for each run. The loop
executes model and tool turns and produces a
RunResult.
What The Harness Adds
The runtime already knows how to deliver messages and preserve ordering. The harness adds a higher-level story on top of that:
Taskgives ordinary application orchestration a home above the runtime- the lower-level addressed
Agentkeeps static configuration and resumable conversation state together DefaultAgentprovides the smaller localrun(...)surface for straightforward usageDefaultAgentalso providesrun_stream(...)for live model events on that same primary conversation lineDefaultAgent.run_events(...)provides a broader ordered event feed for model events, agent and LLM lifecycle, tools, handoffs, and compact state snapshots- shims can adjust effective instructions, tools, conversation history, and transient turn context without widening the core harness types
Runnerexecutes the model loop for one run- tools and handoffs become first-class parts of that loop
That separation matters because queueing and persistence are different problems from model reasoning. The harness keeps them apart.
The Main Flow
At a high level, a run moves like this:
Application / caller
|
| run(...), run_stream(...),
| or run_events(...)
v
+---------------------------+
| DefaultAgent |
| high-level local agent |
+-------------+-------------+
|
v
+---------------------------+
| RuntimeEngine |
| routing + instance reuse |
+-------------+-------------+
|
| addressed caller path:
| send_message(run_input)
v
+---------------------------+
| Agent |
| descriptor + lifecycle |
+-------------+-------------+
|
v
+---------------------------+
| AgentLifecycle |
| queue + persisted state |
+-------------+-------------+
|
v
+---------------------------+
| Bound shims |
| turn preparation + |
| optional message changes |
+-------------+-------------+
|
v
+---------------------------+
| Runner |
| model loop |
| tools + handoffs |
+------+------+-------------+
| |
| +--------------------+
| |
v v
+--------------+ +-------------------+
| ToolExecutor | | runtime messaging |
| local tools | | delegated agents |
+--------------+ +-------------------+
The important point is that the runtime still owns delivery and instance reuse. The harness owns the meaning of a run.
Why Lifecycle And Runner Are Separate
The lifecycle owns sequencing. If multiple inputs arrive for the same concrete agent, it queues them and makes sure only one run is active at a time.
Bound shims sit between lifecycle and runner. They are bound once for the concrete agent instance, then get a chance to shape each prepared turn before the runner builds the next model request.
The runner owns the model loop inside that run. It builds the next request, calls the model, inspects the response, executes tools or handoffs, and either continues or returns a final result.
That split is what makes resumable runs practical. The lifecycle can keep a
stable baseline of RunState, while the runner works on a private copy for the
current turn.
agentlane.harness.agents.DefaultAgent
sits one level above that lower-level path. It provisions a local runtime when
needed, keeps a primary RunState between repeated run(...) and
run_stream(...) or run_events(...) calls, and still routes through the same lower-level
Agent plus Runner stack underneath.
Request Ownership
The harness public boundary is deliberately not a low-level message-dict API.
Instead:
- callers either use
DefaultAgent.run(...)locally or sendRunInputto the lower-level addressedAgent - the lifecycle turns that into a working
RunState - bound shims can adjust the prepared turn, including persisted conversation history, and may replace the final message list for one model call
- the runner turns that prepared turn into canonical model messages
- provider clients receive the shared
agentlane.modelsrequest shape
That keeps raw provider wire formats out of application code.
The same boundary applies to streaming. DefaultAgent.run_stream(...) remains
the model-event-only primitive, and DefaultAgent.run_events(...) is the
higher-level lifecycle stream built from the same runner loop. Run events wrap
model stream events and add agent start/end, LLM start/end, tool start/end,
first-class handoff start/end, and compact state snapshots at run start,
prepared-turn, tool-round, and run-end boundaries.
When you use runtime.send_message(...), you still get one final result after
the run finishes. Live per-event streaming is currently available through the
local harness streaming APIs instead. Distributed transport streaming is a later
concern.
Approval requested/resolved events are exposed by ToolApprovalBroker.events().
Hosts can pass that async event source to run_events(approval_events=...) to
merge brokered approval lifecycle events into the same ordered run-event stream
without adding a second approval path to the runner.
Where To Read Next
Start with Harness Tasks if you need orchestration without an LLM loop. Read Harness Default Agents for the smallest local developer surface. Read Harness Shims if you need to extend run behavior without changing the core harness types. Read Tools for first-party tool definitions and shim integration. Read Harness Compaction for persisted history rewrite contracts. Read Harness Agents to understand the lower-level addressed agent type. Read Harness Runner when you want the actual loop behavior. Read Harness Distributed Agents when a harness agent needs to coordinate explicit worker runtimes.