scripts/kb add wrote onto a working-tree kb.pl that had lost 276 of its 801
clauses and reported success, because kb_consistent checks consistency, not
completeness (improvise-usus). Every write now runs kb-merge.pl `complete`
on the candidate before kb_consistent. It refuses unless every clause HEAD
has is still present by key, or its removal was recorded by `kb rm` in a
per-worktree ledger in the git dir, which prunes itself on every write.
The candidate is now built beside kb.pl, so publishing it is a rename(2)
rather than a possible cross-device copy that a kill could truncate.
Exit codes: 0 done, 1 refused, 2 usage, 3 HEAD's kb.pl unreadable. kb-test.sh
now builds its fixtures from HEAD and adds cases 18-22. Argument in
context/code/kb-tool.md, evidence and rivals in context/test-evidence/tooling.md.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
Two user rulings.
Documentation should not spend itself on history -- that is bloat. Prose docs
now lead with the rule and keep only the line that stops it looking arbitrary;
the worked examples and the how-we-found-it move into doctrine/2 text, which is
what that predicate is for and where a reader goes deliberately rather than
while looking for a command. Trimmed: both skills, context/code/kb-tool.md, and
the kb paragraph in AGENTS.md.
Anything an agent posts to GitHub goes out under the owner's account, so every
comment, review and issue body now opens with a line naming the model and that
an agent wrote it. A PR body's generated-with footer already did this; comments
carried nothing, and an unattributed one reads as his own words to every reader
-- including a reviewer deciding how much weight to give it. The reply on PR #68
has been amended in place.
improvise-xfsb files the structural fix: a GitHub App or machine user so agents
post under their own identity. An attribution line is a convention an agent can
forget; a separate identity cannot be.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
Three gaps in the tool as first written, each found by asking what it could not
do rather than by running it:
- There was no way to REMOVE a clause. Every correction could add or overwrite,
so a fact that went false could only be rewritten, never dropped. `kb rm`
takes keys and refuses when any is absent -- a typo'd key would otherwise
report success and leave the clause in place.
- `doctrine(kb_is_maintained_through_the_mcp)` is retired for
kb_is_maintained_by_scripts_kb. The NAME was the claim that went false, which
is the repair opfs_is_run_but_bindings_is_not needed for the same reason. The
replacement records why nobody should go back, and that READING through the
MCP is unaffected.
- AGENTS.md said kb.pl was "MCP-maintained, whole-predicate replacement" and
told agents to strip host_rpc shims after each save. Both described the
retired mechanism. This is a_fact_stated_twice_is_changed_by_grep landing
inside the PR that depends on that doctrine being true.
Also fixes a printed value that was an unevaluated expression: `rm` reported
"530-529 removed" rather than 1.
kb-test.sh is 11 cases now, both new ones proved by injection.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
loadProgram REPLACES a whole predicate, so adding one clause meant re-sending
every existing clause of it through an agent's context window. Measured on the
wave-3 batch: ~113KB read and ~113KB written to deliver ~25KB of new text. That
sets the behaviour -- writing becomes a thing you batch and do rarely -- and the
batch is where a short payload silently drops the rest of a predicate, which has
cost this repo 22 decisions and 19 doctrines once already.
scripts/kb reads clauses as DATA and never consults them, so the cost of adding
a clause is the clause. Canonical form is read_term in, portray_clause out --
measured, not designed: all 495 existing facts round-trip byte-identically and
the form is a fixed point in one pass. Rules move to one line, which is why the
reformat lands here rather than earlier: the MCP would re-expand them on its
next save and the two would thrash.
trealla moves into the devShell. Reaching it through `nix shell` per invocation
took the tool's first real use past a two-minute timeout for a write that takes
0.42s with tpl on PATH.
Four guards, each added because injecting its fault showed the tool accepting
what it should refuse. None was found by reading:
- an unparseable delta dropped trealla into its REPL, which HANGS with a
terminal and EXITS 0 without one -- a refusal that reads as acceptance
- `-g main,halt` exits 0 on a throw AND on a failure, because a goal that fails
never reaches the halt beside it; halt(2) alone exits 2, so the default is the
hazard. The goal now guarantees a halt on every path (0/2/1)
- `42.` parses, is not callable, and landed as a fact whose head is a number;
kb_consistent has no opinion about stray terms
- fmt discarded the merge's exit code and would have committed a failed
canonicalization
Plus the two that were designed in: the result is read back and required to be
the clauses intended (variant equality -- == compares variable identity and
reports every rule as different), and kb_consistent runs on the CANDIDATE, so a
bad delta leaves the knowledge base untouched rather than needing a revert.
scripts/kb-test.sh proves all nine by injection. context/code/kb-tool.md carries
the argument, including what it does not reach.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH