Commit Graph
464 Commits
Author SHA1 Message Date
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 89a50bc3c6 tools: run-plan: pure sessions, resume after the limit, row named
Three changes from the M5b run (implementer lessons T47, T48):
- each session runs `opencode run --pure` with
  OPENCODE_DISABLE_CLAUDE_CODE=1, so no global plugin or borrowed rules
  file reaches the implementer; PLUGINS=1 restores both;
- with RESUME=1, a session stopped at SESSION_LIMIT_MIN with work left
  uncommitted is resumed once, as one that ended by itself already was;
  an idle stop is still not resumed;
- the prompt names the log row's Task cell, so a task's heading cannot
  mislead the row the driver then looks for.

Checked against a stub opencode in scratch repositories (17 checks:
the flags and variable, PLUGINS=1, a resume after the limit, no resume
without work or without RESUME).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 18:33:11 -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
kyleandClaude Opus 5.5 a28ba8792d M5b plan: task 04, PGP private key blocks too
The private-key rule also refuses a line that starts `-----BEGIN` and
ends `PRIVATE KEY BLOCK-----`, OpenPGP's armor header (RFC 9580
section 6.2.1; RFC 4880 section 6.2). The two endings are a given
table, PRIVATE_KEY_ENDS; the skeleton's private_key comment names it.
A PGP private key block joins the attack corpus, a PGP public key
block and a signature the code-work corpus, and the edge test gains an
indented PGP header (refused), a PGP public header and a PGP header
without its closing dashes (both pass).

Checks: the reference passes the gate on titan, 997 tests (unchanged:
the cases are inside existing tests), /tmp unchanged around the run;
the task's edits applied to a fresh ref-03 copy are byte-identical to
the reference; 8 private-key mutations, 4 of them new for PGP, all
caught; tag and private-key-header greps over the task and given files
are empty; given files pass rustfmt and the 500-line limit.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 03:41:52 -07:00
kyleandClaude Opus 5.5 ebadc8250e M5b spec: refuse PGP private key blocks too
The private-key rule named only PEM's ending, so an armored PGP
private key passed. Public-key blocks still pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 03:37:31 -07:00
kyleandClaude Opus 5.5 ed4cef9ec5 M5b plan: task 04, sanitize::refuse
The refuse half of spec 6.1 in bxcore: M5a's control and invisible
rules and tables, the tag rule narrowed to task 03's exact matching,
and new rules for private use, noncharacters and known-prefix secrets
(GitHub, GitLab, sk-, Slack, AWS, private-key lines). A skeleton with
eight small todo!()s; the rule table, sentences and secret tables are
given filled in. memoryd is untouched until task 06.

Checks: given tests fail before (E0432, then 16 not yet implemented);
the reference passes the gate on titan, 997 tests (981 + 16), /tmp
unchanged around the run; the task's edits applied to a fresh ref-03
copy are byte-identical to the reference; 36 mutations, all caught
(one needed a new case: lower case after AKIA); tag grep over the task
and given files is empty; 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 03:34:59 -07:00
kyleandClaude Opus 5.5 fb3c483b50 M5b plan: task 03, the model's tags and neutralise
Task 03 is now the tag list and sanitize::neutralise; refuse moves to 04 and flag
to 05. The list is the 33 control and user-defined entries of the model file
straylight's router loads (Ornith-1.5-35B-Q4_K_M.gguf, snapshot 12393612fd4f,
llama-server b10964-b29c606), each checked with /tokenize on 2026-09-26 to be one
token with its id. Review focus 1 is decided by the same measurement: a tag
inside a word is still the token and is neutralised; upper or mixed case, a
zero-width, space or line-feed split, and full-width brackets are not, and are
left as is. Each shape is pinned by a given test.

