The restated list said cargo fmt --all --check, which reaches one of two workspace roots, and omitted direnv exec, checkers.sh and node-test.sh. agent-rules gains two rules from PRs #176 and #177. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
11 KiB
Standing rules for a wave agent
Every work-unit agent reads this in full before its first command. A brief
points here and adds only what is specific to its wave: the expected base
commit, the agent's ground, its BEADS_ACTOR, and its tickets. Every rule below
was bought by a failure.
This file holds each agent-side rule once. The brief checklist is
SKILL.md beside it. Where AGENTS.md ("The Wave Workflow") carries a longer
history, the rule here points there. Change a rule here, not in a brief, and
not in a second copy.
Base, branch, push
- Verify your base against the commit your brief names, first. Run
git log --oneline -1and compare. Worktrees do not reliably branch from the orchestrator's HEAD: one was created three merges behind, and it was only noticed because the agent was told to look. - Branch from
gh/main, nevermainororigin/main.git fetch gh && git checkout -b <branch> gh/main. A worktree handed to you may already sit on unpushed commits. Why:AGENTS.md, "Shipping a wave" (the two remotes both spelledmain, and the worktree-base vector). - Before opening a PR, check
git diff --numstat gh/main...<branch>. Do not usegh pr view --json files: that is the union of your commits' files, not the effective diff. - Push with an explicit refspec:
git push gh HEAD:refs/heads/<branch>. A branch cut fromgh/maintracksgh/main, so a bare push targetsmainand is refused. - Before pushing a fix to an existing PR, check
gh pr view <n> --json state. GitHub deletes a merged PR's branch. A push to it then prints* [new branch]and lands nowhere. Why:AGENTS.md, "Shipping a wave". - The "Session Completion" block near the end of
AGENTS.mddoes not apply to you. It saysgit merge origin/mainand then a baregit push. On a wave branch the first is the #145 trap and the second is refused (rule 4). Rules 1–5 replace it. The block sits between bd's generatedBEGIN/END BEADS INTEGRATIONmarkers, so it is not edited by hand (improvise-x1uk).
Commit early: a worktree is not durable storage
Commit a WIP as soon as your first test run is green, and again after every meaningful step. Push your branch (rule 4) once it is worth keeping. An agent finished a whole ticket with every gate green and lost all of it, uncommitted, when its worktree was deleted in a disk cleanup. A local commit survives a deleted worktree. Tidying history before the PR is fine; losing it is not.
Gates
direnv exec . cargo test --workspace --no-fail-fast
direnv exec . cargo clippy --workspace --all-targets -- -D warnings
direnv exec . ./scripts/fmt-workspaces.sh # NOT cargo fmt --all; exit 2 = the gate did not run
direnv exec . python3 scripts/check-comment-runs.py --check-evidence
direnv exec . ./scripts/checkers.sh
direnv exec . ./scripts/node-test.sh 'web/tests/*.test.js'
- Never
cargo fmt --all. It reaches only one of this repo's two workspace roots, becauseharness/opfs-contractdeclares its own[workspace]. The obvious repair is worse:cargo fmt -p opfs-contractfrom the repo root exits 1, "not a member of the workspace", which looks exactly like a real formatting failure (improvise-b4ks). Both spellings fail in the direction that looks like success. - Run every gate under
direnv exec .../scripts/checkers.shscrubs the environment to an allowlist, so a bare run dropstploffPATHand reports spurious failures. - Never pipe a gate.
$?must be the gate's own. Andcheck-comment-runs.py --check-evidenceprints two lines with the comment cap first, so| tail -1silently drops half of it. --no-fail-fast, because plaincargo test --workspacestops at the first failing binary. Use it for any claim about blast radius.- If you touch a browser-bundle crate (core, formula, io, protocol, command,
wasm-client, worker-server), also run
cargo check --target wasm32-unknown-unknown -p <crate>for each crate. CI checks each one separately, so feature unification cannot hide a leak. - If you touch
web/orharness/, run the Playwright suite inharness/. - If you change the grid's row or column count, also grep
harness/fixtures/*.improvandharness/tests/*.spec.js. On PR #176 a Playwright spec pinned the old collapsed-group row count.cargo testcannot see that. - A PR that conflicts with its base gets no CI run. So anything "left to CI" is unchecked until the PR is mergeable (PR #177).
- Measure your own baseline before changing anything, and report it with the machine it was measured on. A figure from the brief is a reference, not your baseline.
- Merge
gh/mainand re-run every gate before calling a PR green. Then check that no test disappeared. Diff test names, not counts: a textually clean merge can delete a test module with no conflict marker, and the other side's new tests mask the loss in a count. - The shell guard refusing a compound command means NEITHER part ran. Split the command. After a refusal, re-check the state it was meant to produce.
- Re-run the full suite after reverting an injected fault. A revert applied to the wrong occurrence once shipped two faults past every other gate.
Discipline per ticket
- Claim first:
export BEADS_ACTOR=<your actor>in every shell, thenbd update <id> --claim. Exit 13 means another actor holds it. Pick another; do not retry. bdneeds.envrcallowed in your worktree. Without it you get a version mismatch, not a clean failure. Rundirenv allowin the worktree, or rely on the[whitelist] prefixin~/.config/direnv/direnv.toml.direnv statusreportsFound RC allowed 0, and0means allowed (the enum is 0=Allowed, 1=NotAllowed). It reads like the opposite.- Reproduce at HEAD before fixing. A dozen tickets were stale-open in one
campaign, one fixed two days before it was filed. If it does not reproduce,
find the fixing commit, confirm with
git merge-base --is-ancestor <sha> HEAD, report the SHA, and close nothing. - Write a failing test first, then inject a rival and watch it fail. Run it; do not reason about it. Report where it failed, by case name. An injection that does not compile reads as a clean run, so get a positive signal that the build ran.
- Check your test against every vacuity shape seen here:
- an assertion the default state already satisfies;
f(x) == f(x): calling a pureCmdtwice against one immutable context;- a disjunctive assertion whose other branch is the bug's own symptom;
- a rival that fails the setup, which proves the neighbouring question
(e.g. setup and subject share an
EffectKind); - a fixture that is right by accident (expected value 1, group at row 0, bystander already on the relayed value);
- a fixture or stub that lacks the structure the hazard needs, or fails in a wider shape than the real fault;
- hand-picked fixture cases where the set could be enumerated: four
vacuities in
fmt-workspaces-test.shwere each a case nobody thought of (improvise-90ea); - a rendered-output test at a width where the thing asserted is never drawn;
- a fold test that batches differently from the real producer (a
Binding::Sequenceis ONE effect vec); - the cursor at index 0: "the item under the cursor" and "the first item" are indistinguishable there, and that was four of five findings in one review. Seat the cursor at a nonzero index of a list with at least two elements;
- a determinism check against ONE context instance: every
HashMapiterates the same way twice. Build two equal contexts independently (improvise-hzjo); - two derivations of one fact where the fixture makes them agree (drawn vs
committed, edit text vs value,
1200vs1.23456789). Use a value where they disagree.
- Record each bug in
context/test-evidence/<area>.md, keyed by test name (module-qualify the name when the leaf is not unique). It is append-at-EOF, so expect a conflict. Keep both sides and verify line by line against both parents:--check-evidencecannot see a dropped entry. - Comment runs are at most two lines. Reasoning goes in
context/, not in comments (context/design-principles.md§6). - If the acceptance criteria look wrong, argue it rather than following them silently. Three agents inverted theirs in one campaign, and all three were right.
Files you must not write
.beads/** (never commit issues.jsonl, never git checkout it),
prolog-sessions/kb.pl, context/repo-map.md: AGENTS.md, "Who owns what".
Also AGENTS.md, unless your brief grants you a named section. Report proposed
text for any of these in your hand-back.
GitHub
ghis shell-aliased to a different tool. Usenix shell nixpkgs#gh --command gh …, and pass bodies with-F body=@file.- Everything posted goes out under the repository owner's account. Open
every PR body and comment with the attribution line your brief gives you,
verbatim. It names your agent and model, which is how our posts are told
apart from an automated Codex review (
AGENTS.md, "Shipping a wave"). - The attribution line, commit trailers and PR footer all come from your brief. They name the agent and the session, so this shared file cannot hold them.
- Read reviews with
./scripts/pr-reviews <n>, and do not pipe it. How to tell a ChatGPT Codex review from ours, and why Copilot is not coming:AGENTS.md, "Shipping a wave". Answer each Codex review on its thread. - Do not merge, and do not close beads. Avoid stacked PRs. If you must stack, never let the base branch be deleted.
Hand-back, per ticket
Report:
- your branch, head SHA and PR number. Review is dispatched against the SHA;
- what changed, per ticket;
- the claim result;
- your measured baseline;
- whether the ticket reproduced (the SHA if it did not);
- the rival and where it failed;
- what you could not verify, and why;
- proposed repo-map and
kb.pltext; - what the ticket did not predict, consistently the most valuable part.
Disk
Each worktree's target/ grows to 3–6G, and a full disk (ENOSPC) once stopped
every agent. When you hand back, delete your own build output, and only
yours.
Run pwd and git rev-parse --show-toplevel first, and read the answer. Then
delete by literal absolute path, typed out:
rm -rf /Users/.../.claude/worktrees/agent-<you>/target.
Never interpolate a computed path into rm -rf. One agent's worktree had
been removed, so $(git rev-parse --show-toplevel) printed nothing and the
command became rm -rf "/target". A guarded one-liner was tried, but the
worktree shell guard refuses it as too complex, which pushed agents back to an
unguarded form. A literal path is the version that is both allowed and safe.
Keep your branch and any uncommitted work. Delete only target/ (and
harness/opfs-contract/target if you built it). If you see ENOSPC, stop and
report rather than deleting anything outside your worktree.