The ubc agent command

ubc agent is the engine behind Pharaoh: a set of subcommands that read your graph and your [workflow] config and report what to do next, what the graph looks like, and whether it is ready to release. All of them are read-only with two exceptions: run drives your AI, and install / update scaffold config — nothing else changes your requirements.

Caution

ubc agent is experimental and intentionally hidden from ubc --help. It works today, but the subcommand set and the JSON shapes may change between releases. List the subcommands with ubc agent --help and a specific one with ubc agent <command> --help.

Three conventions worth knowing

Every verb pins its project and its need the same way. -p <PATH> (--project) names the project root — a repository can hold several ubproject.toml files, and without it a verb runs against the current directory. The verbs that read a single need all take it as --id <NEED_ID>; the inspect verbs (audit, context, impact) also accept the id positionally.

Everything is JSON. Every verb prints a JSON object on stdout, designed to be parsed by an AI agent rather than read as prose. The reports (next, status, review-brief) always exit 0 on a readable graph; the gates (gaps, trace, release-check, verdict-check, config-validate, doctor, update) exit non-zero exactly when their check fails; the inspect verbs (audit, context, impact) exit non-zero when the requested id does not exist; and the two write verbs (run, verdict-submit) exit non-zero when they cannot produce their artefact.

The loop verbs act on the “active stream”, not the whole graph. next and run derive the stream you are working on from git diff (dirty-vs-HEAD locally, branch-vs-merge-base on a feature branch), or from the stream anchored on --id <NEED_ID>. An --id that is not a need exits non-zero rather than silently falling back to the whole graph, so a typo can never be mistaken for progress. Pre-existing gaps elsewhere are surfaced as non-blocking open_streams so one stream’s work is not drowned out by another’s.

Driving the loop

next

Report the single next actionable stage (0 or 1, never a list) for the active stream, as JSON. A report, not a gate — always exits 0. When there is nothing to do it returns stage: null with a reason (empty / done / blocked). This is the heartbeat you poll to drive authoring. Pass --stage <STAGE_ID> to brief an explicit stage — for example a global risks / decisions arm — instead of the computed recommendation; an unknown stage id exits non-zero.

run

Execute the recommended stage through the configured runner, then rebuild and report what was produced. --id <NEED_ID> picks a stream (required on a clean tree); --skill <NAME> runs a named skill instead of the stage’s default — the execution surface for targeted intents such as the change-request cascade. The produced artefact is left uncommitted for human or UI approval.

status

The full heartbeat across the whole graph (it takes no stream anchor): one row per stage with its state (done / ready / blocked), coverage counts, failing gates, and route. Always exits 0.

Inspecting the graph

audit --id <ID>

A read briefing for one need: its id, title, type, status, body, and source pointer, plus a trace sub-object and the full map of every named link on it. Exits non-zero if the id is absent (it never substitutes a stand-in).

context --id <ID>

One need’s neighbourhood: the anchor plus every directly linked need (forward and _back), each with its own body and the relation it came in on. This is the briefing the authoring skills read. --no-code (alias --graph-only) returns the graph briefing without attempting to resolve related source.

impact --id <ID>

The blast radius of one need: the multi-hop, up-and-down trace closure of everything that depends on it, to a --depth bound, each entry tagged with its depth and direction and carrying its code/test URLs. Also lists stale_gaps already attributed to the anchor or anything it reaches. Used by the change-request cascade to scope an edit.

Gating and coverage

gaps

The full structural gap list ({gaps, by_category, summary, ok}). Exits non-zero when any gap is found — the whole-graph structural gate. --scope <NEED_ID> narrows it to the one stream that need anchors — its entry root and the whole trace-subtree beneath — and recomputes the tally and exit code over that subset. Scoping is by the trace graph, not a specs/ path; an --scope id that resolves to no need exits non-zero rather than gating nothing.

trace

The bidirectional trace-coverage matrix: one row per declared trace edge with forward and backward covered/total counts and the surviving gap ids. Exits non-zero if any edge is uncovered in either direction.

release-check

The release gate. By default every open structural gap fails it. With --baseline <PATH> it gates only new gaps against a baseline of accepted debt: a baselined gap is reported as non-blocking, a gap absent from the baseline fails. Add --update-baseline to ratchet the baseline down on a passing run — fixed gaps are dropped so they can never be readmitted; without the flag the gate never mutates state. --with-verdicts folds the substance gate in: the combined gate is green only when both the structural gaps and the AI-review verdicts are clear.

verdict-check

The AI-review substance gate. Reads the review verdicts from .pharaoh/verdicts/ and reports which review-required needs block a release: missing (no valid verdict), failing (a recorded failure), malformed (an unusable file), outdated (the need changed after its verdict was recorded), and unverifiable. Exits non-zero on any of those; a leftover stale verdict (for a need no longer in the graph) warns but never blocks.

Reviewing needs

See Reviews and quality analysis for the model these two verbs serve; the entries here are the reference.

review-brief

The reviewer’s contract, as JSON — the review-arm counterpart to next. review-brief <TYPE> (or --ids <ID…>) emits, per reviewed type: the review_skill to run, the absolute verdicts_dir, the review_needs still needing a verdict (already-fresh needs are listed under fresh_skipped and excluded unless --all), each need’s current one-hop content fingerprint, the resolved criteria pack (its guidance and scored axes), the derived verdict_schema, and the submit_command template. A report — exits 0 even when everything is already fresh.

verdict-submit

Write one need’s verdict. verdict-submit <NEED_ID> --file <PATH|-> reads a scored verdict (- = stdin) and writes it atomically to .pharaoh/verdicts/<NEED_ID>.json. The engine stamps the provenance (schema, need, criteria, and both fingerprints), validates the scores against the need type’s resolved pack, and refuses an in-repo draft path — so a reviewer cannot forge a verdict’s identity. --fingerprint and --criteria-fingerprint arm race guards that reject a verdict scored against content or a rubric that has since changed. Exits non-zero, with a machine-readable reason, on any rejection.

Setup and health

config-validate

Cross-check [workflow] against the ubproject.toml ontology as JSON ({ok, errors, warnings}): every type a stage produces or traces against must be a declared type, every referenced link a declared link, every transition state a declared lifecycle state. It also validates the [quality] gate against the loaded criteria packs — a floor above an axis’s max_score, or a floor naming an axis no pack declares, is an error. Reads config only — never builds the graph.

doctor

The pre-flight an agent runs before driving the loop, so it fails on a misconfiguration before spending tokens. Composes config-validate and then probes only the tools the resolved config actually requires (a diagram renderer when the diagram gate is on; codelinks when a code-trace gate is configured). A required-but-missing tool is a blocking error with a remediation hint.

install / update

Bootstrap a project into the loop from a profile, and reconcile a previously installed project against the current profile. Covered in the quick start (install, update).

See also

Quick start shows these verbs in the order you actually use them; Configuring the workflow explains the [workflow] keys the gates enforce.