Plan 03 — the Trellis daemon
References: design §4 (the specification layer, lowering, the daemon), §6 (hashing and locks), docs/tr-grammar.md, docs/lock-schema.md.
Goal
The long-running service that makes Trellis a language: parses .tr
files, computes the hashes, maintains locks and the derived manifest,
assembles context bundles, runs lowering jobs against an agent CLI with
the MCP tool surface, and serves the CLI (and later the IDE and LSP).
Execution is delegated to soil0 throughout.
Scope
.trparsing. Frontmatter, reserved fenced blocks, block mini-languages (signature, requires/ensures/invariant predicates, test call-arrow lines withwithbindings, property, cram, reference, exports, allow), validity rules — all per docs/tr-grammar.md. Unknown blocks pass through as prose.- Hashing. The three-part hash (formal/test/prose per tr-grammar §1),
soil_hashwith content addressing (free variables replaced by callee hashes, private helpers folded in), the recursive-type cycle hash. Canonicalization rules documented; hashes must be reproducible across machines. - Locks and manifest. Read/write
.locksidecars per docs/lock-schema.md in canonical JSON; derivesoil.lockby merge (definitions,soil_privatenodes, escape-hatch audit, trusted packages); enforce the schema invariants (accepted gating, oracle acceptance rule, block-author pinning). - Incremental state. Watch the Soil root; on change, recompute hashes, apply the invalidation table (lock-schema §8), update statuses.
- Context bundles. The fixed directory layout per design §4.6:
spec.md,callees/(signatures only; demoted refinements shown as base type + note),tests.json,examples/(prelude corpus),reference.pyor CLI-oracle stanza,previous.soil. - MCP server + lowering jobs. The seven tools (
read_context,check_types,check_refinements— a stub returningnoneuntil soilc,run_tests(sandboxed, fakes only),write_soil,read_spec— pinned spec sections by anchor, added 2026-08-25 with the daemon contract,ask_human); one headless agent invocation per lowering (Claude Code provider first) with turn/time/cost caps, isolated agent home, every tool call logged tof.log;ask_humanends the invocation, the answer is written into the.trprose, and the job re-runs fresh; serial queue with invalidation when a dependency is edited mid-flight. - Test running. Expect/property via
soil0 testwith fakes; contradiction pre-flight (mechanical same-input/different-output check before any tokens are spent); cram runner (temp dir,with filefixtures, literal output,[n]exit codes) andtrellis call(real capabilities) — real mode never exposed to the lowering sandbox. - Thin CLI.
trellis check|status|lower|test|call|replspeaking to the daemon. REPL: call any definition with JSON args (Trellis level), feeding later REPL-to-test promotion. - Telemetry. Tokens, cost, retries, provider, model per lowering, to
f.log; provider/model into the lock’s lowering record.
Adoptions (2026-08-23, agentlanguages survey)
The survey adoptions (design doc, entries marked “adopted 2026-08-23”;
record in docs/plans/extra/agentlanguages-adoptions.md) land here:
- Repair-class registry, drift-gated (design §4.6): every daemon/LSP
diagnostic carries a stable code mapping to a typed repair class plus a
spec_refinto the sectioned Soil spec; CI fails when registry, docs, and emissions disagree. soil0’s contract §2 registry is the substrate. - Budgeted context packer (design §4.6): the bundle assembler is
trellis context <def> --budget <n>with fixed priority (spec > testscallee signatures > corpus examples > module prose).
- Decision blocks in bundles (design §4.3, tr-grammar §5.2):
_project.trdecisions in every bundle, module decisions in the module’s; rule-shapedask_humananswers write back to decisions. tr-grammar §9’s open question (flag vs invalidate) was resolved 2026-08-24: per-entry hashes with reliance edges, editorial reclassification, and the triage sweep (design §4.3; impl plan §9.4). - Generated skill (design §4.6):
trellis skillassembles the lowering skill from the toolchain’s registries + the pinned prelude; drift-gated in CI. - Toolchain pin, refuse-on-mismatch (design §8): the daemon refuses
to lower/verify under a mismatched pin;
trellis toolchain updateis the explicit upgrade event. partial(holes)lock state (design §3.16, lock-schema §4): the lowering loop treats the hole as the retry unit; budget exhaustion pauses with goals open instead of failing whole.- Vacuity probes in the contradiction pre-flight (design §4.5).
- Canonical
soil_hash(design §6.1): hashing consumes soil0’s step-11 printer (canonical text of the alpha-normalized AST); the lowerer’swrite_soilcanonicalizes. - Deferred but shaped here: speculative proof-delta tools,
trellis mutants(lock fields reserved) — design §10.
Non-goals
The IDE (plan 06), LSP beyond bare diagnostics, refinement checking, concurrent lowerings, raw-API provider, hosted anything.
Testing
- Hash stability: golden hashes over
examples/; mutation tests (edit prose → onlyprose_hashmoves, etc. — one test per invalidation row). - Lock round-trip:
examples/*.lockre-emitted byte-identical. - A scripted fake agent provider (plays back canned tool-call sequences) to test the job loop, question channel, caps, and log without spending tokens; one live smoke test against real Claude Code headless.
- End-to-end: a fixture project lowers
mean.tr-sized definitions to green through the fake provider.
Exit criteria
examples/fully round-trips: parse → hash → lock regeneration matches the checked-in sidecars (update examples if the daemon exposes spec drift — spec first, then code).- One real headless lowering of a trivial definition completes: bundle →
agent →
write_soil→ checks → tests → lock, withf.logpopulated. - A question round-trip works:
ask_human→ IDEless CLI answer → prose diff → re-invoke → green.
Decision points — resolved 2026-08-22
- IPC: JSON-RPC over a Unix socket
(
$XDG_RUNTIME_DIR/trellis/<root-hash>.sock); LSP speaks its own stdio transport; TCP is the future remote-serving path. - Language: Rust, in the
rust/workspace astrellis-daemon, linkingsoil0andsoil-rt(thesoil0CLI remains the oracle contract regardless). - Sandboxing: all three layers. Tool allow-list (the scope-6 MCP tools, no shell) + the agent CLI’s own sandbox and isolated home + a chroot-style OS jail around the agent process (unprivileged via user namespaces / bubblewrap in practice). Defense in depth from v1.
- Incremental state: explicit refresh — hashes re-checked at request
boundaries plus
trellis refresh; the watcher arrives with the IDE milestone on the same invalidation code path.