Configuring the workflow¶
Everything Pharaoh does is driven by ubproject.toml. There is no hidden state:
the process model, the ontology, and the AI runner all live in the same file your
project already uses for linting and
need types.
This page walks through the pieces of a complete V-model configuration and
explains why each is there. For the exhaustive, key-by-key reference of the
[workflow] section, see its configuration page.
Note
ubc agent install writes all of this for you. You only need to understand
it when you want to change the process — add a stage, rename a type, or point
a route at a different folder.
The ontology: types and links¶
A workflow can only produce and trace types that exist. So the first job is to
declare the need types and the links between them — the same
[[needs.types]] and [needs.links] tables you
would use in any ubCode project:
[[needs.types]]
directive = "user_story"
title = "User Story"
prefix = "US_"
[[needs.types]]
directive = "req"
title = "Requirement"
prefix = "REQ_"
[[needs.types]]
directive = "arch"
title = "Architecture"
prefix = "ARCH_"
# The implementation- and verification-arm needs, materialised from one-line
# code markers (see "Implementation and verification as needs" below).
[[needs.types]]
directive = "impl"
title = "Implementation"
prefix = "IMPL_"
[[needs.types]]
directive = "test"
title = "Test"
prefix = "TEST_"
Each link the workflow traces along must be declared too, with its outgoing
and incoming names. The incoming name (for example implements_back) is what
lets the engine ask a parent “what implements me?”:
[needs.links.traces_to]
outgoing = "traces_to"
incoming = "traces_from"
[needs.links.satisfies]
outgoing = "satisfies"
incoming = "satisfied_by"
[needs.links.implements]
outgoing = "implements"
incoming = "implements_back"
[needs.links.verifies]
outgoing = "verifies"
incoming = "verifies_back"
Important
Every link a [workflow] trace edge references must be declared in
[needs.links]. A need authored with an undeclared link option has that
option dropped, with an “Unknown need directive option” warning at build
time — and ubc agent config-validate (or doctor) reports the mismatch
before you ever author. If a trace gate never goes green, run the pre-flight
first: an undeclared link is the usual cause.
The process: [workflow] stages¶
The [workflow] section is the process model — an ordered list of stages, each
producing one need type and declaring the traceability it must satisfy.
[workflow]
# Spec artefacts are decomposition steps, gated by their own trace edges rather
# than by code coverage — so they are exempt from the orphan check.
orphan_exempt_types = ["user_story", "req", "arch"]
[[workflow.stages]]
id = "reqs"
produces = "req"
depends_on = ["user_stories"]
author_skill = "draft-requirement"
review_skill = "review-requirement"
authoring = "Decompose the use case into the complete set of requirements it implies — the happy path, the error and edge cases, and the relevant non-functional constraints."
[workflow.stages.route]
scope = "stream"
granularity = "per-root"
path = "specs/{stream}/requirements.rst"
[[workflow.stages.trace]]
up = "user_story"
link = "traces_to"
direction = "both"
min = 1
Reading that stage top to bottom:
produces/depends_onThis stage authors
reqneeds and cannot start until theuser_storiesstage is satisfied. An unmet dependency reports the stage asblocked.author_skill/review_skillThe names of the AI skills the harness runs to draft and to review this stage’s artefacts (see the harness assets and the command reference).
Providing a
review_skillalso activates the quality gate for this stage: it is heldready— never reporteddone— until every need it produces carries a fresh review verdict that clears the per-axis integer floors set in[quality], whether the global[quality.required]map or a per-type[quality.type_configs.<type>.required]override. A stage with noreview_skillhas no quality gate — only its structural gates apply. See Reviews: the substance gate below and the [quality] configuration reference.authoringA project-specific directive injected into the authoring prompt. Process policy — “produce the complete set of requirements” — lives here in config, not baked into the generic skill, so you can tune it without editing skills.
[workflow.stages.route]Where the artefacts are authored, and into how many authoring runs the stage is split.
scope = "stream"withgranularity = "per-root"means one run per stream, writing to thepathtemplate. The route keys reward a closer look — see the route, in detail below.[[workflow.stages.trace]]The traceability obligation.
direction = "both"is bidirectional: everyreqmust link up to auser_storyviatraces_to, and everyuser_storymust be covered down. This one edge is what the structural gate enforces for the stage.
Implementation and verification as needs¶
The two lower arms of the V are special: instead of authoring an .rst
directive, the stage produces a need from a one-line codelinks marker in your
real source. The code stage scans src/ and the tests stage scans
tests/:
[[workflow.stages]]
id = "code"
produces = "impl"
depends_on = ["archs"]
author_skill = "draft-impl"
[workflow.stages.route]
scope = "global"
granularity = "global"
path = "src"
[[workflow.stages.trace]]
up = "arch"
link = "implements"
direction = "both"
min = 1
The marker itself is an ordinary code comment, for example in src/login.py:
# @Password hashing, IMPL_PWHASH, impl, [ARCH_AUTH]
def hash_password(raw: str) -> str:
...
ubc build materialises that into a real impl need whose implements link
points at ARCH_AUTH. The stage’s bidirectional trace edge then counts the
arch’s incoming implements_back: an architecture element with no implementing
marker fails the gate, and the code stage stays ready until one covers it.
The tests stage works identically with verifies markers in your test tree.
The grammar of the marker (its field order and the link-field name) is configured
under [codelinks]; see the codelinks guide for the
full mechanism. One detail is handled for you: a marker only becomes a need where
a .. src-trace:: directive with the matching :project: appears in a built
.rst file, and the authoring skills emit that directive alongside the markers —
it matters only if you author markers by hand.
The key point: implementation and test coverage are graph facts derived from your code, not fields you maintain by hand.
Note
The alternative way to gate code coverage, [workflow.stages.require_code],
checks the presence of a field such as code_url instead of a trace link.
This profile prefers the trace edge: it attributes a missing-code gap to the
stage that should close it, and it assumes no fixed code-versus-test directory
layout. See the require_code reference
for the field mechanism and when to prefer it.
The AI runner: [agent.runner]¶
ubc agent run executes the next stage through a headless AI process. The
[agent.runner] table configures which one:
[agent.runner]
command = "claude -p --permission-mode acceptEdits --output-format stream-json --verbose"
model = "sonnet"
timeout_secs = 600
The assembled prompt (recommended skill + context briefing + resolved route) is
passed on the process’s stdin; the command defaults to claude -p when the
table is absent. The [agent.runner] reference
documents each key, the defaults, and the UBC_AGENT_RUNNER* environment
overrides.
Note
[agent.runner] only affects the headless ubc agent run path. Driving the
loop interactively from Copilot or Claude Code uses that assistant directly and
ignores this table.
Reviews: the substance gate¶
A stage with a review_skill is held until each need it produced carries a fresh,
passing review verdict. What “passing” means is configured separately, in the [quality]
table: the rubric each reviewed type is scored against (a criteria pack) and the per-axis
floors that gate it.
[quality]
enabled = true
[quality.type_configs.req]
criteria_file = "quality/requirement.toml" # a pack ubc agent install writes for you
[quality.required]
parent_fit = 1 # the gate: the minimum score per axis
ubc agent install writes this table and the criteria packs. The
Reviews and quality analysis guide explains the model, and the
[quality] configuration reference documents every key and the
criteria-pack format.
Presentation: diagrams and icons¶
Two optional sub-tables tune how the workflow is presented and gated:
[workflow.diagram]
enabled = false # keep the diagram gate off...
format = "mermaid" # ...but tell the runner to author diagrams as Mermaid
[workflow.icons]
req = "req" # icon shown for each type in the VS Code Workflow view
arch = "arch"
test = "test"
[workflow.diagram] controls the diagram gate (validation of Mermaid/PlantUML
blocks) independently of the authoring format hint.
[workflow.icons] maps a need type to one of the built-in, presentation-neutral
icon names used by the Workflow view. The icon name is independent
of your own type names, so a software_requirement type can render with the
req icon. See the full icon reference for every
name — from feature, req, and arch to hazard, safety_goal, and
component — with the glyph each one renders as.
See also
Workflow configuration reference — every
[workflow]key.Quality analysis configuration — the
[quality]gate and criteria packs.Codelinks — the one-line marker mechanism.
Need schema — field-level schema validation.