원클릭으로
원클릭으로
| name | crank |
| description | Execute epics through waves. |
Quick Ref: Execute every open issue in an epic via wave-based workers using
spawn_agent,wait_agent,send_input, andclose_agent. Output: closed issues + final validation.
You must execute this workflow. Do not just describe it.
Crank (lead agent)
|
+-> bd ready (current wave)
|
+-> Build a wave task packet
|
+-> spawn_agent per issue (worker or explorer role)
|
+-> wait_agent for all worker ids
|
+-> Validate results + bd update
|
+-> Loop until epic DONE
spawn_agent is available.agent_type=worker for implementation agents and agent_type=explorer for discovery agents when the runtime exposes roles.send_input only for short steering or retry prompts.close_agent for stalled or unnecessary agents.spawn_agent, wait_agent, send_input, and close_agent instead.When this skill runs in Codex hookless mode (CODEX_THREAD_ID is set or
CODEX_INTERNAL_ORIGINATOR_OVERRIDE is Codex Desktop), ensure startup context
before the first wave:
ao codex ensure-start 2>/dev/null || true
ao codex ensure-start is the single startup guard for Codex skills. It records
startup once per thread and skips duplicate startup automatically. Leave
ao codex ensure-stop to closeout skills after the implementation wave ends.
| Flag | Default | Description |
|---|---|---|
--test-first | off | SPEC -> TEST -> IMPL wave sequence. Workers classify tests by pyramid level (L0-L3) per the test pyramid standard (test-pyramid.md in the standards skill). When $plan includes test_levels metadata, carry it into metadata.validation.test_levels. |
MAX_EPIC_WAVES = 50 (hard limit). Typical epics use 5-10 waves.
After each wave, output one of:
<promise>DONE</promise> - epic complete, all issues closed<promise>BLOCKED</promise> - cannot proceed, with reason<promise>PARTIAL</promise> - incomplete, with remaining itemsNever claim completion without the marker.
When a task fails during wave execution, classify as RETRY (transient — re-add with adjustment, max 2), DECOMPOSE (too complex — split into sub-issues, terminal), or PRUNE (blocked — escalate immediately). Budget: 2 per task.
Mutation logging on failure: DECOMPOSE logs task_removed + task_added per sub-task. PRUNE logs task_removed. RETRY logs nothing (task identity unchanged).
Given $crank [epic-id | .agents/rpi/execution-packet.json | plan-file.md | "description"]:
if command -v ao &>/dev/null; then
ao lookup --query "<epic-title>" --limit 5 2>/dev/null || true
ao ratchet status 2>/dev/null || true
fi
Apply retrieved knowledge: If learnings are returned, check each for applicability to this epic. For applicable learnings, treat as implementation constraints and cite by filename. Record citations with the correct type: ao metrics cite "<path>" --type applied when the learning influenced a decision, or --type retrieved when loaded but not referenced.
Section evidence: When lookup results include section_heading, matched_snippet, or match_confidence fields, prefer the matched section over the whole file — it pinpoints the relevant portion. Higher match_confidence (>0.7) means the section is a strong match; lower values (<0.4) are weaker signals. Use the matched_snippet as the primary context rather than reading the full file.
if bd ready --json >/dev/null 2>&1 && bd list --type epic --status open --json >/dev/null 2>&1; then
TRACKING_MODE="beads"
else
TRACKING_MODE="tasklist"
fi
Create the shared notes file for cross-wave context persistence. See references/shared-task-notes.md for the full pattern.
mkdir -p .agents/crank
cat > .agents/crank/SHARED_TASK_NOTES.md <<EOF
# Shared Task Notes — Epic ${EPIC_ID:-unknown}
> Cross-wave context for workers. Read before starting. Report discoveries in task output.
> Maintained by the crank orchestrator — workers do NOT write to this file directly.
EOF
Create the JSONL file that tracks every plan mutation during execution. See references/plan-mutations.md for the full schema and mutation budget.
mkdir -p .agents/rpi
: > .agents/rpi/plan-mutations.jsonl
# Budget counters
MUTATION_TASK_ADDED=0
MUTATION_TASK_ADDED_LIMIT=5
MUTATION_TASK_REORDERED=0
MUTATION_TASK_REORDERED_LIMIT=3
Helper function:
log_plan_mutation() {
local mutation_type="$1" task_id="$2" before="$3" after="$4"
local ts
ts=$(date -Iseconds)
if [[ "$mutation_type" == "task_added" ]]; then
MUTATION_TASK_ADDED=$((MUTATION_TASK_ADDED + 1))
if [[ $MUTATION_TASK_ADDED -gt $MUTATION_TASK_ADDED_LIMIT ]]; then
echo "WARN: task_added budget exceeded ($MUTATION_TASK_ADDED/$MUTATION_TASK_ADDED_LIMIT). Consider re-running $plan."
fi
elif [[ "$mutation_type" == "task_reordered" ]]; then
MUTATION_TASK_REORDERED=$((MUTATION_TASK_REORDERED + 1))
if [[ $MUTATION_TASK_REORDERED -gt $MUTATION_TASK_REORDERED_LIMIT ]]; then
echo "WARN: task_reordered budget exceeded ($MUTATION_TASK_REORDERED/$MUTATION_TASK_REORDERED_LIMIT)."
fi
fi
echo "{\"timestamp\":\"$ts\",\"wave\":$wave,\"task_id\":\"$task_id\",\"mutation_type\":\"$mutation_type\",\"before\":$before,\"after\":$after}" \
>> .agents/rpi/plan-mutations.jsonl
}
Mutation types: task_added, task_removed, task_reordered, scope_changed, dependency_changed.
Beads mode:
bd list --type epic --status open 2>/dev/null | head -5Execution-packet/file mode:
.agents/rpi/execution-packet.json, read objective, epic_id, tracker_mode, done_criteria, and validation_commandsepic_id exists inside the execution packet, keep that epic as the execution spineepic_id is absent, keep the packet objective as the execution spine and continue in file-backed mode instead of inventing an epic IDBefore wave-1 commit, refuse to crank on main/master. Cut crank/<epic-id> to prevent parallel-session reset clobbers. See references/branch-isolation.md for the gate script and override flag.
Beads mode:
bd show <epic-id> 2>/dev/null
Execution-packet/file mode:
bd readyBeads mode:
bd ready 2>/dev/null
bd ready returns all unblocked issues - these can run in parallel.
Execution-packet/file mode:
.agents/rpi/execution-packet.json or the plan file.agents/council/ for pre-mortem evidence.beads and scripts/bd-audit.sh exists, run the backlog audit before spawning workers.--skip-audit only when you intentionally want to bypass that gate.Detect project language (go.mod -> Go, pyproject.toml -> Python, etc.) and read applicable standards from $standards. Include a Testing section in worker prompts.
Crank follows the FIRE loop for each wave:
Read cross-wave context to include in worker prompts:
SHARED_NOTES=""
if [ -f .agents/crank/SHARED_TASK_NOTES.md ]; then
SHARED_NOTES=$(cat .agents/crank/SHARED_TASK_NOTES.md)
fi
If SHARED_NOTES exceeds ~50 lines, summarize older waves (keep last 3 in full detail, preserve [CRITICAL] entries).
Create one packet per ready issue. Do not use CSV fan-out.
mkdir -p .agents/crank
cat > ".agents/crank/wave-${wave}-tasks.json" << EOF
{
"wave": $wave,
"epic_id": "$EPIC_ID",
"tasks": [
{
"issue_id": "bd-123",
"subject": "Short issue summary",
"description": "Issue details and acceptance criteria",
"files": ["path/to/file.go"],
"validation_cmd": "go test ./...",
"metadata": {
"issue_type": "feature"
}
}
]
}
EOF
Each task packet must include metadata.issue_type.
wave_tasks = [tasks from packet]
all_files = {}
for task in wave_tasks:
for f in task.files:
if f in all_files:
CONFLICT -> serialize into sub-waves
all_files[f] = task.id
Display an ownership table before spawning workers. If conflicts exist, split into sub-waves and keep file ownership disjoint.
For waves with 2+ workers, three tiers prevent sibling-worker clobber without re-introducing worktree sprawl. Read references/parallel-wave-isolation.md for the full tier definitions, the worker prompt template, the preflight-swarm.sh escalation criterion, and the check-worktree-disposition.sh cleanup gate.
Tier 1 (always): inject the branch-isolation prompt rule (worker's first git op = git checkout -b feat/<epic>-<slug> origin/main; never git switch, stash pop, reset --hard).
Tier 2 (escalate on preflight-swarm.sh non-zero): ephemeral per-worker worktree.
Tier 3 (wave-end): scripts/check-worktree-disposition.sh flags stragglers.
Spawn one agent per issue. Prefer worker roles for implementation and explorer roles for file discovery when the runtime exposes agent_type.
spawn_agent(
agent_type="worker",
message="You are worker-<issue-id>.
Assignment: <subject>
<description>
---
Context from prior waves (read before starting):
<SHARED_NOTES content, or 'First wave — no prior context.' if empty>
---
FILE MANIFEST (files you are permitted to modify):
<list of files>
Rules:
1. Stay within your assigned files
2. Run validation: <validation_cmd>
3. Keep your response short
4. Write any durable notes to .agents/crank/results/<issue-id>.md or .agents/crank/results/<issue-id>.json
5. DISCOVERY REPORTING: If you discover codebase quirks, failed approaches,
convention requirements, or dependency constraints, include a section in your
output titled '## Discoveries' with one bullet per finding.
Use the repo's current Codex primitives only."
)
If a task is missing its file manifest, spawn a short-lived explorer agent first:
spawn_agent(
agent_type="explorer",
message="You are explorer-<issue-id>.
Task: identify the files that must be created or modified for this issue.
Return a JSON array of paths only."
)
wait_agent(targets=["agent-id-1", "agent-id-2"])
If a worker needs a short correction, use send_input(target=..., message=...).
If a worker stalls or is no longer needed, use close_agent(target=...).
External Gate Enforcement: After each worker completes, the orchestrator (not the worker) runs the gate command. Workers must not declare their own completion. See references/external-gate-protocol.md.
For each completed worker:
Update beads with evidence:
COMMIT_SHA=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
CHANGED_FILES=$(git diff --name-only HEAD~1 2>/dev/null | head -10 | tr '\n' ' ' | sed 's/ $//')
bd close "$issue_id" --reason "commit:${COMMIT_SHA} files:[${CHANGED_FILES}]" 2>/dev/null
bd update "$issue_id" --status blocked --append-notes "Wave $wave FAIL: $reason" 2>/dev/null
After all workers complete:
git diff for the wave..github/workflows/*.yml, run bash scripts/validate-ci-policy-parity.sh; on non-zero exit treat the wave verdict as FAIL and surface the drift report. Trigger pattern (narrow — workflow YAML only):
if git diff --name-only "$WAVE_START_SHA" HEAD -- | grep -qE '^\.github/workflows/.*\.ya?ml$'; then
bash scripts/validate-ci-policy-parity.sh || exit 1
fi
See references/wave-patterns.md "CI-Policy Parity Gate" for the worked example and the soc-lmww1 / commit c587b361 motivation.FILES_CHANGED_JSON="${FILES_CHANGED_JSON:-$(git diff --name-only "${WAVE_START_SHA:-HEAD~1}..HEAD" | jq -R -s -c 'split("\n")[:-1]')}"
GIT_SHA="$(git rev-parse HEAD)"
cat > ".agents/crank/wave-${wave}-checkpoint.json" << EOF
{
"schema_version": 1,
"wave": $wave,
"epic_id": "$EPIC_ID",
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"tasks_completed": ${TASKS_COMPLETED_JSON:-[]},
"tasks_failed": ${TASKS_FAILED_JSON:-[]},
"files_changed": $FILES_CHANGED_JSON,
"git_sha": "$GIT_SHA",
"acceptance_verdict": "${ACCEPTANCE_VERDICT:-WARN}",
"commit_strategy": "${COMMIT_STRATEGY:-wave-batch}",
"mutations_this_wave": $(grep -c "\"wave\":${wave}" .agents/rpi/plan-mutations.jsonl 2>/dev/null || echo 0),
"total_mutations": $(wc -l < .agents/rpi/plan-mutations.jsonl 2>/dev/null | tr -d ' '),
"mutation_budget": {
"task_added": {"used": ${MUTATION_TASK_ADDED:-0}, "limit": 5},
"task_reordered": {"used": ${MUTATION_TASK_REORDERED:-0}, "limit": 3}
}
}
EOF
bash skills-codex/crank/scripts/validate-wave-checkpoint.sh ".agents/crank/wave-${wave}-checkpoint.json"
Do not copy or consume the checkpoint downstream until validation passes. The validator fails closed when git_sha does not resolve in the current repo, timestamp is invalid or more than 5 minutes in the future, or required checkpoint fields are missing/malformed.
Harvest discoveries from completed workers and append to the shared notes file:
WAVE_DISCOVERIES=""
for result_file in .agents/crank/results/*; do
if [ -f "$result_file" ]; then
DISCOVERIES=$(sed -n '/^## Discoveries/,/^## /{ /^## Discoveries/d; /^## /d; p; }' "$result_file" 2>/dev/null)
if [ -n "$DISCOVERIES" ]; then
WAVE_DISCOVERIES="${WAVE_DISCOVERIES}${DISCOVERIES}\n"
fi
fi
done
if [ -n "$WAVE_DISCOVERIES" ]; then
cat >> .agents/crank/SHARED_TASK_NOTES.md <<EOF
## Wave ${wave} ($(date -Iseconds))
$(echo -e "$WAVE_DISCOVERIES")
EOF
fi
Capture: Failed approaches, codebase quirks, convention discoveries, dependency notes. Skip: Full error logs, implementation details, task status.
After processing wave results, log mutations for any plan changes. Call log_plan_mutation for each:
task_removed for original, task_added for each sub-tasktask_removed with block reasonscope_changed when file manifest updated after explorationdependency_changed when blocked-by list modifiedtask_reordered when task moves between waves# Example: task decomposed into sub-tasks
log_plan_mutation "task_removed" "$decomposed_id" \
"{\"subject\":\"$ORIGINAL_SUBJECT\",\"status\":\"decomposed\"}" "null"
log_plan_mutation "task_added" "$sub_id" "null" \
"{\"subject\":\"$SUB_SUBJECT\",\"reason\":\"Split from $decomposed_id\"}"
# Example: scope change after exploration
log_plan_mutation "scope_changed" "$task_id" \
"{\"files\":$ORIGINAL_FILES}" \
"{\"files\":$UPDATED_FILES,\"reason\":\"$REASON\"}"
Mutations are append-only to .agents/rpi/plan-mutations.jsonl. Read by $post-mortem for drift analysis.
Lead-only commit - workers write files, lead validates and commits once per wave:
for f in $WORKER_FILES_CHANGED; do
git add -- "$f"
done
git commit -m "feat(<scope>): wave $wave - $COMPLETED_COUNT issues completed"
wave=$((wave + 1))
if [[ $wave -ge 50 ]]; then
echo "<promise>BLOCKED</promise>"
echo "Global wave limit (50) reached."
exit 1
fi
REMAINING=$(bd ready 2>/dev/null | wc -l)
if [[ $REMAINING -eq 0 ]]; then
ALL_CLOSED=$(bd children "$EPIC_ID" 2>/dev/null | grep -c "CLOSED" || echo 0)
ALL_TOTAL=$(bd children "$EPIC_ID" 2>/dev/null | wc -l || echo 0)
if [[ $ALL_CLOSED -eq $ALL_TOTAL ]]; then
echo "<promise>DONE</promise>"
else
echo "<promise>BLOCKED</promise>"
echo "No ready issues but $((ALL_TOTAL - ALL_CLOSED)) issues remain unclosed."
fi
else
# Continue to next wave - return to Step 3
fi
When the epic is DONE:
$vibe validate the completed epic
Move the shared notes to an archive after epic completion:
if [ -f .agents/crank/SHARED_TASK_NOTES.md ]; then
mkdir -p .agents/crank/archives
mv .agents/crank/SHARED_TASK_NOTES.md \
".agents/crank/archives/SHARED_TASK_NOTES-${EPIC_ID:-unknown}-$(date +%Y%m%d-%H%M%S).md"
fi
bd comments add "$issue_id" "retry $N: $reason"| Scenario | Action |
|---|---|
| Worker timeout | Mark BLOCKED, log reason, continue wave |
| Test failure | Identify breaking change, retry once |
| All workers fail | <promise>BLOCKED</promise> with diagnostics |
| File conflict detected | Split into sub-waves, re-run |
Bitbucket CLI for Data Center and Cloud. Use when users need to manage repositories, pull requests, branches, issues, webhooks, or pipelines in Bitbucket. Triggers include "bitbucket", "bkt", "pull request", "PR", "repo list", "branch create", "Bitbucket Data Center", "Bitbucket Cloud", "keyring timeout".
Check phase. Parallel review: code quality + security + tests. Outputs PASS/WARN/FAIL per dimension. Validates spec coverage.
Core router. Always active. Auto-invokes matching skill before every response. Runs confusion protocol on high-risk ambiguity.
Go phase. Reads approved SPEC, maps requirements to tasks, executes via TDD, integrates verifying acceptance criteria.
Complete orbit — autonomous spec through ship. Choose interactive or council mode, then hands-off until PR.
Ship phase. Isolated integration test in fresh worktree, PR creation, CI monitoring, auto-fix on failure.