## sanitizer-rules **What you see.** Not a failure: the procedure for changing a sanitizer rule, which the entries `memory-write-refused` and `memory-write-flagged` send you to when a rule catches ordinary text, or which you follow when an attack gets through. The rules are in `crates/bxcore/src/sanitize/`: `refuse.rs` (6.1 of the M5b spec), `tags.rs` (the chat-template tags that are neutralised, 6.2) and `flag.rs` with `normalise.rs` (6.3). **Why.** The rules are compiled in rather than read from a file, so that every change passes the two corpora and review, and a typo cannot switch a rule off (M5b spec, section 6.5). A rule that refuses code is as much a bug as one that lets an attack through: the agent works on code projects, and a flagged note makes every session that recalls it untrusted. **Confirm.** The evidence, before anything changes: - a rule's name in `bxctl memory log` (`flagged `), in `memoryd`'s log, or in a refusal the agent passed on; - `bxctl sanitize check` with the text, which names each rule that fires and writes nothing; - a device check, a review or new research that shows an attack the rules miss. **Fix.** In this order, one commit: 1. **The case first.** Add the text to a corpus, with a comment saying where it was seen: the attack corpus if it must be caught, the code-work corpus if it must pass. The corpora are the given tests `crates/bxcore/tests/sanitize_refuse_corpus.rs` (refuse), `sanitize_corpus.rs` (neutralise) and `sanitize_flag_corpus.rs` (flag). Build a tag or a key from pieces, as the files already do: never write one whole. Run `cargo test -p bxcore` and see the new case fail. 2. **The rule.** Add or narrow the entry in the rule's table, with a comment naming its source. Keep it as narrow as the evidence: a flag rule ported from Hermes keeps its attribution. 3. **The gate.** `make gate` runs both corpora for every action, so a rule that catches code fails before it lands. 4. **The record.** A policy change (a new kind of refusal, or a rule moved from flag to refuse) gets a row in `docs/decisions.md`; an ordinary rule change is its commit. A change reaches an agent when it is rebuilt and restarted. Stored memory is never rewritten: an identity entry that breaks a new refuse rule leaves the memory block at once (see `memory-entry-excluded`), and a note flagged under an old rule stays as it was written. **Check.** `bxctl sanitize check` with the text gives the new answer (nothing, and exit 0, for a text that must pass). After the rebuild, the agent's next write of it is answered as expected.