| name | marketing |
| position | end |
| description | Write a SLICE's marketing lens as a grounding doc (marketing.md) — how the slice is found and reached (SEO / AEO / GEO), the accessibility bar it meets, and the reach signals worth capturing. The END of the FUNCTIONAL realize pipe (ux → agentic → marketing): it closes the functional pipe (commit → propose → review → merge). An internal tool answers discoverability "not applicable", plainly. Reads the hub from the spine (functionality grounding + profile), never another lens. Writes only the slice's marketing lens. |
| user-invocable | true |
marketing
Write a shaped slice's marketing lens as the grounding doc marketing.md: how the slice is
found and reached, the accessibility bar it meets, and the reach signals worth capturing. The lens
is four things and only four — intent, discoverability (SEO / AEO / GEO), accessibility (moved here
from the profile), and marketing analytics. An internal tool behind auth answers discoverability
"not applicable", plainly and with the reason. /marketing reads the slice's hub — its
functionalities' grounding docs plus the profile (both from the spine) — and never another realize
lens.
Pipeline position: end. /marketing is the END of the functional realize pipe (ux → agentic →
marketing): it runs on the branch /ux already started, injects no start-change head, and — after
the lens is persisted and verified — injects the close sequence commit-change → propose-change → review-change → merge-change, committing the functional pipe's lenses, opening the PR, taking the
verdict, and merging to main. It writes the persistent product model (the slice's marketing lens) on
the already-started branch. (#437; 3-pipe realize 2026-06-26)
Compiled From
This play was compiled from the marketing ICE (reference/ice.md) by play-editor (#466 Batch C;
#467 Batch B). Intent defines constraints (C1–C13) and failure conditions (F1–F14); 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 marketing 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 (C12).
Forbidden: hand-writing the lens or a decision; writing anything other than this slice's
marketing.md and a decision (C2); reading or grounding on another realize lens (C7); manufacturing
public-marketing reach where the functionalities and profile don't warrant it (C8); deferring
accessibility to the profile (C9); persisting by any route other than scripts/apply_marketing.py;
persisting before the checkpoint gate resolves; closing COMPLETED without the stop-condition
verdict held (C13).
Agent boundaries:
| Agent | Domain | Skill it invokes | Phases |
|---|
product-os-keeper | Author the slice's marketing lens (intent / discoverability / accessibility / analytics) from the hub + KB pattern grounding | kb-search, author-marketing-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 marketing --config .garura/core/config.yaml
Then resolve the slice and its hub from the spine — the readiness gate 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/marketing/ — 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/marketing/status/session-stamp-marketing.json" \
--cwd "$(pwd)" --branch "$(git branch --show-current)"
Resume check: if {stm_base}_realize/marketing/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.
[T1] Draft the lens blockedBy: []
[T2] Validate the draft blockedBy: [T1]
[T3] Checkpoint (approval) blockedBy: [T2]
[T4] Persist blockedBy: [T3]
[T5] Verify persisted blockedBy: [T4]
[TE1] commit-change (injected — end #1) blockedBy: [T5]
[TE2] propose-change (injected — end #2) blockedBy: [TE1]
[TE3] review-change (injected — end #3) blockedBy: [TE2]
[TE4] merge-change (injected — end #4) blockedBy: [TE3]
[T6] Scenario Validation blockedBy: [TE4]
[T7] Close blockedBy: [T6]
Mark each task in-progress before its step and completed right after its eval passes.
Workflow
Phase: Draft
Step 1 — Draft the lens · Owner: product-os-keeper · Depends on: pre-flight
The agent invokes author-marketing-lens to draft the slice's marketing.md (intent /
discoverability / accessibility / marketing analytics, per the Marketing lens template) from the hub
(the functionality grounding docs + the profile) and KB pattern grounding, plus a
marketing-manifest.yaml (the grounding map) and any material marketing decision:
{
"task": "author the slice's marketing lens (intent/discoverability/accessibility/analytics) from its hub; ground the reach in the functionalities + profile and the patterns in the KB",
"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/marketing.md" },
"outputs": { "draft_dir": "<working>/draft/", "manifest": "<working>/draft/marketing-manifest.yaml" }
}
The skill reads the hub read-only and writes only the draft (the marketing.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/marketing.md
python3 scripts/validate_marketing.py --draft <working>/draft --manifest <working>/draft/marketing-manifest.yaml --slice-file <product_base>/<slice_file>
python3 scripts/check_kb_grounding.py --manifest <working>/draft/marketing-manifest.yaml --kb-root <kb_root> --proposals-dir <working>/draft/proposals
Then run the content-quality eval over marketing.md: spawn an isolated, clean-context sub-agent
handed the judge prompt (standards/rules/grounding-eval.md), the doc, and the Marketing 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 — marketing.md conforms to the Marketing lens template
("Intent", "Discoverability", "Accessibility", "Marketing analytics"), no missing/extra/empty section.
SE-3 (F4/C4): the content-quality eval gate (grounding_gate.py) passes — marketing.md is
self-explaining and clears the stranger test.
SE-4 (F5/C5): validate_marketing.py — the reach assessment grounds in the slice's
functionalities and the profile; any material marketing choice names a decision that resolves.
SE-5 (F6/C6): validate_marketing.py — the assessment considers every functionality the slice
bundles (coverage).
SE-6 (F7/C7): validate_marketing.py — the assessment grounds on no other realize lens.
SE-7 (F8/C8): validate_marketing.py — reach is honest: a slice with no public surface answers
discoverability "not applicable", and public-marketing reach is not manufactured.
SE-8 (F9/C9): validate_marketing.py — accessibility is set concretely in the lens (a bar + the
means), not deferred to the profile or omitted.
SE-9 (F11/C11): check_kb_grounding.py exits 0 — the discoverability/accessibility patterns trace
to a KB learning or a recorded proposal.
On any GAP, apply the matching recovery (REC3–REC11) 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 (C12) — the agent never skips it on its own judgment. It is a
conditional gate per standards/rules/gate-config.md (#467; /marketing is one of the eleven
conditional document plays). Resolve, first match wins: pinned (n/a here) →
gates.plays.marketing → 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 marketing --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 marketing --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 intent, discoverability,
accessibility, and analytics inline, plus any 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-11 (F12/C12): 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-13 (F14/C12): 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 marketing.md (re-derive) and decisions (skip-if-exists):
python3 scripts/apply_marketing.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 (marketing.md landed in the model tree).
Step 5 — Verify persisted · Owner: play · Depends on: Step 4
Verify the persist was surgical:
python3 scripts/check_marketing.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-10 (F2/F10/C2/C10): the only file changed in the slice folder is lens/marketing.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-12 (F13/C13): 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 (REC13).
Phase: End sequence (injected — D2 position: end)
After the lens is persisted and verified, the D2 rule injects the close sequence — each a sub-play
dispatched with parent_run_id, resolving its own context from the branch + config — to commit the
functional pipe's lenses, raise the PR, take the verdict, and merge to main.
Step E1 — commit-change · blockedBy: Step 5
{ "play": "commit-change", "parent_run_id": "<this run id>", "inputs": {}, "outputs": { "result": "{stm_base}_realize/marketing/end/commit-change.json" } }
Step E2 — propose-change · blockedBy: E1
{ "play": "propose-change", "parent_run_id": "<this run id>", "inputs": {}, "outputs": { "result": "{stm_base}_realize/marketing/end/propose-change.json" } }
Step E3 — review-change · blockedBy: E2
{ "play": "review-change", "parent_run_id": "<this run id>", "inputs": {}, "outputs": { "result": "{stm_base}_realize/marketing/end/review-change.json" } }
Step E4 — merge-change · blockedBy: E3
{ "play": "merge-change", "parent_run_id": "<this run id>", "inputs": {}, "outputs": { "result": "{stm_base}_realize/marketing/end/merge-change.json" } }
Each end member owns its own evals (commit grouped by concern, PR opened, verdict posted, branch
merged + cleaned); they are not re-checked here. A review-change reject stops the chain before merge.
Phase: Scenario Validation
Step 6 — Scenario evals · Owner: play · Depends on: the end sequence
- SCE-1 (S1 — growth lead, first run):
marketing.md is a valid Marketing Lens doc clearing the
linter + the content eval, and the spine/slice/profile/other lenses are byte-identical.
- SCE-2 (S2 — product owner, honest reach): an internal tool's discoverability reads "not
applicable" with the reason rather than an invented SEO/AEO/GEO claim.
- SCE-3 (S3 — accessibility reviewer): accessibility names a concrete bar and how the slice meets
it.
- SCE-4 (S4 — architect, hub-only): no other realize lens was read or written.
- SCE-5 (S5 — product owner, re-run): a re-run re-derives only
marketing.md; everything else
byte-identical; no accepted decision edited in place.
- SCE-6 (S6 — reviewer, the checkpoint): the checkpoint showed the lens 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. /marketing 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/marketing/status/session-stamp-marketing.json")
python3 scripts/check_stop_condition.py \
--manifest "<play-dir>/stop-condition.yaml" \
--base "${stm_base}_realize/marketing/" \
--out "${stm_base}_realize/marketing/status/stop-condition-marketing.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.
/marketing runs on the slice-realize issue /ux opened, so it is project-scoped:
evidence_base="${stm_base}${issue}/evidence/marketing/" 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
marketing, run_id marketing-${ts}, slice slug, started/completed, status per C0, exit_reason;
artifacts: the slice's marketing.md, the manifest, any decision, the stop-condition verdict; the
content-eval verdict; step + scenario evals SE-1…SE-13 / SCE-1…SCE-6; checkpoint decision (incl. any
gate skipped by config or gate auto-passed by learned policy row); the end-sequence results; 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: ## marketing Delivered — ${slug}, the Run Summary table (incl. the stop-condition verdict), the Pipeline Steps table, the
Artifacts Produced table (the marketing lens + any decision), Next Steps (the functional pipe is
closed — run the non-functional pipe: /architecture), and a pointer to $evidence_dest. Always
emitted.
Scenario Validation
| Scenario | Persona | Eval |
|---|
| S1 — first run | growth lead | SCE-1 |
| S2 — honest reach | product owner | SCE-2 |
| S3 — accessibility | accessibility reviewer | 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 /marketing runs | human |
| F2 | a write touched something beyond this slice's marketing.md or a decision | revert the out-of-scope write; /marketing writes only the slice's marketing.md and any decision | autonomous |
| F3 | marketing.md fails the template/shape or carries out-of-scope content | re-emit to the Marketing lens template (the four sections only) | autonomous |
| F4 | marketing.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 reach assessment, or a material choice with no decision | re-tie the assessment to the functionalities and the profile, and record the marketing decision | autonomous |
| F6 | a functionality was not considered | extend the assessment to consider the missing functionality | autonomous |
| F7 | /marketing read or depended on another lens | remove the dependency; /marketing derives only from the slice's hub | autonomous |
| F8 | public-marketing reach was manufactured for a slice with no public surface | reset discoverability to the honest answer ("not applicable" with the reason) | autonomous |
| F9 | accessibility was deferred to the profile or omitted | set the concrete accessibility bar and the means in the lens | autonomous |
| F10 | a non-lens/non-decision file changed, or an accepted decision was edited in place | restore it and re-apply only marketing.md and the new decision, after a human confirms the restore | human |
| F11 | a discoverability/accessibility pattern 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 |
| F12 | 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 |
| F13 | 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_marketing.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 |
| F14 | 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:ff47c11de1dcfb51fe56870b022ff87048e9ee19008bb0105b69db3a930cd1b0 (of reference/ice.md) |
| compiled_by | play-editor (#467 Batch B) |
| pipeline_position | end (closes the functional realize pipe; injects commit → propose → review → merge) |
| 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-marketing-lens |
| scripts | 13 (preflight, check_ready_slice, lint_grounding, grounding_gate, validate_marketing, check_kb_grounding, apply_marketing, check_marketing, check_stop_condition — Done-means gate, session_stamp — #463 identity stamp, classify_change + gate_eval + distill_gate_policy — #467 conditional gate) |
| step_evals | 13 (SE-1…SE-13) |
| scenario_evals | 6 (SCE-1…SCE-6) |
| recovery_entries | 14 (one per failure condition; 11 autonomous / 3 human) |