diff --git a/docs/decisions.md b/docs/decisions.md new file mode 100644 index 0000000..ba67bca --- /dev/null +++ b/docs/decisions.md @@ -0,0 +1,19 @@ +# Decisions + +Newest first. A decision that changes `docs/design.md` lands in the same commit as the change. + +## Decided + +| Date | Decision | Reason | +|---|---|---| +| 2026-09-17 | The harness runs on straylight, the same host as `llama-server`. | Owner's choice. One host to secure, and no inference traffic crosses the tailnet. | +| 2026-09-17 | Container runtime is rootless Podman. | It is already the standard runtime on straylight and the owner's other hosts. | +| 2026-09-17 | Implementation is done by Laguna S 2.1 through OpenCode on straylight. Design, specs, measurement and review are done by a stronger model. Ornith-1.5-35B-A3B remains the model the harness serves. | The owner wants to test a local model on real implementation work. Plans must be written as small closed tasks with tests specified up front. | +| 2026-09-17 | M0 is run by the design model, not by Laguna. | M0 is measurement and interpretation, and its findings bind the design. | + +## Open + +| Decision | Needed by | +|---|---| +| Secret store backend, and where the v0 file's key lives. straylight has no secrets manager today. | M3 | +| Whether cloud-led sessions are ever allowed, and for which data classes. | M6 | diff --git a/docs/design.md b/docs/design.md index 9aa2378..f2e39ce 100644 --- a/docs/design.md +++ b/docs/design.md @@ -1,27 +1,20 @@ -# Boxmaker: kickoff pack +# Boxmaker design brief Boxmaker is a sovereign personal agent harness. The name is from *Count Zero*: the remnant AI in the Straylight cores that patiently assembles small boxes from fragments. -Three parts: - -1. **Design brief** — commit as `docs/design.md` in the new repo. Every session reads it. -2. **Session 1 prompt** — paste into a fresh Opus session in an empty repo directory. -3. **Milestone outline and per-milestone prompt template** — one session per milestone. +This brief is binding. Changes to it land as their own commit and are recorded in +`docs/decisions.md`. Milestones and session prompts are in `docs/milestones.md`. Crate names and llama.cpp server parameters below are from memory or secondhand research. -The prompts tell the session to verify each one against primary docs before use. +Verify each one against primary docs before use. ---- - -## Part 1: Design brief (`docs/design.md`) - -### Goal +## Goal A personal agent harness that maximizes sovereignty: local inference by default, no ambient authority, state in plain files the owner can read, a codebase small enough to audit. -### Target environment +## Target environment - Inference host: `straylight`, AMD Strix Halo, 128 GB unified memory, llama.cpp `llama-server`, ~128k context configured. @@ -40,12 +33,12 @@ authority, state in plain files the owner can read, a codebase small enough to a ACLs restrict what the host as a whole may reach. - First channel: the owner's existing self-hosted Mattermost server, reached over the tailnet. -### Non-goals (v0) +## Non-goals (v0) Plugin registry, skill marketplace, web dashboard, multi-user, voice, more than one chat channel, cloud-led orchestration as the default, in-process third-party code of any kind. -### Roles (separate binaries, narrow interfaces) +## Roles (separate binaries, narrow interfaces) | Role | Holds | Can reach | Never holds | |---|---|---|---| @@ -57,7 +50,7 @@ cloud-led orchestration as the default, in-process third-party code of any kind. v0 runs all roles on one host. The interfaces must not assume co-location with `llama-server`. -### Network isolation +## Network isolation The host's tailnet identity is shared by every container that has a network, so a container with ordinary outbound networking can reach the whole tailnet and the internet as the host. @@ -74,7 +67,7 @@ ordinary outbound networking can reach the whole tailnet and the internet as the 5. Tailnet ACLs (kept in `deploy/`) limit the host's node to `llama-server` and Mattermost, plus whatever tool grants need. This is defence in depth, not the role boundary. -### Channel: Mattermost +## Channel: Mattermost - Bot account with a token held in the `SecretStore`. REST API for posting, WebSocket for events. Implement directly over HTTP and WebSocket; do not adopt a community SDK without checking its @@ -85,7 +78,7 @@ ordinary outbound networking can reach the whole tailnet and the internet as the - Mattermost stores messages unencrypted in its database. That is acceptable because the server is the owner's, and it is recorded in `docs/egress.md`. -### Inference contract (the part existing harnesses get wrong) +## Inference contract (the part existing harnesses get wrong) 1. **Baseline budget.** System prompt plus tool schemas at session start: 3,000 tokens or less, measured with the server's tokenizer, enforced by a test. @@ -115,7 +108,7 @@ to chat-completions. Switch only if the M0 cache measurements fail. Known risk: templates drop earlier thinking blocks from history, which changes the rendered prefix at the last assistant turn. -### Authority contract +## Authority contract 1. `loopd` has no authority. Every tool call goes to `brokerd` as a request. 2. A **grant** is a file the owner writes: tool, argument constraints (paths, hosts, patterns), @@ -131,7 +124,7 @@ assistant turn. unless a grant allows that class out. Every outbound cloud payload is logged in full. 7. Scheduled jobs are re-validated against current grants on every run. -### Cloud consult (M6) +## Cloud consult (M6) - One tool, `consult`, behind `brokerd` like any other. The endpoint is any OpenAI-compatible URL, set in config. Nothing in the code is specific to one vendor. @@ -145,7 +138,7 @@ assistant turn. - Hosted GLM always thinks. Treat `consult` as slow and asynchronous. - Payloads are built from an explicit allowlist of fields, never from raw session history. -### Memory (M5) +## Memory (M5) Memory is what carries continuity across epoch compaction, so it is required. It must not break the inference contract, and it must not become a way for injected text to persist. @@ -176,7 +169,7 @@ the inference contract, and it must not become a way for injected text to persis the index passes about one million chunks or measured `recall` latency exceeds one second, and then prefer an embedded, file-backed approximate index over a server. -### State +## State Files are the source of truth. SQLite is allowed only for rebuildable indexes and queues. @@ -188,7 +181,7 @@ Files are the source of truth. SQLite is allowed only for rebuildable indexes an - Secrets: behind a `SecretStore` trait. v0 backend is an encrypted file whose key is not stored beside it. No plaintext secrets on disk, none in config, none in the repo. -### Code constraints +## Code constraints - Rust stable, Cargo workspace. Crates: `proto` (shared types), `loopd`, `brokerd`, `gatewayd`, `inferproxy`, `toolkit` (tool container entrypoints), `bxctl` (owner CLI). @@ -197,101 +190,3 @@ Files are the source of truth. SQLite is allowed only for rebuildable indexes an `brokerd`'s policy module can construct. - Dependencies are few and justified in `docs/dependencies.md`. `cargo-deny` runs in the gate. - No telemetry, no update checks, no outbound call not listed in `docs/egress.md`. - ---- - -## Part 2: Session 1 prompt (M0 spike + M1 skeleton) - -```text -You are starting a new Rust project: Boxmaker, a sovereign personal agent harness. Read docs/design.md -first; it is the design brief and it is binding. If anything in it looks wrong or contradicts -what you measure, stop and tell me rather than working around it. - -This session has two deliverables, in order. - -DELIVERABLE 1 — M0 measurement spike (throwaway code, kept findings) -The inference host is `straylight` (Strix Halo, llama.cpp llama-server, Ornith-1.5-35B-A3B, -~128k context). Before any architecture is built, measure what the design assumes. Ask me for -the server URL and how you can reach it; if you cannot reach it, stop and say exactly what is -missing. - -Fetch the llama-server README for the build running on straylight and the model card before -using any endpoint or parameter. Do not rely on memory for request fields. Then measure and -record in docs/inference-contract.md: - a. Prompt-processing and generation throughput at depth 0 and ~32k (llama-bench or timed - requests; say which). - b. A 3-turn conversation with 4 tool schemas and thinking enabled: tokens processed versus - tokens reused from cache on each turn. Show the raw timing fields. - c. Whether tool calls come back correctly parsed through the chat-completions endpoint with - the model's shipped template. Run 20 trials across 4 tools and report the failure count. - d. Whether requests can be pinned to a slot, and whether a second session on another slot - leaves the first slot's cache intact. - e. What happens to the cache when an earlier assistant turn contained a thinking block. - f. The exact launch flags in use, and the flags you recommend, with reasons. - g. Whether llama-server can listen on a Unix socket, and whether it runs on the same host as - the harness containers. This decides whether `inferproxy` is needed. -Spike code goes in spike/ and is labelled throwaway. Conclude with a recommendation on the open -question in the brief: server-side chat-completions, or in-process template rendering. - -Stop after M0 and show me the findings before starting M1. - -DELIVERABLE 2 — M1 workspace skeleton - - AGENTS.md at the repo root: project standards drawn from docs/design.md "Code constraints", - plus how to run the gate and how to verify against straylight. - - Cargo workspace with the crates named in the brief. `proto` gets the real shared types: - session log records, tool request and result, data-class labels, grant, decision, audit - record. Other crates are minimal binaries that compile. - - Makefile with `make gate`: cargo fmt --check, clippy with warnings denied, cargo test, - cargo-deny, and a check that fails on any source file over 500 lines. - - docs/dependencies.md and docs/egress.md, initially short and accurate. - - Unit tests for `proto` serialization round trips and for the rule that a `Decision` cannot - be constructed outside the policy module (a compile-fail test is acceptable). - -Working rules - - Verify every external crate's current API on docs.rs before using it, and check its - maintenance status and license. If you cannot fetch documentation, say so; do not guess. - - Write a short plan before M1 and execute it with subagents. Tests first. - - Run `make gate` before declaring anything done. Report the exit status and last lines. - - One logical change per commit. Never commit runtime data, secrets, or spike output that - contains conversation content. - - Keep it small. If a feature is not in the brief, do not add it. Propose it instead. -``` - ---- - -## Part 3: Milestones (one session each) - -| # | Milestone | Proves | Verified by | -|---|---|---|---| -| M0 | Measurement spike | The cache and tool-call assumptions hold on straylight | Numbers in `docs/inference-contract.md` | -| M1 | Workspace, `proto`, gate | The type-level authority model compiles | `make gate` | -| M2 | `loopd` core + `bxctl chat` | Append-only loop, baseline ≤ 3k tokens, self-test, runaway control, with one fake in-process tool | Prefix-extension property test; turn-2 cache hit on straylight | -| M3 | `brokerd` | Grants, deny by default, hash-chained audit, rootless container runner, four tools (read file, write file, sandboxed shell, HTTP fetch with host allowlist), `ask` approvals through `bxctl` | Tests for deny paths; a container with no network grant cannot reach the network | -| M4 | `gatewayd` + Mattermost | Bot connection, user-ID allowlist, threads as sessions, async delivery, approvals by reply or reaction, no listening port | End-to-end direct message on the real server; a non-allowlisted user gets no response | -| M5 | Scheduler + memory | Heartbeat and cron on their own slot in isolated sessions; core block, `recall` and `remember` with provenance, FTS5 index, flush-then-compact at idle | Scheduled run leaves the main slot's cache intact; a fact from epoch 1 is recalled in epoch 2; a note written after reading untrusted content is marked on recall | -| M5b | Embedding index (when justified) | Local embedding server, hybrid retrieval behind the `Retriever` trait, rebuild on model change | A paraphrased query finds a note that lexical search misses; no memory text leaves the host | -| M6 | Subagents + cloud consult | Subagents on their slot returning summaries; consult tool gated by data-class grants with full payload log | A tainted session is refused; payload log matches what was sent | -| M7 | Split deployment | Each role in its own rootless container; `loopd` with no network; socket volume permissions; egress proxy for tools; host ACL policy in `deploy/` | From inside the `loopd` container, requests to the internet, the tailnet and the host all fail; a tool container without a host grant cannot reach the tailnet | - -### Per-milestone prompt template - -```text -Read AGENTS.md, docs/design.md and docs/inference-contract.md. This session delivers milestone -M: . - -Scope: . Out of scope: everything else, including later milestones. - -Start by restating the acceptance checks from the milestone table as tests or scripted checks. -Write a plan, show it to me, then execute it with subagents, tests first. Verify external APIs -from their documentation before use. Finish with `make gate` and the on-device verification -named in the table; report exit status, last lines, and what you observed on straylight. -If the design brief turns out to be wrong, stop and propose the change to docs/design.md as its -own commit before building on it. -``` - -### Decisions still open - -- Container runtime. Default proposal: rootless Podman. -- Which host runs the harness containers: straylight itself, or another tailnet node. -- Secret store backend beyond the v0 encrypted file. -- Whether cloud-led sessions are ever allowed, and for which data classes. diff --git a/docs/milestones.md b/docs/milestones.md new file mode 100644 index 0000000..cc3911e --- /dev/null +++ b/docs/milestones.md @@ -0,0 +1,93 @@ +# Boxmaker milestones and session prompts + +One session per milestone. The design brief is `docs/design.md`. + +## Milestones + +| # | Milestone | Proves | Verified by | +|---|---|---|---| +| M0 | Measurement spike | The cache and tool-call assumptions hold on straylight | Numbers in `docs/inference-contract.md` | +| M1 | Workspace, `proto`, gate | The type-level authority model compiles | `make gate` | +| M2 | `loopd` core + `bxctl chat` | Append-only loop, baseline ≤ 3k tokens, self-test, runaway control, with one fake in-process tool | Prefix-extension property test; turn-2 cache hit on straylight | +| M3 | `brokerd` | Grants, deny by default, hash-chained audit, rootless container runner, four tools (read file, write file, sandboxed shell, HTTP fetch with host allowlist), `ask` approvals through `bxctl` | Tests for deny paths; a container with no network grant cannot reach the network | +| M4 | `gatewayd` + Mattermost | Bot connection, user-ID allowlist, threads as sessions, async delivery, approvals by reply or reaction, no listening port | End-to-end direct message on the real server; a non-allowlisted user gets no response | +| M5 | Scheduler + memory | Heartbeat and cron on their own slot in isolated sessions; core block, `recall` and `remember` with provenance, FTS5 index, flush-then-compact at idle | Scheduled run leaves the main slot's cache intact; a fact from epoch 1 is recalled in epoch 2; a note written after reading untrusted content is marked on recall | +| M5b | Embedding index (when justified) | Local embedding server, hybrid retrieval behind the `Retriever` trait, rebuild on model change | A paraphrased query finds a note that lexical search misses; no memory text leaves the host | +| M6 | Subagents + cloud consult | Subagents on their slot returning summaries; consult tool gated by data-class grants with full payload log | A tainted session is refused; payload log matches what was sent | +| M7 | Split deployment | Each role in its own rootless container; `loopd` with no network; socket volume permissions; egress proxy for tools; host ACL policy in `deploy/` | From inside the `loopd` container, requests to the internet, the tailnet and the host all fail; a tool container without a host grant cannot reach the tailnet | + +## Per-milestone prompt template + +```text +Read AGENTS.md, docs/design.md and docs/inference-contract.md. This session delivers milestone +M: . + +Scope: . Out of scope: everything else, including later milestones. + +Start by restating the acceptance checks from the milestone table as tests or scripted checks. +Write a plan, show it to me, then execute it with subagents, tests first. Verify external APIs +from their documentation before use. Finish with `make gate` and the on-device verification +named in the table; report exit status, last lines, and what you observed on straylight. +If the design brief turns out to be wrong, stop and propose the change to docs/design.md as its +own commit before building on it. +``` + +## Session 1 prompt (M0 spike + M1 skeleton) + +As written in the kickoff pack. See `docs/decisions.md` for what has changed since. + +```text +```text +You are starting a new Rust project: Boxmaker, a sovereign personal agent harness. Read docs/design.md +first; it is the design brief and it is binding. If anything in it looks wrong or contradicts +what you measure, stop and tell me rather than working around it. + +This session has two deliverables, in order. + +DELIVERABLE 1 — M0 measurement spike (throwaway code, kept findings) +The inference host is `straylight` (Strix Halo, llama.cpp llama-server, Ornith-1.5-35B-A3B, +~128k context). Before any architecture is built, measure what the design assumes. Ask me for +the server URL and how you can reach it; if you cannot reach it, stop and say exactly what is +missing. + +Fetch the llama-server README for the build running on straylight and the model card before +using any endpoint or parameter. Do not rely on memory for request fields. Then measure and +record in docs/inference-contract.md: + a. Prompt-processing and generation throughput at depth 0 and ~32k (llama-bench or timed + requests; say which). + b. A 3-turn conversation with 4 tool schemas and thinking enabled: tokens processed versus + tokens reused from cache on each turn. Show the raw timing fields. + c. Whether tool calls come back correctly parsed through the chat-completions endpoint with + the model's shipped template. Run 20 trials across 4 tools and report the failure count. + d. Whether requests can be pinned to a slot, and whether a second session on another slot + leaves the first slot's cache intact. + e. What happens to the cache when an earlier assistant turn contained a thinking block. + f. The exact launch flags in use, and the flags you recommend, with reasons. + g. Whether llama-server can listen on a Unix socket, and whether it runs on the same host as + the harness containers. This decides whether `inferproxy` is needed. +Spike code goes in spike/ and is labelled throwaway. Conclude with a recommendation on the open +question in the brief: server-side chat-completions, or in-process template rendering. + +Stop after M0 and show me the findings before starting M1. + +DELIVERABLE 2 — M1 workspace skeleton + - AGENTS.md at the repo root: project standards drawn from docs/design.md "Code constraints", + plus how to run the gate and how to verify against straylight. + - Cargo workspace with the crates named in the brief. `proto` gets the real shared types: + session log records, tool request and result, data-class labels, grant, decision, audit + record. Other crates are minimal binaries that compile. + - Makefile with `make gate`: cargo fmt --check, clippy with warnings denied, cargo test, + cargo-deny, and a check that fails on any source file over 500 lines. + - docs/dependencies.md and docs/egress.md, initially short and accurate. + - Unit tests for `proto` serialization round trips and for the rule that a `Decision` cannot + be constructed outside the policy module (a compile-fail test is acceptable). + +Working rules + - Verify every external crate's current API on docs.rs before using it, and check its + maintenance status and license. If you cannot fetch documentation, say so; do not guess. + - Write a short plan before M1 and execute it with subagents. Tests first. + - Run `make gate` before declaring anything done. Report the exit status and last lines. + - One logical change per commit. Never commit runtime data, secrets, or spike output that + contains conversation content. + - Keep it small. If a feature is not in the brief, do not add it. Propose it instead. +```