Skip to content
Documentation menu

Reference

Three-Harness Architecture — trusty-tools

Status: Accepted Version: v2 Subsystem: HARNESSES Owner: Engineering / Architecture Last-updated: 2026-08-07


Purpose & Scope

The trusty-tools workspace organises its AI orchestration crates into three harnesses, each serving a different principal. This page defines what each one is, the two shared crates all three build on, how events flow, and how a harness hands work to another harness.

In scope: harness identity, responsibilities, shared surface, event streams, and the delegation edges that exist in source today.

Out of scope: per-harness internals, tool inventories, model routing, and release procedures. Those live in each harness's own documentation (trusty-mpm, trusty-code, trusty-agents).

Every claim below is stated against the crate source in crates/. Where the source does not settle a question, the page says so rather than guessing — see Known gaps.


Table of Contents

SectionTopic
Harness DefinitionsThe three harnesses: purpose, binary, scope
Comparison TableSide-by-side summary
Shared FoundationsThe two crates all three build on
Event StreamsThe event-driven principle and where each harness stands
Inter-Harness DelegationWhich harness invokes which, and how
Known gapsWhat this page does not establish

Harness Definitions

1. trusty-code — the coding harness

Crate: crates/trusty-code/ — package trusty-code, binary tcode Analogy: Claude Code — a per-project coding orchestration harness.

A tcode serve process runs the PM main loop for one project, reading that project's .claude/ configuration the way Claude Code does: agents, skills, MCP descriptors, CLAUDE.md, and permission grants. It is single-instance per project root — multiple CLI and TUI clients attach to the one daemon. Serving without a project is also a first-class state, used for chat and planning before a project is chosen.

Clients speak JSON-RPC 2.0 to it, over either NDJSON on stdio (serve --stdio) or loopback HTTP (serve --http, POST /rpc plus GET /health). Exactly one transport is selected per process. The method surface covers session lifecycle (session.create, session.status, session.send, session.cancel, session.get_transcript), task execution (task.run), workstreams, and agent/skill/filesystem introspection. Session events reach an attached client as a session.event notification on the stdio transport, or as an SSE stream at GET /sessions/{id}/events — which replays a session's backlog before going live — on the HTTP transport.

What it owns:

  • The tcode binary, its JSON-RPC surface, and the sessions it holds
  • Per-project agent configuration, read from .claude/agents/<name>.md (markdown with YAML frontmatter — the older TOML loader was removed)
  • Per-project skill loading from .claude/skills/
  • Workstreams: durable named groupings of sessions bound to the daemon's project
  • Per-agent model routing across AWS Bedrock and OpenRouter
  • The PM main loop for code-generation, edit, run, and test cycles

What it does not own:

  • Multi-project session management (trusty-mpm)
  • Non-coding assistant workflows (trusty-agents)
  • Claude Code hooks. tcode is API/CLI/TUI-driven and has no hooks support; hooks are a Claude Code shell-level feature, and tcode operates above that layer via its event bus.
  • Search, memory, or analysis infrastructure (trusty-search / trusty-memory / trusty-analyze / trusty-common)

tcode run-workflow is declared in the CLI but not implemented — it exits non-zero with an explanatory message. Treat declarative workflow execution as absent from this harness today.


2. trusty-mpm — the meta-harness

Crate: crates/trusty-mpm/ — package trusty-mpm, binaries tm and trusty-mpm (the same program under two names) Analogy: Claude MPM — a PM-style multi-agent orchestrator over coding work.

trusty-mpm is the operator's control plane for multi-project, multi-session work. The daemon, TUI, Telegram bot, Slack integration, and MCP bridge are all subcommands of the one tm binary, not separate executables:

SurfaceInvocation
Background daemon (loopback HTTP API)tm daemon, or tm start / tm serve
MCP stdio bridge for Claude Codetm serve --stdio
Terminal dashboardtm tui
Telegram bottm telegram
Slack integrationtm slack
Unattended fleet supervisortm supervisor

tm serve --stdio is a thin proxy: it forwards JSON-RPC from an MCP client to the daemon's loopback POST /rpc, auto-starting the daemon if needed. This is the form wired into .mcp.json. The daemon is the durable process; the bridge is stateless.

