| name | arch |
| position | start |
| description | Write a SLICE's architecture lens as a grounding doc (architecture.md) — the components the slice threads (each in its layer, with its contract), the stack (tech + versions) behind them, and the vertical build that runs the slice end-to-end. The START of the NON-FUNCTIONAL realize pipe (arch → quality → run), run on a shaped slice. Every component is selected from the slice's functionalities' systems + the profile surfaces, never invented; the build is one vertical end-to-end. Reads the hub from the spine (functionality grounding + profile) and MAY read the functional lenses, never the measure or run lens. Writes only the slice's architecture lens. |
| user-invocable | true |
arch
Write a shaped slice's architecture lens as the grounding doc architecture.md: the
components the slice threads (each in its layer, with its contract), the stack behind them, and
the vertical build that runs the slice end-to-end. /arch reads the slice's hub — its
functionalities' grounding docs plus the profile box (both from the spine) — and MAY read the
already-merged functional lenses (ux/agentic/marketing), never the measure or run lens.
Pipeline position: start. /arch OPENS the non-functional realize pipe (arch → quality → run):
the D2 rule prepends start-change — resolve or create the slice-realize issue, cut the branch
off fresh main, optional worktree, init STM — so /quality and /run run on this already-started
branch. No close sequence is injected here; the non-functional pipe closes at /run. It writes the
persistent product model (the slice's architecture lens) on the started branch.
Compiled From
This play was compiled from the arch ICE (reference/ice.md) by play-editor (#466 Batch C,
Level 3 rollout per ADR 025; #467 Batch B). Intent defines constraints (C1–C12) and failure
conditions (F1–F13); the expectation defines success scenarios (S1–S6), a Done means (D1–D3,
baked to stop-condition.yaml), and one recovery entry per failure condition. To modify this
play, update reference/ice.md and recompile with play-editor. Do NOT edit this file manually —
it is a compiled artifact. (#467 Batch B) checkpoint upgraded to a conditional learned gate; see
gate-config.md.
Role
You are the orchestrator. You own the workflow and step order. You delegate the domain work —
authoring the architecture lens grounding doc — to the product-os-keeper agent via a JSON
contract over files on disk, and you run the mechanical checks (readiness/hub resolution, the
shape linter, the content-quality eval, grounding + coverage, KB grounding, the allowlisted apply,
the verify) through bundled scripts and an isolated judge. You never write the lens yourself, and
you never persist before the human approves the single checkpoint (C11).
Forbidden: hand-writing the lens or a decision; writing anything other than this slice's
architecture.md and a decision (C2); reading or grounding on the measure or run lens (C7);
persisting by any route other than scripts/apply_arch.py; persisting before the checkpoint;
closing COMPLETED without the stop-condition verdict reading held (C12/F12).
Agent boundaries:
| Agent | Domain | Skill it invokes | Phases |
|---|
product-os-keeper | Author the slice's architecture lens (components + stack + vertical build) from the hub + KB architecture/technology grounding | kb-search, author-architecture-lens | Draft |
product-os-keeper is the single domain agent this play uses (1 of the ≤5 budget). The
content-quality judge always runs as an isolated, clean-context sub-agent (optionally on a
configured different model) — never the orchestrator's own context.
Pre-flight
| Check | Constraint | Action on Failure |
|---|
Resolve config + product_base (.garura/core/config.yaml) | — | Hard halt |
Resolve grounding-eval.judge (optional model override) | C4 | Default: sub-agent on the session model |
Slice ready + hub resolves (check_ready_slice.py) | C1 | Hard halt (REC1) |
Resolve the pre-flight facts mechanically with the bundled resolver:
python3 scripts/preflight.py --play arch --config .garura/core/config.yaml
Then resolve the slice and its hub from the spine — the readiness gate that every realize lens
shares:
python3 scripts/check_ready_slice.py --product-base <product_base> --slice <slice-id>
It asserts the profile is set (from the spine), resolves the slice record, and resolves every
functionality_ref through the spine to its functionality.md grounding doc — the hub. If the
slice is absent, a functionality does not resolve, or the profile is not firmed, hard halt
(C1/REC1).
The run's working root (<working> below) is {stm_base}_realize/arch/ — the draft dir, the
manifests, the captured verify output (arch-checks.json), and the status/ markers all live
under it; the stop condition evaluates against it.
Right after the resolver, record the session identity stamp's start marker (#463 — soft-fail,
never a halt):
python3 scripts/session_stamp.py --phase start \
--marker "{stm_base}_realize/arch/status/session-stamp-arch.json" \
--cwd "$(pwd)" --branch "$(git branch --show-current)"
Resume check: if {stm_base}_realize/arch/status/<slice-id>.json exists, resume — skip
completed steps, reset any in-progress step to pending, continue.
Task DAG
Create ALL tasks immediately after resolving config — before any domain work.
[T0] start-change (injected — start, head) blockedBy: []
[T1] Draft the lens blockedBy: [T0]
[T2] Validate the draft blockedBy: [T1]
[T3] Checkpoint (approval) blockedBy: [T2]
[T4] Persist blockedBy: [T3]
[T5] Verify persisted blockedBy: [T4]
[T6] Scenario Validation blockedBy: [T5]
[T7] Close blockedBy: [T6]
Mark each task in-progress before its step and completed right after its eval passes.
Workflow
Phase: Start (injected — D2 position: start)
Step 0 — start-change · Owner: start-change (sub-play) · Depends on: pre-flight
Run the start-of-pipeline member as a sub-play, dispatched with parent_run_id so it emits only
its own C1 evidence. It resolves or creates the slice-realize issue, cuts the branch off fresh
main, sets up a worktree iff config calls for it, and initializes STM. /quality and /run run on
this branch; /run closes it.
{
"play": "start-change",
"parent_run_id": "<this run id>",
"inputs": { "title": "<realize arch: the slice>" },
"outputs": { "result": "{stm_base}_realize/arch/start/start-change.json" }
}
Phase: Draft
Step 1 — Draft the lens · Owner: product-os-keeper · Depends on: Step 0
The agent invokes author-architecture-lens to draft the slice's architecture.md (components +
stack + vertical build, per the Architecture lens template) from the hub (the functionality
grounding docs + the profile box) and KB architecture/technology grounding, plus an
arch-manifest.yaml (the grounding map) and any material-choice decision:
{
"task": "author the slice's architecture lens (components/stack/vertical build) from its hub; ground components to functionalities' systems or profile surfaces and the stack to the KB or a decision",
"inputs": { "slice_ref": "<domain>/<slice>",
"slice_file": "<slice record>",
"functionality_groundings": "<from check_ready_slice>",
"profile": "<spine profile>", "product_base": "<product_base>",
"lens_rel": "product-os/<domain>/slices/<slice>/lens/architecture.md" },
"outputs": { "draft_dir": "<working>/draft/", "manifest": "<working>/draft/arch-manifest.yaml" }
}
The skill reads the hub read-only (and may read the functional lenses) and writes only the draft
(the architecture.md, the manifest, any decision, any KB proposals).
Step 2 — Validate the draft · Owner: play · Depends on: Step 1
Run the guards over the draft before the checkpoint — shape first, then content, then grounding.
python3 scripts/lint_grounding.py --doc <working>/draft/product-os/<domain>/slices/<slice>/lens/architecture.md
python3 scripts/validate_arch.py --draft <working>/draft --manifest <working>/draft/arch-manifest.yaml --slice-file <product_base>/<slice_file>
python3 scripts/check_kb_grounding.py --manifest <working>/draft/arch-manifest.yaml --kb-root <kb_root> --proposals-dir <working>/draft/proposals
Then run the content-quality eval over architecture.md: spawn an isolated, clean-context
sub-agent handed the judge prompt (standards/rules/grounding-eval.md), the doc, and the
Architecture lens per-section guidance, on the model from grounding-eval.judge.model. Gate the
verdict:
python3 scripts/grounding_gate.py --verdict <verdict.json>
SE-1 (F1/C1): check_ready_slice.py passed at pre-flight — the slice is ready and its hub
resolves; an unready slice halted (REC1).
SE-2 (F3/C3): lint_grounding.py exits 0 — architecture.md conforms to the Architecture
lens template (Intent/Components/Stack/Vertical build), no missing/extra/empty section.
SE-3 (F4/C4): the content-quality eval gate (grounding_gate.py) passes — architecture.md
is self-explaining and clears the stranger test.
SE-4 (F5/C5): validate_arch.py — every component in the manifest grounds to a functionality's
system or a profile surface; the stack grounds to the KB or a decision.
SE-5 (F6/C6): validate_arch.py — every functionality the slice bundles threads through at
least one component (coverage; the vertical is end-to-end).
SE-6 (F7/C7): validate_arch.py — no component grounds on the measure or run lens.
SE-7 (F8/C8): validate_arch.py — the stack names a decision that resolves.
SE-8 (F10/C10): check_kb_grounding.py exits 0 — the stack and architecture choices trace to
a KB learning or a recorded proposal.
On any GAP, apply the matching recovery (REC3–REC10) and re-run before the checkpoint.
Phase: Checkpoint (class: standard, C11)
Step 3 — Human review (class: standard, conditional) · Owner: play · Depends on: Step 2
This is the single checkpoint (C11) — the agent never skips it on its own judgment. It is a
conditional gate per standards/rules/gate-config.md (#467; /arch is one of the eleven
conditional document plays). Resolve, first match wins: pinned (n/a here) → gates.plays.arch →
the learned policy → gates.classes.standard → gates.default (absent ⇒ on).
For the learned-policy step, classify the draft-vs-live change shape mechanically:
python3 scripts/classify_change.py --play arch --draft <working>/draft --live <product_base> --out <working>/shape.json
Look the emitted shape_key up in the config-resolved policy file (gates.conditional.policy,
default .garura/core/gate-policy.yaml). The --ts value below is the run's timestamp derived
the same way the close derives ts (date -u +%Y%m%d-%H%M%S), passed by the orchestrator.
- Auto-pass — the shape is in the policy's
auto: block AND not in never_auto: AND the
draft carries no blocking finding (no Step 2 lint gap, no content-eval fail): do NOT wait.
Record gate auto-passed by learned policy (shape: <shape_key>, policy v<version>) as a
Checkpoint Decisions row plus the draft's diff summary, append the ledger line, and proceed:
python3 scripts/gate_eval.py append --ledger <gates.conditional.ledger> --play arch --issue <issue> \
--shape <shape_key> --predicted auto --human auto_pass --ts <ts>
- On (anything else, no config off) — render the approval prompt
(
standards/templates/approval-prompt.md): present the proposed components (with contracts),
the stack, and the vertical build inline, plus the decision, and wait for the typed
response. Approve → persist; cancel → halt, nothing written. Then append the ledger line with
the human's real action — the same gate_eval.py append command with --predicted gate --human <approved_clean|approved_edited|rejected>.
- Off (config) — record
gate skipped by config (<resolution path>) as a Checkpoint
Decisions row in the evidence and proceed on the validated draft.
EVERY crossing of this gate appends exactly one live-eval ledger line — gated or auto.
SE-10 (F11/C11): the lens is persisted only after this gate resolves — a typed approval, a
recorded config skip, or a recorded policy auto-pass; Step 4 is the sole writer and depends on
this step.
SE-12 (F13/C11): the crossing left exactly one live-eval ledger line, and any auto-pass fired
only on a shape the policy lists in auto: (and not never_auto:) with no blocking finding.
Phase: Apply
Step 4 — Persist · Owner: play · Depends on: Step 3
First snapshot the live spine and the slice folder so Step 5 can verify (cp the spine to
<working>/spine-before.yaml; cp -R the slice folder to <working>/slice-before). Then persist
on the fixed allowlist — only this slice's architecture.md (re-derive) and decisions
(skip-if-exists):
python3 scripts/apply_arch.py --draft <working>/draft --product-base <product_base> --out-manifest <working>/apply-manifest.json
Step 5 — Verify persisted · Owner: play · Depends on: Step 4
Verify the persist was surgical — capture the checker's JSON output; its ok field is the stop
condition's D3 input:
python3 scripts/check_arch.py --cap-before <working>/slice-before --cap-dir <product_base>/product-os/<domain>/slices/<slice> --spine-before <working>/spine-before.yaml --spine-after <product_base>/product-os/_spine.yaml > <working>/arch-checks.json
SE-9 (F2/F9/C2/C9): the only file changed in the slice folder is lens/architecture.md
(decisions may be added, never edited in place); the spine — and with it the profile, the slice
record, and the other lenses — is byte-identical. Nothing outside the allowlist was written.
Phase: Scenario Validation
Step 6 — Scenario evals · Owner: play · Depends on: Step 5
- SCE-1 (S1 — architect):
architecture.md is a valid Architecture Lens doc clearing the
linter + the content eval, and the spine/slice/profile/other lenses are byte-identical.
- SCE-2 (S2 — build lead): every functionality the slice bundles maps to ≥1 component.
- SCE-3 (S3 — architect): every component traces to a functionality's system or a profile
surface; the stack to a decision that resolves.
- SCE-4 (S4 — reviewer): no measure or run lens was read or written.
- SCE-5 (S5 — architect, re-run): a re-run re-derives only
architecture.md; everything else
byte-identical; no accepted decision edited in place.
- SCE-6 (S6 — reviewer): the checkpoint showed the components, stack, and vertical build
inline, and no product-model file was written before approval — or, on the auto-pass path, the
change shape is policy-listed and a recorded auto-pass + live-eval ledger line + diff summary
exist, with no wait.
Phase: Evidence & Close
Step 7 — Close · Owner: play · Depends on: Step 6
Run the Standard Play Close. /arch is a slice-realize play — record evidence per the D1 rule.
SE-11 (F12/C12): the stop-condition verdict is held before the run closes COMPLETED; a close
over an unmet or unevaluable verdict reads HALTED, never COMPLETED (REC12).
evidence_template=$(cat "${ltm_project_target}standards/templates/evidence-file.md")
delivery_template=$(cat "${ltm_project_target}standards/templates/delivery-report.md")
ts=$(date -u +%Y%m%d-%H%M%S)
evidence_dest="${evidence_base}${ts}.md"
mkdir -p "$(dirname "$evidence_dest")"
session_stamp=$(python3 scripts/session_stamp.py --phase close \
--marker "${stm_base}_realize/arch/status/session-stamp-arch.json")
python3 scripts/check_stop_condition.py \
--manifest "<play-dir>/stop-condition.yaml" \
--base "${stm_base}_realize/arch/" \
--out "${stm_base}_realize/arch/status/stop-condition-arch.yaml"
sc_exit=$?
python3 scripts/distill_gate_policy.py \
--ledger "$(yq '.gates.conditional.ledger' .garura/core/config.yaml)" \
--policy "$(yq '.gates.conditional.policy' .garura/core/config.yaml)" \
--streak "$(yq '.gates.conditional.streak' .garura/core/config.yaml)" \
--project "$(yq '.project.name' .garura/core/config.yaml)" || true
/arch opened a slice-realize issue via start-change, so it is project-scoped:
evidence_base="${stm_base}${issue}/evidence/arch/" and slug="#${issue}".
Step C0 — bind the verdict. sc_exit == 0 (held) permits status: COMPLETED. Anything else
closes HALTED with exit_reason: stop_condition_unmet and the evidence's Stop Condition
section names every unmet clause — a run that never applied (checkpoint cancelled, validation
failed) is a HALTED run, not a done one; fix the state per REC12 (re-run the verify capture, or
complete the missing step) and re-evaluate; the close stays HALTED until the verdict reads held.
An unevaluable verdict is never a pass.
Step C1 — Write evidence file. Gated by the resolved evidence.record flag. When false, skip
and record evidence skipped (record=false). Otherwise fill the evidence-file.md slots (play
arch, run_id arch-${ts}, slice slug, started/completed, status per C0, exit_reason;
artifacts: the slice's architecture.md, the manifest, any decision, the stop-condition verdict;
the content-eval verdict; step + scenario evals SE-1…SE-12 / SCE-1…SCE-6; checkpoint decision
(incl. any gate skipped by config or gate auto-passed by learned policy row); the session identity stamp fields from $session_stamp
(#463): session_id, ledger_file, ledger_start_offset, ledger_end_offset (null when unresolved —
never blocks the close); and stop_condition per C0 with the Stop Condition section filled) and
write to $evidence_dest. Do NOT hand-author the body.
Step C2 — Render delivery report. Also render the Next line: resolve this play in standards/rules/pipeline-next.md and emit **Next:** /<command> — <why>. Or run /next to see all recommended actions. (only /next pointer, or omit, when the mapped command is null), per play-close.md. Fill the delivery-report.md slots: ## arch Delivered — ${slug}, the Run Summary table, the Pipeline Steps table, the Artifacts Produced table (the
architecture lens + any decision), Next Steps (run /quality on this slice), and a pointer to
$evidence_dest. Always emitted.
Scenario Validation
| Scenario | Persona | Eval |
|---|
| S1 — first run | architect | SCE-1 |
| S2 — end-to-end | build lead | SCE-2 |
| S3 — grounded | architect | SCE-3 |
| S4 — foundation discipline | reviewer | SCE-4 |
| S5 — re-run | architect | SCE-5 |
| S6 — the checkpoint | reviewer | SCE-6 |
Recovery
| For | Trigger | Direction | Handoff |
|---|
| F1 | the slice is absent, a functionality does not resolve, or the profile is not firmed | halt and route to /shape or /understand before /arch runs | human |
| F2 | a write touched something beyond this slice's architecture.md or a decision | revert the out-of-scope write; /arch writes only the slice's architecture.md and the stack decision | autonomous |
| F3 | architecture.md fails the template/shape or carries out-of-scope content | re-emit to the Architecture lens template (Intent/Components/Stack/Vertical build only) | autonomous |
| F4 | architecture.md fails the content-quality eval | rewrite the failing section to the judge's cited fixes and re-judge until the gate passes | autonomous |
| F5 | an invented/ungrounded component or stack pick | drop it, or re-tie the component to a functionality's system or a profile surface, and the stack to a decision | autonomous |
| F6 | a functionality threads through no component | add the component(s) that build the missing functionality, so the vertical is end-to-end | autonomous |
| F7 | /arch read or depended on the measure or run lens | remove the dependency; /arch derives from the hub and may read only the functional lenses | autonomous |
| F8 | the stack was set with no decision | record the slice-level stack decision (reuse the product one if it exists) | autonomous |
| F9 | a non-lens/non-decision file changed, or an accepted decision was edited in place | restore it and re-apply only architecture.md and the new decision, after a human confirms the restore | human |
| F10 | an architecture choice with no KB learning and no recorded proposal | search the KB via kb-search and ground the choice, or raise a KB-learning-gap proposal | autonomous |
| F11 | the lens was persisted before the checkpoint gate resolved | revert the premature write and re-present the checkpoint; persist only after the gate resolves (approval, a recorded config skip, or a recorded policy auto-pass) | human |
| F12 | the run is about to close COMPLETED with the Done means unmet | close HALTED (stop_condition_unmet) with the unmet clauses named; fix the state — re-run the verify capture, or complete the missing draft/apply step — and re-evaluate; the close stays HALTED until the verdict reads held | autonomous |
| F13 | a conditional-gate crossing left no live-eval ledger line, or an auto-pass fired for a shape the policy does not list as auto (or with a blocking finding) | re-append the missing ledger line via gate_eval.py; when the auto-pass was unearned, revert any premature persist and re-run the gate as a live wait | autonomous |
Pause and Resume
Steps run top to bottom. On entry, resolve config, resolve the target slice, check the status
marker, skip completed steps, reset any in-progress step to pending, and continue.
Compilation Metadata
| Field | Value |
|---|
| fingerprint | sha256:bf289863a14eb6d707d504f48fd0c9b139dae1f8a23a410d20b752afb3ecd340 (of reference/ice.md) |
| compiled_by | play-editor (#467 Batch B) |
| pipeline_position | start (non-functional pipe head; the non-functional pipe closes at /run) |
| workflow_structure | A (single checkpoint — class: standard, conditional learned gate per gate-config.md; stop-condition gated close) |
| stop_condition | stop-condition.yaml (D1–D3), gate live at Step C0 |
| domain_agents | 1 (product-os-keeper) |
| utility_agents | 0 |
| skills_used | kb-search, author-architecture-lens |
| scripts | 13 (preflight, check_ready_slice, lint_grounding, grounding_gate, validate_arch, check_kb_grounding, apply_arch, check_arch, check_stop_condition, session_stamp, classify_change + gate_eval + distill_gate_policy — #467 conditional gate) |
| step_evals | 12 (SE-1…SE-12) |
| scenario_evals | 6 (SCE-1…SCE-6) |
| recovery_entries | 13 (one per failure condition; 10 autonomous / 3 human) |