From 4d1120032a6567ff0fbb701df56d34b5c6e82ad2 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Sun, 27 Sep 2026 19:15:09 -0700 Subject: [PATCH] docs: M5b landed; CLAUDE.md and decisions follow decisions: M5b settles the invalid-arguments reason, tag neutralisation and the shared secret store (their open rows go), and the verdict on the run's two driver trials. CLAUDE.md's current state: M5b done, M5c (Honcho, roncho 1.0.0) next; straylight's clone is boxmaker-m5b. Co-Authored-By: Claude Opus 5.5 (1M context) --- CLAUDE.md | 15 +++++++++------ docs/decisions.md | 5 ++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 53e7481..461c171 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,14 +21,17 @@ approval asked by a non-approver; no finding is open. M5 (memory, then the sched 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 +check found that a recalled tainted note left a session trusted, fixed by task 21. P24 split the +rest of M5. M5b (hardening: the sanitizer, tag neutralisation in `loopd`, images and untrusted +links as code in `gatewayd`, the invalid-arguments reason, `bxcore::secrets`, one instance per +daemon; spec `docs/specs/2026-09-26-m5b-hardening.md`, 14 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 the self-test overran `inferproxy`'s burst (fixed in review). M5c is Honcho, +through the owner's `roncho` (v1.0.0 on crates.io; `docs/specs/2026-09-25-roncho-requirements.md`), +and is next, with no spec yet; 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 +on straylight, where `~/src/boxmaker-m5b` 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, diff --git a/docs/decisions.md b/docs/decisions.md index fd2e2e4..1cb370b 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -6,6 +6,8 @@ Newest first. A decision that changes `docs/design.md` lands in the same commit | Date | Decision | Reason | |---|---|---| +| 2026-09-27 | M5b settles three open items. An invalid-arguments denial carries its reason (`ToolResponse::Denied.detail`, one line, 300 characters at most). `loopd` neutralises the model's chat-template tags when text from outside is first logged (user messages, tool results after their cap, the memory block), checked against the tokenizer by the self-test in two `/tokenize` requests. The secret store is `bxcore::secrets`, behind the `secrets` feature, for every role but `loopd`. | M5b spec, sections 4, 5 and 8; landed 2026-09-27, device-checked on straylight (review in `docs/implementer-log.md`). The self-test's per-tag requests overran `inferproxy`'s burst on the device and were batched (review finding 10). | +| 2026-09-27 | The two driver trials of M5b's run, judged: `RESUME` stays, and now also resumes a session stopped at the time limit, the case where it was needed (task 10); `SLOTS_URL` stays, though it never fired. Sessions run with `opencode run --pure` and `OPENCODE_DISABLE_CLAUDE_CODE=1` by default (`PLUGINS=1` restores both), and the prompt names the log row. | The run's evidence (implementer lessons T47, T48): global plugins rewrote the system prompt mid-session and cost 76 of 111 minutes of prompt processing in tasks 01 to 10; task 10 stopped at the limit with its work uncommitted; task 14's row took its name from the heading. `89a50bc`. | | 2026-09-26 | `proto` becomes `bxcore`, the one shared crate: it keeps everything `proto` held and gains `secrets` (behind a Cargo feature that `loopd` never enables) and `sanitize`; later shared code goes there too. The rule becomes: every role depends on `bxcore` and on no other workspace crate, and `bxcore` depends on no workspace crate. The rename is made by changing the crate and manifests and fixing what the build reports, not by search-and-replace. | Owner's decision (M5b design). A second shared crate for secrets was the alternative; one crate keeps the rule simple. Not `core`, which shadows Rust's own crate (checked). A spike found the four layers the rename touches (compiler, formatting, fixture path strings, the crate-dependency check); M5b spec, section 3. | | 2026-09-26 | Every commit holding work by Ornith or another local model ends with `Co-Authored-By: `, the model named as in `OPENCODE_MODEL` after the provider (for example `ornith-1.5-35b-a3b`). `tools/run-plan.sh` puts the exact line in the session prompt and stops a run whose commit lacks it; the log's Model column is filled the same way. When the design model commits a local model's work, it adds that line beside its own. It replaces the `Implemented-By` trailer. | Owner's rule, for tracking which model wrote what across the owner's repositories. The implementer often cannot know its own model, so the driver supplies the name. `f98ec2a`. | | 2026-09-26 | Boxmaker adopts the combined Ornith guidance (`~/projects/ornith/GUIDANCE.md`, from this repository's `ASSESSMENT.md` and two others'): in `AGENTS.md`, experiments under `.state/scratch/`, a refused tool call does not end a turn, one `todo!()` at a time, the gate's 600000 ms timeout, commit messages by `git commit -F` (`0356c87`); a project `opencode.json` with an `implement` agent whose permissions deny reads outside the repository, edits to the plans and design documents, subagents, web access and destructive git commands (`31fe078`); in the driver, a 90-minute session limit and a message for a `stopped` row (`411d9e0`); a pre-handover checklist, `docs/plans/CHECKLIST.md` (`224617e`); and in task files, environment facts and pasted contracts. Two driver guards are trials, switched on for M5b's run and judged at its review: `RESUME=1` (one resume when a session leaves work uncommitted) and `SLOTS_URL` (stop a session when no llama slot has been busy for 10 minutes). Not adopted yet: a verdict classifier, the spec-extraction pipeline (for M5c's Honcho contract, if needed), smaller `tool_output` limits and a custom agent prompt. | Owner's decision. Each adopted item answers a recorded failure in one of the three projects; the trials and the omissions are the guide's own untested items, taken one at a time so their effect can be seen. OpenCode settings were checked against its 1.18.30 schema and permission docs. | @@ -69,12 +71,9 @@ Newest first. A decision that changes `docs/design.md` lands in the same commit | Thinking cap size. It starts at 4,096 tokens so that the right size can be found from data. From M2b every completion logs its reasoning-token count and whether the cap fired. Revisit once 200 completions are logged, or sooner if the owner sees answers cut short: raise the cap if more than 5% of completions hit it. Each hit also costs a re-read of that turn. | after M2b has run for a while | | Whether `proto`'s structs should refuse the JSON array form. serde's derived `Deserialize` accepts `[…]` in field order, so `"body":[]` decodes as `Empty`; true of every `proto` struct since M1. Not an authority problem (the values are validated the same way); closing it needs hand-written `Deserialize` for every struct. Found by the M3a plan checks. | before M7 | | Repository licence. Crates are `publish = false` until it is chosen. | before publishing | -| A reason in an invalid-arguments refusal. `loopd` answers every `invalid_arguments` denial with one fixed sentence, because `proto::DenyReason` carries no detail; on the device (2026-09-25) the model needed two extra calls to find that `note` takes no `file`. Giving the reason means a detail in `ToolResponse::Denied`: a wire change (fixtures, `loopd`, the audit log), and a choice about echoing model-written text. | M5b | | Audit integrity beyond a self-contained hash chain (for example anchoring the chain head outside `brokerd`). Deferred by the owner on 2026-09-17 to a later revision. | after v0 | | How an approval whose arguments do not fit one Mattermost post is shown. Settled on 2026-09-18: what is being approved is never truncated, in any channel, because the unseen tail is where injected text would put its payload; `bxctl` prints arguments in full. Leading option for Mattermost: a thread of numbered chunks split on character boundaries of the escaped text, where only a reply or reaction on the last post counts and that post repeats the id, the chunk count and the hash of the whole; above some number of chunks, the post points to `bxctl approvals` and approval is local only. The post size limit and rate limits must be checked against the Mattermost API reference first. | M4 | -| Secret store: settled 2026-09-23 (systemd credentials, environment, owner-only file; P15 proposed). Where `brokerd` reads tool secrets from, and whether `SecretStore` moves out of `gatewayd` into a shared place, is decided when a tool first needs a secret. | when a tool needs a secret | | Whether cloud-led sessions are ever allowed, and for which data classes. | M6 | -| Neutralizing chat-template tags in everything `loopd` sends (the owner's and other agents' messages, tool results, recalled text): measured 2026-09-24, a tag inside any message becomes Ornith's control token, so a fetched page can end its tool result and forge a system turn (`docs/research/2026-09-24-chat-template-tokens.md`). Proposed: replace each tag in a fixed list with an escaped look-alike when the text is first appended and logged (replay stays byte-identical), the list checked against the tokenizer by the self-test. Memory writes already refuse the tags (M5a). | M5b; the exposure exists since M3 | | Session-scoped and standing approvals for tools other than memory: answering an approval with `!approve 42 session` (the same pattern for the rest of the session) or `!approve 42 always` (standing), words after the id as `!deny 42 ` has (owner's syntax, 2026-09-24). To settle: what "session" covers (this call's tool and path or host, or the whole grant it matched); and where "always" writes, since `brokerd` cannot write grants (a proposed grant for the owner, or a separate directory of chat-approved grants). A temporary grant is new authority (the grants are the owner's files, and `brokerd` cannot write them), so it needs its own design. Owner's point, 2026-09-24: a harness should not ask a stream of questions. | after M5 | | Projects in memory. Honcho can separate projects by a workspace per project (hard walls, shared naturally by every agent on the project, but Honcho's picture of the owner splits per workspace) or by scopes inside one workspace (one picture, recall filtered by project; semantics not yet read). Either way a project is a `memoryd` setting: its channels and threads, its workspace or scope, `write` and `trust`. A shared project workspace is written by several agents, so its trust is the owner's call per project. Test both on rift before choosing. Owner's idea, 2026-09-24. | M5c or later | | A session's trust across compaction. From M5b (plan task 09) `loopd` reads whether a session has read untrusted content from its log: true once any tool result in `sessions//0.jsonl` is labelled untrusted, and it never goes back. Compaction starts a new epoch file, so the state must be carried into the new epoch (a record at its head, or `untrusted()` reading every epoch), or a compacted session would read as clean and `gatewayd` would leave its links live. | the milestone that adds compaction |