What it owns:

  • The always-on daemon: HTTP API, hook relay, session registry, watchers (crates/trusty-mpm/src/daemon/)
  • Session control, service discovery, and agent/skill deployment via the tm CLI
  • Managed sessions: provisioned in isolated git worktrees, running a runtime inside a tmux pane (crates/trusty-mpm/src/session_manager/)
  • The MCP tool catalog — 33 tools spanning orchestration, session lifecycle, console metrics, the project registry, and session-manager proxying (crates/trusty-mpm/src/mcp/tools/)
  • Session overseer and circuit breaker (src/core/overseer.rs, src/core/circuit.rs)
  • Cross-project session registry (src/core/session_store.rs)
  • The OrchestratorBackend trait binding the MCP layer to the daemon (src/mcp/mod.rs)

What it does not own:

  • Executing code itself. It provisions and oversees a runtime that does.
  • General knowledge-worker assistant workflows (trusty-agents)
  • Search, memory, or analysis infrastructure

The runtime a managed session spawns is selected by RuntimeKind (crates/trusty-mpm/src/runtime/): claude-code (the default — the Claude Code CLI over OAuth) or tcode (trusty-code over the direct Anthropic API). Both are launched into a tmux pane. So trusty-mpm delegates to trusty-code only when an operator selects that runtime; it is an alternative backend, not the sole path.


3. trusty-agents — the agentic harness

Crate: crates/trusty-agents/ — package trusty-agents, binary tagent

A general-purpose agentic harness for non-coding workflows: knowledge retrieval, memory management, scheduling, communications, and domain-specific assistant personas. It uses a PM-orchestrator-plus-sub-agent pattern, but it is not a coding harness.

Sub-agents run one of two ways, chosen per agent: as a subprocess exchanging NDJSON over stdin/stdout, or as a tokio task in the PM process. The in-process path avoids a per-delegation startup cost and is used for read-heavy agents; agents that shell out or execute user code stay on the subprocess path, where process isolation is part of the contract.

What it owns:

  • The PM orchestrator loop and the in-process delegate_to_agent tool (src/ctrl/, src/tools/delegate.rs)
  • A heuristic intent classifier and the deterministic backend router (src/intent/)
  • MCP service bridge: wraps any MCP server's tools as ToolExecutor instances (src/tools/mcp_service_tools.rs)
  • Tool registry, RBAC tiers, and identity primitives (src/tools/, src/rbac/, src/identity/)
  • Assistant personas defined as TOML agent configs, with markdown skill injection
  • Transports: interactive REPL and CTRL multi-project dispatcher (src/repl/, src/ctrl/), HTTP API with an SSE event stream (src/api/), Slack (src/slack/), Telegram (src/telegram/), and a stdio MCP server (tagent mcp-serve)
  • A declarative workflow engine for phase-based pipelines (src/workflow/)
  • A tmux session manager of its own (src/tm/), distinct from trusty-mpm

