Owner's decision. The hardening half (sanitizer, tag neutralisation, image links escaped, the invalid-arguments reason, and the secret store available to every role) needs neither roncho nor Honcho and can be specified now. Honcho waits on roncho's tagged release. Earlier documents keep the old numbers; P24 says so. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
145 lines
10 KiB
Markdown
145 lines
10 KiB
Markdown
# 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), M1 (workspace, `proto`, gate,
|
|
`Decision`), M2a (the inference path: `inferproxy`, `loopd`'s HTTP, SSE and llama client, the
|
|
startup self-test) and M2b (sessions, the turn loop, `loopd serve`, `bxctl chat`) are done. M3 is split:
|
|
M3a (`brokerd`'s decision path, spec `docs/specs/2026-09-18-m3a-decision-path.md`) is done and
|
|
merged, reviewed in `docs/implementer-log.md`; its open findings (17, 18, 20) are listed there; 14 was
|
|
closed by M3b task 02. M3b (the container runner and the four tools, `docs/specs/2026-09-22-m3b-runner.md`) is done and
|
|
merged: tool containers run from the image `deploy/tools-image.nix` builds, checked on straylight.
|
|
M4 is split: M4a (`gatewayd`, conversations over Mattermost, spec
|
|
`docs/specs/2026-09-23-m4a-gateway.md`) is done and merged, reviewed in `docs/implementer-log.md`
|
|
and checked end to end against the owner's Mattermost; no finding is open. M4b (approvals over
|
|
Mattermost, spec `docs/specs/2026-09-24-m4b-approvals.md`, 18 tasks) is done and merged, reviewed
|
|
in `docs/implementer-log.md` and checked end to end against the owner's Mattermost, including an
|
|
approval asked by a non-approver; no finding is open. M5 (memory, then the scheduler) is split into
|
|
M5a, M5b and M5c by P17 to P21 (applied 2026-09-24). M5a (local memory, `memoryd`, spec
|
|
`docs/specs/2026-09-24-m5a-local-memory.md`, 22 tasks) is done and merged, reviewed in
|
|
`docs/implementer-log.md` and checked on straylight against the owner's Mattermost; the device
|
|
check found that a recalled tainted note left a session trusted, fixed by task 21. Its one open
|
|
item, a reason in invalid-arguments refusals, is in `docs/decisions.md` for M5b. P24 split the
|
|
rest of M5: M5b is hardening (sanitizer, tag neutralisation, the secret store for every role) and
|
|
is next, with no spec yet; M5c is Honcho, through the owner's `roncho` once it has a tagged
|
|
release (`docs/specs/2026-09-25-roncho-requirements.md`); M5d is the scheduler. P22 (proposed, not applied) makes Boxmaker a fleet of
|
|
independent agents, one per host, deployed with Nix in a last milestone, M8; `flake.nix` builds
|
|
every binary, the tool image and a dev shell. Work happens on titan (macOS, no Nix); build and run
|
|
on straylight, where `~/src/boxmaker-m5a` is a clone built with `nix build`. The owner's Honcho
|
|
client `roncho` is at `~/src/roncho`. Branches land on master by rebase and
|
|
fast-forward, never a merge commit. The owner's Mattermost settings and the
|
|
bot's token are in `mattermost.env` at the repository root (on straylight,
|
|
`~/.config/boxmaker/mattermost.env`): ignored by git, never committed, and the token is never
|
|
printed.
|
|
Straylight now serves Ornith as four slots over one 262,144-token pool; see
|
|
`docs/inference-contract.md`, "Deployment change, 2026-09-20", before relying on cache behaviour.
|
|
`docs/runbook.md` has an entry for every
|
|
fail-closed state; any new one needs an entry and a pointer in its message. 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.
|
|
- `nix build` (on a host with Nix, such as straylight) builds every binary; `nix build
|
|
.#tools-image` the tool image; `nix develop` gives the toolchain. See `docs/dependencies.md`.
|
|
- `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. `tools/check-m3a-device.sh`
|
|
runs `brokerd`, `loopd` and an approval end to end against it.
|
|
- `bxctl` is the owner CLI: `bxctl chat` talks to a running `loopd serve --config <path>`;
|
|
`bxctl approvals`, `approve`, `refuse`, `grants check` and `audit verify` talk to `brokerd serve`
|
|
(or read the audit log directly); `bxctl reindex` comes in M5a. `gatewayd serve --config <path>` is the
|
|
Mattermost channel (M4a).
|
|
|
|
## Architecture in brief
|
|
|
|
Separate binaries in one Cargo workspace. Each role holds as little authority as possible:
|
|
|
|
- `loopd` owns sessions and prompt assembly. It has no credentials and no network. Its only I/O
|
|
is Unix sockets to `gatewayd`, `brokerd`, `inferproxy` and (from M5a) `memoryd`.
|
|
- `memoryd` (M5) owns memory: `memory/soul.md`, `memory.md`, `user.md` (identity), `notes.md` (experience), the FTS5
|
|
index, and from M5c the sync to the fleet's Honcho. It runs memory tool calls `brokerd` has
|
|
decided, and reaches Honcho and nothing else.
|
|
- `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
|
|
`!approve`/`!deny` replies 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, the three memory files) 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.
|