Polling a feature done-signal in an agent loop
Gate an agent loop on one feature’s completion – not the whole project’s –
using ctxgrd status --lineage and --exit-code.
Background
ctxgrd pools documents by type: all ADRs share docs/adrs/, all SPECs share
docs/specs/, and so on. A repo routinely holds several in-flight features at
once. Running ctxgrd status --exit-code on the whole project asks “is the
entire repo done?” – which is almost never the right question for an agent
loop driving a single feature.
--lineage <ID> changes the scope. It follows the depends_on graph in
reverse: every document that transitively depends on <ID>, plus <ID>
itself. That set is one feature’s lineage. Combined with --exit-code, it
gives a process exit that answers “is this feature done?” without
text-scraping – satisfying ctxgrd’s machine-readability contract (stdout
clean for piping, exit code for branching).
Exit codes
| Code | Meaning |
|---|---|
0 |
Selected frontier is empty and there are no blockers – feature is done. |
1 |
Frontier is non-empty or a blocker is present – keep working. |
2 |
Config error or dependency cycle – abort. |
The report body is still printed to stdout regardless of which exit code is
returned; no file is modified.
Step 1 – Identify the lineage root
Pick the document at the root of your feature’s depends_on graph. For a
feature driven by a PRD, that is usually the PRD:
ctxgrd status --lineage PRD-3 --exit-code
Shared documents – depended on by more than one lineage root – are included
in each lineage’s count and marked in the output. A shared document’s other
parents do not need to be done for your lineage’s verdict to be 0.
Step 2 – Wire the poll loop
The canonical shell loop for an agent driving PRD-3:
while true; do
ctxgrd status --lineage PRD-3 --exit-code
code=$?
if [ "$code" -eq 0 ]; then
echo "Feature done -- stopping loop."
break
elif [ "$code" -eq 2 ]; then
echo "Config or cycle error -- aborting." >&2
exit 2
fi
# code is 1: feature still in progress; do the next unit of work, then poll again
sleep 30
done
Exit 0 stops the loop. Exit 1 means there is still work in the frontier;
the agent does the next unit of work and polls again. Exit 2 is a hard
failure the agent cannot recover from by itself.
Step 3 – Route on the JSON output for finer decisions
When the loop needs more than a binary branch – for example, to know which namespace is the current bottleneck or which documents are in the frontier – request structured output:
ctxgrd status --lineage PRD-3 --format json
Key fields:
frontier– the namespaces that currently have unresolved documents.next_action– a short human-readable hint (identical to thetip:line in text mode).stages[].state– per-namespace state:done,current,pending, orblocked.stages[].verdict– the gate verdict for the stage (e.g.accepted, oremptywhen no documents are counted).stages[].shared– documents in this stage that belong to more than one lineage.
stdout is a clean JSON stream; pipe it directly to jq:
ctxgrd status --lineage PRD-3 --format json | jq '.frontier'
Step 4 – Fold Definition-of-Done checkboxes into the gate (optional)
core.acceptance-complete is an opt-in builtin rule that fires on any
document at a terminal status that still has an unchecked - [ ] item under
its acceptance heading(s). When it fires, the document’s stage remains blocked
– and --exit-code stays 1 – until every box is checked.
Enable it per namespace in ctxgrd.toml:
[TASK."core.acceptance-complete"]
headings = ["Acceptance", "Definition of Done"]
terminal = ["done"]
headings defaults to ["Acceptance", "Definition of Done"]. terminal
defaults to the shared terminal-status set (accepted, done, fixed,
implemented, closed, superseded, wontfix, invalid, duplicate,
n/a); supply a narrower list to restrict the rule to specific statuses.
The rule scans only the configured heading window(s). An open checkbox under
Out of scope or Open Questions does not fire – those sections are deferred
work, not unmet criteria.
Example. A TASK at status: done with this body:
## Acceptance
- [x] Endpoint returns 200 for valid input
- [ ] Rate-limit header present in response ← fires core.acceptance-complete
## Open Questions
- [ ] Should we log 429s to Datadog? ← does not fire
The open box under Acceptance holds the stage. After the box is checked and
ctxgrd is re-run, the diagnostic clears and the gate can advance.
Cross-references
- SPEC-003 (
docs/specs/003-status-done-gate-and-per-lineage-scope.md) – full requirements for the done-gate and lineage scope. - ADR-056 –
--exit-codeandcore.acceptance-completedesign. - ADR-059 –
--lineagegraph-scope design.