Commit Graph
454 Commits
Author SHA1 Message Date
kyleandClaude Opus 5.5 5465946d98 M5c-1 plan: task 03, gatewayd sets the speaker from the burst's one author
A batch carries the Mattermost user id of each message; the turn's speaker
is the username of the one user who wrote all of it (Client::username,
kept in a map on the event loop), and None for two or more people or a
failed lookup. The turn's text is unchanged.

Checks: reference replayed from the task text on a fresh ref-02 worktree
with its own build dir, byte-identical; make gate ok with 1148 tests
(1138 + 10); /tmp unchanged around both gate runs; 21 mutations, all
caught; new tests stressed 40 times under load, no failure; every before
block found once at ref 02; no chat-template tags.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 00:30:24 -07:00
kyleandClaude Opus 5.5 4e823a76e6 docs: decisions: one toolchain, Rust 1.98.1
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 00:04:38 -07:00
kyleandClaude Opus 5.5 d2e6cfa51f flake: Rust 1.98.1 from rust-overlay; rust-version 1.98
The owner's hosts (titan, talos) run 1.98.1; nixos-26.05's Rust is
1.95.0, so builds and lints differed by host. The flake gains a second
input, oxalica's rust-overlay (following our nixpkgs, so host configs
can still follow theirs), and both the package build and the dev shell
use its stable 1.98.1 with clippy and rustfmt. The workspace's
rust-version rises to 1.98 to match; AGENTS.md, docs/dependencies.md
and the M5c-1 plan follow.

Checked on straylight: nix build gives all seven binaries; the dev
shell has rustc, cargo and clippy 1.98.1, rustfmt 1.9.0, cargo-deny
0.19.6; make gate in it passes with 1122 tests, as on titan.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 00:04:38 -07:00
kyleandClaude Opus 5.5 0a59dc0b22 M5c-1 plan: task 02, loopd logs the speaker and sends the turn's number
loopd copies the Turn's speaker onto the User record it logs, as it
came (not checked, not neutralised), and never into a request; after a
finished turn it sends SessionUpdated with the turn's number, the count
of User records in the session's log (Session::turns), so the first
turn is 1, a failed turn that was logged counts, and the count goes on
after a reopen or a restart. Failed turns are still not told.

Given: speaker_turn.rs (8 tests) and new versions of loopd's memory.rs,
turn_broker.rs, end_to_end.rs and support/turn.rs (run_turn gains
speaker, MemorySource::updated gains turn). Task 01 now copies
memory.rs-task01, its own version.

Checks: the reference passes make gate with 1138 tests (1130 + 8); the
task replayed from its text on a fresh ref-01 worktree with its own
target is byte-identical to the reference and passes the gate; /tmp
unchanged around both gate runs; 15 one-line mutations (speaker not
logged or dropped by the channel or neutralised, speaker in the request
at log time or at replay, turn 0-based or +1, counting Assistant
records, all records or only speakers' turns, a global or per-process
counter, told before logging or on failure, MemoryPort sending 0) all
caught; speaker_turn run 60 times under load, no failure; no
chat-template tag in the plan or given files; task lines within 100
characters but tables.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 23:49:35 -07:00
kyleandClaude Opus 5.5 82b7898e84 M5c-1 plan: task 01, the speaker, SessionUpdated's turn, the session-state messages
Task 01 changes bxcore's wire and log types for M5c-1: Turn.speaker and
LogRecord::User.speaker (optional, skipped when None, so old frames and logs
keep their bytes), SessionUpdated.turn (required, refused when missing), and
the new SessionState / SessionStateReply messages. The other crates only
construct the new fields (speaker: None, turn: 0) and brokerd's kind() names
the two new kinds; later tasks give them values. 20 given files.

Checks, against a reference at 7b40686 with its own build directory:
- every "before" block found exactly once at 7b40686 (and at master, which
  changed no crate since);
