Terret
A Ruby-native, model-agnostic agent harness where everything is a plugin, informed by DeepSeek Harness. A terret is the ring on a horse harness that the driving reins pass through: the small component that lets one driver guide any horse. Hames is the kernel underneath (the load-bearing pieces of the harness): services in a context, typed events with four dispatch modes, reversible effects, dependency-driven boot.
Twelve gems in one repo:
-
gems/hamesis the kernel. Services in a context, typed events, reversible effects, dependency-driven boot. It knows nothing about LLMs and is reusable for any plugin-composed application. -
gems/terret-coreis the harness built on it. Session log, tools pipeline, agent loop, LLM seam (vocabulary,AdapterBaseretry policy,FakeAdapter). -
gems/terret-openrouteris the one real adapter: OpenRouter's OpenAI-compatible API behindctx.llm, streaming SSE with tool calling and usage accounting. The transport is injectable, so its unit tests need no network and no gems; only the defaultAsyncTransportrequiresasync-http. -
gems/terret-store-sqliteis the durable session store: the append-only log, one event per row in SQLite (WAL), behind thectx[:session_store]seam. Memory and JSONL providers live interret-core; the store row is explicit in every boot. -
gems/terret-wsis the v1 interface: one WebSocket per agent behindctx[:ws], the wire frames, exact replay-then-tail on the session log; the wire contract is indocs/protocol.md; only the real endpoint requiresasync-websocket. -
gems/terret-acpis the second interface: an Agent Client Protocol server behindctx[:acp]so an editor can drive an agent over JSON-RPC on stdio. It consumessession/eventand drivesctx[:loop], the same two seams the socket does, on a different transport, with no change to core; mapping indocs/acp.md. stdlib-only, no network gem. -
gems/terret-mcpis the MCP client: manceps-backed stdio and streamable-HTTP servers mounted asmcp__<server>__<tool>sources behindctx[:tools], per-server approval, per-call timeouts, the allow list interret-core; mapping indocs/mcp.md. -
gems/terret-morphis actx[:summarizer]provider: Morph's Compact API on the wire proven in the deployed agora integration. It compresses a session's history extractively instead of asking a model to write a summary. Every failure declines to nil and never raises. An injectable transport keeps its unit tests off the network. -
gems/terret-execis the execution world:ctx[:fs], whose every path is contained to a granted workspace directory;ctx[:subprocess], spawn and PTY under the one reactor with cooperative cancellation;ctx[:shell], one persistent bash per agent whose cwd and environment survive between calls;ctx[:terminals], named long-lived PTYs; and thectx[:sandbox]seam every argv passes through before it spawns. -
gems/terret-tools-stdis the standard tool roster, carrying Claude Code's names verbatim (Read,Write,Edit,Glob,Grep,Bash,WebFetch, andterminal_open/input/read/close), registered on those seams with honestmutatingandapprovalmetadata.Bashderives its approval from whether the sandbox isolates.WebFetchsits behind a deny-by-default domain policy re-checked on every redirect hop. -
gems/terret-sandbox-dockeris the container sandbox: one patch row moves the whole execution world into a long-lived container, with each workspace directory bind-mounted at the same absolute path and--network noneby default. -
gems/terretis the meta-gem: the composition layer and thetrtcommand. Bundles ship ordered config rows, profiles stack bundles, patches adjust rows by id, andTerret.boothands the result to the Hames loader. Which plugins run, in what order, and with what config, is a question YAML answers; nobody edits Ruby to change it. Shipsterret-base(the log, the harness, the model seam, the execution world sandboxed with the network denied, the standard tool roster, and a policy floor that starts closed), theheadlessprofile template, andtrt boot/trt dump-config/trt doctor/trt acp. Contract indocs/composition.md.
Status
Milestones M0 through M8 are shipped: the Hames kernel, the session log with
the "model-visible means logged" invariant, the tools pipeline, the agent
loop, the OpenRouter adapter, durable SQLite sessions, the WebSocket
interface, and the MCP client. Long-lived agent hardening shipped durable
approvals, resumable turns that survive a kill -9, context compaction behind
a summarizer seam, session titling, per-session cost accounting, and
hot-reloadable per-agent policy. The execution world added the filesystem,
subprocess, shell, and terminal seams under workspace scoping, the standard
tool roster built on them, credential redaction at both the tool pipeline and
the log-append boundary, and a sandbox seam whose docker provider moves
everything a tool executes into a container from one config row. M8 added
subagents and the release. The Task tool delegates to a child agent over a
subagent seam: a fresh session, run to completion, with its text returned.
Background job_* tools and TodoWrite shipped alongside it, plus a tool
barrier that runs a message's concurrency: :parallel calls together on the
reactor while a :serial tool is a barrier of one. The meta-gem's composition
layer and the trt CLI (boot/dump-config/doctor/acp) shipped too, and
so did the ACP editor interface, a bench lane with regression floors, and a
ctx[:credentials] seam (ENV-first, an optional AES-256-GCM store, every
resolved value fed to the scrubber). trt doctor validates a profile against
each service's Hames::Schema. A security pass closed out the milestone: it
made the allow-list floor authoritative, folded hash keys through the log
scrubber, gated config-borne Ruby behind an explicit consent flag, and capped
socket replay. LLM::FakeAdapter (canned script replay) remains the test/demo
default; the OpenRouter path is proven by canned-wire tests plus a live smoke
lane. Session payloads are primitives at the append boundary; typed parts
encode through LLM.encode_part.
Install with gem install terret: the meta-gem depends on the base roster
and ships the terret-base bundle. All twelve gems are published to
RubyGems. Ten are at 0.1.0; hames and terret-core are at 0.1.1,
which is the first build of those two carrying their full file list. Every
dependency is ~> 0.1, so the versions resolve together.
The full roadmap is docs/terret-implementation-plan.md; see its §12 for
milestone detail and §14 for the deferrals recorded along the way. Note the
plan has drifted from the code in places: it specifies RSpec (this uses
minitest), Ruby 3.4+ (this targets 4.0.6), and a separate terret-llm gem
(the vocabulary lives in terret-core). Treat the code as current and the
plan as intent.
Commands
rake test # all suites, plain minitest, no bundler needed
rake events:catalog # regenerates docs/events.md
rake config:catalog # regenerates docs/config-catalog.md
rake bench # bench/README.md — chunk throughput + dispatch overhead
rake bench BENCH_FLOORS=1 # also asserts against bench/floors.yml (CI runs this)
trt doctor --profile headless # validate a profile's config without booting
trt acp --profile zed # ACP on stdio for an editor; needs terret-acp (docs/zed.md)
ruby examples/headless_demo.rb
OPENROUTER_API_KEY=... ruby examples/openrouter_demo.rb # real model; needs async-http
bundle exec ruby examples/ws_demo.rb # real websocket loopback demo
bundle exec ruby examples/mcp_demo.rb # MCP tools from a local stdio fixture
ruby examples/lifecycle_demo.rb # park/resume, compaction, titling, cost, hot policy
ruby examples/exec_demo.rb # file tools, shell, terminals, redaction; the container act needs docker
ruby examples/subagent_demo.rb # Task delegation, background jobs, TodoWrite, the tool barrier
ruby examples/boot_demo.rb # bundles, profiles, patches; dump-config, boot, one turnRuby 4.0.6, pinned in .ruby-version and mise.toml. hames and
terret-core have zero runtime dependencies beyond stdlib, a deliberate
design constraint. Network-touching dependencies belong in adapter/interface
gems (terret-openrouter carries async-http), never in the kernel or core.
Documentation
-
docs/terret-implementation-plan.mdis the full roadmap and design rationale. -
docs/hames-primer.mdcovers the kernel on its own terms: services in a context, reversible effects, the four dispatch modes, config rows and reconfigure, andHames::Schema. It carries no LLM or agent vocabulary, because the kernel has none. -
docs/cookbook/holds worked, end-to-end recipes for building on Terret: adding a tool, adding a provider, adding a bundle. Start atdocs/cookbook/README.md. -
docs/protocol.mdis the WebSocket wire contract (terret-ws). -
docs/acp.mdis the Agent Client Protocol mapping (terret-acp). -
docs/mcp.mdis the MCP tool-source mapping (terret-mcp). -
docs/exec.mdcovers the execution world: the seams, workspace scoping, the sandbox, the std tool roster, and redaction. -
docs/security.mdis the threat model the execution world is built against, and where its boundaries honestly stop. -
docs/events.mdis the generated event catalog; regenerate withrake events:catalogwhenever an event's contract changes. -
docs/config-catalog.mdis the generated config catalog, one section per service with a schema; regenerate withrake config:catalogwhenever a service's config surface changes.trt doctorvalidates a profile against these same schemas.
License
MIT. See LICENSE.txt.