| name | ux |
| position | start |
| description | Write a SLICE's UX lens as a grounding doc (ux.md) — the screens (each with a low-fidelity layout) that make the slice's functionalities visible, the states each holds, and the product's visual core (color + typography). The START of the FUNCTIONAL realize pipe (ux → agentic → marketing), run on a shaped slice. Accessibility is not here (it lives in the marketing lens); flows are the build's to derive. Reads the hub from the spine (functionality grounding + profile), never another lens. Writes only the slice's ux lens. |
| user-invocable | true |
ux
Write a shaped slice's UX lens as the grounding doc ux.md: the screens that make every
functionality visible, the states each holds, and the product's visual core. Just enough to
anchor the intended experience and let the build figure the rest. /ux reads the slice's hub
— its functionalities' grounding docs plus the profile (both from the spine) — and never another
realize lens.
Pipeline position: start. /ux OPENS the functional realize pipe (ux → agentic → marketing):
the D2 rule prepends start-change — resolve or create the slice-realize issue, cut the branch
off fresh main, optional worktree, init STM — so /agentic and /marketing run on this
already-started branch. No close sequence is injected here; the functional pipe closes at
/marketing. It writes the persistent product model (the slice's ux lens) on the started branch.
Compiled From
This play was compiled from the ux ICE (reference/ice.md) by play-editor (#466 Batch C; #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. (#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 UX 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
ux.md and a decision (C2); reading or grounding on another realize lens (C7); persisting by any
route other than scripts/apply_ux.py; persisting before the checkpoint gate resolves; closing
COMPLETED without the stop-condition verdict held (C12).
Agent boundaries:
| Agent | Domain | Skill it invokes | Phases |
|---|
product-os-keeper | Author the slice's UX lens (screens + states + visual core) from the hub + KB pattern grounding | kb-search, author-ux-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 ux --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 is <working> = {stm_base}_realize/ux/ — the draft, snapshots, apply
manifest, and status markers all live under it (the stop-condition gate 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/ux/status/session-stamp-ux.json" \
--cwd "$(pwd)" --branch "$(git branch --show-current)"
Resume check: if {stm_base}_realize/ux/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. /agentic and /marketing run
on this branch; /marketing closes it.
{
"play": "start-change",
"parent_run_id": "<this run id>",
"inputs": { "title": "<realize ux: the slice>" },
"outputs": { "result": "{stm_base}_realize/ux/start/start-change.json" }
}
Phase: Draft
Step 1 — Draft the lens · Owner: product-os-keeper · Depends on: Step 0
The agent invokes author-ux-lens to draft the slice's ux.md (screens + states + visual core,
per the UX lens template) from the hub (the functionality grounding docs + the profile) and KB
pattern grounding, plus a ux-manifest.yaml (the grounding map) and the visual-core decision:
{
"task": "author the slice's UX lens (screens/states/visual core) from its hub; ground screens to functionalities and the visual core 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/ux.md" },
"outputs": { "draft_dir": "<working>/draft/", "manifest": "<working>/draft/ux-manifest.yaml" }
}
The skill reads the hub read-only and writes only the draft (the ux.md, the manifest, the
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/ux.md
python3 scripts/validate_ux.py --draft <working>/draft --manifest <working>/draft/ux-manifest.yaml --slice-file <product_base>/<slice_file>
python3 scripts/check_kb_grounding.py --manifest <working>/draft/ux-manifest.yaml --kb-root <kb_root> --proposals-dir <working>/draft/proposals
Then run the content-quality eval over ux.md: spawn an isolated, clean-context sub-agent
handed the judge prompt (standards/rules/grounding-eval.md), the doc, and the UX 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 — ux.md conforms to the UX lens template
(Intent/Screens/States/Visual core), no missing/extra/empty section.
SE-3 (F4/C4): the content-quality eval gate (grounding_gate.py) passes — ux.md is
self-explaining and clears the stranger test.
SE-4 (F5/C5): validate_ux.py — every screen in the manifest grounds to a functionality or a
persona/journey; the visual core grounds to the KB or a decision.
SE-5 (F6/C6): validate_ux.py — every functionality the slice bundles is visualized by at
least one screen (coverage).
SE-6 (F7/C7): validate_ux.py — no screen grounds on another realize lens.
SE-7 (F8/C8): validate_ux.py — the visual core names a decision that resolves.
SE-8 (F10/C10): check_kb_grounding.py exits 0 — the visual core, navigation, and responsive
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
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; /ux is one of the eleven
conditional document plays). Resolve, first match wins: pinned (n/a here) → gates.plays.ux →
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 ux --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 ux --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 screens (with layouts), states,
and visual core 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 ux.md (re-derive) and decisions (skip-if-exists):
python3 scripts/apply_ux.py --draft <working>/draft --product-base <product_base> --out-manifest <working>/apply-manifest.json
The apply manifest carries the machine applied field the close's stop-condition gate reads (#464):
lens_applied: true (ux.md landed in the model tree).
Step 5 — Verify persisted · Owner: play · Depends on: Step 4
Verify the persist was surgical:
python3 scripts/check_ux.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
SE-9 (F2/F9/C2/C9): the only file changed in the slice folder is lens/ux.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.
SE-11 (F12/C12): the close is stop-condition gated — check_stop_condition.py over the baked
stop-condition.yaml (D1–D3: draft lens, grounding manifest, lens_applied) reads held before
the run may close COMPLETED; anything else closes HALTED with the unmet clauses named (REC12).
Phase: Scenario Validation
Step 6 — Scenario evals · Owner: play · Depends on: Step 5
- SCE-1 (S1 — product designer):
ux.md is a valid UX Lens doc clearing the linter + the
content eval, and the spine/slice/profile/other lenses are byte-identical.
- SCE-2 (S2 — product owner): every functionality the slice bundles maps to ≥1 screen.
- SCE-3 (S3 — ux researcher): every screen traces to a functionality or persona/journey; the
visual core to a decision that resolves.
- SCE-4 (S4 — architect): no other realize lens was read or written.
- SCE-5 (S5 — product owner, re-run): a re-run re-derives only
ux.md; everything else
byte-identical; no accepted decision edited in place.
- SCE-6 (S6 — reviewer): the checkpoint showed the screens, states, and visual core 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. /ux is a slice-realize play — record evidence per the D1 rule.
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/ux/status/session-stamp-ux.json")
python3 scripts/check_stop_condition.py \
--manifest "<play-dir>/stop-condition.yaml" \
--base "${stm_base}_realize/ux/" \
--out "${stm_base}_realize/ux/status/stop-condition-ux.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
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. An unevaluable verdict is never a pass.
/ux opened a slice-realize issue via start-change, so it is project-scoped:
evidence_base="${stm_base}${issue}/evidence/ux/" and slug="#${issue}".
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
ux, run_id ux-${ts}, slice slug, started/completed, status per C0, exit_reason; artifacts: the
slice's ux.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: ## ux Delivered — ${slug}, the Run Summary table (incl. the stop-condition verdict), the Pipeline Steps table, the
Artifacts Produced table (the ux lens + any decision), Next Steps (run /agentic on this slice), and
a pointer to $evidence_dest. Always emitted.
Scenario Validation
| Scenario | Persona | Eval |
|---|
| S1 — first run | product designer | SCE-1 |
| S2 — validates the shape | product owner | SCE-2 |
| S3 — grounded | ux researcher | SCE-3 |
| S4 — hub-only | architect | SCE-4 |
| S5 — re-run | product owner | 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 /ux runs | human |
| F2 | a write touched something beyond this slice's ux.md or a decision | revert the out-of-scope write; /ux writes only the slice's ux.md and the visual-core decision | autonomous |
| F3 | ux.md fails the template/shape or carries out-of-scope content | re-emit to the UX lens template (Intent/Screens/States/Visual core only) | autonomous |
| F4 | ux.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 element | drop it, or re-tie the screen to a functionality or persona/journey, and the visual core to a decision | autonomous |
| F6 | a functionality is covered by no screen | add the screen(s) that visualize the missing functionality | autonomous |
| F7 | /ux read or depended on another lens | remove the dependency; /ux derives only from the slice's hub | autonomous |
| F8 | the visual core was set with no decision | record the slice-level visual-core 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 ux.md and the new decision, after a human confirms the restore | human |
| F10 | a UX pattern 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 | produce the missing artifact — re-run the failed step, or re-run apply_ux.py so the apply manifest carries the machine field — then re-evaluate the stop condition; 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:d52bdd45deebe0217502f49c8443dd54b692b84f7253fbf5123ef9a7cc750048 (of reference/ice.md) |
| compiled_by | play-editor (#467 Batch B) |
| pipeline_position | start (functional pipe head; the functional pipe closes at /marketing) |
| workflow_structure | A (single checkpoint — class: standard, conditional learned gate per gate-config.md; 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-ux-lens |
| scripts | 13 (preflight, check_ready_slice, lint_grounding, grounding_gate, validate_ux, check_kb_grounding, apply_ux, check_ux, check_stop_condition — Done-means gate, session_stamp — #463 identity 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) |