harness
v0.5.1Meta-worker that composes the modular Node workers backing the iii chat surface.
full markdown
/workers/harness.md?version=0.5.1. paste it into an llm prompt or pipe it through curl from a worker.install
dependencies
readme
The iii harness
The harness is not a layer on top of your backend. On iii, it is the backend.
Many setups keep the agent loop in one process and everything else (queues, HTTP, state, traces) in another. Tool calls cross that boundary; retries and traces rarely line up.
On iii, agents are workers. Tools are functions. Handoffs use the same triggers and queues as the rest of the system.
This package is the production harness for that model: turn orchestration, approvals, sessions, providers, context compaction, and budgets, all as iii workers next to shell, storage, database, and whatever you add.
Read The Harness Is the Backend by Mike Piccolo for the full argument.
What you get
One trace. Each hop is a trigger() on the bus. Trace IDs propagate across workers, languages, and queue steps. You debug one runtime, not separate logs aligned by timestamp.
Live discovery. Workers register functions on connect; the engine keeps a catalog. Agents and the console see what the system can do today, including workers added without redeploying the orchestrator. Providers self-register; the model catalog fills from discovery, not a hardcoded seed.
Composition, not frameworks. Thin vs thick harnesses map to how many functions you register and how you wire triggers. Fewer functions for a lean loop; approval rules and extra workers for more structure.
New capability, new worker. When the harness needs something else (shell, database, coder, another provider), you add a worker, not a fork of the orchestrator. Published workers install from the iii worker registry with iii worker add ; they register on the iii engine and show up in the live catalog.
Turns, approvals, budgets. Seven-state durable turn FSM with queue-backed steps. Approval gate with YAML permissions, parallel tool batches, pending state across reload, fail-closed when policy is unreachable. Workspace and agent budget caps. Five provider workers behind one registry.
Context compaction. Long sessions exceed model windows. The context-compaction worker compacts history as turns accumulate and backs the console /compact command.
What ships here
Fifteen workers in one TypeScript package, one folder per worker, one feature per file:
| Concern | Workers |
|---|---|
| Orchestration | turn-orchestrator (durable turn FSM), hook-fanout |
| Governance | harness (permissions, provider registry, UI fanout), approval-gate |
| Sessions | session (branching session tree + inbox queues) |
| Context | context-compaction (keeps long sessions inside the model window) |
| Models | models-catalog, provider-anthropic, provider-openai, provider-kimi, provider-lmstudio, provider-llamacpp |
| Cost | llm-budget |
Rust workers (shell, iii-directory) and engine builtins (state::*, stream::*, iii::durable::*) stay on the same bus; this package does not reimplement them.
Quickstart
- Install iii:
curl -fsSL https://install.iii.dev/iii/main/install.sh | sh - Verify the install:
iii --version - Add the harness and console workers:
iii worker add harness console - Start the engine:
iii --config config.yaml - Open the console at
http://127.0.0.1:3113
Chat, approve/deny, model picker, and trace explorer ship in one binary (console).
Tools, orchestration, governance, and observability use the same worker, trigger, and function model as the rest of iii.
Further reading
api reference (json)
{
"functions": [
{
"description": "Internal: fans out a newly-created session id to ui::sessions::changed::<browser_id>.",
"metadata": {},
"name": "harness::fanout::session_created",
"request_schema": {},
"response_schema": {}
},
{
"description": "List providers declared to the harness.",
"metadata": {},
"name": "harness::provider::list",
"request_schema": {},
"response_schema": {}
},
{
"description": "Read a host file via shell::fs::read, drain its channel, and return a {content:[{text}], details:{size, truncated, bytes_read}} envelope (max 256 KiB inline by default).",
"metadata": {},
"name": "harness::fs::read_inline",
"request_schema": {},
"response_schema": {}
},
{
"description": "Remove a browser's subscription to a session (or its all-sessions sub if session_id is null).",
"metadata": {},
"name": "ui::unsubscribe",
"request_schema": {},
"response_schema": {}
},
{
"description": "Register a browser's interest in a session (or all sessions if session_id is null).",
"metadata": {},
"name": "ui::subscribe",
"request_schema": {},
"response_schema": {}
},
{
"description": "Internal: agent::events fanout handler.",
"metadata": {},
"name": "harness::fanout::agent_event_handler",
"request_schema": {},
"response_schema": {}
},
{
"description": "Check a function call against iii-permissions.yaml; returns allow, deny, or needs_approval.",
"metadata": {},
"name": "policy::check_permissions",
"request_schema": {
"$ref": "#/definitions/CheckPermissionsPayload",
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"CheckPermissionsPayload": {
"additionalProperties": false,
"properties": {
"args": {
"additionalProperties": {},
"type": "object"
},
"function_id": {
"minLength": 1,
"type": "string"
}
},
"required": [
"function_id"
],
"type": "object"
}
}
},
"response_schema": {}
},
{
"description": "Browser kickoff: forward payload to run::start. Used by console/web over the iii-browser-sdk.",
"metadata": {},
"name": "harness::trigger",
"request_schema": {},
"response_schema": {}
},
{
"description": "Resolve a provider credential + settings (api_url, max_tokens) from the harness configuration. Server-side only.",
"metadata": {},
"name": "harness::provider::resolve",
"request_schema": {},
"response_schema": {}
},
{
"description": "Self-declare an LLM provider (id, config schema, defaults) into the dynamic harness configuration schema.",
"metadata": {},
"name": "harness::provider::register",
"request_schema": {},
"response_schema": {}
}
],
"triggers": []
}