What it does not own:

  • Per-project coding workflow enforcement (trusty-code)
  • Multi-project session management, circuit breaker, or hook relay (trusty-mpm)
  • Search infrastructure (trusty-search)
  • Memory storage engine (trusty-common's memory-core feature, plus trusty-memory)

The harness-adapter framework — which recognises whether a Claude Code, claude-mpm, Codex, Gemini, or shell process occupies a given pane — and the JSON-backed session ledger both live in trusty-agents-common and are re-exported by this crate.


Comparison Table

Dimensiontrusty-codetrusty-mpmtrusty-agents
AnalogyClaude CodeClaude MPMA general agentic assistant
Primary userDeveloper / CI pipelineOperator / PM roleKnowledge worker
ScopeOne project, coding tasksMany projects, orchestration controlAny domain, non-coding workflows
Binarytcodetm (alias trusty-mpm)tagent
Core loopPM main loop per projectDaemon + session manager + hook relayPM main loop per persona
Agent modelCoding sub-agents from .claude/agents/*.mdDeploys agents; oversees the runtime that runs themDomain personas plus an MCP tool bridge
TransportsJSON-RPC over stdio or loopback HTTP; TUI clientLoopback HTTP daemon; MCP stdio bridge; TUI; Telegram; SlackREPL/CTRL; HTTP API with SSE; Slack; Telegram; MCP stdio
Delegates toIts own sub-agentsA runtime per managed session (claude-code or tcode)tcode or tm, via dispatch_task
LicenseMITMITMIT

Every crate in the workspace inherits license = "MIT" from [workspace.package] in the root Cargo.toml.


Shared Foundations

Two crates carry the commonality. No harness's behaviour is built on another harness's library surface: where one harness needs another, it invokes its binary, as described under Inter-Harness Delegation.

trusty-common

crates/trusty-common/ is the workspace-wide foundation, used well beyond the three harnesses. Its module surface sits behind feature flags so each consumer pulls in only what it needs.

Always on, no feature flag:

ModuleRole for harnesses
chatChat-completions client — the LLM call abstraction all three use
claude_configReads .claude/ configuration (agents, CLAUDE.md, permissions)
project_discoveryLocates project roots from a working directory
shutdownSIGTERM + SIGINT graceful-shutdown signal for every daemon
log_bufferBounded in-memory log ring buffer for log-tail endpoints
sys_metricsRSS / CPU sampling for health endpoints

Feature-gated modules the harnesses draw on:

FeatureModulePurpose
mcptrusty_common::mcpJSON-RPC 2.0 / MCP primitives — envelope types, the stdio dispatch loop, OpenRPC discovery. Every MCP server in the workspace imports from here.
rpctrusty_common::rpcGeneral-purpose JSON-RPC client plus stdio and HTTP transports
stdio-mcp-clienttrusty_common::stdio_mcp_clientClient that spawns and talks to a stdio MCP server
memory-coretrusty_common::memory_coreMemory palace storage engine — trusty-memory's backend, also used by trusty-agents
session-namingtrusty_common::session_namingThe one canonical tmux session-naming rule, shared so trusty-mpm's and trusty-agents' session managers never orphan each other's sessions
search-indextrusty_common::search_indexBest-effort "ensure this project is indexed" entry point, used by trusty-mpm at session launch and trusty-code at task start
axum-servertrusty_common::serverShared axum middleware and the same-origin write guard used by every HTTP daemon
inference-clienttrusty_common::inferenceShared OpenAI-compatible adapter layer and provider key configuration
catchuptrusty_common::catchupIncremental catch-up context generation across git, memory, and session sources

trusty-agents-common

crates/trusty-agents-common/ is the harness-layer shared crate. All three harness crates depend on it. It holds what the harnesses share with each other but not with the rest of the workspace:

  • ToolExecutor, ToolResult, ToolExecutionTier, ServiceTier (RBAC tiers), and AgentPlugin — the plugin surface an external agent crate implements
  • adapters — the harness-adapter framework and its registry
  • session_registry — the JSON-backed session ledger
  • runner — the AgentRunner dependency-injection seam and its value types
  • events — the unified HarnessEvent envelope and process-global bus (see below)
  • harness_doc — the shared harness-understanding instructions consumed by both trusty-mpm's session-manager prompt and trusty-code
  • compress — portable tool-output compression
  • perf — portable token/cost value types

Event Streams

Architectural principle: the harnesses are event-driven. A harness that blocks synchronously at each step cannot fan out to sub-agents, stream progress to a UI, or relay across a process boundary. Each harness therefore publishes to a broadcast channel that subscribers fan out from, rather than operating in pure request-response mode.

That principle holds in all three. The unification does not yet.

Where each harness stands

HarnessBusPayload typeExternal stream
trusty-agentsProcess-global tokio::sync::broadcast (src/events.rs)Its own Event enumSSE over the HTTP API; UDS NDJSON MessageBus between projects (src/bus/)
trusty-codeProcess-global tokio::sync::broadcast (src/events.rs)SessionEventEnvelope wrapping a tagged Event, with a per-session monotonic sequence numbersession.event JSON-RPC notifications on stdio; SSE at /sessions/{id}/events on HTTP
trusty-mpmDaemon-held tokio::sync::broadcast (src/daemon/state/)Untyped serde_json::ValueSSE at /events and /sessions/{id}/events; a poll endpoint at /events/poll

trusty-agents and trusty-code each also relay events from a child process to their parent by prefixing NDJSON lines on stderr. Both use the same prefix, __OMPM_EVENT__ , each from its own crate-local constant rather than a shared one.

The unified envelope, and why it is not yet the wire format

trusty_agents_common::events defines HarnessEvent — one envelope carrying a HarnessSource, a tagged HarnessPayload, and a lag-aware subscription API — together with the process-global bus and a subscription Filter. It was landed as a foundation with no emit sites migrated onto it.

That is still the state. No harness crate imports trusty_agents_common::events: the three buses above each carry their own payload type, and the envelope's own relay prefix, __HARNESS_EVENT__ , is not the one either subprocess relay actually emits. Consuming the unified envelope is the outstanding work, and until it lands, an event crossing a harness boundary is re-encoded rather than forwarded.


Inter-Harness Delegation

Delegation graph

All three are independently operator-facing — each has its own human entry points, and none is a front door to the others. Two delegation edges leave trusty-agents, both through the same tool; one leaves trusty-mpm.

     operator             operator              operator
  REPL / Slack /       tm CLI / TUI /         tcode CLI /
  Telegram / HTTP        Telegram                 TUI
         │                    │                    │
         ▼                    ▼                    ▼
 ┌───────────────┐   ┌───────────────┐   ┌───────────────┐
 │ trusty-agents │   │  trusty-mpm   │   │  trusty-code  │
 │    agentic    │   │     meta      │   │    coding     │
 └───────┬───────┘   └───────┬───────┘   └───────▲───────┘
         │                   │                   │
         │  (2) dispatch_task, route = Tm        │
         ├──────────────────►│                   │
         │                                       │
         │  (1) dispatch_task, route = Tcode     │
         └──────────────────────────────────────►│
                             │                   │
                             │  (3) --runtime tcode
                             └──────────────────►│

Delegation edges

#FromToTriggerTransportReturned
1trusty-agentstrusty-codedispatch_task with route_task returning Tcode — a repo-file token or a coding verb in the task textOne-shot subprocess tcode run-task pm <task> --project <dir> --json, which blocks until the daemon-owned session reaches a terminal stateThe full transcript, with backend identity scrubbed
2trusty-agentstrusty-mpmdispatch_task with route_task returning Tm — orchestration, project, session, issue, or multi-agent vocabulary, and the default when no signal is presentSpawns tm serve --stdio as an MCP client, calls session_new, polls session_activity, then session_decommissionObserved pane transcript, identity scrubbed
3trusty-mpmtrusty-codeA managed session created with --runtime tcode instead of the default claude-codeTcodeAdapter verifies tcode is on PATH and sends tcode run-task <agent> <task> --project <cwd> into the session's tmux paneSession events via the hook relay and pane capture

Two properties of edge 2 are worth stating plainly. trusty-mpm has no synchronous run-this-and-block RPC, so the bridge polls with a bounded attempt count and returns whatever transcript it has observed when the budget runs out. And the bridge scrubs backend identity from both success and error paths, so a persona calling dispatch_task never learns that tm or tcode exists.

Which backend a task routes to is decided by intent::route::route_task, a pure function with no I/O: a repo-file token or a hard coding verb wins outright, then any orchestration vocabulary, then a generic code verb, and Tm otherwise.

What is not cross-harness delegation

  • trusty-search, trusty-memory, and trusty-analyze are tool-layer services, not harnesses. All three harnesses call them over HTTP or MCP as tools.
  • A harness invoking its own sub-agents is intra-harness delegation.
  • trusty-mpm spawning the Claude Code CLI — its default runtime — is not a trusty-tools harness boundary at all.

Known gaps

Stated explicitly so nothing here reads as more settled than it is.

  • The boundary between trusty-mpm and trusty-agents is not fully resolved in source. Both carry a session manager, both carry tmux integration, and trusty-agents carries a workflow engine that trusty-code does not. The shared tmux session-naming rule in trusty_common::session_naming exists precisely so the two managers do not orphan each other's sessions, which is a mitigation of the overlap rather than a resolution of it. This page describes what each crate contains; it does not assert where the line should finally fall.
  • Event unification has a landed foundation and no consumers. The section above states which bus each harness actually runs. Anything beyond that — a migration order, a target date — is not established by source.
  • trusty-mpm's hook relay carries Claude Code hook events, which is why edge 3 (--runtime tcode) has a different observability shape from the default runtime: trusty-code emits no hooks. How completely the pane-capture path substitutes for that is not something this page establishes.

References

Source of truth for everything above, in the repository:

  • crates/trusty-code/src/lib.rs, src/main.rs, src/serve/ — the coding harness
  • crates/trusty-mpm/src/bin/tm/cli/mod.rs — the tm subcommand surface
  • crates/trusty-mpm/src/mcp/tools/mod.rs — the MCP tool catalog
  • crates/trusty-mpm/src/runtime/RuntimeKind and the two runtime adapters
  • crates/trusty-agents/src/tools/pm_bridge.rs, src/tools/pm_bridge_backend.rs, src/intent/route.rs — the dispatch_task bridge and its router
  • crates/trusty-agents-common/src/lib.rs — the harness-layer shared surface
  • crates/trusty-common/Cargo.toml — the feature list behind the table above
  • docs/adr/0004-three-harnesses-shared-event-driven-common.md and docs/adr/0019-unified-ipc-messaging-on-event-bus.md — the decision records behind the three-harness split and the event bus

trusty-tools

One Cargo workspace for the trusty-* tooling ecosystem. MIT licensed.

© 2026 · MIT · github.com/bobmatnyc/trusty-tools