Planning a task graph
You will be able to turn an idea — or an existing codebase — into a task graph that commission can dispatch, and re-apply that plan safely as it changes, without ever creating a half-written graph.
Audience: whoever is deciding what gets built. Assumes: your first task.
One task, when one task is the answer
commission add "Rotate the signing key" -c "old key revoked" -c "runbook updated" --gate shippedcommission add is right for a thing you thought of while doing something else. It
is the wrong tool for a quarter’s work, because you will end up with thirty
tasks and no dependencies between them, which is a list rather than a graph.
The plan document
Everything real goes through commission apply:
cat > plan.json <<'PLAN'
{
"project": "myapp",
"tasks": [
{ "key": "schema", "title": "Design the events table",
"criteria": ["migration reviewed", "indexes justified"],
"touches": ["src/db"] },
{ "key": "ingest", "title": "Webhook ingest endpoint",
"deps": ["schema"], "gate": "shipped", "touches": ["src/http"],
"criteria": ["signature verified", "replays rejected"] },
{ "key": "feed", "title": "Event feed in the web view",
"deps": ["schema"], "touches": ["src/web"] }
]
}
PLAN
commission apply -f plan.jsonapplied to 'myapp': 3 created, 0 updated
+ myapp-1 (schema)
+ myapp-2 (ingest)
+ myapp-3 (feed)The full field list — parents, links, priority, autonomy, external keys — is in the generated apply reference, and the JSON Schema itself is one command away:
commission apply --schemaFour properties worth knowing
It is atomic. Local keys resolve to real ids inside one transaction. A
plan that names a gate you have not defined, or a dependency that does not
exist, creates nothing:
unknown gate 'tier1' for project 'myapp'
hint: 'myapp' has no gate profiles yet — define one: commission gates --set tier1=test,commitYou never have to clean up after a rejected plan.
It upserts by key. Re-applying the same document does not duplicate anything; it updates in place and creates whatever is new:
applied to 'myapp': 1 created, 2 updated
+ myapp-3 (c)
~ myapp-1 (a)
~ myapp-2 (b)So the plan file can live in the repository and grow with the work. It is not a one-shot import.
It previews. --dry-run prints the exact result and writes nothing:
commission apply -f plan.json --dry-rundry-run for 'myapp': 2 created, 0 updated
+ myapp-1 (a)
+ myapp-2 (b)
nothing written — land it with: commission apply -f <file>It takes stdin. commission apply -f - reads the document from a pipe, which is
how an agent lands a plan it just wrote without touching the filesystem.
Designing the graph
Three rules, and they all point the same way: keep the ready frontier wide.
depsonly for real ordering. A dependency that encodes a preference serialises work for no reason. If B could start before A finishes, B does not depend on A.- Split by area, not by phase. “Implement the endpoint” then “test the endpoint” is a chain of two. “The ingest endpoint” and “the event feed” is two parallel tasks. Phases are how a plan becomes a queue.
- Label
toucheshonestly. It is what lets commission suggest which ready tasks can be dispatched together — see parallel work for exactly how much that promises.
Use parent for containment, not for ordering. A parent task cannot close while
any child is open, and that refusal is not forceable: a parent’s exit
criterion is its children.
Check the shape before you dispatch:
commission ready --project myapp
commission board --project myappIf blocked is long and ready is one item, the graph is a queue and the
agents will run one at a time.
Criteria, gates, and evidence
- Criteria are the task’s own definition of finished, ticked one at a time. Write them as checkable facts — “replays rejected”, not “handle replays properly”.
- Gates are the evidence class the task belongs to, defined once for the project and referenced by name.
commission gates --set shipped=pr,test
commission gatesDeclare gates before applying a plan that references them; the whole plan is
refused otherwise. Gates live on the project row: commission gates --set and
commission init --gates write them, and the gates block in .commission.json is
not yet applied.
Letting an agent do the planning
commission define myapp
commission define myapp --repo-path .define prints a brief — not a plan. It walks the agent through interrogating
the idea, writing a project vision, designing the graph, and ending in a
reviewed commission apply, with an explicit instruction not to skip the dry run.
--repo-path grounds it in a codebase that already exists, so the plan
describes the system you have rather than the one the model imagined.
The vision it produces is stored on the project and injected into every task bundle, so a session six weeks later inherits the principles that settled the arguments rather than re-litigating them.
Before planning anything, check whether it was already decided:
commission search "rate limit"Full-text search covers task titles, bodies, notes, questions, and answers. “Did we already try this?” is answerable in one command, and the answer is usually a note somebody left at 2am.
The whole sequence
commission gates --set shipped=pr,test
commission search "rate limit"
commission apply -f plan.json --dry-run
commission apply -f plan.json
commission ready --project myapp
commission board --project myappEvery command on this page is executed by tests/docs.test.ts against a scratch
database.
Related
- Apply reference — every field, generated from the schema.
- Parallel work — dispatching the frontier you just built.
- Migration — when the plan already exists somewhere else.
Part of Guides.
This page is docs/guides/planning.md in the Commission
repository, rendered in place — the site keeps no copy of it. The repository is private, so there is no edit link to follow.