Checks: the given tests fail before (E0432, no bxcore::sanitize); the reference
passes make gate on titan (rustc and clippy 1.98.1) with 981 tests (963 + 18);
16 mutations of tags.rs, all caught; no failure message prints a tag; /tmp
unchanged around the gate; the task's edits applied to a fresh ref 02 tree give
a tree byte-identical to the reference; no plan or given file holds a tag whole;
given files pass rustfmt and are under 500 lines.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 03:11:31 -07:00
kyleandClaude Opus 5.5 0f1de0d6d4 M5b plan: task 02, the secret store moves to bxcore
The task moves gatewayd's secret store to bxcore::secrets behind the
Cargo feature `secrets` (spec, section 5). The moved module is given
whole; SecretSpec and SecretSource move with it, so gatewayd's config
keeps its [secrets.<name>] table with the per-secret type from bxcore.
load and read_checked take the role's name first, so the three
sentences that named gatewayd literally now name the role; gatewayd
passes "gatewayd" and its bytes are unchanged (its tests/main.rs pins
them and is not touched). A given loopd test parses the three
manifests (loopd's, the workspace entry, bxcore's features) and checks
the cfg line in bxcore's lib.rs; the task also checks `cargo tree -p
loopd -e features` and builds loopd on its own.

Checked: the reference, replayed from the task text applied
mechanically to the start tree (master plus task 01), is byte for byte
the reference; every before block found exactly once there. Step 2's
failures recorded from that tree: ten test targets with E0432 on
`bxcore::secrets`, two of four no_secrets tests. make gate on titan's
toolchain: gate: ok, 963 tests (956 + 3 + 4), clippy clean, /tmp
unchanged across the run. 23 one-line mutations of the reference, all
caught; removing the zeroize wrapper is not observable and not counted. Given
files pass fmt and the line limit; no chat-template tags.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 02:56:26 -07:00
kyleandClaude Opus 5.5 fa5bcae503 M5b plan: task 01, the invalid-arguments detail
Task file and 13 given files (2 new: the wire fixture with a detail and
brokerd/tests/invalid_detail.rs; 11 new versions of existing tests, most
only adding `detail: None`).

Checked against a reference in a scratch worktree with its own target/,
on titan (rustc 1.98.1, clippy 0.1.98): make gate prints gate: ok with
956 tests (944 + 12 new); /tmp has no new entries after the gate. All 21
quoted before-blocks occur once in master, and applying the edits to
master gives the reference byte for byte. The given files are the
reference's, formatted, none over 500 lines; no chat-template tag in
the plan.

Before the edits, strict's envelopes_reject_unknown_keys_at_every_depth
fails and ten test targets do not compile, as step 2 lists.

Mutations: 21 one-line breaks of the reference; 20 caught by the given
tests. The one not caught (dropping `default` from the detail attribute)
cannot change behaviour: serde reads a missing Option field as None.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 02:35:35 -07:00
kyleandClaude Opus 5.5 56b969d55b M5b plan: task 00 done; task 14 is the runbook only
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 01:49:47 -07:00
kyleandClaude Opus 5.5 67985023c9 design: the shared crate is bxcore (decision of 2026-09-26)
Code constraints: `proto` became `bxcore` in M5b task 00 and holds the
code roles share as well as the shared types. Recorded in
docs/decisions.md with the M5b spec.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 01:49:21 -07:00
kyleandClaude Opus 5.5 9672cb5e53 AGENTS.md, CLAUDE.md, dependencies: the shared crate is bxcore
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 01:49:21 -07:00
kyleandClaude Opus 5.5 2b3ae080b4 proto becomes bxcore, the one shared crate (M5b task 00)
Done the way the owner asked and the spike mapped: the crate and the
manifests renamed, then what the build reported fixed. Layers: 138
"proto unresolved" errors across 169 files; cargo fmt reordering
imports; six fixture paths built as strings in brokerd's and bxctl's
tests, which the compiler cannot see, and comments naming
crates/proto/; the crate-dependency check and its self-test. One more
the spike did not reach: a `\nproto =` key inside a printf format in
the self-test, which a word-boundary replacement skips because `n` and
`p` are both word characters.

make gate: gate: ok, 944 tests; no new entry in /tmp.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 01:48:38 -07:00
kyleandClaude Opus 5.5 727e027fcf M5b plan: the outline (README), for the owner's review of the breakdown
Task 00 (proto -> bxcore) by the design model before the run; fourteen
Ornith tasks: the invalid-arguments detail, the secrets move, the
sanitizer in three tasks, memoryd, loopd's neutralising and self-test,
TurnDone.untrusted, gatewayd's links, bxctl sanitize check, the instance
lock in two tasks, and the runbook. Global constraints, five review-focus
inputs each owned by a task, and the interfaces between tasks. The task
files and the reference come next, through docs/plans/CHECKLIST.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 01:42:26 -07:00
kyleandClaude Opus 5.5 a13d584f7a M5b spec approved; decisions: bxcore, the one shared crate
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 01:40:32 -07:00
kyle 0a3b8d70dc move debug handoff to research
maybe not the best place but the closest we have
2026-09-26 01:39:39 -07:00
kyleandClaude Opus 5.5 67f497f482 M5b spec: one instance per daemon, by a lock file
Every daemon removes a leftover socket before binding, which also lets a
second copy silently take a running one's sockets. Owner's decision:
refuse to start instead. Each daemon takes an exclusive std File lock
(stable since 1.89, checked on 1.95) on <home>/run/<role>.lock, or
<socket>.lock for inferproxy, before touching its sockets; the system
releases it however the process ends. A runbook entry, a security note
and a test per daemon.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 01:34:35 -07:00
kyleandClaude Opus 5.5 2b052b24d3 T44: test paths removed by guards; the checklist compares /tmp around a gate run
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 01:29:46 -07:00
kyleandClaude Opus 5.5 a15c428ea6 tests: every temporary path is removed by a guard; no leftovers in /tmp
About 10,000 test leftovers had built up in titan's /tmp. loopd's Home
and FakeServer, its fake broker's socket, bxctl's temp sockets, fake
loopd and command homes, inferproxy's socket, and a few brokerd and
toolkit tests created uniquely named directories or sockets and never
removed them, or removed them by hand at the end, which a failing test
skips. Each is now a guard removed on drop (bxctl gains
tests/support/temp.rs). Two races also left gatewayd homes behind: the
test Running only signalled its server to stop, and two Stage structs
declared the home first, so it was removed while the server could still
write into it. Running's drop now waits for the thread (with a 5-second
limit), and every home is declared last.

Checked: make gate twice, then the gate and three rounds of gatewayd and
loopd tests; no new entry in /tmp; 944 tests pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 01:29:32 -07:00
kyleandClaude Opus 5.5 53532e04ff M5b spec: the plain-URL fetch was marked, not observed
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 01:09:18 -07:00
kyleandClaude Opus 5.5 a73c702685 M5b spec: rewrap the status line
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 01:06:16 -07:00
kyleandClaude Opus 5.5 85ff384bb5 M5b spec: the preview test on the owner's server; section 14 answered
Code spans stop both the link preview and the server's image fetch;
Markdown links are not previewed; images are fetched by the server at
posting time. Emoji refusal accepted, phrases move to flag, Hermes'
patterns ported at plan time.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 01:05:55 -07:00
kyleandClaude Opus 5.5 aea594a1ad M5b spec (draft): hardening
bxcore (compiler-driven rename, with the spike's four layers named); the
invalid-arguments reason on the wire; the secret store moved into
bxcore behind a feature loopd never enables; bxcore::sanitize with
refuse, neutralise and flag, its lists, hand-written normalisation, two
corpora, a rule-change procedure and `bxctl sanitize check`; memoryd
refusing and flagging (a flag keeps the text as an untrusted note);
loopd neutralising tags on append, with a tokenizer self-test and the
session's trust on TurnDone; gatewayd escaping images and, in untrusted
sessions, links; a table of every link between the roles; runbook
entries; attack tests per trust boundary and the device checks. Four
points left for the owner in section 14.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 01:00:26 -07:00
kyleandClaude Opus 5.5 e2993ad869 decisions: the local-model trailer, and adopting the combined Ornith guidance
Owner's decisions, 2026-09-26. The second row lists what was adopted,
which driver guards are trials for M5b's run, and what was left out.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 00:39:50 -07:00
kyleandClaude Opus 5.5 411d9e04a0 run-plan: a session time limit, a stopped-row message, trial resume and watchdog
From the combined Ornith guidance (§6). Each session runs in its own
process group and is stopped after SESSION_LIMIT_MIN minutes (default
90), killed by group so the cargo it started goes too, never with pkill
(titan has no `timeout`, so the limit is plain shell). A task that logs
`stopped` gets its own message with the row. Two guards for a trial run:
RESUME=1 resumes a session once when it leaves work uncommitted, and
SLOTS_URL stops a session when no llama slot has been busy for
IDLE_LIMIT_MIN minutes (default 10).

Tested against a stub opencode outside a throwaway repository: a clean
task, a stopped row, a wrong trailer, an uncommitted session with and
without RESUME, a hang past a 1-minute limit (its child process killed
with it), and a session with no busy slot for 1 minute.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 00:29:30 -07:00
kyleandClaude Opus 5.5 224617ee65 docs/plans/CHECKLIST.md: the checks before a plan is handed over; T41 to T43
One list for the design model to work through per task, gathering the
task-writing tips that apply before hand-over (T18, T19, T24 to T29,
T32 to T35, T39, T40) and three new ones from the combined Ornith
guidance: walk each given test by hand against the task's rules (T41),
grep earlier given files for anything the task changes (T42), and run
the gate's own checks on the given files (T43).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 00:28:25 -07:00
kyleandClaude Opus 5.5 31fe078e64 opencode.json: an implement agent for unattended runs; run-plan uses it
From the combined Ornith guidance (§8.2), checked against OpenCode
1.18.30's schema and permission docs (last matching rule wins). Reads
and edits outside the repository are denied, except OpenCode's own
tool-output and temporary directories; web access, questions, subagents
and doom-loop prompts are denied; edits to AGENTS.md, CLAUDE.md,
opencode.json, the design documents, specs, plans and tools/ are denied;
shell commands are allowed except push, reset, rebase, amend, switch,
checkout, find /, locate, sudo, rm -rf / and curl/wget. A denied call
continues the loop (experimental.continue_loop_on_deny, untested with
Ornith). steps = 400 is a backstop. No custom prompt: OpenCode already
reads AGENTS.md, and a prompt may replace its tool guidance.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 00:18:03 -07:00
kyleandClaude Opus 5.5 0356c877dd AGENTS.md: scratch space, refusals, one todo at a time, gate timeout, commit -F
From the combined Ornith guidance (~/projects/ornith/GUIDANCE.md, §4):
experiments go under the ignored .state/scratch/, never /tmp (which
reconciles I16 with crossbar's in-repo experiments); a refused tool call
does not end the turn; todo!() bodies are filled one at a time with
cargo check between; the gate runs with a 600000 ms shell timeout;
Cargo.lock is staged when it changed; commit messages are written to a
file and committed with -F, since quotes broke -m in crossbar.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 00:15:36 -07:00
kyleandClaude Opus 5.5 f98ec2a908 Local-model commits carry Co-Authored-By: <model> <agent@wntrmute.dev>
Owner's rule. The implementer often cannot know which model it is, so
run-plan.sh derives the name from OPENCODE_MODEL (the part after the
provider), gives the exact trailer and the Model column's value in the
session prompt, and stops the run when a task's commit lacks that exact
line. AGENTS.md replaces the old Implemented-By trailer and the "owner
fills in the model" rule.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 00:10:00 -07:00
kyleandClaude Opus 5.5 cc04b3923b P24: M5b is hardening, M5c Honcho, M5d the scheduler
Owner's decision. The hardening half (sanitizer, tag neutralisation,
image links escaped, the invalid-arguments reason, and the secret store
available to every role) needs neither roncho nor Honcho and can be
specified now. Honcho waits on roncho's tagged release. Earlier
documents keep the old numbers; P24 says so.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 23:53:12 -07:00