- step 2's failures as the task names them: two strict tests, fifteen test
  targets with E0559/E0560/E0432/E0599 only;
- make gate: gate: ok, 1130 tests (1122 + 8); /tmp unchanged around it;
- replay from the task text in a fresh worktree of 7b40686 with its own
  target/: crates/ byte-identical to the reference, gate ok, 1130; removed;
- 23 mutations: 21 caught; the two that drop `default` from an Option
  field cannot change behaviour (serde reads a missing Option as None);
- no chat-template tag whole in the task or the given files; lines over
  100 characters only in tables, fixtures and verbatim strings.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 23:22:37 -07:00
kyleandClaude Opus 5.5 c151a7516d direnv: enter the flake's dev shell on hosts with Nix
`.envrc` runs `use flake` when Nix is present, so on the NixOS hosts
(programs.direnv.enable brings direnv and nix-direnv) cd'ing into a
clone gives the pinned Rust toolchain, clippy, rustfmt, cargo-deny and
make; on titan, which has no Nix, it does nothing. nix-direnv's cache,
.direnv/, is ignored. Checked on straylight in a throwaway copy:
cargo 1.95.0, cargo-deny 0.19.6, rustc from the Nix store.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 23:12:55 -07:00
kyleandClaude Opus 5.5 b8ebfd45ed M5c-1 plan: Honcho responses recorded from rift (task 00)
29 request/response pairs, each request exactly as roncho 1.0.0's
blocking client sends it, recorded in a throwaway workspace
(boxmaker-plan-probe) created for it and deleted afterwards; no other
workspace was touched. For the fake Honcho server of task 10. Found:
creation is get-or-create (201, then 200); adding messages to a missing
session creates it; a 422 echoes the whole input; a deleted conclusion
answers 204 with an empty body, and 404 the second time.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 22:59:04 -07:00
kyleandClaude Opus 5.5 7b40686def memoryd: depend on roncho 1.0.0 (M5c-1 task 00)
The owner's Honcho client, blocking, with TLS: default features off,
feature tls. It adds only itself to the lock file (checksum a5ccccc2…,
as checked against crates.io in review). cargo deny passes. The
dependency and the egress to rift are documented; the egress table also
lists the design model's probe of rift in a throwaway workspace.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 22:53:19 -07:00
kyleandClaude Opus 5.5 76a655062e M5c-1 plan: the owner's answers (speaker opt-in; rift probe workspace only)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 22:45:50 -07:00
kyleandClaude Opus 5.5 339de0fdc2 M5c-1 plan: outline for the owner's review
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 22:34:43 -07:00
kyleandClaude Opus 5.5 bc4eb3f271 docs: milestones: M5c as the approved spec has it
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 22:33:18 -07:00
kyleandClaude Opus 5.5 ee50adf78c docs: decisions: P23 applied as amended; the M5c design
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 22:33:18 -07:00
kyleandClaude Opus 5.5 cdedf26987 design: Honcho as the M5c spec settles it (P23, amended)
Memory items 4 and 5: two workspaces per agent and one fleet workspace
for untrusted notes; the private ceiling, secret sessions never synced;
trusted memory writes mirrored as conclusions; the agent's peer not
observed; no rebuild, corrections by contra-fact or deleting our own
conclusion; the fleet workspace always untrusted. Recorded in
docs/decisions.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 22:32:32 -07:00
kyleandClaude Opus 5.5 4abed5bdd9 M5c spec: approved
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 22:32:12 -07:00
kyleandClaude Opus 5.5 fdc03cf8ba M5c spec: the owner's answers
A turn without complete speaker information is not sent; every peer in
the fleet workspace is a member, so fleet notes are messages that one
search finds, with a retraction message for an undone note (Honcho
cannot delete a message); the fleet workspace derives nothing
(reasoning, summaries and dreaming off); API keys per workspace are
first-class, with a runbook entry for a refused key.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 22:25:20 -07:00
kyleandClaude Opus 5.5 23c9ebec12 M5c spec: Honcho, draft for the owner's review
Designed with the owner on 2026-09-27: per agent a trusted and an
untrusted workspace plus one fleet workspace for untrusted notes;
private ceiling, secret sessions never synced; no rebuild (contra-facts,
or deleting our own conclusion); the owner and other people observed,
the agent not; every trusted memory write mirrored as a conclusion; a
decision ledger that doubles as the outbox; recall over the local index
and the fleet; ask_memory by workspace chat. Grounded in roncho 1.0.0
and Honcho 3.2.0 (source read at v3.2.0) and rift's live metrics.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 22:11:31 -07:00
kyleandClaude Opus 5.5 4d1120032a 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) <noreply@anthropic.com>
2026-09-27 19:15:09 -07:00
kyleandClaude Opus 5.5 d423d87115 docs: the M5b device check, passed on straylight
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 19:08:42 -07:00
kyleandClaude Opus 5.5 6f243ad779 docs: M5b review finding 10, the tag check against inferproxy; tip T49
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 19:08:42 -07:00
kyleandClaude Opus 5.5 5a2464d81d loopd: the tag check asks /tokenize twice, not once per tag
Found in the M5b device check on straylight: the self-test asked about
each of the 33 tags and each neutralised form, 66 requests, one
connection each, and inferproxy's token bucket (burst 10) refused the
ninth, so loopd would not start. Now it asks about all the tags at once,
one per line, and then all their neutralised forms: the answer to the
first must hold each tag's recorded token in order, the second none of
them. Each tag is a special token, split out before anything else, so
the lines between cannot change it; and a neutralised form is only
dangerous as a tag's own token, so that is what the second answer must
not hold. The errors name a tag by its id, as before.

