| name | auto-engineer |
| description | Autonomously drive vibix work end-to-end — pick an unblocked issue, plan it, implement, push a PR, resolve CI and review-bot feedback, merge when green, then repeat. Use when the user says "auto-engineer", "auto-pilot", "go run the loop", or invokes `/auto-engineer`. |
auto-engineer
Read these shared playbooks first:
docs/agent-playbooks/sdlc.md
docs/agent-playbooks/build-run.md
docs/agent-playbooks/testing.md
docs/agent-playbooks/pr-review.md
docs/agent-playbooks/prioritization.md
If /etc/vibix-host-hints.md exists, read it too — it carries host-specific guidance (e.g. larger Bash-tool timeouts when the container is running under emulation) that overrides the default assumptions in these playbooks. On native amd64 hosts the file is absent and there's nothing to read.
Closes the full SDLC loop without prompting the user between steps. Each invocation runs one cycle (pick → plan → implement → PR → wait → merge), then reschedules itself via ScheduleWakeup with prompt /auto-engineer for the next cycle. Stops cleanly when it runs out of work or hits something it can't safely resolve.
This skill is a deliberate override of the project's default "don't merge your own PRs" rule — merging is the whole point of closing the loop. It only applies while auto-engineer is driving.
When to invoke
- User says "run the loop", "auto-engineer", "auto-pilot the next issue", or similar.
- User types
/auto-engineer (optionally with #NN to scope to a single issue for one cycle, no reschedule).
- This skill re-invokes itself at the end of each successful cycle via
ScheduleWakeup.
Entry flags and phase state
All persistent state lives in the wakeup prompt — /compact can summarize away conversation state, so nothing important should rely on being in context.
Parse these flags on every entry before doing any work:
| Flag | Meaning |
|---|
--iteration N | Current cycle number (1-indexed). Absent → treat as 1. |
--phase wait | Re-entry into the PR-wait poll loop (jump to step 6b). Requires --pr. |
--pr M | PR number being waited on (only meaningful with --phase wait). |
#NN | Scope to a single issue for one cycle, then stop without rescheduling. |
A bare /auto-engineer is iteration 1, phase "pick".
Iteration budget
Soft cap: 8 iterations per user-initiated run. After the 8th merged PR, stop and wait for the user to restart. Prevents runaway loops.
The --iteration N flag is what enforces the cap across /compact boundaries — always carry it in every ScheduleWakeup call.
Cycle
1. Pick an unblocked issue
The picking rules live in docs/agent-playbooks/prioritization.md — this step is the
mechanical implementation of that policy. The north star is the minimal useful kernel:
work P0s first, then P1s, and so on.
1.0. Survey other auto-engineers' in-flight work
Before listing candidate issues, enumerate what other auto-engineer instances are already
working on. Since all auto-engineers act as the dburkart GitHub identity, the signal is
"open PRs authored by dburkart that are not yet merged":
gh pr list --author dburkart --state open \
--json number,title,headRefName,labels,createdAt
For each open PR, resolve the issue it closes (parse Closes #<N> from the PR body, or
derive <N> from the m<N>-<slug> branch prefix), then pull that issue's labels to
collect the area:* and track:* values. Build a set of in-flight areas and
in-flight tracks — the domains other agents are currently touching.
Record this set and note it in your next message to the user (one short line, e.g.
"Other auto-engineers in flight: #401 (area:mem, track:userspace), #407 (area:fs) —
will prefer a different area.").
1.1. Enumerate candidate issues
gh issue list --search 'no:assignee' --state open \
--json number,title,labels,body,assignees
Filter out:
- Issues labeled
blocked, needs-discussion, question, wontfix.
- Issues whose body references an unresolved dependency (e.g. "blocked on #NN" where #NN is still open).
Sort preference (apply in order — do not fall back to "oldest first" until you have
exhausted the priority ordering):
priority:P0 before priority:P1 before priority:P2 before priority:P3. Issues with
no priority:* label rank after priority:P3 — they are un-triaged.
- Within a priority bucket, prefer candidates whose
area:* and track:* sets do not
overlap with the in-flight sets from step 1.0 — parallel work in disjoint subsystems
merges cleanly; two agents in the same area tend to collide on the same files, fight
over locks, or race on overlapping refactors. Ties here fall through to the
track-preference order (track:userspace, track:filesystem, track:terminal,
track:posix, untracked).
- Within an (priority, area-disjoint) bucket, apply the track preference as a
secondary sort.
- Within a (priority, track) bucket, lowest-numbered first.
Priority still wins. Do not skip a P0 to avoid an area collision — if the only
available P0 overlaps with another agent's area, pick it anyway. De-conflicting by area
only matters when candidates are otherwise tied on priority.
If the top candidate's prerequisites are still open, skip it and try the next one rather
than starting work that cannot land. If a candidate is un-triaged (no priority:* label),
prefer triaging it via the file-issue skill's label rules before working it — don't mine
the un-triaged tail for "easy" work and bypass the priority queue.
If the candidate list is empty → stop (see Stopping below) with message "no unblocked issues — auto-engineer idle."
Assign the issue to yourself before anything else — this both signals ownership to other
agents and is load-bearing for the split path below:
gh issue edit <N> --add-assignee dburkart
1a. Sanity-check issue size; split if too large
Before branching, read the issue body and any linked context. If the work is genuinely too
big to land cleanly in a single PR (multiple independent subsystems, >~500 LoC of real
change, two or more distinct risk surfaces that each deserve their own review, or a design
that needs to be staged behind feature gates), split it instead of attempting it.
Heuristics for "too large" — any one is sufficient:
- The natural plan has 3+ independent sub-tasks that could each land on their own PR
without the others.
- Implementation touches two or more subsystems where a mistake in one would be hard to
bisect from the other.
- The issue body already lists a multi-step checklist and no single step is the obvious
minimal starting point.
- A
Plan sub-agent (step 2) would return a roadmap rather than a patch plan.
If not too large, skip to step 1b.
If too large, do all of the following and then restart from the top of step 1 (fresh
gh issue list, fresh selection) — do not proceed to step 2 with the original issue:
- Keep the assignment on the original issue (already set above) — it prevents other
agents from racing in while the split is in flight.
- Draft a decomposition: 2–5 concrete, independently-landable sub-tasks, dependency-first.
- File each sub-task via the
file-issue skill. Each new issue must carry the parent's
priority:* label (the chunks inherit the parent's priority) plus the correct
area:* / track:* labels per docs/agent-playbooks/prioritization.md. Cross-link
each sub-issue's body back to the parent with Follow-up of #<parent>.
- Comment on the parent issue listing every new sub-issue number and a one-line summary
of its scope.
- Close the parent issue with reason
not planned and a final comment:
"Decomposed into #A, #B, #C — closing in favor of the sub-issues. Auto-engineer will
pick one of them on the next iteration." Use gh issue close <N> -r "not planned".
- Restart selection from step 1. The new sub-issues are now candidates; by priority rules
the highest-priority sub-task (tie-broken by lowest number) should be picked next.
Do not plan or implement the split itself as a code change — splitting is an issue-tracker
operation only.
1b. Branch
Per docs/agent-playbooks/sdlc.md:
git checkout main && git pull
git checkout -b <branch>
Then rename the tmux window so the human glancing at the terminal can see which
issue this cycle is on. <slug> is the same short slug used in the branch name
(3–4 words, kebab-case).
The mechanism differs by environment because Claude's Bash tool has no
controlling terminal, so neither OSC 2 escapes nor /dev/tty writes can reach
the host tmux pane from inside the container:
- Inside the container: write the slug to
$VIBIX_AE_SLUG_FILE (set by
scripts/auto-engineer.sh to a bind-mounted path). A poller on the host tails
that file and runs tmux rename-window "AE -> <slug>" against the host tmux.
- On the host (no container): call
tmux rename-window directly.
- Outside tmux entirely: no-op.
Unified command:
if [ -n "${VIBIX_AE_SLUG_FILE:-}" ]; then
printf '%s' "<slug>" > "$VIBIX_AE_SLUG_FILE"
elif [ -n "${TMUX:-}" ] && command -v tmux >/dev/null 2>&1; then
tmux rename-window "AE -> <slug>"
fi
2. Delegate planning to a sub-agent
Use the Agent tool with subagent_type: "Plan". Pass the full issue body, its label list, and any linked issues you fetched. Ask for:
- Files to create / modify (absolute paths).
- Functions or structs to add / change, with signatures.
- Tests to add (host unit vs. QEMU integration vs. smoke marker).
- Risks and rollback plan.
Return format: plain markdown, ≤300 words. The parent reads the result as conversation text — do not enter plan mode for the sub-agent.
3. Auto-approve and implement
The parent proceeds directly with Edit / Write per the returned plan. No user confirmation. If the sub-agent's plan is clearly wrong (wrong file paths, contradicts code you read), re-spawn it once with a corrective prompt; if it's still wrong after that, stop and report.
4. Build + test
Run in order, following the shared build and testing playbooks:
cargo xtask build
cargo xtask test
cargo xtask smoke
When invoking these through the Bash tool, pick the timeout parameter from /etc/vibix-host-hints.md if present — emulated hosts need the full 600000 ms cap for cargo xtask test. If the hints file is absent, native amd64 timings apply and the tool's 120000 ms default is usually fine for build/smoke but still too tight for test (budget ~180000 ms).
On failure: diagnose and fix in place. Maximum 3 fix attempts per cycle. If the 3rd attempt still fails → stop with a summary of the failure and what was tried.
5. Commit and open PR
Per docs/agent-playbooks/sdlc.md:
- Coherent commits (not a single mega-commit, not micro-commits). Each with the standard
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> trailer.
git push -u origin <branch>.
- Open the PR via
mcp__github__create_pull_request with the standard Summary + Test plan body. No "generated with" footer.
- Always include
Closes #<N> on its own line near the top of the PR body (where <N> is the issue picked in step 1). Without it, the squash-merge in step 7 won't auto-close the issue, the assignment persists, and step 1's no:assignee candidate set stays polluted on the next cycle.
- Record the PR number and URL for the rest of the cycle.
6. Wait for CI and review
Auto-engineer owns the PR-wait loop directly — do not delegate to the wait-for-pr
skill. Delegating would hand off the ScheduleWakeup thread and there would be no path back
into this skill when CI finishes.
The wait-for-pr skill is for manual invocations only.
6a. First poll tick (immediately after opening the PR)
Record the PR open time. Run one poll tick (step 6b below), then either proceed or sleep.
6b. Poll tick (re-entry via --phase wait --pr M)
-
Check CI state:
gh pr checks <M> --json name,state,bucket,link
-
Count review-bot activity. Bots post in two different places — check both:
gh api "repos/{owner}/{repo}/issues/<M>/comments" \
--jq '[.[] | select(.user.login | test("coderabbitai|greptile|github-advanced-security|cursor"))] | length'
gh api "repos/{owner}/{repo}/pulls/<M>/comments" \
--jq '[.[] | select(.user.login | test("coderabbitai|greptile|github-advanced-security|cursor"))] | length'
Sum both counts. Also fetch formal reviews via mcp__github__get_pull_request_reviews.
Why two endpoints? issues/<M>/comments returns issue-level (top-of-page) comments.
pulls/<M>/comments returns inline review comments attached to specific diff lines.
Cursor Bugbot posts exclusively inline; missing the second endpoint means all Cursor
findings are invisible. The bot login is cursor[bot], matched by the cursor pattern.
CodeRabbit rate-limit handling. A CodeRabbit comment that contains the marker
<!-- This is an auto-generated comment: rate limited by coderabbit.ai --> (or a
"Rate limit exceeded" heading with a "Please wait N minutes and M seconds" body)
is not a real review — it is a deferral notice. Do not count it toward the
"≥1 review-bot comment" gate in step 3. Instead:
- Parse the wait duration from the comment body (e.g.
6 minutes and 23 seconds →
383 seconds). If parsing fails, default to 600 seconds.
- Schedule the next poll tick with
delaySeconds = waitSeconds + 60 (a one-minute
buffer so the rate-limit window has fully reset on CodeRabbit's side), overriding
the normal cadence table for this one tick.
- On that next poll tick, if no newer CodeRabbit review has appeared, trigger a
re-review by posting
@coderabbitai review as a PR comment via
mcp__github__add_issue_comment, then resume normal polling cadence.
- Do this at most once per PR — if CodeRabbit rate-limits again after the
re-review request, treat the rate-limit comment as a terminal "no bot review" and
fall back to the 30-minute-elapsed rule in step 3 to unblock merge.
-
Evaluate "done" criteria (from docs/agent-playbooks/pr-review.md):
- Every check is in a terminal state (
success, failure, skipped, cancelled,
neutral, timed_out, stale) — nothing in_progress, queued, or pending.
- At least one review-bot comment exists or 30 minutes have elapsed since PR opened.
-
If done: proceed to step 6c.
-
If not done: schedule the next poll tick and end the turn — do not proceed further.
ScheduleWakeup(
delaySeconds=<cadence>,
prompt="/auto-engineer --iteration N --phase wait --pr M",
reason="auto-engineer: waiting on CI/review for PR #M (iteration N)"
)
Cadence (same as wait-for-pr playbook):
| Elapsed since PR opened | delaySeconds |
|---|
| 0–10 min | 120 |
| 10–30 min | 180 |
| 30 min+ (bots only) | 1200 |
| Never use 300 s — it's a cache-miss with no payoff. | |
If any check is action_required → stop instead (human must approve).
6c. Review-response
Carry a --fix-round R counter (default 0) forward in the wakeup prompt when looping back
here. Max 3 fix rounds — don't give up on review feedback easily; most findings are
fixable given one more honest attempt. If R == 3 and findings still remain, park the
PR (see "Parking the PR" below) and continue to the next iteration rather than stopping
the whole loop.
Auto-fix CI failures (before classifying review findings):
| Failed check pattern | Fix command | Canonical commit subject |
|---|
fmt / rustfmt / format | cargo fmt --all | fix: apply rustfmt |
clippy / lint | cargo clippy --fix --allow-dirty --allow-staged && cargo fmt --all | fix: address clippy lints |
Before applying: check git log origin/<base>..HEAD --format='%s' — if the canonical subject
is already there and the check still failed, do not re-apply. Flag as "auto-fix already
attempted, did not resolve" and stop.
After applying a fix: commit with the canonical subject + Co-Authored-By: trailer, git push,
then schedule another poll tick:
ScheduleWakeup(
prompt="/auto-engineer --iteration N --phase wait --pr M --fix-round R",
...
)
Read all review findings before classifying. There are three sources — collect all before
deciding:
mcp__github__get_pull_request_reviews — formal approve/request-changes reviews.
mcp__github__get_pull_request_comments — inline diff comments (where Cursor Bugbot
and other bots post line-level findings). Do not skip this even if the issue-level comment
count was ≥1 — bots may have posted summaries at issue level AND findings inline.
gh api "repos/{owner}/{repo}/issues/<M>/comments" — top-level issue comments.
Classify review findings using docs/agent-playbooks/pr-review.md:
- All green, no actionable findings → proceed to merge (step 7).
- Actionable bugs → apply follow-up commits (never amend),
git push, then reschedule
another poll tick with --fix-round R+1.
- Out-of-scope nits → reply on the PR via
mcp__github__add_issue_comment ("deferred to
future work"), open a follow-up issue via an Agent() call (see "Calling file-issue from a loop" below), then proceed to merge.
7. Merge
Only merge when all are true:
- Every CI check is
success or skipped (none failure, action_required, timed_out, pending).
- No unresolved actionable review findings.
- Either ≥1 review bot posted and every actionable finding is addressed, or 30 min elapsed since PR open with nothing actionable.
Merge method is always squash — the repo's established convention and the only style auto-engineer uses. Don't try to infer it from recent history; the gh pr list --json mergeCommit field returns a SHA for every merge method, so it can't distinguish squash from merge-commit or rebase anyway.
mcp__github__merge_pull_request(pull_number=<N>, merge_method="squash")
Post-merge:
git checkout main && git pull
git branch -d <branch>
8. Capture follow-ups
Before reloading the next issue, scan for work that surfaced during this cycle but wasn't in scope. Sources to check:
- The merged diff for new
TODO, FIXME, XXX, or unwrap() / expect() call sites added in this PR (git show --stat main + git log -p -1).
- Deferred review comments posted in step 6 ("deferred to future work") that didn't already get an issue opened.
- Build/test warnings observed during step 4 that weren't blocking but deserve follow-up.
- Anything the sub-agent's plan (step 2) explicitly listed as "out of scope" or "risks."
For each distinct follow-up, open an issue via an Agent() call (see "Calling file-issue
from a loop" below). Do not call Skill("file-issue") — that replaces this skill's
context and terminates the loop. Feed each Agent():
- A specific title.
- A 1–3 sentence motivation, including
discovered while merging #<PR> so the origin is
recoverable later.
- Concrete work items.
- Context (file paths, commit SHAs, related issue numbers).
If nothing worth filing, skip silently. Do not file speculative or "nice-to-have" issues — only things with concrete motivation. Over-filing clutters the backlog and degrades the no:assignee candidate set that step 1 depends on.
9. Check session quota
Before compacting and kicking off the next cycle, check quota by running the probe script
directly — do not call Skill("usage"), which would terminate the loop:
bash .claude/skills/usage/probe.sh
Parse the single-line JSON output (see docs/agent-playbooks/skill-composition.md for why
direct invocation is required here). Derive remaining_pct as
max(0, 100 - 5h.utilization*100) and reset_ts as an ISO-8601 string of 5h.resets_at.
- ≥ 10% remaining → proceed to step 10.
- < 10% remaining → don't start a new cycle; the next one could tip over mid-PR and leave an orphaned branch. Instead:
- Read
reset_ts from the probe JSON output.
- Compute
secondsUntilReset = reset_ts - now. Add a 60 s buffer so the wake-up lands after the reset, not on its edge.
- Compaction decision:
- If
secondsUntilReset ≤ 90 min: skip /compact. The wait is short enough that one or two un-compacted hops won't burn meaningful quota, and compaction itself costs tokens we want to save.
- If
secondsUntilReset > 90 min: run /compact first. Otherwise the un-compacted context idles through many 55-min hops and each tick pays full token cost for a large prompt — the compaction one-time cost amortizes.
ScheduleWakeup with delaySeconds = min(secondsUntilReset + 60, 3300) (ScheduleWakeup is clamped to ≤ 3600; 3300 leaves headroom), prompt = "/auto-engineer --iteration <N>" (preserve the current iteration count), reason = "auto-engineer: paused for quota reset at <ts>".
- End the turn.
When the wake fires, auto-engineer re-enters from step 1. If quota is still < 10% (e.g. because the reset was > 55 min out and we just slept one hop), step 9 will trip again and schedule another hop — the loop naturally chains until the reset actually happens.
10. Compact and re-enter
If iteration budget not exhausted and no stop condition tripped:
- Run
/compact to shrink the context before the next cycle. Do not use /clear — it wipes the iteration counter, the list of issues already tried this run, and any stop-reason history auto-engineer uses to avoid repeating itself. /compact summarizes instead, which is what we want.
- Schedule the next cycle with the iteration counter incremented:
ScheduleWakeup(
delaySeconds=60,
prompt="/auto-engineer --iteration <N+1>",
reason="auto-engineer: next cycle (iteration N+1 of 8)"
)
The --iteration flag is what enforces the 8-cycle cap across /compact boundaries — don't omit it. Never carry --phase, --pr, or --fix-round into a fresh cycle; those are intra-cycle state that resets each iteration.
- End the turn. The next wake-up re-enters this skill from step 1.
Parking the PR (soft stop — loop continues)
Some failure modes are cycle-local: the current PR can't progress, but the overall loop
still has useful work to do on other issues. For these, park the PR instead of stopping
the whole loop.
Park semantics:
- Leave the branch, PR, and issue assignment all in place. Do not close the PR.
- Add the
needs-human-review label to the PR via mcp__github__update_pull_request
(or gh pr edit <M> --add-label needs-human-review). Create the label first if it
doesn't exist.
- Post one comment on the PR via
mcp__github__add_issue_comment that explains:
- Which park reason triggered (e.g. "review feedback unresolved after 3 fix rounds").
- A concrete summary of what was tried across all fix rounds.
- A list of the remaining findings, quoted or linked, so the human reviewer can pick up
cold.
- The commit SHAs of each fix-round push.
- Do not unassign the issue. Keeping the assignment is what prevents the next
iteration's step 1 from re-selecting it —
gh issue list --search 'no:assignee' will
skip it naturally. When a human eventually merges the PR, the Closes #<N> line
closes the issue cleanly.
- Continue the loop: proceed straight to step 8 (Capture follow-ups, skipping the
merged-diff scan since nothing was merged), then step 9 (quota), then step 10
(compact +
ScheduleWakeup for iteration N+1). The --iteration N counter still
ticks — a parked PR consumes one of the 8 iterations.
Park conditions (soft stops — loop continues):
--fix-round reached 3 and actionable review findings remain.
- A CodeRabbit rate-limit attempt already re-requested review once and hit the limit
again (existing 30-min fallback applies first; park only if that also fails to yield
a merge).
Stopping (hard stop — loop terminates)
For conditions that mean the whole loop should halt, not just the current PR:
- Leave the current branch and PR in place — do not delete or close anything.
- If a PR exists for the current cycle, post one comment on it via
mcp__github__add_issue_comment summarizing why auto-engineer paused and what it tried.
- End the turn with a plain-text status to the user. Do not call
ScheduleWakeup.
Hard-stop conditions:
- No unblocked issues remain (nothing left to do).
- 3 consecutive build/test fix attempts failed (likely environmental, needs human).
- Any check in
action_required state (explicit human-approval gate).
- Any security / advisory finding from
github-advanced-security[bot] (must not
auto-adjacent around security issues).
- Merge conflict against
main that isn't cleanly resolvable by git pull --rebase.
- Iteration budget of 8 reached.
- The issue was reassigned away from
dburkart while auto-engineer held it.
Calling file-issue from a loop
Skill("file-issue") replaces the current skill's execution context — calling it would
terminate this loop. Instead, spawn a subagent via Agent():
Agent(
description="File follow-up issue: <title>",
subagent_type="general-purpose",
prompt="""
Read .claude/skills/file-issue/SKILL.md and docs/agent-playbooks/prioritization.md.
Then file one GitHub issue against dburkart/vibix with:
title: <title>
motivation: <1-3 sentences, include "discovered while merging #<PR>">
work items: <concrete sub-tasks>
context: <file paths, SHAs, related issues>
Return the new issue number and URL.
"""
)
Key points:
- The Agent() call returns to this skill when done — the loop continues.
- Pass all context explicitly in the prompt; the subagent starts cold with no conversation
history.
- One Agent() call per issue; do not batch multiple issues into one subagent call.
Never
- Force-push, rebase pushed branches, or rewrite reviewed commits.
- Edit
main directly.
- Merge without green CI.
- Delegate the PR-wait loop to the
wait-for-pr skill — it would steal the ScheduleWakeup thread. Use the embedded poll loop in step 6 instead.
- Call
Skill("file-issue") or Skill("usage") inline — use Agent() for file-issue and the probe script for usage. Skill() replaces the current context and terminates the loop.
- Continue past 8 iterations without a fresh user invocation.
- Auto-fix test or build logic in step 6c (fmt and clippy only; real fixes are follow-up commits).
- Close an issue manually — let the squash-merge do it via the PR body's
Closes #N.
- Ask the user for input — never use
AskUserQuestion or pause for a response. If information is missing, make the most defensible choice and continue; if a stop condition applies, stop and report but do not ask.