Files
boxmaker/CLAUDE.md
T
kyleandClaude Fable 5.1 e156975649 Add M2b plan: ten tasks, tests, recordings and the first system prompt
The tasks build the agent loop on M2a's client: channel messages and
the usage record in proto, four config tables, the tool port and
registry with find_tool and call_tool, the baseline and replay, the
session store, the turn loop with its limits and the append-only
property test, the channel server, loopd serve, bxctl chat, and the
device checks including a four-turn conversation with a restart.

Checked against a private reference implementation: the gate passes
after every task in order, the new suites pass under CPU load, and the
reference passes make verify-device on straylight with no cache loss.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-18 17:19:02 -07:00

6.7 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Current state

Boxmaker is a sovereign personal agent harness written in Rust. Work proceeds one milestone at a time (M0 to M7, table in docs/milestones.md). M0 (measurements) and M1 (workspace, proto, gate, Decision) and M2a (the inference path: inferproxy, loopd's HTTP, SSE and llama client, the startup self-test) are done. M2b (sessions, turn loop, bxctl chat) is specified in docs/specs/2026-09-18-m2b-agent-loop.md and planned in docs/plans/M2b/. Check docs/implementer-log.md for what is in flight.

  • docs/design.md is the binding design brief. If it looks wrong or conflicts with a measurement, stop and say so. Changes to it land as their own commit and are recorded in docs/decisions.md, which also lists proposed changes that are not yet applied.
  • docs/inference-contract.md holds the M0 measurements from straylight. Where it and the brief disagree, the measurements are newer.
  • docs/specs/ holds design specs; docs/plans/M<n>/ holds the task files Laguna works from, and files/ under it holds the tests and fixtures the tasks copy in. AGENTS.md holds the implementer's standing rules; its code rules apply to any code written here too.
  • docs/implementer-log.md is Laguna's own record, one row per task. Reviews go at the bottom.
  • docs/implementer-lessons.md turns review findings into tips, for Laguna and for writing its tasks. Every milestone review updates it, and short general rules are promoted into AGENTS.md. Do not edit the repo while Laguna has a task in flight: its tasks require a clean working tree.
  • spike/ is throwaway measurement code, not harness code.

Roles: implementation is done by Laguna S 2.1 (served by straylight) through OpenCode on this machine, which reads AGENTS.md. Builds are deployed to straylight afterwards. Design, specs, measurement and review are done here. Write plans for Laguna as small closed tasks: exact paths, given type signatures, verified crate APIs, tests specified first, and "stop and report" instead of judgement calls. The model the harness serves is Ornith-1.5-35B-A3B.

The inference server is shared with other sessions. Before using Ornith slot 1 or sending unpinned requests, check GET /slots?model=ornith-1.5-35b-a3b so you do not evict someone's cache.

Commands

  • make gate runs offline: cargo fmt --check, clippy with warnings denied, cargo test, cargo-deny, and a check that fails on any source file over 500 lines. Run it before calling any work done, and report the exit status and last lines.
  • Single test: cargo test -p <crate> <test_name>.
  • make audit runs cargo deny check advisories; it needs the network.
  • make verify-device (from M2a task 13) runs the ignored tests in crates/loopd/tests/device.rs against straylight through a private inferproxy. It uses Ornith slot 0.
  • bxctl is the owner CLI (bxctl chat from M2, bxctl reindex from M5). Until then every binary prints "not implemented" and exits 2.

Architecture in brief

Separate binaries in one Cargo workspace. Each role holds as little authority as possible:

  • loopd owns sessions, prompt assembly and memory. It has no credentials and no network. Its only I/O is Unix sockets to gatewayd, brokerd and inferproxy.
  • brokerd is the only place authority lives. It reads owner-written grants (it cannot write them), runs each approved tool call in a fresh rootless container, and writes a hash-chained JSONL audit log.
  • gatewayd is the Mattermost channel. Outbound only, no listening port; approvals arrive as replies or reactions over the WebSocket.
  • inferproxy is a ~100-line byte forwarder from a Unix socket to the shared llama-server router on the host. It is required, because loopd has no network.
  • proto holds shared types. toolkit holds tool container entrypoints.

Structural rules that span crates:

  • No crate depends on another role's crate. Crates depend only on proto.
  • Authority is encoded in types. A tool can't run without a Decision. It is defined in brokerd with a private field and no Deserialize, so only the policy module can construct one (covered by a compile-fail test). proto carries a plain DecisionRecord.
  • No source file over 500 lines.
  • Files are the source of truth (sessions/, memory/, grants/, audit/). SQLite is only for rebuildable indexes and queues.

Inference contract (the constraint most likely to be broken by accident)

Prompt processing on straylight is slow, and the hybrid-attention model can't partially rewind its KV cache, so any change to an earlier byte of the prompt forces an expensive full re-read.

  • Each turn's request must be a strict extension of the previous one. Volatile content (time, heartbeat notes, memory refreshes, recalled memory) goes only in the newest message, never in the system prompt or earlier history.
  • Assistant messages are logged and replayed exactly as the server returned them, including reasoning_content and tool_calls.
  • The tools array is fixed per epoch; changing it re-reads the whole prompt. Other tools are found with find_tool and called through the call_tool meta-tool. Never prompt the model to call an undeclared tool: the server's grammar forces the call into a declared one.
  • Tool results are size-capped when first appended and never trimmed later.
  • The baseline (system prompt, tool schemas, memory/core.md) stays at 3,000 tokens or less, measured with the server's tokenizer.
  • Compaction happens only when the session is idle, and starts a new epoch (sessions/<id>/<epoch>.jsonl). Old logs are kept.
  • Every request carries id_slot, and a session never changes slot. The server is shared, so slots are not reserved: a cold cache is a normal event that loopd logs, never an error.
  • Streaming always, with return_progress: true. Timeouts are "no bytes for N seconds", never total deadlines, plus a longer limit for waiting on a busy slot or a model load.

Working rules from the brief

  • Verify every external crate API on docs.rs, and every llama-server or Mattermost request field against primary docs, before use. Crate names and server parameters in the brief come from memory and aren't verified. If you can't fetch the docs, say so; don't guess.
  • Keep dependencies few. Justify each in docs/dependencies.md. Any outbound call must be listed in docs/egress.md. No telemetry and no update checks.
  • If a feature isn't in the brief, propose it; don't build it. Stay inside the current milestone's scope.
  • Write tests first. Make one logical change per commit. Never commit runtime data, secrets, or spike output that contains conversation content.