The fake tokenizer reads text with several tags in it; the tests cover
each tag as ordinary tokens, as another listed or unlisted token, a
duplicated token in the middle and at the end, a form holding each tag,
and both requests failing. Spec section 8 and the runbook entry follow.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 19:08:42 -07:00
kyleandClaude Opus 5.5 1fb0ec8a06 docs: the M5b review, and tips T45 to T48
The review of tasks 01 to 14 in the implementer log (task 14's row
renamed to its file's name), and four tips for writing tasks: what to
do with a skeleton's comments, golden files for scanners, naming the
log row, and running the implementer without plugins.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 19:08:42 -07:00
kyleandornith-1.5-35b-a3b 27a6354a80 docs: runbook: sanitizer-rules, how to change a sanitizer rule
Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
2026-09-27 19:08:42 -07:00
kyleandornith-1.5-35b-a3b 7cbc1d7342 brokerd, memoryd, gatewayd: a lock file keeps each to one instance
Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
2026-09-27 19:08:42 -07:00
kyleandornith-1.5-35b-a3b fd0437a032 bxcore, loopd, inferproxy: a lock file keeps each to one instance
Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
2026-09-27 19:08:42 -07:00
kyleandornith-1.5-35b-a3b 54f102bbe1 bxctl: sanitize check shows which sanitizer rules a text breaks
Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
2026-09-27 19:08:42 -07:00
kyleandornith-1.5-35b-a3b 648469cd67 gatewayd: images, and links in untrusted replies, are posted as code
Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
2026-09-27 19:08:42 -07:00
kyleandornith-1.5-35b-a3b 0d3048c3c4 bxcore, loopd: TurnDone says whether the session has read untrusted content
Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
2026-09-27 19:08:42 -07:00
kyleandornith-1.5-35b-a3b 91992b9743 loopd: the self-test checks the model's tags against the tokenizer
Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
2026-09-27 19:08:42 -07:00
kyleandornith-1.5-35b-a3b b4d631e249 loopd: neutralise user messages, tool results and the memory block
Text from outside the model (the user's message, every tool result, and the
memory block) is neutralised the moment it is first logged, so every later
request replays the same bytes and a tag in that text cannot reach the model as
one of its own control tokens. The model's own output, replay, and the system
prompt are left byte-for-byte.

Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
2026-09-27 19:08:42 -07:00
kyleandornith-1.5-35b-a3b ca8b50cd69 memoryd: refuse and flag memory writes through bxcore::sanitize
Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
2026-09-27 19:08:42 -07:00
kyleandornith-1.5-35b-a3b b3e9af7586 bxcore: sanitize::flag, text shaped like an attack
Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
2026-09-27 19:08:42 -07:00
kyleandornith-1.5-35b-a3b 41d425f73e bxcore: sanitize::refuse, what a memory write may never hold
Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
2026-09-27 19:08:42 -07:00
kyleandornith-1.5-35b-a3b 1817e7f117 bxcore: the model's tags and sanitize::neutralise
Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
2026-09-27 19:08:42 -07:00
kyleandornith-1.5-35b-a3b f594e0d9d4 bxcore, gatewayd, loopd: the secret store moves to bxcore::secrets
Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
2026-09-27 19:08:42 -07:00
kyleandornith-1.5-35b-a3b de60c9e13a bxcore, brokerd, loopd: an invalid-arguments denial says why
Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
2026-09-27 19:08:42 -07:00
kyleandClaude Opus 5.5 34ca1ef191 docs: decisions: a session's trust must survive compaction
Open item from M5b task 09: loopd reads a session's trust from its
epoch-0 log, so compaction must carry it into the new epoch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 17:30:44 -07:00
kyleandClaude Opus 5.5 ba3c89791f M5b plan: README brought up to the written tasks
The task table follows the files (03 tags, 04 refuse, 05 flag) and
gives each task's gate count and mutations; the interfaces are the ones
the tasks built (task 02's role argument and SecretSpec, Refusal.at,
Flag, TurnDone.untrusted required, disarm, LockError::line); the review
focus names the tasks that pin each item; which task adds each runbook
entry, and the -taskNN convention for replaced given copies.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 17:30:44 -07:00
kyleandClaude Opus 5.5 81d849d4ba M5b plan: task 14, the runbook entry sanitizer-rules
The last runbook entry, the rule-change procedure of spec 6.5; the
entries memory-write-refused and memory-write-flagged now send the
reader to it instead of to the spec. Tasks 06, 08 and 12 added the
other five, because the gate needs an entry for every pointer.

Checks: the edits, applied from the task's text to ref 13, give a
runbook byte-identical to the reference; each before block found once;
the gate passes on the reference with 1118 tests; /private/tmp unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 17:29:50 -07:00
kyleandClaude Opus 5.5 5f48ba65b0 M5b plan: task 13, instance lock in brokerd, memoryd and gatewayd
Each locks <home>/run/<role>.lock in main (gatewayd after its secret,
so nothing is made before the secret is read). brokerd's old
second-instance test in serve.rs expected the audit lock's message,
so serve.rs has a new version; task 01 now copies serve.rs-task01.

Checks: given tests fail at ref 12 as the task says (2 each in
brokerd, memoryd, gatewayd; serve.rs passes); the task's quoted edits
applied to ref 12 reproduce ref 13's tree exactly; replay gate ok with
1118 tests (1113 - 1 + 6), /tmp unchanged; 14 mutations, all caught;
the six instance test files stressed 25 runs under 16 busy loops and
20 under 32, no failure.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 17:21:14 -07:00
kyleandClaude Opus 5.5 291d82f21b M5b plan: task 12, instance lock in bxcore, loopd and inferproxy
bxcore::instance::lock (File::try_lock, file 0600, missing directory
0700), LockError::line for the two lines the daemons print, and the
already-running runbook fragment; loopd locks <home>/run/loopd.lock
before its socket and self-test, inferproxy <socket>.lock.

Checks: given tests fail at ref 11 as the task says (bxcore E0432;
loopd 3 and inferproxy 3 fail); the task's quoted edits applied to
ref 11 reproduce ref 12 byte for byte; replay gate ok with 1113 tests
(1099 + 14), /tmp unchanged; 25 mutations, 24 caught (the uncaught
one maps TryLockError::Error to Held, not reachable from a test);
given files rustfmt-clean, under 500 lines; no chat-template tags.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 17:21:14 -07:00
kyleandClaude Opus 5.5 ef2add03f0 M5b plan: task 11, bxctl sanitize check
The task file, the skeleton crates/bxctl/src/sanitize.rs (five todo!()
bodies: parse_sanitize, code_point, token, prefix, tag_count) and two
given tests: sanitize_check.rs (8, runs the binary) and
sanitize_report.rs (10, the library).

Decisions: the one argument after `check` is the text, whatever it
looks like; none means standard input. Output: refuse line (first rule
only; a character as U+XXXX, a tag as its token id, a secret as its
prefix and an ellipsis), flag line (rule only), neutralise line (every
occurrence of every tag). Exit 0 nothing fires, 1 something fires, 2
usage error or unreadable text. Nothing of the text is printed.

Checks: every given test fails before the task as the task says (the
report file does not compile: one E0432, two E0599; the binary tests
exit 2); the skeleton state gives 10 and 8 not-yet-implemented
failures; the reference passes make gate with 1099 tests (1081 + 18);
the task's edits applied to a fresh ref-10 copy with its own build
directory give a tree byte-identical to the reference; 21 mutations,
each caught; 40 runs under load, no failure; /private/tmp unchanged
around a gate run; rustfmt and the line limit on the given files; no
tag or key-shaped string in the plan or given files.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 10:18:55 -07:00
kyleandClaude Opus 5.5 7eb65a276b M5b plan: task 10, gatewayd posts images and untrusted links as code
Task 10 (spec 9, the owner's option C): a new module gatewayd::links with
disarm(text, untrusted) -> Disarmed { text, links }, given as a skeleton
with 16 small todo!() helpers, and deliver posting each answer through
answer_posts: disarm, split_answer, disarm each part again, NOTICE once
at the end of the last post when a link was put in a code span.

Checks, against a reference kept off master:
- Replayed from ref 09 in a scratch worktree with its own target/: the
  task's edits (applied from the task text) plus the given files plus the
  reference bodies are byte-identical to the reference. make gate on
  rustc/clippy 1.98.1: gate: ok, 1081 tests (1058 + 7 + 7 + 9). /tmp
  holds nothing new after the gate.
- Step 2 (E0432 only) and step 3 (6/1, 7, 9 failing, all "not yet
  implemented"; warnings as the task lists) seen in the replay.
- 71 one-line breaks of the reference, each caught by the given tests.
- Differential fuzz of generated Markdown through Mattermost's marked
  fork (mattermost/marked c211e4c, the webapp's options) and CommonMark:
  on the final code, 600k replies in each mode, half with inline LaTeX
  on, render no image and (untrusted) no link.
- disarm(disarm(x)) == disarm(x) on 200k generated replies, both modes.
- Given files pass rustfmt; skeleton comments read beside their
  signatures; no chat-template tag in the plan or given files;
  deliver_links 40 runs under CPU load, no failure.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 09:21:57 -07:00
kyleandClaude Opus 5.5 8658af58b7 M5b plan: task 09, TurnDone.untrusted from the session's log
bxcore's TurnDone gains untrusted: bool, always written and required
when read (no serde default: a missing field would read as a clean
session, the wrong way to fail). loopd's new Session::untrusted is true
once any ToolResult in the log is labelled untrusted, and channel::handle
puts it in the TurnDone; the log is the one source, so it holds across
resumes and restarts. gatewayd, bxctl and brokerd sources are unchanged;
their tests that build a TurnDone get new versions. Task 01's broker.rs
is kept as broker.rs-task01, and task 01 now copies it.

Checks: the given tests fail at ref 08 as the task says (strict's
turn_done.json, and 13 test targets with E0560/E0609 only); the task's
edits applied from its text to a fresh ref 08 tree are byte-identical to
the reference; the gate passes with 1058 tests; 12 mutations caught;
/private/tmp and TMPDIR unchanged around the runs; no tag in plan or
given files.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 06:37:03 -07:00
kyleandClaude Opus 5.5 d4b7f42b56 M5b plan: task 08, the self-test checks the tags against the tokenizer
loopd's self-test gains a check, run second (after "server matches
config", before any prompt): each tag in bxcore::sanitize::TAGS must be
one token from /tokenize, its recorded id, and its neutralised form
more than one. A failure names the tag by its id, never its text, and
points at a new runbook entry, loopd-tags-unexpected, given as a
fragment; SelfTestError::pointer picks the entry per failure. One
request per text (66): /tokenize has no batch form. The fake server
gains respond and a fake tokenizer; selftest.rs, serve.rs, pointers.rs
and support/mod.rs get M5b copies.

Checks, on a reference at ref 08 (titan, rustc 1.98.1, own target/):
- /tokenize fields from llama.cpp tools/server/README.md at b10964
  (b29c606) and its handler; on straylight all 33 tags gave their ids
  and every neutralised form 7 to 11 tokens, 66 requests in ~0.3 s
- given tests before the change: 4 fail as the task lists, selftest_tags
  does not compile (7 E0599); pointers passes
- make gate: gate: ok, 1050 tests (1042 + 8); /tmp and $TMPDIR
  unchanged around it; the four suites 25 runs under load, no failure
- 20 mutations, each caught: id not compared; neutralised check
  dropped; > 1 as >= 1, as == 1 missing 0, as > 2; first or last tag
  skipped; tag asked twice; pointer dropped or wrong (all or Tokenize);
  check skipped in serve; check run last; parse_special false;
  add_special left out; id missing from the message; the tag's text in
  the message; tokenize error as Infer; wrong count; wrong other id
- the task's edits applied to a fresh ref-07 copy give files
  byte-identical to the reference; every before block found once
- no chat-template tag whole in the task or given files; task lines
  within 100 characters except tables and verbatim lines; given files
  under 500 lines

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 06:14:58 -07:00
kyleandClaude Opus 5.5 7998f0be17 M5b plan: task 07, loopd neutralises text when it is first logged
loopd neutralises the user's message (the User record), every text
appended as a tool result (results, recall's, denials, failures and
loopd's own answers share the one append in run_turn), after the cap,
and the memory block in Baseline::assemble_with. The model's output,
system.md and replay are untouched: an old log with a raw tag is
replayed byte for byte.

Checks, on a reference at ref 07 (titan, rustc 1.98.1, own target/):
- given tests before the change: 7 of 9 fail as the task lists; the
  two that guard what must not change pass before and after
- make gate: gate: ok, 1042 tests (1033 + 9); /tmp unchanged around it;
  the two new suites 20 runs, no failure
- 14 mutations, each caught: user not neutralised; neutralised before
  the cap; recall path missed; only untrusted results; truncated from
  the final length; block not neutralised; model content, reasoning or
  tool arguments neutralised; replay re-neutralises user, tool or
  system; open rewrites records; system.md neutralised too
- the task's edits applied to a fresh ref-06 copy give files
  byte-identical to the reference; every before block found once
- no chat-template tag whole in the task or given files; task lines
  within 100 characters except tables; given files under 500 lines

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 05:42:35 -07:00
kyleandClaude Opus 5.5 10d2e41f43 M5b plan: task 06, memoryd refuses and flags through sanitize
memoryd's writes go through bxcore::sanitize::refuse, then flag; check.rs
and its test go. A refused write answers with the rule's sentence and is
logged with the rule's name; a flagged identity write becomes untrusted
notes with the rule in the answer, the journal (new optional field `flag`)
and the log, and is never staged for review; the block leaves out an
entry that breaks a refuse rule and logs the rule. The three runbook
entries the new pointers need are given here as fragments, because the
gate's runbook check fails on a pointer without its entry.

Checks, on titan (rustc 1.98.1, clippy 0.1.98):
- given tests fail before as the task says: 7 failing in edit, serve and
  serve_sanitize; files and sanitize do not compile (E0609; E0432, E0609,
  E0615)
- the task's edits, parsed from the task file and applied to a fresh
  ref-05 worktree with its own target/, give a tree byte-identical to the
  reference; its gate: ok, 1033 tests; /private/tmp unchanged
- 26 one-line mutations of the reference, each caught by the given tests
- new tests stressed under load: 30 runs, 0 failures
- no chat-template tag and no whole fake key in the task or given files

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 05:14:10 -07:00
kyleandClaude Opus 5.5 9ee215337f docs: dependencies: credit Hermes for the ported flag rules
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 04:32:14 -07:00
kyleandClaude Opus 5.5 ad60f84a65 M5b spec: the flag port as planned in task 05
Records what porting Hermes' patterns decided: seven rules kept,
"dump the conversation" narrowed, agent-configuration changes left out
(Boxmaker's agent loads no such files), about fifty look-alikes,
apostrophes folded, and a flag in bxctl names only its rule. A false
positive also marks a session that recalls the note untrusted.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 04:32:14 -07:00
kyleandClaude Opus 5.5 0c2946c99e M5b plan: task 05, narrower flag rules
The design model's review of task 05: a recalled flagged note taints the
reading session, so a false positive on a code-work note costs a session.

- dump-conversation: the object must end its clause (a new part, Ends,
  and a function to write, ends_clause): punctuation after it, the last
  word, or one of so far, above, to, into, in, with, verbatim next. "include
  the conversation id in each log line" and CLAUDE.md's "spike output that
  contains conversation content" now pass (code-work corpus).
- persist-agent-config dropped: Boxmaker's agent never loads AGENTS.md,
  CLAUDE.md, .cursorrules or .clinerules as its instructions. Listed under
  not ported; its notes are code-work cases, its attack a decided edge.
- normalise folds the typographic apostrophes U+2018 and U+2019 to '.
- The corpus is split: other scripts and decided edges move to
  sanitize_flag_edges.rs, keeping each file under 500 lines.

Checks: make gate on the reference (ref 05, 818dbc7), rustc 1.98.1: gate
ok, 1023 tests (22 + 2 + 2 new); the task applied to a fresh ref-04 copy
is byte-identical to it; 60 mutations, each caught; /tmp unchanged around
the gate; no chat-template tag in the task or given files; given files
pass rustfmt and the 500-line limit.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 04:30:59 -07:00
kyleandClaude Opus 5.5 e087ebb0ff M5b plan: task 05, sanitize::normalise and sanitize::flag
Task 05 adds normalise (case, full width, white space and zero-width,
leetspeak, fifty Cyrillic and Greek look-alikes from UTS #39
confusables 18.0.0) and flag: nine rules, eight ported from Hermes
Agent's tools/threat_patterns.py (NousResearch/hermes-agent at d0288be,
MIT) as word sequences with bounded gaps, and M5a's six phrases. Given:
two skeletons (eleven small todo!()s, tables whole) and two test files
(21 unit tests, 4 corpus tests including Russian and Greek notes and a
table of decided edges).

Checks: the given tests fail before for unresolved imports only (E0432);
the reference (ref 05) passes make gate on rustc 1.98.1, 1022 tests;
the task applied to a fresh ref-04 copy is byte-identical to it; 45
one-line mutations, each caught; /tmp unchanged around the gate; no
chat-template tag in the task or given files; given files pass rustfmt
and the 500-line limit; every todo!() comment read beside its signature.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 04:16:27 -07:00