docs: a wave skill, so the brief stops being retyped
Four agents were dispatched without worktree isolation and all edited the same working tree: one git status held one agent's effect.rs, another's harness/build.sh and a third's scripts/node-test.sh. Any commit would have swept up three units' half-finished work. The brief had carried that rule before and lost it, which is what a retyped brief does. It is now a skill: worktree isolation first, partition by crate rather than ticket count, the base commit by name, the baseline, the never-write list, the injection discipline, pr-watch --expect, and attribution. Two doctrines: every_wave_agent_gets_its_own_worktree records the tell and the per-agent recovery (copy only what an agent can PROVE it touched -- two units can legitimately both be in improvise-protocol); the_orchestrator_delegates_substantial_work records the user's rule and the non-obvious half of it, that orchestrator work lands in the shared primary tree and collides with agents who own that ground. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
This commit is contained in:
@@ -218,6 +218,8 @@ doctrine(a_write_must_not_be_able_to_disarm_its_own_gate,'scripts/kb validates a
|
||||
doctrine(a_read_validate_write_sequence_is_a_lost_update,'scripts/kb read the knowledge base, merged, validated and moved the result into place, with nothing between the read and the move. Two agents doing that concurrently -- which is ordinary here, waves run agents in parallel -- both read the same file, both validate, and the later move silently discards the earlier addition. Exactly the class this tool was built to remove, reintroduced one level up in the tool itself. Closed by stamping the file content at read time and refusing to commit if it changed, plus an mkdir lock (flock is absent on macOS) so the collision is rare rather than merely loud. The stamp is the part that matters: a lock prevents the common case, a compare-and-swap is what makes an uncommon one impossible to lose silently.').
|
||||
doctrine(design_against_the_model_before_implementing,'AGENTS.md has always said Prolog is a design tool for reasoning about tickets, and it was the least-used line in the file: kb.pl was written retrospectively. \`scripts/kb check --with <design.pl>\` closes that -- proposed facts are merged into a throwaway candidate and kb_consistent runs over the union, so a design can be refused before code exists. The model already DERIVES rather than merely records: illegal_dependency, may_use, depends_transitively, portable, snapshot_safe, view_state_admits, record_obligation, needs_runtime_check, and eleven kb_error classes including layering_inverted and field_misplaced_in_view_state. Demonstrated on three proposals in one sitting: one added edge (improvise_core depending on improvise_command) produced SIX derived layering violations through the transitive closure, which no author would have enumerated; a proposal to declare decision/3 functional was refused with conflicting_values naming improvise-907, correctly, because one ticket carries several decisions; and a mis-ordered layer/2 argument was named as layer_names_unknown_crate rather than silently accepted. What makes this worth doing rather than decorative is the same rule as everywhere else -- the model must be able to REFUSE, and you must watch it refuse. A design language nothing can reject is documentation with parentheses.').
|
||||
doctrine(a_key_rule_must_ask_whether_the_relation_is_multi_valued,'scripts/kb keyed every clause on the functor plus all arguments but the last, which is right for doctrine(Name,Text) and wrong for any RELATION: crate_depends_on(A,B) then keyed on A, so a crate depending on two crates read as one key twice and was unaddable. Found by trying to model a proposed crate, not by any test -- the tool had only ever been pointed at single-valued facts. The fix cannot read the KB own functional/1, because that check keys an arity-3 predicate on its FIRST argument, which is right for crate/3 and wrong for decision/3 where one ticket holds several decisions under different names. So key_arity/2 is a hand-maintained table and anything unlisted keys on the WHOLE head, which is the safe direction: an unlisted relation accepts many clauses and only an exact repeat is a duplicate.').
|
||||
doctrine(every_wave_agent_gets_its_own_worktree,'Dispatching parallel agents without worktree isolation puts all of them in the SAME working tree. Observed 2026-09-20 with four agents: one git status showed one agent effect.rs beside another harness/build.sh and a third scripts/node-test.sh, so any commit would have swept up half-finished work from three units and any checkout would have destroyed it. The tell is cheap and worth checking early -- run git status in the primary tree after dispatch and see whether it holds files from more than one unit. Recovery is per-agent: create a worktree, copy across only the files that agent can PROVE it touched, then git checkout -- those files in the primary tree; an agent that guesses which files are its own makes it worse. Ambiguity is real -- two units can legitimately both be in crates/improvise-protocol -- so the instruction is to check an edit history, not to infer from the ticket.').
|
||||
doctrine(the_orchestrator_delegates_substantial_work,'User instruction 2026-09-20: any substantial work goes to an agent in a worktree rather than being done in the orchestrator, and repeated tasks become a script or a skill rather than being retyped. Two reasons, one obvious and one not. The obvious one is that heavy orchestrator work blocks every sub-agent waiting on it. The other is that work done in the orchestrator lands in the SHARED primary tree, so it collides with whatever the agents are doing there -- an orchestrator fixing a bug in scripts/ while an agent owns scripts/ is a conflict the agent did not agree to. Corollary for briefs: a brief retyped by hand drifts between dispatches, which is what the wave skill exists to stop.').
|
||||
work_done('improvise-8tk',bug_fix,'ZZ/:wq quit even when the save failed, silently discarding the model. Save now aborts the effect batch on failure; save_to_current_path/1 returns Result<String,String>; three tests in ui/effect.rs.').
|
||||
work_done('improvise-907',bug_fix,'Import wizard step machine redesign: validation_error() gate at ReviewProposals (no-category and over-limit), history-stack previous() bound to Left on every step, and Done made a real confirmation step holding the built workbook. Superseded improvise-gfq, improvise-o1c, improvise-c6j.').
|
||||
work_done('improvise-6sq',wont_do,'Merging EditOrDrill and EnterEditAtCursorCmd into one EnterEdit{mode} is not safe: they differ in WHEN the cell value is read, not in a mode parameter. Added ui/app.rs::tests::i_and_o_diverge_on_an_empty_records_grid as executable evidence.').
|
||||
|
||||
Reference in New Issue
Block a user