Your first milestone¶
This is the whole path, start to finish, for the most common setup: one human, one coding agent to hand (a Claude Code, Codex, or similar session), and a brand-new project. Install CodeCrew, scaffold a repo, and drive one milestone from opening to audited close. No GitHub Apps, no configuration beyond what the tool writes for you, no prior knowledge assumed — and no paid GitHub plan: everything here works on the free tier.
Two ideas carry everything else. First, project state lives in GitHub —
issues, sub-issues, labels, and PRs — so there is nothing to keep in sync
and nothing to lose. Second, the verbs refuse: when a gate isn't met,
the CLI exits with refused[CODE]: detail telling you exactly what's
missing. A refusal is the guide-rail working, not an error. This guide walks
you into each one you'll meet, on purpose.
You and your agent¶
The expected division of labour: your agent runs the loop, you make the
calls. The agent acts as orchestrator and implementer in one session — it
runs the verbs, writes plans into task issues, does the work, opens the
PRs, and stops at every gate; you review, resolve raised gates, confirm
merges, and give verdicts. Start your agent in the scaffolded repo and
say "Let's build this project!" — init writes the entry point it reads
(.codecrew/AGENTS.md, with a root AGENTS.md pointing at it and a
CLAUDE.md importing that), so it knows where it is; point it at this page
too if you want it to follow the long form. The refusals keep it honest even when you're not watching.
Every command below is equally pasteable by a human — the protocol is fully human-operable end to end, and running one milestone by hand is a fine way to learn what your agent is doing. But the framework earns its keep when the agent does the typing and the record proves what happened.
0. Prerequisites¶
A GitHub account, gh 2.50.0 or later
installed and signed in (gh auth login; gh --version to check —
task finish reads gh pr checks --json, and a distribution-packaged
gh can be older), git, and (recommended) your coding agent of choice.
One thing to know before your first PR: task finish refuses a PR that
reports no CI check at all, with no override — init does not write a
workflow, so step 5 shows the ten-line one you add yourself.
You and your agent are the whole crew today (SPEC §5 calls this solo
routing; the What next ladder covers expanding it).
1. Install and scaffold¶
gh extension install radiusred/gh-codecrew
gh codecrew version # confirm what you got (gh extension list agrees)
gh repo create my-project --private --clone # or use an existing repo
cd my-project
gh codecrew init
One thing worth knowing up front: gh never auto-updates extensions — not
for patches, not for majors. Updating is always your act:
gh extension upgrade codecrew, then gh codecrew version to confirm.
init writes your .codecrew/config.yml (the project's pointer file, with a
routing table declaring all five roles — ~ means "held by you"), a
ROADMAP.md seed, the five role contracts under .codecrew/roles/ — each
with a blank .codecrew/roles/<role>.local.md beside it, the place for this
project's own additions to a contract (the file says what belongs there; it
adds nothing until you write into it) — and the entry point for any agent
you later dispatch: .codecrew/AGENTS.md, which holds the instructions and
is CodeCrew's to rewrite, a root AGENTS.md of two lines pointing at it
(a sentence naming the path and an @.codecrew/AGENTS.md import), and a
CLAUDE.md importing that root, because Claude Code loads CLAUDE.md and
never AGENTS.md. If you already have an AGENTS.md or a CLAUDE.md,
init keeps it untouched and prints the exact lines to paste into it —
unless it already points at the instructions, so rerunning init on a repo
it scaffolded asks you for nothing.
It commits exactly the files it wrote
(chore: scaffold codecrew, with a body naming init, its release and
protocol, and each file, as SPEC §4 asks of a housekeeping commit), leaving
anything else you had staged or modified as it was. It never pushes; that is yours (run init at the
repository root — it refuses a subdirectory, since the pointer belongs at
the root):
(init prints the exact command for the branch it committed on.)
If your default branch requires pull requests, init will have put the
commit on a branch it made from the default branch, codecrew-bootstrap,
and said so: git push -u origin codecrew-bootstrap and open the scaffold
PR — the one merge you do yourself, since no milestone or task exists yet
for task finish to merge it through. Everything after it goes through a
task.
2. Open a milestone¶
gh codecrew milestone new --title "Walking skeleton" \
--goal "A deployed hello-world proving the delivery pipeline end to end." \
--requirement "visiting the app's URL returns a greeting"
This creates the milestone tracking issue — the canonical milestone object
— and nothing else; ROADMAP.md gets its row when the milestone is
finished (step 7). Each
--requirement (repeat it for more) lands under the issue's
Requirements section with a bold ID the tooling checks "done" against
— numbered by the CLI, M1-R1, M1-R2, … in the order you gave them, and
printed back so you see what the close gate will count:
Only bold IDs in that section are requirements; ones written under Goal are prose, and a milestone with none refuses to close. To add or reword one later, edit it in the web UI, or safely from the shell with fetch-edit-put — pull the current body down, change only the Requirements section, put it back:
gh issue view 1 --json body -q .body > milestone.md
# edit milestone.md — fill in the Requirements section, leave the rest
gh issue edit 1 --body-file milestone.md && rm milestone.md
Never a raw gh api PATCH built from shell variables: one empty variable
silently replaces the whole body.
3. Open a task, plan it, start it¶
The task issue is attached to the milestone as a sub-issue — gh codecrew
status now shows your project. Try to start it:
It refuses: refused[NO_PLAN]. Plans come before work — edit the task
issue's Plan section with your intended changes (a trivial task may have
a trivial plan; it may not have an absent one), then run task start again.
It assigns you and creates a linked branch.
4. Do the work¶
git fetch && git switch task/3-serve-the-greeting
# ...write the code, in commits that reference the task...
git commit -m "feat: serve the greeting (#3)"
git push
gh pr create --fill --body "Closes #3"
The PR description is the task's summary artifact — say what was done and
which requirement it satisfies. If a decision came up mid-task (a trade-off,
a rejected alternative), record it as an issue or PR comment starting
**Decision:** at the moment you make it; if the work departs from the
plan, a comment starting **Deviation:** with a **Why:** line. Both
shapes are collected automatically at milestone close — one record per
comment, so give each its own.
5. Finish the task¶
Refused again — no approving review from a non-author, and you can't approve your own PR. Solo has an honest degradation:
This records an explicit confirmation comment on the PR — stating that
author and operator are the same principal — then rebase-merges and deletes
the task branch. The task issue closes via its Closes #3 keyword.
If your repo has no CI yet, you'll meet one more refusal first:
refused[NO_CHECKS]. The deterministic gate can't be satisfied by a repo
that runs nothing, and there's no override — the fix is a ten-line workflow:
# .github/workflows/test.yml
name: Test
on: [pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: make test # or your language's test command
Push it to the task branch, let the check report, and task finish (with
failing or pending checks it refuses too — that's the gate having teeth).
When the gate is expensive: let the workflow gate itself¶
The ten lines above run everything on every pull request, which is right
until the gate takes minutes and a run of docs-only PRs pays for it twice
over. The temptation is [skip ci] in the commit message. Don't: GitHub
creates no workflow run and no check run for a skipped push, so there is no
fact for task finish to read, and it refuses NO_CHECKS — correctly, and
with no override. The commit message is author-controlled; a reported check
is the platform's own fact, and the gate only ever trusts the second one.
(GitHub reads the marker out of the message wherever it appears, so a commit
that merely mentions it skips the run too — which is how this section
found out.)
What does satisfy the gate is a check that reports skipping. So let the
committed workflow decide, from the diff, whether the heavy job needs to
run — a cheap job that always reports, and the expensive one behind
needs: and if: on its output:
# .github/workflows/test.yml
name: Test
on: [pull_request]
jobs:
changes:
name: Changed files
runs-on: ubuntu-latest
outputs:
code: ${{ steps.classify.outputs.code }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # the base commit has to be in the clone
- id: classify
run: |
git diff --name-only \
${{ github.event.pull_request.base.sha }}...HEAD > changed.txt
# Docs are an allowlist: anything not matching is code. A new
# top-level directory is code until you say otherwise.
if grep -qvE '^(docs/.*|[^/]*\.md)$' changed.txt; then
echo "code=true" >> "$GITHUB_OUTPUT"
else
echo "code=false" >> "$GITHUB_OUTPUT"
fi
- run: make docs-check # your cheap docs gate: seconds, not minutes
test:
name: Go build and test
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: make test
A docs-only PR now costs one short job; everything else runs the full gate. Three things make it hold up:
- Gate the job, not the workflow.
on: pull_request: paths:looks like the same idea, but a workflow that never triggers reports nothing at all — a required check filtered that way stays pending forever andtask finishsees the same absence[skip ci]produces. A job skipped byif:inside a run that did happen reportsSkipped, and that is a fact. - Classify by allowlist.
.github/**, theMakefile,scripts/**and every source path are code. Invert the test — list what counts as docs, treat the rest as code — so a directory nobody thought about defaults to running the gate. - It is no weaker than what you had. A pull request could always neuter the workflow; both the change and the classification are in the diff, and the reviewer reads that diff either way.
6. Verdict — you hold the qa role¶
Try to close the milestone:
refused[VERDICT_MISSING] — every requirement that has not been struck
needs a QA verdict, and the qa role is unrouted, which means it's yours.
Change hats: build what was merged, exercise the requirement's intent
(not just what the code's own tests prove), and post a comment on the
milestone issue, one line per requirement, in exactly this form:
The evidence is yours, from your own execution — .codecrew/roles/qa.md is the
contract you just performed.
The one exception is a requirement struck by a recorded decision: one the
coordination layer withdrew from scope. It needs no verdict, because nothing
was built. The Decision comes first, then gh codecrew milestone strike
posts the struck line on the milestone issue, and close counts that line as
terminal (SPEC §4).
7. The milestone document¶
One refusal left: refused[DOC_MISSING] — and above it, the CLI prints
every **Decision:** and **Deviation:** comment recorded during the
milestone. That's the raw material. Write docs/milestones/1-<slug>.md from
it — the "why" record: what was decided, what was traded off, what deviated
from plan — add the milestone's row to ROADMAP.md, already Done and
linking the document
(| M1 | Walking skeleton | [#<issue>](…) | [Done](docs/milestones/1-<slug>.md) |),
and land it on the light path the protocol gives the record (SPEC
§4): no task this time — a docs: commit
referencing the milestone issue, a PR whose body names that issue without a
closing keyword (the close closes it, not the merge), a clean-context review
if you like the discipline, then your operator confirmation comment on the
PR — you are the author and there is no one independent to approve it —
and a rebase merge. task finish --operator-confirm is not involved: there
is no task for it to finish. The roadmap lists finished milestones;
gh codecrew status reports the open one.
8. Close¶
All gates pass: tasks closed, verdicts satisfied, document merged. Any task branch still lying around is swept first (only merged or empty ones — the output says what went and what stayed), then the milestone issue closes with the record comment, and everything above — the plan, the decisions, the confirmation, the verdicts, the document — is permanent, linked, and public to whoever you choose.
What next¶
The loop above is the same shape at every size — more requirements per
milestone, more tasks per requirement, CI required checks to give task
finish's gate real teeth (SPEC §8). The interesting growth is
in the crew, and it's a ladder:
- Split the roles across sessions. Your agent doing the work and then
verdicting its own milestone shares one context's blind spots. The cheap
fix: have your harness launch a sub-agent per role, each briefed with the
role's contract from
.codecrew/roles/(plus any.codecrew/roles/<role>.local.mdbeside it —gh codecrew roles show <role>prints the two composed) — so a fresh-context QA reading.codecrew/roles/qa.mdprobes what the implementer's context wouldn't. Or cross model families: dispatch another LLM through its own CLI for the reviewer or qa seat. Same identities, same commands — just different eyes. - Give crew members their own identities. When you want the record to
show which agent did what — and GitHub itself to enforce that the
approver isn't the author — route roles to other humans
(
identity: user:<login>), to a GitHub team (identity: team:<org>/<slug>, any member holds the seat), or to GitHub App identities (identity: app:<slug>): identities.md. The protocol doesn't change; only the routing table does. - Full orchestration platforms — an orchestrator dispatching the whole crew against the routing table, webhooks instead of polling. Run end to end on Paperclip: three milestones on a proving-ground repo, the third with one gate and no operator touch on the workflow besides it, then a fourth cycle on a fresh repo driven by a dedicated coordinator agent — the findings and what they changed are on #119 and #164. Platform interop is the page written from them: the coordinator seat, credentials, wake paths, the onboarding checklist, what four cycles cost, and the recipe.