Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
124 lines
8.1 KiB
Markdown
124 lines
8.1 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, summarised in the M4a spec, section 12) is next and not yet specified. The owner's Mattermost settings and the
|
|
bot's token are in `mattermost.env` at the repository root: 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.
|
|
- `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 M5. `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, 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.
|