Split kickoff pack into brief, milestones and decisions

docs/design.md now holds only the design brief, as the pack intended.
The session prompt and milestone table move to docs/milestones.md
verbatim. Open decisions move to docs/decisions.md, which also records
the four decisions made at kickoff.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-09-17 00:17:55 -07:00
co-authored by Claude Fable 5.1
parent fa9d78366e
commit 96bf688b20
3 changed files with 128 additions and 121 deletions
+19
View File
@@ -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 |
+16 -121
View File
@@ -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 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. the Straylight cores that patiently assembles small boxes from fragments.
Three parts: 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`.
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.
Crate names and llama.cpp server parameters below are from memory or secondhand research. 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.
--- ## Goal
## Part 1: Design brief (`docs/design.md`)
### Goal
A personal agent harness that maximizes sovereignty: local inference by default, no ambient 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. 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`, - Inference host: `straylight`, AMD Strix Halo, 128 GB unified memory, llama.cpp `llama-server`,
~128k context configured. ~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. ACLs restrict what the host as a whole may reach.
- First channel: the owner's existing self-hosted Mattermost server, reached over the tailnet. - 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, 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. 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 | | 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`. 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 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. 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 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. 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. - 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 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 - 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`. 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, 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. 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 templates drop earlier thinking blocks from history, which changes the rendered prefix at the last
assistant turn. assistant turn.
### Authority contract ## Authority contract
1. `loopd` has no authority. Every tool call goes to `brokerd` as a request. 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), 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. 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. 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, - 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. 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. - Hosted GLM always thinks. Treat `consult` as slow and asynchronous.
- Payloads are built from an explicit allowlist of fields, never from raw session history. - 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 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. 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 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. 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. 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 - 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. 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`, - Rust stable, Cargo workspace. Crates: `proto` (shared types), `loopd`, `brokerd`, `gatewayd`,
`inferproxy`, `toolkit` (tool container entrypoints), `bxctl` (owner CLI). `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. `brokerd`'s policy module can construct.
- Dependencies are few and justified in `docs/dependencies.md`. `cargo-deny` runs in the gate. - 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`. - 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<n>: <one-line goal from the table>.
Scope: <bullets>. 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.
+93
View File
@@ -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<n>: <one-line goal from the table>.
Scope: <bullets>. 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.
```