Agent Playbook
How autonomous AI agents ship work on Resilient without blocking each
other and without a human mediating every decision. This is the
operational companion to CLAUDE.md (rules) and
agent-scripts/README.md (tooling).
Target audience: the agent writing code and the orchestrator spawning agents. A second agent reading this should reach the same conclusions as the first without talking to them.
1. Lifecycle of a ticket
pick-ticket → worktree + claim → implement → draft PR →
verify-scope → sync-integration → mark ready →
agent-auto-merge (CI green → squash to main) → claim released
Each step is backed by a script in agent-scripts/
or a workflow in .github/workflows/. Stages
are idempotent — re-running a stage on the same ticket is a no-op or
safely re-converges.
Stage details
| Stage | Mechanism | Output |
|---|---|---|
| Pick | pick-ticket.sh |
<issue-number>\t<title>\tagent-ready |
| Dispatch | dispatch-agent.sh --issue N |
Worktree at .claude/worktrees/res-N/, draft PR with Closes #N |
| Implement | (agent free-form) | Follow feature-isolation pattern in CLAUDE.md |
| Verify | ready-or-bail.sh --pr P |
Runs verify-scope.sh + sync-integration.sh; marks PR ready on green |
| Sync | sync-integration.sh (called by ready-or-bail) |
Rebases branch onto origin/agents/integration, fast-forwards integration, stamps PR with integration-synced label |
| Auto-merge | agent-auto-merge.yml (CI) |
Enables gh pr merge --auto when every check is green AND PR is labeled integration-synced |
| Follow-main | integration-follow-main.yml (CI) |
Fast-forwards agents/integration to main after every merge |
| Release | release-file-claims.yml (CI) |
agent-scripts/file-claims.json cleared for the branch |
2. Coordination without communication
Two agents working concurrently must converge without talking to each other. We achieve this with three mechanisms:
2a. File claims (agent-scripts/file-claims.json)
A JSON ledger of { "file-path": "branch-name" }. Every agent:
- Before editing core files, runs
agent-scripts/claim-files.sh <branch> <file>.... - Before dispatching a new ticket, runs
agent-scripts/check-overlaps.sh <file>...to refuse work that would collide with an active claim or an open PR.
Claims are automatically released by
.github/workflows/release-file-claims.yml
on PR merge, and by agent-scripts/release-claims.sh locally when a
branch is abandoned.
Stale-claim rule: any claim older than 30 minutes (measured by the
commit timestamp on the claim-adding commit on main) is treated as
abandoned. A new dispatcher may overwrite it. This is enforced by
check-overlaps.sh treating stale entries as absent.
2b. Append-only extension points
resilient/src/{main.rs,typechecker.rs,lexer_logos.rs} are shared by
every feature. They contain explicit comment markers:
// <EXTENSION_TOKENS>
// <EXTENSION_KEYWORDS>
// <EXTENSION_PASSES>
All cross-feature edits to these files go inside those blocks, and the blocks are append-only (new lines are always added to the end, never inserted mid-block, never reordered). Two agents appending to the same block produce a mechanical merge conflict that is always resolved by keeping both sides.
agent-scripts/auto-resolve-extensions.sh
does exactly that. It refuses to run on any file outside the allowlist
(the three core files plus file-claims.json), so it can’t quietly
corrupt logic code.
2c. The agents/integration live branch
agents/integration is the shared staging branch that always holds
every in-flight agent commit. It’s always at-or-ahead of main:
- When
mainadvances (a PR merges),integration-follow-main.ymlfast-forwardsagents/integrationto the new main. - When an agent runs
sync-integration.sh, the agent’s rebased HEAD is fast-forward-pushed intoagents/integration, making that work visible to every sibling agent within seconds.
The rule: before marking a PR ready, the agent must sync. That is
exactly what ready-or-bail.sh enforces — it runs verify-scope.sh
first, then sync-integration.sh. If either fails, the PR stays draft.
sync-integration.sh does:
git fetch originand rebase the current branch ontoorigin/agents/integration.- If the rebase hits conflicts, check each file against the
append-only allowlist (main.rs, typechecker.rs, lexer_logos.rs,
file-claims.json). If all conflict files are in the allowlist, run
auto-resolve-extensions.shand continue. Otherwise abort — a human or a sonnet-tier agent must resolve. git push --force-with-leasethe rebased feature branch.git push origin HEAD:refs/heads/agents/integrationas a fast forward. Retry once if another agent raced us.- Stamp the PR with the
integration-syncedlabel.
2d. Auto-merge on green
Once a PR is marked ready AND labeled integration-synced,
agent-auto-merge.yml runs on every check_suite.completed event.
When all required checks succeed it calls
gh pr merge --squash --auto --delete-branch. The --auto flag
defers the merge until GitHub itself has re-verified freshness, so a
last-second force-push or status change still blocks the merge.
Concrete rule for implementers: you no longer merge your own PR.
You mark it ready (via ready-or-bail.sh) and let auto-merge land it.
3. Conflict-resolution protocol
When gh pr update-branch reports conflicts:
# Step 1: sync the branch into a local worktree.
git fetch origin
git checkout <branch>
git merge origin/main # produces conflict markers
# Step 2: classify the conflicts.
git diff --name-only --diff-filter=U
# Step 3: if ALL conflict files are in the allowlist, auto-resolve.
agent-scripts/auto-resolve-extensions.sh <files>
git add <files>
git commit --no-edit
git push
# Step 4: if any conflict is outside the allowlist, STOP.
# Post a comment on the PR and hand off to a human or a
# sonnet-tier agent with enough context to do it by hand.
The auto-resolver refuses to touch files outside the allowlist, so a mistaken invocation is a no-op error rather than silent corruption.
4. Cost tiers — model routing
Pick the cheapest model that can do the job. The orchestrator chooses the tier; the agent itself does not escalate.
| Tier | Model | Use for |
|---|---|---|
| Scrape | Haiku | gh issue list, gh pr list, picking tickets, reading status JSON |
| Default | Sonnet | Feature implementation, lexer/parser changes, typechecker rules, docs, test authoring, merge-conflict auto-resolve |
| Heavy | Opus | Z3 verifier passes, SMT encodings, refinement-type inference, any algorithm where correctness depends on subtle mathematical reasoning |
Rules of thumb:
- If the ticket title starts with
lexer:,parser:,stdlib:,lsp:,lint:,repl:,cli:,docs:→ Sonnet. - If it mentions
smt,z3,proof,refinement,verifier,liveness,bounded model check→ Opus. - Orchestration, PR sweeping, ticket triage → Haiku.
Do not run Opus loops for scraping or Haiku for SMT work.
5. Anti-patterns (hard NO)
These violate the rules in CLAUDE.md. CI will reject them; the
guardrail will keep the PR in draft.
- Modifying existing tests to make a PR green. Fix the code.
- Deleting or weakening an assertion — treated identically to deleting the test.
- Force-pushing a reviewed branch. Use
gh pr update-branch(a merge commit) instead. - Bypassing CI (
--no-verify, skipping hooks). - Adding
unsafewithout a soundness justification comment. - Adding a dependency without stating why in the PR.
- Touching
.github/workflows/outside a workflow-specific ticket. - Half-landing a feature with a
TODO— finish or scope a follow-up ticket.
6. Orchestrator flow (summary)
digraph orchestrator {
rankdir=LR;
pick [label="pick-ticket.sh"];
dispatch [label="dispatch-agent.sh"];
impl [label="sub-agent\n(sonnet/opus)"];
verify [label="ready-or-bail.sh"];
sweep [label="sweep-drafts.sh"];
release [label="release-file-claims\n(CI)"];
pick -> dispatch -> impl -> verify -> sweep -> release;
verify -> impl [label="fail\n(comment, stay draft)"];
sweep -> verify [label="CI red"];
}
One command to drain the queue:
agent-scripts/orchestrator.sh --loop
One command to land the already-done work:
agent-scripts/sweep-drafts.sh --go
7. When two agents don’t talk
A reminder of how the system survives an unsupervised second agent:
- They can’t both claim the same file (
claim-files.shis atomic). - They can’t both pick the same ticket (
pick-ticket.shexcludes tickets referenced by any open PR). - They can both touch the same extension block; the resolver handles it.
- They can both push to the same branch — but they won’t, because
dispatch-agent.shcreates a new worktree per ticket. - Their PRs are merged by whichever
sweep-drafts.shruns next; the earlier PR just lands first and the later one picks up the newmainviagh pr update-branch.
The contract is: the ledger, the extension points, and the ticket system are the only shared state. Everything else is agent-local.