| name | shape |
| position | none |
| description | Select what to build in one domain and compose it into deliverable verticals — confirm or prune its capabilities against the firmed profile + KB, select which of the functionalities /understand created to build now, create the personas and user journeys, and bundle the functionalities into vertical slices, each behind a user-facing surface scaffolded slice by slice. The product-owner step of the ProductOS strategy pipeline, after /vision and /understand. Selects against the profile but never writes it; composes slices but never cuts epics or plans order. Opens no delivery issue. |
| user-invocable | true |
shape
Take one domain that /understand has detailed — capabilities detail: detailed with
their functionalities created, profile set — and select what to build and compose it
into deliverable verticals. /shape is the product owner: it confirms the capabilities
that stay (prunes the rest), selects which existing functionalities to build now, creates
the personas served and the user journeys they travel, and bundles those functionalities
into the domain's vertical slices, each behind a user-facing surface. It reads the
profile to judge fit but never writes it, it never creates a functionality or authors
ICE (/understand did), and it never cuts epics (/grill) or plans order (/roadmap). One
domain per run.
Always scaffold the UI. Every slice names at least one surface a persona opens and
checks, and that surface is a thin scaffold — this slice's piece of screen, not the whole
UI. The product surface accretes slice by slice.
Pipeline position: none. /shape is a MIDDLE play of the strategy pipeline (vision → understand → shape → roadmap): it expects to run on the branch /vision already started, injects no start-change head and no close sequence, stops when its work is done, and leaves the branch as-is for the next play to pick up. The close belongs to /roadmap. It writes the persistent product model directly, on the already-started branch. (#437)
Compiled From
This play was compiled from the shape ICE (reference/ice.md) by play-editor
(#466 Batch C, Level 3 rollout per ADR 025; #467 Batch B — the checkpoint upgraded to a
conditional learned gate, see standards/rules/gate-config.md). Intent defines
constraints (C1–C14) and failure conditions (F1–F15); the expectation defines success
scenarios (S1–S8), 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.
Role
You are the orchestrator. You own the workflow and the step order. You delegate the
domain work — the grounded selection-and-composition bundle — to the product-os-keeper
agent via a JSON contract over files on disk, and you run the mechanical work (readiness
gate, bundle validation, the allowlisted persist, and the post-apply spine diff) through
bundled scripts. You never write the model YAML yourself, you never create a functionality
or write the product profile, and you never persist before the single checkpoint (C12)
resolves — a typed approval, a recorded config skip, or a recorded policy auto-pass.
Forbidden: hand-writing slice/persona/journey/decision YAML or the spine; creating a
functionality or authoring ICE (that is /understand's); writing the product profile by any
route; reparenting, renaming, or deleting nodes; editing a capability beyond its status
flip; cutting epics or planning slice order; persisting by any route other than
scripts/apply_shape.py; persisting before Step 3 resolves; closing COMPLETED without
the stop-condition verdict reading held (C14/F14).
Agent boundaries:
| Agent | Domain | Skill it invokes | Phases |
|---|
product-os-keeper | Confirm/prune capabilities and select existing functionalities against the firmed profile + KB; create personas, user journeys, decisions; compose the domain's vertical slices behind scaffolded surfaces | search-kb, author-shape-bundle | Draft |
product-os-keeper is the single domain agent this play uses (1 of the ≤5
budget). No utility agents are needed — git/issue machinery is absent (position none).
Pre-flight
| Check | Constraint | Action on Failure |
|---|
Resolve config + product_base (.garura/core/config.yaml) | — | Hard halt |
Profile firmed (set) AND target capabilities detail: detailed with functionalities | C1 | Hard halt (REC1) |
Resolve config mechanically with the bundled resolver; /shape has no branch or issue
(position none):
python3 scripts/preflight.py --play shape --config .garura/core/config.yaml
python3 scripts/check_ready.py --spine <product_base>/product-os/_spine.yaml --domain <domain-id>
preflight.py returns config facts (product_base, stm_base, evidence_record).
check_ready.py is the readiness gate (C1): it halts unless the product profile is set
(firmed by /understand) and every target capability in the domain is detail: detailed
with at least one functionality. The target domain is the play argument (e.g.
/shape token-dash). If the profile is still directional or a capability is not yet
detailed, hard halt and route to /understand (REC1).
The run's working root (<working> below) is {stm_base}_shaping/shape/<domain>/ — the
routing, the draft, the applied record (apply-manifest.json), and the captured
post-apply checks (shape-checks.json) all live under it; the stop condition evaluates
against it. Status markers live at {stm_base}_shaping/shape/status/.
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}_shaping/shape/status/session-stamp-shape.json" \
--cwd "$(pwd)" --branch "$(git branch --show-current)"
Resume check: if {stm_base}_shaping/shape/status/<domain>.json exists, resume —
skip completed steps, reset any in-progress step to pending, continue from the first
incomplete.
Task DAG
Create ALL tasks immediately after resolving config — before any domain work.
The play owns this DAG; the agent must not edit its top-level tasks.
[T1] Draft selection bundle blockedBy: []
[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.
No runtime reordering. On resume, skip completed and reset in-progress to pending.
Workflow
Phase: Draft
Step 1 — Draft selection bundle · Owner: product-os-keeper · Depends on: pre-flight
The persisted model does not record which KB shelf a domain came from, and the product
domain slug is not the KB shelf name. So the agent first recovers the shelf with
search-kb, then invokes author-shape-bundle to draft the domain's selection-and-
composition bundle to STM — confirm/prune per capability, select which existing
functionalities to build, the personas, the user journeys through surfaces, the
decisions, AND the domain's vertical slices (each naming at least one user-facing
surface a named persona opens and checks, bundling functionalities by spine id, plus the
_deferred bucket), with stable ids:
{
"task": "recover this domain's KB shelf via search-kb, then SELECT among the functionalities /understand already created against the firmed profile + that shelf, and COMPOSE the domain's vertical slices. Never create a functionality or author ICE. Every slice MUST name >=1 user-facing surface (a thin UI scaffold a named persona opens and checks) — a backend-only/horizontal/whole-UI slice is invalid. Journeys are user journeys ON named surfaces. Name surfaces; never design them. Every selected functionality in a slice or _deferred; slices reference functionalities by spine id; no order/effort/depends_on",
"inputs": { "domain": "<id + slug + path under product-os>",
"product_base": "<product_base>" },
"outputs": { "routing": "<working>/routing.yaml",
"draft_dir": "<working>/draft/",
"manifest": "<working>/draft/shape-manifest.yaml",
"slices": "<working>/draft/product-os/<domain>/slices/" }
}
The agent passes the shelf search-kb resolved to author-shape-bundle as its kb_shelf.
It returns the contract with the output paths on disk — never inline content. The skill
reads the live spine read-only and writes only the draft.
SE-1 (F1/C1): the readiness gate passed at pre-flight — the profile is set and the
target capabilities are detail: detailed with functionalities; otherwise the run halted
(REC1).
Phase: Validate
Step 2 — Validate the draft · Owner: play · Depends on: Step 1
Run the bundle validator over the draft before the checkpoint:
python3 scripts/validate_shape.py --draft <working>/draft \
--manifest <working>/draft/shape-manifest.yaml --spine <product_base>/product-os/_spine.yaml
SE-2 (F2/C2): the draft selects and composes only — it created no functionality and
authored no ICE, and wrote no profile (the draft holds only slices, personas, journeys,
decisions, and the spine delta of status flips + slice index).
SE-3 (F3/C3): grounding holds — every kept capability and selected functionality in the
manifest carries a KB shelf or a recorded proposal; none is invented.
SE-4 (F5/C5): schema + integrity — personas, journeys, decisions, and slices carry
their required fields; every slice functionality_ref resolves to a real functionality in
the live spine; every journey persona and surface reference resolves.
SE-5 (F6/C6): placement holds — every selected functionality appears in a slice or the
_deferred bucket; validate_shape.py reports no unplaced functionality.
SE-6 (F7/C7): every slice is a user-facing scaffold — it names at least one surface
(name + persona + user_action), and is no backend-only, one-per-capability horizontal, or
whole-UI slice.
SE-7 (F8/C8): surfaces are named, not designed — no slice surface carries wireframe,
component, layout, or visual-design content.
SE-8 (F9/C9): journeys are user journeys through surfaces — each traverses at least one
named surface, none is a backend pipeline, and every surface a slice names is reached by a
journey.
SE-9 (F10/C10): slices reference functionalities by spine id (not copied) and carry no
order, effort, or resolved depends_on.
SE-10 (F11/C11): a decision is recorded for every prune and every material selection.
On any GAP, apply the matching recovery (REC2–REC11) and re-run before the checkpoint.
Phase: Checkpoint (conditional gate, C12)
Step 3 — Human review (class: standard, conditional) · Owner: play · Depends on: Step 2
This checkpoint is a conditional gate (#467) per standards/rules/gate-config.md —
/shape is one of the eleven conditional document plays. Resolve it first match wins:
pinned (n/a here) → gates.plays.shape → the learned policy → gates.classes.standard
→ gates.default (absent ⇒ on). For the policy lookup, classify the draft-vs-live
change shape first:
python3 scripts/classify_change.py --play shape --draft <working>/draft \
--live <product_base> --out <working>/shape.json
Look the shape key up in the config-resolved policy (gates.conditional.policy):
auto-pass iff the shape is in the policy's auto: block AND not in never_auto:
AND Step 2 stands with no blocking finding (a validate_shape.py gap — /shape's
lint-equivalent guard). On auto-pass, do NOT wait: record
gate auto-passed by learned policy (shape: <shape-key>, policy v<version>) as a
Checkpoint Decisions row, include the draft's diff summary in the run record, append
the crossing's live-eval ledger line, and proceed to Step 4:
python3 scripts/gate_eval.py append --ledger <gates.conditional.ledger> --play shape \
--issue <strategy issue> --shape <shape-key> --predicted auto --human auto_pass \
--policy-version <policy version> --ts <run ts>
Anything else resolves the gate on (an explicit gates.plays.shape: off instead
records gate skipped by config (<resolution path>) as a Checkpoint Decisions row and
proceeds). When on, present the domain's selection bundle inline — the kept
capabilities, the pruned capabilities (each with its reason), the selected
functionalities, the personas, the journeys, AND the vertical slices (each led by the
user-facing surface it exposes, with its outcome and the functionalities it bundles)
plus the _deferred bucket. Say plainly that the surface's design — and the slices'
order, effort, and dependencies — come later (/realize designs the surface; /roadmap
plans the slices; /grill cuts the epics), not here. Render the approval prompt
(standards/templates/approval-prompt.md) and wait for the typed response. Approve →
continue to persist; cancel → halt with nothing written to the model. Then append the
crossing's live-eval ledger line with the human's real action:
python3 scripts/gate_eval.py append --ledger <gates.conditional.ledger> --play shape \
--issue <strategy issue> --shape <shape-key> --predicted gate \
--human <approved_clean|approved_edited|rejected> --ts <run ts>
<strategy issue> is the strategy-pipeline issue the run's branch carries (opened by
/vision's start-change). <gates.conditional.ledger> / <gates.conditional.policy>
resolve from config gates.conditional (defaults .garura/core/gate-evals.jsonl /
.garura/core/gate-policy.yaml); <policy version> is the policy file's version:
field. <run ts> is the run's own UTC timestamp, derived the same way the close
derives ts (date -u), passed by the orchestrator.
SE-11 (F12/C12): the selection 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; no product-model file is written before Step 4.
SE-15 (F15): every crossing of this gate appended exactly one live-eval ledger line
(shape, predicted gate|auto, the human's real action or auto_pass), and an
auto-pass fired only for a shape the policy lists in auto: (and not in never_auto:)
with no blocking finding standing.
Phase: Apply
Step 4 — Persist · Owner: play · Depends on: Step 3
First snapshot the live spine so Step 5 can diff it:
cp <product_base>/product-os/_spine.yaml <working>/spine-before.yaml
Then persist on the fixed allowlist. apply_shape.py merges the spine delta — it flips
only the status field on the named capabilities (read-modify-write), adds the slice index
entries skip-if-exists, and attaches persona/journey/decision refs onto the capabilities —
and copies the slice/persona/journey/decision records skip-if-exists. It is never handed the
profile and never touches a functionality:
python3 scripts/apply_shape.py --draft <working>/draft --product-base <product_base> \
--manifest <working>/draft/shape-manifest.yaml --out-manifest <working>/apply-manifest.json
Phase: Verify
Step 5 — Verify persisted · Owner: play · Depends on: Step 4
Diff the before/after spine and check the written decisions — capture the checker's JSON
output; its ok field is the stop condition's D3 input:
python3 scripts/check_shape.py --apply-manifest <working>/apply-manifest.json \
--shape-manifest <working>/draft/shape-manifest.yaml \
--spine-before <working>/spine-before.yaml \
--spine-after <product_base>/product-os/_spine.yaml --domain <domain-id> \
> <working>/shape-checks.json
SE-12 (F4/C4): each flipped capability differs from its before-snapshot in the status
field only — a prune is deprecated, never a delete; no reparent, rename, create/delete, or
other out-of-scope capability edit.
SE-13 (F13/C13): the spine diff is confined to /shape's allowlist — capability status
flips, new slices, and new persona/journey/decision refs; no functionality, no domain, and
no profile field changed, and a re-run added no duplicate record.
SE-14 (F14/C14): the close is stop-condition gated — check_stop_condition.py over the
baked stop-condition.yaml (D1 the applied record apply-manifest.json exists; D2 it
stamps applied: true; D3 the captured shape-checks.json reads ok: true) must read
held before any COMPLETED close; a run whose persist or verification did not land
closes HALTED, never COMPLETED (REC14).
Phase: Scenario Validation
Step 6 — Scenario evals · Owner: play · Depends on: Step 5
- SCE-1 (S1 — product owner, select + compose): kept capabilities are
active, the
selected (already-existing) functionalities are placed into slices, personas and journeys
are created and schema-valid, no functionality was created or changed, and the
stop-condition verdict reads held.
- SCE-2 (S2 — architect, grounding): every kept capability and selected functionality
traces to a KB shelf or a recorded proposal in the manifest.
- SCE-3 (S3 — product owner, prune is soft): a pruned capability is
deprecated with a
recorded decision and still present — not deleted.
- SCE-4 (S4 — reviewer, box and functionalities untouched): the profile is byte-identical
before and after, and the spine diff shows no change to any functionality or domain.
- SCE-5 (S5 — delivery lead, slices reference by id): each slice has a name, outcome,
acceptance intent, and functionalities referenced by a resolvable spine id, with no copied
content and no
order/effort/depends_on.
- SCE-6 (S6 — planner, full coverage): the union of all slices'
functionality_refs and
the _deferred bucket equals the selected functionalities — none unplaced.
- SCE-7 (S7 — product owner, every slice is a user-facing scaffold): every slice names at
least one surface (name + persona + user_action) with no design content; zero surface-less
slices, no one-per-capability horizontal slice; every named surface is reached by a journey.
- SCE-8 (S8 — reviewer, the checkpoint + re-run): the checkpoint showed kept/pruned
capabilities, functionalities, personas, journeys, and the slices + deferred bucket inline
before any write — or, on the auto-pass path (a policy-listed shape), the gate resolved
with no wait and the recorded auto-pass, the appended ledger line, and the diff summary
stand in the approval's place; a re-run added no duplicate persona, journey, or slice.
Phase: Evidence & Close
Step 7 — Close · Owner: play · Depends on: Step 6
Run the Standard Play Close. /shape is a product-scoped play (no issue) — use the
product-scoped evidence base and slug. Evidence recording is play-only and config-gated
per the D1 evidence rule (standards/rules/evidence-recording.md).
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}_shaping/shape/status/session-stamp-shape.json")
python3 scripts/check_stop_condition.py \
--manifest "<play-dir>/stop-condition.yaml" \
--base "${stm_base}_shaping/shape/<domain>/" \
--out "${stm_base}_shaping/shape/status/stop-condition-shape.yaml"
sc_exit=$?
python3 scripts/distill_gate_policy.py --ledger "<gates.conditional.ledger>" --policy "<gates.conditional.policy>" --streak <gates.conditional.streak> --project "<project name from config>" || true
/shape is product-scoped: evidence_base="${product_base}_evidence/shape/" and
slug="${product_slug}" (the domain slug, e.g. token-dash).
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 — fix the state per REC14 (re-run the
allowlisted persist, or re-capture the post-apply verification) 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 (global
- per-play
evidence.plays.shape; first match wins, absent ⇒ record). When false, skip
the write and record evidence skipped (record=false) in the report's pointer line.
Otherwise fill the evidence-file.md slots (play shape, run_id shape-${ts},
product_slug, started_at/completed_at, status per C0, exit_reason; artifacts produced: the
capability status flips, the selected functionalities, personas, journeys, decisions, the
vertical slices (each with its user-facing surface) + deferred bucket, the shape + apply
manifests, the captured shape-checks.json, the stop-condition verdict; step and scenario
eval results SE-1…SE-15 / SCE-1…SCE-8; checkpoint decision from Step 3 including
kept/pruned capabilities and the slices (or the gate skipped by config /
gate auto-passed by learned policy row when the gate resolved without a wait) plus the
gate ledger line(s) appended this run; 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 and output the
report: ## shape Delivered — ${product_slug}, the Run Summary table, the Pipeline
Steps table from the task DAG, the Artifacts Produced table (status flips, selected
functionalities, personas, journeys, decisions, the vertical slices + deferred bucket),
Next Steps (run /roadmap to plan the slices — order, dependencies, effort — across
domains; then /grill cuts a chosen slice into epics), and a pointer to $evidence_dest.
Always emitted; never gated.
Scenario Validation
| Scenario | Persona | Eval |
|---|
| S1 — select + compose | product owner | SCE-1 |
| S2 — grounding | architect | SCE-2 |
| S3 — prune is soft | product owner | SCE-3 |
| S4 — box + functionalities untouched | reviewer | SCE-4 |
| S5 — slices reference by id | delivery lead | SCE-5 |
| S6 — full coverage | planner | SCE-6 |
| S7 — every slice is a user-facing scaffold | product owner | SCE-7 |
| S8 — the checkpoint + re-run | reviewer | SCE-8 |
Recovery
| For | Trigger | Direction | Handoff |
|---|
| F1 | the profile is directional, or a target capability is not detailed, at start | halt and route to /understand to firm the profile and detail the capabilities first | human |
| F2 | /shape created a functionality, authored ICE, or wrote the profile | strip the over-reach — remove the created functionality/ICE, revert the profile write; detailing and the box are /understand's | autonomous |
| F3 | a selected functionality or kept capability has no KB shelf match and no proposal | ground it against the KB, or record a propose-kb-node proposal; never keep an invented selection | autonomous |
| F4 | a capability edited beyond status, a reparent/rename, a create/delete, or a hard-deleted prune | revert the out-of-scope mutation; /shape flips capability status only and prunes soft (deprecated) | autonomous |
| F5 | a persona/journey/decision/slice fails its schema, or a slice/journey reference does not resolve | re-emit the failing record to conform, and fix the reference to a real spine functionality/persona/surface | autonomous |
| F6 | a selected functionality is in neither a slice nor the _deferred bucket | place it — add it to a slice, or record it in _deferred with a reason — before persisting | autonomous |
| F7 | a surface-less/horizontal slice, or a "whole UI at once" slice | re-cut the slice vertically toward one surface a persona opens and checks, as a thin scaffold; fold or defer functionalities that serve no surface | autonomous |
| F8 | /shape designed a surface (wireframe/component/layout/visual) instead of naming it | strip the design to the surface's name, persona, and user action; design is /realize's UX lens | autonomous |
| F9 | a journey is not a user journey on a surface, or a named surface no journey reaches | rewrite the journey as the persona's steps on a named surface; ensure every slice surface is reached by a journey | autonomous |
| F10 | a slice copied a functionality's content, or carries order/effort/depends_on | replace copied content with a spine functionality_ref, and strip the plan fields — /roadmap plans | autonomous |
| F11 | a prune or material selection with no decision record | write the decision (ADR) for each before persisting | autonomous |
| F12 | the selection persisted with no checkpoint approval | revert the premature write and re-present the checkpoint; persist only after the human approves | human |
| F13 | a functionality/domain/profile changed, or a re-run duplicated a record | restore the changed artifact and re-run writing only /shape's allowlist, de-duplicating by stable id, after a human confirms the restore | human |
| F14 | 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 allowlisted persist, or re-capture the post-apply verification — and re-evaluate; the close stays HALTED until the verdict reads held | autonomous |
| F15 | 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 that carried a blocking finding) | re-append the missing ledger line for the recorded crossing; when the auto-pass was unearned, re-run the gate as a live wait — render the approval prompt and wait for the typed response — before proceeding | autonomous |
Pause and Resume
Steps run top to bottom. On entry, resolve config, resolve the target domain from the
play argument or the in-progress draft, check the status marker, skip completed steps,
reset any in-progress step to pending, and continue. A fresh start with no marker runs
everything and creates the marker at Step 1.
Compilation Metadata
| Field | Value |
|---|
| fingerprint | sha256:ee85b09e1a32fc99112f8d51a65f2f79ed142f89124e50854d4fd489109ddd4f (of reference/ice.md) |
| compiled_by | play-editor (#467 Batch B); prior: play-editor (#466 Batch C) |
| pipeline_position | none |
| position_exception | middle of the strategy pipeline — runs on the branch /vision started; the close belongs to /roadmap (#437) |
| workflow_structure | A (single checkpoint — class: standard, conditional gate per gate-config.md #467; 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 | search-kb, author-shape-bundle |
| scripts | 10 (preflight.py, check_ready.py, validate_shape.py, apply_shape.py, check_shape.py, classify_change.py, gate_eval.py, distill_gate_policy.py, check_stop_condition.py, session_stamp.py) |
| step_evals | 15 (SE-1…SE-15) |
| scenario_evals | 8 (SCE-1…SCE-8) |
| recovery_entries | 15 (one per failure condition; 12 autonomous / 3 human) |
Recompiled note (#467 Batch B): checkpoint upgraded to a conditional learned gate;
see gate-config.md.