
Aep Envision
- 50 installs
- 14 repo stars
- Updated July 31, 2026
- memorysaver/agentic-engineering-patterns
Helps with ai & agent building tasks.
About
aep-envision is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- aep-envision
- AI & Agent Building
- AI-coding skill
Aep Envision by the numbers
- 50 all-time installs (skills.sh)
- +1 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #7,298 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/memorysaver/agentic-engineering-patterns --skill aep-envisionAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 50 |
|---|---|
| repo stars | ★ 14 |
| Last updated | July 31, 2026 |
| Repository | memorysaver/agentic-engineering-patterns ↗ |
What it does
Helps with ai & agent building tasks.
Files
Envision
Transform a fuzzy product idea into a precise, testable product definition. First validate the opportunity is worth pursuing, then frame the product with enough precision that downstream agents can work without ambiguity.
Where this fits:
/aep-envision → /aep-map → /aep-scaffold → [ /aep-design → /aep-launch → /aep-build → /aep-wrap ] → /aep-reflect
▲ you are hereSession: Main, interactive with user Input: Product idea (vague or refined) Output: product/index.yaml with opportunity, personas, capabilities, and product sections; product-context.yaml with calibration and changelog sections. In v1 fallback mode (no split), writes everything to product-context.yaml.
YAML Schema: See templates/product-context-schema.yaml for the full structure and field definitions.
---
Before Starting
Check which mode to operate in:
ls product/index.yaml 2>/dev/null
ls product-context.yaml 2>/dev/nullFile Resolution:
- If
product/index.yamlexists → split mode. Product definition lives inproduct/index.yaml, operational state inproduct-context.yaml. - If only
product-context.yamlexists → v1 mode OR migration candidate. Ask the user: "Do you want to migrate to split mode (product/index.yaml + product-context.yaml) or keep the single file?" - If neither exists → new project. Default to split mode. Create
product/directory.
In update mode, read the existing file(s) and ask whether they want to revise or start fresh. Preserve all sections you are not updating.
---
Phase 0: Opportunity Framing
Goal: Determine whether this idea is worth building at all, before investing in product design.
Why this is separate from product framing: Opportunity Framing answers "should we build this?" Product Framing answers "what exactly should we build?" Conflating them causes premature commitment — you start designing a product before validating the opportunity, and sunk-cost bias prevents you from killing a bad idea.
How to run this phase
Let the user describe their idea freely. Do not impose structure yet. Your job is to extract the raw material:
- What triggered this idea? A personal pain point, a market gap, a technology capability?
- Who has this problem today? How do they currently solve it?
- What would change in the world if this product existed?
- What is the user's unique advantage in building this — technical skill, domain knowledge, existing audience?
- What are the strongest reasons this might fail or not matter?
After sufficient divergence, synthesize into an Opportunity Brief (see templates/opportunity-brief.md). The brief is deliberately short — one page. It captures the core bet: "I believe [target user] has [problem], and I can build [solution] because [advantage]."
Kill Point
Present the Opportunity Brief back to the user. This is an explicit decision point: proceed or kill.
If the opportunity does not survive an honest five-minute challenge, it should not consume the resources that subsequent phases require. Killing early is the highest-ROI decision in the entire workflow.
- Proceed → Continue to Phase 1
- Kill → Stop here. The brief is still saved as a record.
- Defer → Save the brief with a revisit condition
Phase 0 Output
Split mode: Write the finalized Opportunity Brief to the opportunity section of product/index.yaml. V1 mode: Write to the opportunity section of product-context.yaml.
---
Phase 1: Product Framing
Goal: Transform the validated opportunity into a precise product definition that downstream agents can consume without ambiguity.
Core premise: The user carries dozens of implicit assumptions — about users, scope, technical constraints, success criteria. Every assumption left implicit will be resolved by a downstream agent through guesswork. This phase makes every assumption explicit.
Stage 1: Diverge
Continue the conversation from Phase 0, now focused on product specifics. Lines of inquiry:
- Problem statement: Sharpen the problem. Not "developers need better tools" but "solo developers building SaaS on edge platforms lose 4+ hours per project setting up agent sandboxing because existing solutions assume AWS/GCP infrastructure."
- Persona / JTBD: Who is the primary user, concretely? What job are they hiring this product to do? What does success look like from their perspective?
- MVP boundary: What is the single most important end-to-end journey the user can complete? What is explicitly excluded, even if adjacent and tempting?
- User activities (story map backbone): What does the user DO, step by step, in the core journey? Map the user's activities as a left-to-right narrative. Each activity is a verb phrase from the user's perspective: "Authenticate", "Create Profile", "Generate Content", "Track Progress", "Download Output". These form the backbone of the story map — the horizontal axis that layers cut across. The activities should read as a coherent sentence: "The user authenticates, then creates a profile, then generates content, then tracks progress, then downloads the output." This comes BEFORE layer definitions — build the backbone first, then draw release lines across it.
- Technical constraints: Non-negotiable stack choices, infrastructure requirements, hard dependencies.
- Quality dimensions: Which dimensions of this product require human judgment that agents cannot provide? Not every dimension needs calibration — only those where "correct but not right" is likely. Common dimensions:
- Visual design — brand identity, color, typography, layout (nearly always needed for user-facing products)
- UX flow — user journey, information architecture, page transitions
- Object model — the noun-first object structure behind the UI (objects, their attributes, relationships, and the actions/CTAs on each). Unlike the others this is a structural gate, not a taste calibration:
/aep-mapauto-drafts an Object Map and/aep-modelgets a short human approval. Declare `object-model` by default for any UI-facing product/capability — it is what stops build agents from inventing one-step-one-screen task-wizard UIs. (Skip only for pure-backend/CLI products.) - API surface — endpoint naming, grouping, error contracts (when external consumers exist)
- Data model — entity naming, field semantics (when domain language matters)
- Copy/tone — brand voice, error messages, empty states
- Scope/direction — mid-build intent correction (common when PM and builder are different people)
- Performance/quality — latency thresholds, retry behavior, caching strategy
For each declared dimension: what layer is it most likely to first need calibration? Why?
- Layered MVP contract: Layer 0 is the walking skeleton — a horizontal slice across the activity backbone, picking the thinnest story from each activity. Each subsequent layer adds capabilities. Later layers may introduce new activities that extend the backbone to the right. Define what the user can accomplish at each layer.
.5 layers are human alignment layers, not just "UI polish." A .5 layer is any point where the team pauses agent execution to calibrate human intent across one or more quality dimensions. Layer 0.5 might be visual design only. Layer 1.5 might be visual design extension + copy tone. The calibration.plan maps layers to expected calibration checkpoints.
Stage 2: Structure
Organize everything into the Context Document (see templates/context-document.md). Present the draft to the user.
Populate product.quality_dimensions from the diverge conversation — for each dimension the user identified as needing human calibration, record the dimension, criticality, first calibration layer, and rationale.
Quality standard: every statement must be convertible into a verification condition. "The system should be performant" fails. "API p95 latency < 200ms" passes. If a statement cannot be tested, it is not precise enough for agents to act on.
Stage 3: Stress Test (Independent Agents)
Hand the Context Document to agents that did not participate in the conversation. They review it cold from three angles:
1. Product viability: Are the user and problem assumptions validated? What are the strongest counter-arguments? 2. Technical feasibility: Are technology choices compatible with each other and with the stated requirements? Known limitations? 3. Scope control: Is the MVP actually minimal? Can any layer be cut?
Each reviewer produces a challenge list. The user resolves each item — either by refining the document or marking it as an explicit open_question with a default assumption and a revisit trigger.
Note: The stress test is itself a form of pre-build calibration — independent agents check alignment before building. Post-build calibration (/aep-calibrate) extends this to dimensions that only become visible after agents have produced output: visual design, UX flow, naming, tone.Record the stress test results in product.stress_test within the YAML.
Phase 1 Output
Split mode:
1. Write the finalized Context Document to product/index.yaml:
opportunity(from Phase 0)personas(extracted from the persona work — use list format withid,description,jtbd)capabilities(at least one entry; single-journey products get one capability)productsubsection:problem,goals,non_goals,mvp_boundary,constraints,layers,activities,failure_model,security_model,success_criteria,quality_dimensions,open_questions,decisions,stress_test
2. Write operational initialization to product-context.yaml:
- Header:
schema: v1,project,version,updated_at,dispatch_epoch: 0 calibration.plan(mapped from quality_dimensions)calibration.history: []changelogentry recording what was created- All other operational sections left empty (populated by
/aep-map)
V1 mode: Write everything to product-context.yaml using templates/product-context-schema.yaml as the structural reference.
On subsequent runs — read the existing file(s), update the relevant sections, and preserve all other sections (e.g., architecture, stories, topology).
If quality dimensions were declared, also write the initial calibration.plan section — mapping each dimension to the layer where calibration is expected. This plan is refined by /aep-map (which has concrete layer definitions) and executed by /aep-calibrate.
Capability Maps (for multi-journey products)
If the product has 2+ distinct user journeys, also create capability map files:
1. Ensure product/index.yaml has multiple entries in capabilities[] 2. For each capability, create:
product/maps/<capability-id>/frame.yaml— scope, boundary, primary user, outcome contract- Story stubs are populated later by
/aep-map
Simple single-journey products get one capability entry but skip frame.yaml and map.yaml.
Before Committing: Validate YAML
See references/yaml-guardrails.md for the full checklist. Run:
# Split mode
python3 -c "import yaml; [yaml.safe_load(open(f)) for f in ('product/index.yaml', 'product-context.yaml')]; print('YAML OK')"
# V1 mode
python3 -c "import yaml; yaml.safe_load(open('product-context.yaml')); print('YAML OK')"If this fails, fix the YAML before committing. Common fixes: quote list items containing colons, flatten nested sub-lists, escape embedded double quotes.
Commit
# Resolve $BASE (integration branch) — see git-ref "Integration Branch" (override → develop → main)
BASE=$(git config --get aep.integration-branch 2>/dev/null || true)
[ -z "$BASE" ] && { git show-ref --verify --quiet refs/heads/develop \
|| git show-ref --verify --quiet refs/remotes/origin/develop; } && BASE=develop
BASE=${BASE:-main}
# Split mode: Write product/index.yaml (opportunity + personas + capabilities + product)
# Split mode: Write product-context.yaml (calibration + changelog, operational sections empty)
# V1 mode: Write product-context.yaml (all sections)
git pull --ff-only origin "$BASE"
git add product-context.yaml product/ docs/
git commit -m "feat: add product context (opportunity brief + context document)"
git push origin "$BASE"---
For Iteration
When revisiting an existing product (triggered by /aep-reflect or the user's own initiative):
1. Read the existing product definition (product/index.yaml in split mode, product-context.yaml in v1 mode) 2. Identify what's changed — new learnings, invalidated assumptions, scope shifts 3. Update the relevant sections (opportunity and/or product) 4. Re-run the stress test on changed sections only 5. Append to the changelog section 6. Commit the updated version (version history is itself valuable)
Boundary: When to Use /aep-envision vs /aep-reflect
Not every post-layer adjustment requires envision. Most learning leads to re-slicing (moving stories between layers), which is handled entirely in /aep-reflect. For details, see docs/decisions/release-line-adjustments.md.
What does NOT trigger `/aep-envision` (handle in /aep-reflect instead):
- Moving stories between layers (e.g., promoting a Layer 2 story to Layer 1)
- Adding new stories to existing activities
- Re-prioritizing the next layer based on what you learned
- Adjusting release line boundaries without changing the backbone
What DOES trigger `/aep-envision`:
- Backbone changes — new activities, removed activities, reordered user journey
- Product framing changes — persona, JTBD, or MVP boundary needs redefinition
- Opportunity hypothesis invalidation — the problem or market shifted
- New activities that extend the backbone to the right
---
Key Principles
- One question at a time — Don't overwhelm with multiple questions
- YAGNI ruthlessly — Remove unnecessary features from all designs
- Explain why, don't stack MUSTs — Every instruction comes with its rationale
- Explicit unknowns over implicit assumptions — Documented unknowns cause agents to stop and ask. Undocumented unknowns cause agents to guess.
- Kill early and often — The best outcome from Phase 0 is sometimes "don't build this"
---
Next Step
Product is envisioned. Proceed to:
/aep-mapThis decomposes the Context Document into a system map, layered story graph, and agent topology.
Generated by scripts/build-skills.sh from skills/product-context/_shared/. Do not edit; edit _shared/ and rebuild.
Orchestration Patterns
Detailed patterns for the control plane's orchestrator — state management, context assembly, layer gating, and failure handling. Read this when setting up or debugging the execution pipeline.
---
Work Graph as State Machine
The work graph is a live state machine. Every story node holds a status and transitions based on events.
State Transitions
pending → ready (all dependency stories reach 'completed')
ready → in_progress (orchestrator dispatches to agent)
in_progress → in_review (agent submits PR)
in_review → completed (verification passes)
in_review → in_progress (verification fails, retry initiated)
in_progress → failed (retry limit exceeded, escalated)
pending → blocked (a dependency story enters 'failed')
any → deferred (user explicitly postpones)Orchestrator Loop
The orchestrator is event-driven, not polling-based:
1. Event received (story completed, PR submitted, verification result, failure). 2. Update state of the affected story in the work graph. 3. Cascade check: Does this transition unlock new stories? (completed → check dependents). Does it block stories? (failed → mark dependents as blocked). 4. Dispatch: For each newly ready story, run conflict detection, assemble context, dispatch to agent per routing rules. 5. Layer check: Are all stories in the current layer completed? If yes, trigger Integration Gate. 6. Alert check: Any cost anomalies? Any critical path blockages? Notify user if needed.
Concurrency Control
- Maximum parallel agents is configurable. Start with 5–10.
- Two stories with overlapping "Files Likely Affected" must not run in parallel — serialize them.
- If two parallel stories produce merge conflicts, the later PR rebases on the merged one and re-verifies.
---
Context Assembly
The Problem Context Assembly Solves
An agent's output quality is directly proportional to the relevance and precision of its input context. Too little context → the agent guesses. Too much context → the agent gets confused or hits token limits. Context assembly is the art of giving each agent exactly what it needs and nothing more.
Assembly Rules
For each agent role, the Agent Topology document defines a context window composition — the ordered list of what goes in. The orchestrator follows this list mechanically:
1. Read the composition spec for the target agent role. 2. Prune the Context Document to the sections listed in the spec. 3. Extract the relevant System Map slice — the story's module and its adjacent interfaces only. Do not include unrelated modules. 4. Collect dependency artifacts — for each completed dependency, extract the public interface (types, exports, API surface). Do not include internal implementation unless the composition spec explicitly requires it. 5. Validate the package — all required fields present, no references to missing artifacts. 6. Measure the package — if it exceeds the target token budget for the role, escalate for manual pruning or split the story.
Common Assembly Failures
- Missing dependency artifact: A dependency is marked
completedbut its output artifact is not found. This usually means the previous agent's output contract was not enforced. Fix: add post-completion validation in the handoff contract. - Stale interface contract: The System Map was amended but the context package still references the old version. Fix: always read interface contracts from the latest System Map, not from cached copies.
- Context overflow: The assembled package exceeds the agent's token budget. Fix: either prune more aggressively (summarize dependency artifacts instead of including full source) or split the story into smaller units.
---
Layer Gating
Gate Design
Each layer has an Integration Gate — tests that verify stories work together. The gate is NOT the sum of individual story tests. It tests emergent behavior at integration boundaries.
Layer 0 gate is the most important test in the pipeline. It executes the exact user journey from the Context Document's Layer 0 MVP Contract. If the walking skeleton doesn't work end-to-end, something is architecturally wrong.
Subsequent layer gates test:
1. All previous layer journeys still work (regression). 2. New capabilities added in this layer work end-to-end. 3. Interface contracts honored under realistic conditions (not just mocks).
Gate Failure Protocol
Gate fails
→ Identify failure boundary (which module interface)
→ Check: implementation vs contract mismatch?
→ Implementation wrong: create fix story → Phase 4
→ Contract wrong: trigger Architecture Review → Phase 2
→ Assess impact on completed stories
→ May require re-execution of affected storiesGate failure on a contract issue is the most expensive failure in the pipeline because it can invalidate already-completed work. This is why Phase 2 (System Map approval) is a human-reviewed gate — catching contract errors early prevents cascading rework.
---
Failure Handling
Why Fresh-Agent Retry Works
When an agent fails and retries, it carries the full reasoning trajectory from its first attempt. If that trajectory led to a dead end, the retry often follows the same path — the agent is stuck in its own logic. A fresh agent receives only the structured failure log, not the reasoning. It approaches the problem without the stuck trajectory.
The failure log's "what was NOT tried" field is the highest-value signal for the fresh agent. It provides starting points the previous agent considered but did not explore.
Failure Log Schema
{
story_id: string,
attempt_number: number,
agent_role: string,
approach_summary: string, // What the agent tried to do
failure_point: string, // Which verification step failed
error_output: string, // Exact error messages or test failures
hypothesis: string, // Agent's best guess about root cause
not_tried: string[], // Alternative approaches considered but not attempted
context_issues?: string, // Any problems with the context package
time_spent_seconds: number,
tokens_used: number
}Cascade Prevention
When a story fails:
1. Mark direct dependents as blocked. 2. Continue executing non-blocked stories in the same layer. 3. If the failed story is on the critical path → alert user immediately (entire layer is blocked). 4. If NOT on critical path → other work continues. User addresses failure asynchronously. 5. When the failed story is eventually resolved (fixed or deferred), unblock dependents and resume normal dispatch.
Escalation Format
When a story reaches human escalation, present:
1. The story spec (what was being attempted). 2. All failure logs from all attempts (what happened). 3. The fresh agent's failure log specifically (the most informed analysis). 4. Current impact: which stories are blocked, is this on the critical path? 5. Suggested options: fix the story, simplify the story, defer it, or modify the architecture.
---
State Persistence
The orchestrator's state must survive crashes.
Storage Options
- File-based (JSON in repo): Simple, version-controlled. Sufficient for most MVP projects. Limitation: does not support concurrent orchestrators.
- SQLite: Supports querying ("show all failed stories") and concurrent access. Better for larger projects.
- External store (Redis, Postgres): For production-grade orchestration with multiple concurrent sessions.
For MVP-stage projects, start with JSON in the repo. Upgrade when the limitation matters.
State Snapshot Schema
{
project_id: string,
current_layer: number,
stories: {
[story_id]: {
status: "pending" | "ready" | "in_progress" | "in_review" | "completed" | "failed" | "blocked" | "deferred",
assigned_agent?: string,
attempt_count: number,
last_updated: ISO8601,
failure_logs?: FailureLog[],
pr_url?: string,
completed_at?: ISO8601
}
},
layer_gates: {
[layer_number]: {
status: "not_started" | "running" | "passed" | "failed",
test_results?: TestResult[],
completed_at?: ISO8601
}
},
cost_summary: {
total_cost_usd: number,
cost_by_layer: { [layer]: number },
cost_by_role: { [role]: number },
cost_by_story: { [story_id]: number }
},
last_updated: ISO8601
}State Inspection
The user should be able to query the current state at any time:
- Progress per layer: completed / in_progress / pending / failed / blocked
- Critical path status: what is the next bottleneck?
- Cost breakdown: where is the money going?
- Blocked stories: what is waiting on what?
Provide a simple CLI command or dashboard that reads the state file and renders this overview.
Telemetry Ingestion & Outcome Auto-Evaluation
How /aep-reflect (and /aep-watch) pull real-world signals automatically, and how a layer's quantitative outcome contract is evaluated without a human. This augments the interactive reflect flow — it never replaces human review by default. (Gap G5.)
Authoring note: this file is canonical in
skills/product-context/_shared/references/;scripts/build-skills.sh
materializes it into each consuming skill's references/.---
1. Automated source ingestion
Pull from read-only sources with bash/curl/jq and reduce each to the normalized observation record the reflect Step 2 classifier consumes:
{
"source": "error_stream | analytics | monitoring | bug_tracker | dogfood",
"signal": "one-line description of what was observed",
"evidence": "url | query | sample (no secrets)",
"story_ref": "<story-id if attributable, else null>",
"suggested_class": "bug | refinement | discovery | opportunity_shift | process | null"
}suggested_class is a hint only — the reflect Step 2 classifier (and the human, unless full_auto) makes the final call. Ingested records are merged with interactive input before classification; automation augments, never replaces.
Source config
Endpoints live under topology.routing.telemetry_sources (a list). Each entry:
telemetry_sources:
- kind: error_stream # error_stream | analytics | monitoring | bug_tracker
endpoint: "https://…/api/…?since={since}" # {since} = last-ingest high-water mark
token_env: SENTRY_TOKEN # NAME of an env var / secret — never the secret itself
metric_map: # for analytics/monitoring: outcome-metric name → query
activation_rate: "SELECT … "Safety: access is read-only; reference credentials by env-var / secret-store name only — never embed secrets in the repo or in `product-context.yaml`.
Dogfood-report adapter (dogfood_report source)
Dogfood runs — local (/aep-build Phase 6), post-deploy (autopilot post-merge guard), or a standalone / ad-hoc live exercise — emit the unified markdown report (## <title> / **Severity:** / **Category:** / **Repro:** / **Observed:** / **Expected:** / **Evidence:**) to .dev-workflow/dogfood-*.md (see patterns/executor/references/dogfood-validation.md → Unified report format). This adapter parses each ## finding into the `/aep-watch` Step 1 finding record (the operative shape Step 3 dedupes and Step 4 turns into a story — _not_ the 5-field telemetry record above, which is the classifier's conceptual input) so the same Step 2 classifier consumes it — closing the G6 self-feeding loop for every dogfood trigger, not just the guard path. It is a file glob, not a network source: self-describing, so coverage_check (§1.5) does not gate it.
Source config (the discriminator key matches the container: type: under watch.sources[], kind: under telemetry_sources[]):
watch:
sources:
- type: dogfood_report
glob: ".dev-workflow/dogfood-*.md" # default; add post-deploy report paths as neededPer-finding mapping (markdown field → finding field):
| Dogfood field | Finding field |
|---|---|
## <title> | title (the story title /aep-watch Step 4 reads) |
**Repro / Observed / Expected / Evidence** | detail (repro steps + observed-vs-expected; no secrets) — also the classifier's evidence |
**Severity:** | priority — blocker/major → high (critical if it blocks a core flow); minor → normal. Dogfood findings have no `count`, so priority comes from Severity, not the count-based escalation other sources use |
**Category:** | suggested_class hint — UX/logic/edge-case/accessibility → bug; visual/performance → bug when Severity ∈ {blocker,major}, else refinement |
| — | signal: dogfood, story_ref: null, external_id: (below); count/first_seen/last_seen unset (the report carries no occurrence count or timestamp) |
suggested_class is a hint only — the Step 2 classifier (and the human, unless full_auto) makes the final call, exactly as for every other source. In particular a finding that reads as calibration / discovery / opportunity-shift / process is not auto-filed; it surfaces to a human (see /aep-watch Step 2).
No high-water mark — dedupe-only. The unified report has no per-finding timestamp, so a dogfood_report source does not advance watch.since (that cursor applies only to time-ordered sources); re-scanning the glob each tick is harmless because idempotency rests entirely on the stable dedupe key. Each finding gets a deterministic external_id = "dogfood:" + slug(report-basename) + ":" + shorthash(slug(title) + "|" + category), so /aep-watch Step 3 dedupes on watch_origin.{source,external_id}: already-filed findings no-op, and a genuinely new finding (new title/category) yields a new id and a new story. The autopilot post-merge guard Path 1 stamps the same external_id on the story it files, so whichever path ingests a given report first wins and the other no-ops — no double-filing.
---
1.5 Deciding which sources to wire (the coverage rule)
You don't list telemetry for its own sake — a source is needed _iff_ some declared signal requires it. The decision is hybrid:
1. Metric-driven (what signals do we need?) — enumerate every quantitative success_metric across product.layers[].outcome_contract plus every topology.routing.post_merge_guard.health_signals entry. That set _is_ the demand for telemetry. 2. Inventory (which tool provides each?) — /aep-scaffold's audit detects the project's observability stack (Sentry, Datadog, PostHog, OpenTelemetry, log drains, /healthz-style endpoints) and records candidate telemetry_sources (kind + endpoint + token_env, no metric_map yet); you can also add candidates by hand. 3. Bind (`/aep-map`) — for each needed signal, attach it to a candidate source by adding a metric_map: { <metric-or-signal>: "<query>" } entry. A needed signal with no measurable source is flagged, not ignored: make the metric qualitative, or record it unmeasured — never leave a quantitative metric silently un-sourced.
coverage_check(needed) — the guard helper
Consumers that rely on telemetry (/aep-watch, /aep-reflect Step 2.75, /aep-autopilot) call this before trusting auto behavior. It is pure config inspection — no network:
coverage_check(needed_signals):
missing = []
for sig in needed_signals: # quantitative success_metric names + health_signals
if no telemetry_sources[*].metric_map has key == sig
(and, for a health_signal, no source/endpoint provides it):
missing.append(sig)
return { covered: missing == [], missing }On `covered == false`: surface "telemetry binding incomplete for <missing> — run /aep-map (observability step)" and block the auto path (watch refuses to claim auto-coverage; reflect falls back to the human pause; autopilot pauses). Missing wiring must block auto, never silently no-op — that's the v2 human-in-the-loop default.
---
2. Outcome-contract auto-evaluation
A layer's outcome_contract carries a success_metric (type + target) and a decision_rule (keep_if / otherwise). Precondition: run coverage_check([success_metric]) (§1.5) first — if the metric isn't bound to a source, take the human-pause path (the binding is incomplete; do not auto-eval). When covered, evaluate per topology.routing.auto_outcome_eval:
Metric type | auto_outcome_eval: quantitative | default (none) |
|---|---|---|
| quantitative (numeric, measurable from a source) | fetch actual value via the matching telemetry_sources query, apply keep_if/otherwise mechanically, record result — no pause | human pause (current behavior) |
| qualitative | human pause — unless full_auto: true (then agent-judgment auto-eval) | human pause |
On a fetch failure or ambiguity, fall back to the human pause (fail safe, not fail open). Record every auto-evaluation in the changelog:
- date: YYYY-MM-DD
type: outcome_evaluation
summary: "Layer N: <metric> = <actual> vs target <target> → passed|failed (auto)"---
3. full_auto interaction (A1)
topology.routing.full_auto (default false) is the master switch. It only changes the qualitative path:
full_auto | auto_outcome_eval | quantitative outcome | qualitative outcome |
|---|---|---|---|
| false (default) | none | human pause | human pause |
| false | quantitative | auto-eval | human pause |
| true | (implied quantitative) | auto-eval | agent-judgment auto-eval |
Default keeps humans in the loop; only an explicit full_auto: true removes the qualitative pause.
---
Cross-references
/aep-reflectStep 1 (Gather Feedback) and Step 2.75 (Evaluate Outcome Contracts)/aep-watch(reuses the normalized observation record for its ingest step)aep-autopilotreferences/tick-protocol.md— Step ⑥ Layer Completion (what the
auto-eval lets advance without a pause)
YAML Guardrails for product-context.yaml
Every skill that writes to product-context.yaml must validate the file before committing. Invalid YAML silently breaks the dashboard and blocks all downstream consumers.
Validation Command
Run this after every edit to product-context.yaml:
npx js-yaml product-context.yaml > /dev/null && echo "YAML OK"If the project has the @agentic-engineering-patterns/api package, use the actual loader for deeper validation (Zod schema + preprocessing):
npx tsx -e "
const { loadProductContext } = require('@agentic-engineering-patterns/api/lib/product-context-loader');
loadProductContext(process.env.PRODUCT_CONTEXT_PATH || './product-context.yaml');
console.log('YAML + schema OK');
"If validation fails, fix the YAML before committing. Do not commit broken YAML under any circumstances.
Common YAML Pitfalls in product-context.yaml
These are the patterns that most frequently break the parser when agents write to the file.
1. List items ending with a colon
A trailing colon makes YAML interpret the item as a mapping key. If the next lines are indented, YAML expects a value — and fails.
# BROKEN — YAML treats this as a mapping key
acceptance_criteria:
- Generate page redesigned for multi-step video workflow:
- Intent prompt input
- Multi-step progress display
# FIXED — quote the entire item, flatten sub-items
acceptance_criteria:
- "Generate page redesigned for multi-step video workflow: intent prompt input, multi-step progress display"Rule: Never end a list item with : followed by indented sub-items. Either quote the item or flatten the sub-list.
2. Embedded double quotes inside list items
YAML interprets "text" as a quoted string boundary. Content after the closing quote is invalid.
# BROKEN — YAML sees "Complete Your Profile" as the full string, then chokes on the rest
- "Complete Your Profile" guard includes link to /profile
# FIXED — wrap in double quotes, use single quotes inside
- "'Complete Your Profile' guard includes link to /profile"
# ALSO FIXED — escape inner quotes
- "\"Complete Your Profile\" guard includes link to /profile"Rule: If a list item contains embedded double quotes, wrap the entire value in double quotes and use single quotes (or escaped quotes) inside.
3. Colons in the middle of list items
A colon followed by a space (: ) triggers YAML key-value parsing.
# BROKEN — YAML tries to parse "Dashboard" as a key
- Dashboard: creator dashboard showing recent generations
# WORKS (preprocessor handles this) — but quoting is safer
- "Dashboard: creator dashboard showing recent generations"Rule: The preprocessYaml function in the loader auto-quotes most of these, but when writing new content, prefer explicit quoting for items containing : .
4. Special characters: @, {, }
# BROKEN — @ is a YAML tag indicator, { starts a flow mapping
- @mention the user
- Use {variable} interpolation
# FIXED
- "@mention the user"
- "Use {variable} interpolation"Rule: Quote list items containing @, {, or }.
5. Nested sub-lists under string items
YAML list items are scalar values — they cannot have children unless the item is a mapping key.
# BROKEN — a string item cannot have sub-items
- Main feature description
- Sub-feature A
- Sub-feature B
# FIXED — flatten into one item or use a mapping structure
- "Main feature description: Sub-feature A, Sub-feature B"Pre-commit Checklist
Before committing any change to product-context.yaml:
1. Run the validation command above 2. If adding acceptance_criteria, description, or any free-text list: scan for colons, quotes, and special characters 3. If the validation command is not available (e.g., no Node.js), at minimum review list items for the patterns above
Generated by scripts/build-skills.sh from skills/product-context/_shared/. Do not edit; edit _shared/ and rebuild.
Agent Topology Template
Defines every agent role in the execution pipeline, how they communicate, and how the orchestrator routes work to them. This is an architectural document — changes here affect the behavior of the entire execution plane.
The core rule: agents communicate through structured artifacts, not free text. Every input and output is schema-defined. Every handoff has validation. No exceptions.
---
Agent Roles
Role: [Role Name]
Purpose: [One sentence — what this agent does in the pipeline.]
Responsibility boundary:
- Does: [Specific responsibilities]
- Does not: [Explicit exclusions — prevents role creep]
Input contract:
[Schema definition of the work object this agent receives. Use TypeScript types, JSON Schema, or equivalent. Be explicit about required fields.]
Example:
{
story_spec: StorySpec, // The full story specification
context_slice: {
context_document: string, // Relevant sections only
system_map_module: ModuleDef, // This story's module definition
adjacent_interfaces: InterfaceContract[],
dependency_artifacts: Artifact[] // Public interfaces from completed dependencies
}
}Output contract:
[Schema definition of what this agent produces.]
Example:
{
implementation: {
branch_name: string,
files_changed: FileDiff[],
pr_url: string
},
verification: {
unit_tests: TestResult[],
contract_tests: TestResult[],
all_passing: boolean
},
status_report: {
story_id: string,
outcome: "success" | "failure",
error_summary?: string,
what_was_not_tried?: string[] // Critical for fresh-agent retries
}
}Context window composition: [Exactly what goes into this agent's context and in what order. Less is more — irrelevant context degrades performance.]
1. Story Spec (full) 2. Context Document (pruned to: Purpose, Technical Constraints, relevant Layer in MVP Contract) 3. System Map (this module + adjacent interface contracts only) 4. Dependency artifacts (public API surface only, not internals)
Cost budget:
- Expected tokens: [range, e.g., 10k–50k input, 5k–20k output]
- Expected duration: [range, e.g., 2–10 minutes]
- Alert threshold: [e.g., > 100k total tokens or > 20 minutes]
[Repeat for each agent role]
---
Standard Roles
Most projects will need at least these roles. Add or remove based on project complexity.
implementer
Takes a story spec and produces code + tests + PR. The workhorse of the execution plane.
contract-verifier
Takes a PR and runs interface contract tests against the System Map. Catches integration incompatibilities before merge.
integration-tester
Runs end-to-end tests for layer gates. Operates on the combined codebase, not individual stories.
failure-analyst
Takes a failed story's trace and produces a structured failure log with root cause hypothesis and unexplored alternatives. Feeds into fresh-agent retries.
---
Handoff Contracts
[Source Role] → [Target Role]
Trigger event: [What causes this handoff — e.g., "implementer completes PR submission"]
Payload schema:
[Exact structure passed from source to target]Pre-handoff validation: [Checks that run before the target agent starts. If validation fails, the handoff is rejected and the source agent is notified.]
- [ ] Payload matches schema
- [ ] All required fields present and non-empty
- [ ] Referenced artifacts (files, PRs) actually exist
- [ ] [Domain-specific checks]
Failure handling: [What happens if validation fails — retry source? escalate?]
---
Routing Rules
Dispatch Policy
Queue model: [FIFO within execution slice / priority-based / other]
Assignment: When a story transitions to ready in the work graph, the orchestrator:
1. Checks conflict detection (see below) 2. Checks concurrency limit 3. Assembles context package per the implementer role's context window composition 4. Dispatches to the next available agent instance
Concurrency
- Maximum parallel agents: [Number — start with 5–10, increase as stability is proven]
- Per-module limit: [Optional — prevent one module from consuming all agent capacity]
Conflict Detection
Stories that modify overlapping files must not run in parallel. The orchestrator checks each ready story's "Files Likely Affected" against in-progress stories. Conflicts are serialized — the later story waits until the earlier one completes.
Retry Routing
Attempt 1: Same agent, error context appended to input
Attempt 2: Same agent, second retry
Attempt 3: failure-analyst produces structured log
→ fresh implementer agent receives story spec + failure log
Attempt 4: Human escalation — story marked 'failed', user notifiedBetween attempt 2 and 3, the failure-analyst role intervenes to extract useful signal from the failures before handing off to a fresh agent.
---
Cost Tracking Schema
Every agent invocation produces a trace record appended to the project's cost log:
{
story_id: string,
agent_role: string,
attempt_number: number,
start_time: ISO8601,
end_time: ISO8601,
tokens_input: number,
tokens_output: number,
cost_usd: number,
outcome: "success" | "failure" | "escalated",
error_class?: string // e.g., "test_failure", "timeout", "context_overflow"
}Cost Alerts
- Per-story alert: Triggered when total cost across all attempts for a single story exceeds [threshold].
- Per-layer alert: Triggered when cumulative layer cost exceeds [threshold].
- Anomaly alert: Triggered when a story's cost is > 3x the median for its complexity class (S/M/L).
---
Topology Diagram
[Optional but recommended. A simple flow diagram showing agent roles, handoff directions, and the orchestrator's position.]
┌─────────────────┐
│ Orchestrator │
│ (Control Plane) │
└────────┬────────┘
│ dispatches
┌────────▼────────┐
│ Implementer │
│ (Execution Plane)│
└────────┬────────┘
│ PR submitted
┌────────▼────────┐
│Contract Verifier │
└────────┬────────┘
│ pass/fail
┌────────▼────────┐
┌─────│ Orchestrator │─────┐
│ └─────────────────┘ │
│ (all layer stories done) │ (failure)
┌────────▼────────┐ ┌─────────▼────────┐
│Integration Tester│ │ Failure Analyst │
└────────┬────────┘ └─────────┬────────┘
│ │
Layer Gate Fresh Implementer
Decision or EscalationContext Document Template
This is the root context document for the project. Every downstream agent inherits this document. Precision here prevents confusion downstream — every vague statement multiplies into ambiguity across parallel agents.
Quality standard: every statement must be convertible into a verification condition. If it cannot be tested, it is not precise enough.
---
Project Identity
- Project name: [Working name]
- Opportunity Brief: [Link to the Phase 0 Opportunity Brief]
- Created: [Date]
- Last updated: [Date]
- Version: [Increment on each meaningful change]
---
Problem Statement
[Who has this problem? What is the problem, specifically? How do they deal with it today? Why is the current approach inadequate? This should be sharp enough that a stranger could read it and understand the pain without further explanation.]
---
Persona / JTBD
Primary Persona
[Describe the primary user concretely. Not "developers" but "solo developers building SaaS products who deploy to edge platforms and want to integrate AI agents without managing infrastructure." Include relevant context: technical skill level, tools they already use, constraints they operate under.]
Job To Be Done
[What job is the user hiring this product to do? Frame it as: "When [situation], I want to [motivation], so I can [expected outcome]."]
---
MVP Boundary
In Scope
[Concrete list of what this system does. Each item should be specific enough to verify.]
Explicitly Out of Scope
[What this system does NOT do, even if users might expect it. This prevents agents from expanding scope.]
Deferred (Possible Future Scope)
[Things that might be added later but are not part of the current effort. Agents should not build toward these unless they come for free.]
Important boundary: "In Scope" defines what the system does. "Goals" (below) defines how you know it's working. A feature can be in scope but not yet a goal for this layer.
---
Goals and Non-Goals
Goals
[Behavior-observable statements. Each must be verifiable by observing the running system. Not aspirations — observable behaviors.
Example: "Poll the issue tracker on a fixed cadence and dispatch work to coding agents" is a goal. "Be a good orchestrator" is not.]
1. [Goal — observable behavior] 2. [Goal — observable behavior]
Non-Goals
[Things a reasonable person might expect this product to do, but it will NOT. Each explains why — preventing scope creep and setting correct expectations for downstream agents.
Example: "Rich web UI — this is a CLI-first tool. A web dashboard is a separate product."]
1. [Non-goal — why excluded] 2. [Non-goal — why excluded]
---
Technical Constraints
Required Stack
[Non-negotiable technology choices and why.]
Preferred Stack
[Preferences that can be overridden with good reason. State the preference and the reasoning.]
Infrastructure
[Where this runs, deployment targets, environment constraints.]
External Dependencies
[Third-party services this project depends on. For each: what it provides, failure behavior, known limitations.]
---
Failure Model
What goes wrong and what the system does about it. Agents building this system need to implement these recovery behaviors. Undocumented failure modes become undocumented bugs.
Failure Classes
| Class | Examples | Detection | Recovery | Escalation |
|---|---|---|---|---|
| [Name] | [What triggers it] | [How the system detects it] | [Automatic recovery behavior] | [When/how a human is notified] |
Degraded Operation
[What the system can still do when a dependency is down. "Nothing" is a valid answer but must be stated.]
---
Security Model
Trust Boundaries
[What is trusted (e.g., "the operator's machine") and what is not (e.g., "user-submitted content"). Draw the line explicitly.]
Authentication & Authorization
[How users prove identity. How access is controlled. "N/A for MVP" is acceptable but must be stated.]
Secret Handling
[Where secrets come from, how they're stored, how they're passed to components. Never in plaintext config files.]
---
User Activities (Story Map Backbone)
The backbone of the user story map. Each activity is a discrete step in the user's journey, ordered left-to-right as a narrative. These are discovered BEFORE defining layers — the backbone comes first, release boundaries (layers) are drawn across it second.
Read the activities as a sentence to verify narrative coherence: "The user [activity 1], then [activity 2], then [activity 3]..."
| Order | Activity ID | Name | Description | Layer Introduced |
|---|---|---|---|---|
| 1 | 0 | |||
| 2 | 0 |
Activities from Layer 0 form the core backbone. Later layers may introduce new activities that extend the backbone to the right.
---
Layered MVP Contract
Each layer is a complete, testable, deployable increment. Layer 0 is the walking skeleton — a horizontal slice across ALL activities in the backbone. Each subsequent layer adds capabilities. Most MVPs need 2–4 layers. More than that suggests scope is too large.
Layer 0: Walking Skeleton
User can: [End-to-end journey in concrete steps. "User does X → sees Y → gets Z." Every step observable and verifiable.]
Verification: [The test scenario that proves Layer 0 works.]
Layer 1: [Name]
User can: [Everything from Layer 0, plus new capabilities.]
Verification: [Test scenario for new capabilities.]
Layer 2: [Name]
User can: [Everything from Layer 1, plus new capabilities.]
Verification: [Test scenario.]
---
Success Criteria
Functional
[What must work for this to be a successful MVP? Specific, testable conditions.]
Non-Functional
[Performance, reliability, security. Each with a measurable threshold.]
---
Open Questions
Decisions explicitly deferred. Downstream agents will see these and know not to assume answers.
| Question | Why Deferred | Default Assumption | Revisit Trigger |
|---|---|---|---|
---
Key Decisions Log
Significant decisions made during Product Framing. Helps downstream agents understand not just what was decided, but why — enabling consistent decisions in ambiguous situations.
| Decision | Reasoning | Alternatives Considered |
|---|---|---|
---
Stress Test Record
Challenges raised during Phase 1 Stage 3, and how they were resolved.
| Challenge | Source Angle | Resolution |
|---|---|---|
| Product viability / Technical feasibility / Scope control | Refined document / Marked as open question / Dismissed with reasoning |
# Object Map Schema — product/maps/<capability>/object-map.yaml
#
# The capability-scoped ORCA / IA projection (OOUX Rounds O→R→C→A plus Round 4
# representation hints). One file per UI-facing capability. This is the
# noun-first bridge between the verb-first story map and the actual UI: it says
# WHICH objects appear, WHAT their fields are, HOW they nest, and WHAT actions
# (CTAs) hang off each — BEFORE any screen is designed.
#
# Lifecycle: /aep-map writes a `status: draft`; /aep-model presents it, asks the
# few high-leverage questions (object boundaries, primary anchor, task-flow
# exceptions), and on approval flips `status: approved`. /aep-dispatch injects
# ONLY the slice a story touches; /aep-launch refuses a UI-facing story whose
# capability has no `approved` object-map.
#
# This file is STRUCTURAL, not visual. Colors/typography/spacing stay in
# calibration/visual-design.yaml; journey/page/transition stay in
# calibration/ux-flow.yaml. The object-map governs object structure + CTA grammar.
#
# See model/references/orca-process.md for derivation rules.
schema: object-map/v1
capability: <capability-id> # product/index.yaml capabilities[].id; in v1/single-journey use the default capability = project slug
# Lifecycle: /aep-map writes `draft`; /aep-model flips to `approved`; /aep-map flips an
# `approved` map back to `stale` when it re-decomposes stories/activities under this
# capability. Dispatch/launch gates treat `draft` AND `stale` as "not ready" (abort → /aep-model).
status: draft # draft | approved | stale
generated_by: aep-map # aep-map (draft) | aep-model (refined/approved)
generated_at: <ISO 8601>
approved_at: null
approved_by: null # "human" — set at the /aep-model review gate
object_model_ref: product/object-model.yaml # the shared ontology this projects from
# Canonical screen-id grammar (used wherever a screen is referenced, incl. coverage):
# <object-id>:<view> where <view> ∈ collection | detail | create | edit
# e.g. "order:collection", "order:detail". screens[] below declares which views exist
# per object; coverage[].screens references them by this composite id.
# ─── ROUND O — objects in play for THIS capability ──────────────
# Subset of product/object-model.yaml. Primary objects get home screens; support
# objects only appear nested inside others.
primary_objects: [] # object ids the user navigates TO (each gets a collection + detail)
supporting_objects: [] # object ids shown only inside another object's views
# ─── ROUND R — Nested Object Matrix (capability-local) ──────────
relationships:
- from: <object-id>
to: <object-id>
cardinality: one_to_many # one_to_one | one_to_many | many_to_many
nested: true # embed `to` inside `from`'s detail view
nav: true # this link is a navigation path the user can follow
# ─── ROUND C — CTA Matrix (object × role → actions) ─────────────
# Verbs are mined from the stories/activities for this capability and hung onto
# the object they act on. role references product/index.yaml personas[].id.
ctas:
- object: <object-id>
role: <persona-id>
actions:
- verb: "create" # the action (imperative)
from_story: <STORY-ID> # provenance: the story whose verb this is (or activity:<id>)
placement: collection # collection | detail | inline | global
priority: primary # primary | secondary
# ─── ROUND A — attribute priority per object ────────────────────
# What shows where. core → card + detail; secondary → detail only;
# metadata → sort/filter keys (not necessarily rendered).
attributes:
- object: <object-id>
core: [] # always visible
secondary: [] # detail only
metadata: [] # sort/filter keys
# ─── ROUND 4 — representation hints (structural IA only) ─────────
# NOT visual design. Just which structural views each object needs.
screens:
- object: <object-id>
views: ["collection", "detail"] # collection (list/grid) | detail | create | edit
card_fields: [] # subset of attributes.core shown on the card
empty_state: "What to show when there are none"
navigation:
anchor_object: <object-id> # the capability's home object (what the user sees first)
entry_points: [] # object ids reachable from primary nav
# ─── OBJECT-FIRST DEFAULT + TASK-ORIENTED ESCAPE HATCH ──────────
# Every flow defaults to object_first (noun→verb: pick an object, then act).
# List a flow here ONLY to deviate to task_oriented (a deliberate wizard), with a
# reason grounded in the decision framework (single goal / novice / linear / low
# off-path tolerance — see orca-process.md and docs/research/ooux-object-modeling.md §6).
interaction_modes:
- flow: <flow-name> # e.g., "onboarding", "checkout"
mode: task_oriented # object_first (default, no need to list) | task_oriented
reason: "Why a wizard wins here."
# ─── STORY COVERAGE INDEX ───────────────────────────────────────
# Which stories realize which objects/screens. /aep-dispatch reads this to inject
# the minimal slice into a story's context package.
coverage:
- story: <STORY-ID>
objects: [<object-id>]
screens: ["<object-id>:collection"]
open_questions: [] # unresolved IA/object decisions surfaced to the human
# Object Model Schema — product/object-model.yaml
#
# The cross-capability, USER-FACING object ontology (OOUX Rounds O + R).
# Created and updated by /aep-model. This is a STABLE product-design file — it
# lives under product/ alongside product/index.yaml, NOT inside the operational
# product-context.yaml. product-context.yaml carries only a thin reference in
# calibration.history (dimension: object-model).
#
# These are MENTAL-MODEL objects — the things the user perceives and acts on —
# NOT backend data entities. They may map onto architecture.domain_model entities
# (cross-linked via `backs_onto`), but the two are different lenses: domain_model
# is how the system stores data; object-model is how the user thinks.
#
# Authoring: /aep-map produces a first draft; /aep-model presents it, asks the
# few high-leverage questions, and the human approves. Do not hand-author the
# whole thing — it is mined from existing AEP artifacts (activities, stories,
# domain_model) and then human-reviewed.
#
# See model/references/orca-process.md for how each field is derived.
schema: object-model/v1
project: <project-name>
updated_at: <ISO 8601>
# ─── OBJECTS (ORCA Round O — Noun Foraging) ──────────────────────
# The shared language of user-facing objects across the whole product.
# Foraged from product.activities, story descriptions, problem/persona text,
# and architecture.domain_model. Keep names consistent with docs/glossary.md
# (ubiquitous language).
objects:
- id: <object-slug> # e.g., "order", "report", "avatar"
name: "Human-readable" # e.g., "Order"
aliases: [] # other names users or the team call this thing
description: "One sentence — what this object is in the user's world"
backs_onto: [] # architecture.domain_model entity names (0..n; may be empty for pure-UI objects)
appears_in: [] # capability ids (from product/index.yaml capabilities[]) that use this object
source_evidence: # noun-foraging provenance — where this object was found
- "activity:<activity-id>"
- "story:<STORY-ID>"
- "domain_model:<entity>"
confidence: high # high | medium | low — extraction confidence (low → ask the human)
naming_decision: null # why this name won (fill when there was a real choice)
open_questions: [] # unresolved questions about this object's boundary
# ─── RELATIONSHIPS (ORCA Round R — cross-capability spine) ───────
# The shared backbone of the Nested Object Matrix. Capability-LOCAL relationships
# live in product/maps/<capability>/object-map.yaml; only put cross-capability
# links here.
relationships:
- from: <object-id>
to: <object-id>
cardinality: one_to_many # one_to_one | one_to_many | many_to_many
label: "owns" # how the user understands the link (verb phrase)
nested: true # does `from` show `to` inline (drives detail-view composition)?
# ─── SHARED ATTRIBUTES ───────────────────────────────────────────
# Attributes an object carries everywhere. Capability-specific attributes belong
# in the capability object-map. kind: core (always shown) | secondary (detail
# only) | metadata (sort/filter keys).
shared_attributes:
- object: <object-id>
attributes:
- name: <attr>
kind: core # core | secondary | metadata
type: string # display-type hint (string | number | date | enum | image | ref)
notes: null
# ─── NAMING DECISIONS (ubiquitous language log) ──────────────────
# Keep the user-facing vocabulary stable. Mirror confirmed names into
# docs/glossary.md when they become product-wide terms.
naming_decisions:
- object: <object-id>
chosen: "Order"
rejected: ["Purchase", "Transaction"]
reason: "Users say 'my orders', not 'my transactions'."
# ─── PROVENANCE ──────────────────────────────────────────────────
provenance:
generated_by: aep-map # aep-map (first draft) | aep-model (refined at review)
rounds_completed: ["O", "R"] # which ORCA rounds this file reflects
reviewed: false # set true after the /aep-model human review gate
reviewed_at: null
Opportunity Brief Template
This document captures the core bet behind a product idea. It is deliberately short — one page maximum. Its purpose is to force clarity before committing resources to product design. If the opportunity cannot be stated clearly in this format, it is not ready for Phase 1.
---
The Bet
I believe [target user — be specific] has [problem — describe the pain, not the solution] and currently [how they deal with it today — workarounds, alternatives, suffering] I can build [solution — one sentence] because [unfair advantage — why you specifically can do this]
---
Why Now
What has changed — in technology, market, regulation, or culture — that makes this opportunity viable today when it was not viable before? If nothing has changed, the opportunity may not be real.
---
Strongest Counter-Arguments
List the 2–3 most compelling reasons this might fail or not matter. Be honest. If you cannot articulate the risks, you have not thought deeply enough.
1. [Counter-argument 1] 2. [Counter-argument 2] 3. [Counter-argument 3]
---
Scale of Impact
If this works, what is the magnitude of change? "Saves 10 minutes per week" is different from "enables a workflow that was previously impossible." Neither is inherently better, but the answer shapes every downstream decision.
---
Kill Criteria
Under what conditions should this idea be abandoned? Define this now, before sunk-cost bias sets in.
- [Condition that would invalidate the opportunity]
- [Condition that would invalidate the opportunity]
---
Decision
- [ ] Proceed to Phase 1: Product Framing
- [ ] Kill — opportunity does not justify further investment
- [ ] Defer — revisit when [condition]
# Product Context Schema v1
#
# Single source of truth for the entire product.
# Created by /aep-envision and /aep-map, updated by /aep-dispatch, /aep-build, /aep-wrap, /aep-reflect, /aep-calibrate.
# Committed to git — version history tracks how the product evolves.
#
# Sections are populated incrementally:
# /aep-envision → opportunity, product (incl. quality_dimensions), calibration.plan
# /aep-map → architecture, stories, topology, layer_gates, cost
# /aep-model → product/object-model.yaml + product/maps/<cap>/object-map.yaml (standalone); calibration.history (dimension: object-model); stories[].object_model_refs
# /aep-calibrate → calibration.history, inline updates to architecture/product sections
# /aep-dispatch → stories[].status, stories[].openspec_change (per story)
# /aep-build → .dev-workflow/signals/status.json (story_status, pr_url, cost_usd)
# /aep-wrap → stories[].status, stories[].completed_at, stories[].pr_url, stories[].cost_usd (read from signals)
# /aep-reflect → changelog, stories (new), architecture (amendments)
schema: v1
project: <project-name>
version: "0.1.0" # semantic: major (opportunity shift), minor (architecture/map change), patch (dispatch/build/wrap)
updated_at: <ISO 8601>
dispatch_epoch: 0 # incremented on every /aep-dispatch run — consistency marker for agents
# ─── SPLIT MODE (v2) ────────────────────────────────────────
# Products can operate in two modes:
#
# 1. Single-file (v1): All sections live in product-context.yaml.
# This is the default for simple projects.
#
# 2. Split-mode (v2): Stable product definition lives in
# product/index.yaml. Operational state lives in
# product-context.yaml. Skills check product/index.yaml first;
# if absent, fall back to product-context.yaml.
#
# In split mode, product-context.yaml omits `opportunity` and
# `product` sections. The `product/index.yaml` file uses the
# `personas` field (list) instead of `product.persona` (object).
#
# Discovery convention used by all skills:
# product_def = product/index.yaml if exists, else product-context.yaml
# operational = product-context.yaml
# ─── CONCURRENCY PROTOCOL ────────────────────────────────────
# Only the MAIN SESSION writes to this file. Workspace agents
# report status through .dev-workflow/signals/. The main session
# (via /aep-wrap, /aep-dispatch, /aep-reflect) reads signals and updates the
# YAML. This prevents git merge conflicts from concurrent writers.
# ─── OPPORTUNITY (/aep-envision Phase 0) ─────────────────────────
# Validates whether the idea is worth building at all.
# Kill early if the opportunity doesn't survive a 5-minute challenge.
opportunity:
bet: "I believe [target user] has [problem], and I can build [solution] because [advantage]"
why_now: "What changed that makes this viable now?"
counter_arguments:
- "Strongest reason this might fail"
scale_of_impact: "What magnitude of change if this works?"
kill_criteria:
- "Condition that would invalidate the opportunity"
decision: proceed # proceed | kill | defer
decided_at: <ISO date>
# ─── PRODUCT (/aep-envision Phase 1) ─────────────────────────────
# Precise product definition. Every statement must be convertible
# into a verification condition. Vague statements are useless to agents.
product:
problem: "Sharp problem statement — who, what, why inadequate"
persona:
description: "Concrete user description with context, skill level, tools, constraints"
jtbd: "When [situation], I want to [motivation], so I can [outcome]"
goals: # behavior-observable statements, verifiable by observing the running system
- "e.g., Poll the issue tracker on a fixed cadence and dispatch work with bounded concurrency"
non_goals: # things the product deliberately will NOT do, with reasoning
- statement: "e.g., Rich web UI"
reasoning: "This is a CLI-first tool. A web dashboard is a separate product."
mvp_boundary:
in_scope:
- "Specific capability (testable)"
out_of_scope:
- "What this does NOT do, even if users might expect it"
deferred:
- "Might add later, but agents should not build toward this"
constraints:
required_stack:
frontend: <framework>
backend: <framework>
database: <engine>
orm: <orm>
preferred_stack: {} # overridable with good reason
infrastructure: "Where this runs, deployment targets"
external_deps:
- name: <service>
provides: "What it does for us"
failure_mode: "Graceful degradation or hard fail?"
layers:
- layer: 0
name: "Walking Skeleton"
user_can: "End-to-end journey in concrete steps"
verification: "Test scenario that proves Layer 0 works"
outcome_contract: # optional — anchors the layer in product outcomes, not just feature completion
hypothesis: "If users can complete the basic flow, architecture is validated"
success_metric:
type: task_completion_rate # task_completion_rate | time_to_complete | error_rate | satisfaction_score
target: ">= 60%"
decision_rule:
keep_if: "metric >= target"
otherwise: "reflect_and_reslice" # /aep-reflect evaluates and may re-slice
- layer: 1
name: <layer-name>
user_can: "Everything from Layer 0, plus..."
verification: "Test scenario for new capabilities"
outcome_contract: null # same structure as above, or null if not yet defined
# ── USER ACTIVITIES (Story Map Backbone) ──
# The horizontal axis of a Jeff Patton story map.
# Each activity is a discrete step in the user's journey, ordered left-to-right.
# Extracted during /aep-envision BEFORE defining layers — backbone first, releases second.
# Stories reference activities by id. Infrastructure stories leave activity null.
activities:
- id: <activity-slug> # e.g., "upload-selfie", "generate-avatar"
name: "Human-readable name" # e.g., "Upload Selfie"
description: "What the user does" # User-centric verb phrase
order: 1 # Sequence in journey (backbone left-to-right)
layer_introduced: 0 # Which layer first enables this activity
# ── FAILURE MODEL ──
# Undocumented failure modes become undocumented bugs.
failure_model:
classes:
- name: "e.g., External Service Failures"
examples: "API timeout, non-200 status, malformed response"
detection: "HTTP status code checks, response schema validation"
recovery: "Retry with exponential backoff, skip dispatch for this tick"
escalation: "Log warning, continue service, alert operator after 3 consecutive failures"
degraded_operation: "What the system can still do when a dependency is down"
# ── SECURITY MODEL ──
# Trust boundaries must be explicit. Implicit trust assumptions cause the worst bugs.
security_model:
trust_boundaries: "What is trusted vs. what is not"
auth: "Authentication and authorization approach (or 'N/A for MVP')"
secret_handling: "Where secrets come from, how stored, how passed to components"
success_criteria:
functional:
- "Specific, testable condition"
non_functional:
- "Measurable threshold (e.g., API p95 < 200ms)"
open_questions:
- question: "Decision explicitly deferred"
default_assumption: "What agents should assume for now"
revisit_trigger: "When to revisit this"
decisions:
- decision: "What was decided"
reasoning: "Why"
alternatives:
- "What else was considered"
stress_test:
- challenge: "What was challenged"
angle: product_viability # product_viability | technical_feasibility | scope_control
resolution: "How it was resolved"
# ── QUALITY DIMENSIONS ──
# Dimensions where human judgment is needed that agents cannot provide.
# Declared during /aep-envision, checked by /aep-reflect, executed by /aep-calibrate.
# Not every dimension needs calibration — only those where "correct but not right" is likely.
# `object-model` is a STRUCTURAL gate (auto-drafted by /aep-model, human-approved),
# not a taste calibration — declare it for UI-facing products so UI stories get a
# noun-first Object Map before build. See skills/product-context/model.
quality_dimensions:
- dimension: visual-design # visual-design | ux-flow | object-model | api-surface | data-model | scope-direction | copy-tone | performance-quality
criticality: high # high | medium | low
first_calibration_layer: 0.5
rationale: "Why this dimension needs human calibration"
# ─── CALIBRATION (/aep-envision + /aep-calibrate) ────────────────────
# Tracks which quality dimensions need human alignment checkpoints
# and the history of calibration decisions.
# Plan populated by /aep-envision (refined by /aep-map), history populated by /aep-calibrate.
# .5 layers are "human alignment layers" — the team pauses agent execution
# to recalibrate intent across one or more quality dimensions.
calibration:
plan:
- layer: 0.5
dimensions: ["visual-design"] # from product.quality_dimensions
trigger: "Condition that activates this calibration"
# /aep-calibrate appends taste-dimension decisions here; /aep-model appends
# object-model approvals (artifact_path → product/maps/<cap>/object-map.yaml).
history:
- dimension: visual-design
calibrated_at: <ISO date>
calibrated_from_layer: 0.5
mode: establishment # establishment | extension
artifact_path: "calibration/visual-design.yaml" # null for inline (light) calibrations
sections_updated: [] # e.g., ["architecture.interfaces"] for inline calibrations
summary: "What was decided"
# ─── ARCHITECTURE (/aep-map Step 1) ──────────────────────────────
# Module boundaries and interface contracts. A wrong boundary costs
# more than any implementation bug. Human review required.
architecture:
style: "e.g., modular monolith, microservices"
overview: "2-3 sentences on structure and why"
technical_spec: null # optional path to standalone technical specification document (e.g., "docs/technical-spec.md")
modules:
- name: <module-name>
kind: backend # ui | backend | shared — `ui` modules render user-facing surfaces (used by /aep-model + the UI-facing story trigger)
responsibility: "What this module does"
does_not: "What this module does NOT do (defines the boundary)"
owns:
- "Data/state/resources this module is authority on"
depends_on:
- <other-module>
technology: null # null = default stack
key_concepts: # domain objects, patterns, abstractions implementers need
- "e.g., tokens, sessions, RBAC roles"
# ── DOMAIN MODEL ──
# Typed entity definitions for cross-cutting domain objects.
# Complements per-module key_concepts with precise schemas when entities span module boundaries.
domain_model:
- name: <entity-name>
purpose: "One sentence — what this entity represents"
fields:
- name: id
type: string
default: null
required: true
notes: "Stable identifier. Derived from [rule]."
normalization_rules:
- "e.g., Identifiers lowercased and slugified"
invariants:
- "e.g., A completed entity always has a non-null completed_at"
# ── PROTOCOL SEQUENCES ──
# Multi-step interaction contracts (handshakes, streaming, stateful exchanges).
# Only needed when interface contracts have ordering, state, or timing constraints.
protocol_sequences:
- name: <protocol-name>
participants: ["module-a", "module-b"]
trigger: "What initiates this protocol"
steps:
- sender: module-a
message_type: "init"
timeout_ms: 5000
payload_example: null # optional illustrative JSON payload (e.g., '{"type": "init", "params": {...}}')
- sender: module-b
message_type: "ack"
payload_example: null
error_behavior: "What happens if a step fails"
timeout_behavior: "What happens if a step takes too long"
interfaces:
- from: <module>
to: <module>
protocol: "HTTP REST | gRPC | function call | message queue"
endpoint: "Specific API path or function signature"
request: {} # exact data structure
response: {} # exact data structure
errors:
- code: <number>
meaning: "What this error means"
sla: null # expected latency, throughput
data_flows:
- journey: "User journey name"
path: "User → [Module] → action → [Module] → response"
third_party:
- name: <service>
provides: "Specific capability"
integration_point: "Which module, how"
failure_mode: "Behavior when down"
deployment:
environments: ["local", "staging", "production"]
module_runtime: {} # { "auth": "bun", "worker": "cloudflare-workers" }
persistence: {} # { "users": "postgres", "cache": "redis" }
amendment_log: # boundary/contract issues found during story decomposition
- proposed_by: <agent-or-module>
module_affected: <module>
proposed_change: "What should change"
reasoning: "Why"
status: pending # pending | accepted | rejected
adrs:
- id: ADR-001
title: "Architecture decision title"
context: "What prompted this"
decision: "What was decided"
reasoning: "Why"
consequences: "What this enables and constrains"
# ─── STORIES (/aep-map Steps 2-3) ────────────────────────────────
# The atomic units of work. Each story is a self-contained spec
# that an agent can implement without asking questions.
#
# State machine:
# pending → ready (all dependencies completed)
# pending → blocked (a dependency failed)
# ready → in_progress (/aep-dispatch assigns)
# in_progress → in_review (PR submitted)
# in_review → completed (verification passes)
# in_review → in_progress (verification fails, retry)
# in_progress → failed (retry limit exceeded)
# any → deferred (user postpones)
#
# Recovery transitions (user-initiated):
# failed → pending (user resets after spec fix)
# blocked → pending (blocking dependency resolved)
# deferred → pending (user un-defers)
stories:
- id: <PROJECT>-001
title: "Short, descriptive"
layer: 0
module: <module-name>
activity: null # user activity from product.activities (null for infrastructure stories)
capability: null # capability id (product/index.yaml capabilities[].id); /aep-map sets it. Null in v1/single-journey → resolves to the default capability (project slug). Used to locate product/maps/<capability>/object-map.yaml.
calibration_type: null # visual-design | ux-flow | api-surface | data-model | scope-direction | copy-tone | performance-quality (null for non-calibration stories)
object_model_refs: [] # /aep-model: object-map slice(s) this UI story realizes, as "<object-map-path>#<object-id>" where <object-id> ∈ object-map primary_objects/supporting_objects, e.g. ["product/maps/dashboard/object-map.yaml#order"] (empty for non-UI stories)
slice: 1 # execution slice within layer (parallel batch)
status: pending
priority: critical # critical | high | medium | low
business_value: null # 1-10 numeric. If null, derived from priority: critical=10, high=7, medium=4, low=1
complexity: S # S | M | L
compile_mode: single_change # single_change | grouped_change | shared_enabler
change_group: null # group ID for grouped_change mode (max 3 stories per group)
dependencies: [] # story IDs that must complete first
description:
what_changes: "Observable difference when complete"
why: "Connection to Context Document / layer"
acceptance_criteria:
- "Specific, automatable test criterion"
interface_obligations:
implements: [] # interface contracts this story creates/modifies
consumes: [] # interface contracts this story calls
contract_tests_required: false
files_affected:
- "path/to/likely/file.ts" # for conflict detection
technical_notes: null # optional — known pitfalls, guidance to prevent mistakes
verification:
unit: ["What to unit test"]
integration: ["Cross-module tests"]
contract: ["Interface compliance tests"]
# ── Dispatch scoring (computed by /aep-dispatch, not manually set) ──
readiness_score: null # 0.0-1.0 spec completeness (acceptance criteria + interfaces + files + verification + open questions)
dispatch_score: null # (business_value + unblock_potential + critical_path_urgency + reuse_leverage) / (complexity_cost + ambiguity_penalty + interface_risk)
on_critical_path: false
# ── Execution tracking (updated by /aep-dispatch, /aep-build, /aep-wrap) ──
assigned_to: null # workspace session name
openspec_change: null # OpenSpec change name
dispatched_at_epoch: null # which dispatch_epoch assigned this story
attempt_count: 0
max_retries: 4 # override per story (default from topology.routing.retry)
cost_usd: null
started_at: null # ISO 8601
completed_at: null # ISO 8601
pr_url: null
failure_logs: # structured — not free text
- attempt: 1
error_class: test_failure # test_failure | timeout | context_overflow | merge_conflict
approach_summary: "What the agent tried"
failure_point: "Which step failed"
root_cause: "Best guess at why"
unexplored_alternatives: [] # critical for fresh-agent retries
timestamp: <ISO 8601>
# ─── TOPOLOGY (/aep-map Step 4) ──────────────────────────────────
# Agent roles, contracts, and routing. Defined at planning time
# because agents need clear boundaries before execution starts.
topology:
roles:
- name: implementer
purpose: "Takes story spec, produces code + tests + PR"
does:
- "implement"
- "unit test"
- "contract test"
- "submit PR"
does_not:
- "decide scope"
- "modify architecture"
- "skip tests"
input_contract: # schema-defined — agents communicate through structured artifacts
story_spec: "Full story from stories section"
context_slice:
context_document: "Pruned to: problem, constraints, relevant layer"
system_map_module: "This story's module definition"
adjacent_interfaces: "Interface contracts where from/to matches module"
dependency_artifacts: "Public API surface from completed dependencies"
output_contract:
implementation:
branch_name: "string"
files_changed: "FileDiff[]"
pr_url: "string"
verification:
unit_tests: "TestResult[]"
contract_tests: "TestResult[]"
all_passing: "boolean"
status_report:
story_id: "string"
outcome: "success | failure"
error_summary: "string (if failure)"
what_was_not_tried: "string[] (critical for fresh-agent retries)"
context_composition:
- "Story spec (full)"
- "Context Document (pruned: problem, constraints, relevant layer)"
- "System Map (story's module + adjacent interfaces only)"
- "Dependency artifacts (public API surface only)"
cost_budget:
input_tokens_max: 50000
output_tokens_max: 20000
alert_threshold_total: 100000
routing:
dispatch: fifo_within_slice # dispatch policy
concurrency_limit: 5 # max parallel agents
conflict_detection: files_affected_overlap
retry: "2x same agent → failure analyst → fresh agent → human escalation"
autonomous: false # true = /aep-autopilot can dispatch without human confirmation
auto_design: false # true = skip /aep-design, go straight to /aep-launch for ambiguous stories
skip_human_eval: none # none | backend | all — which stories skip human eval in /aep-wrap
# ─── v2 autonomy (all default to human-in-the-loop; opt-in only) ───
full_auto: false # master switch — true automates the strategic gates (design escalation, qualitative outcome eval); implies auto_design + auto_outcome_eval + watch.auto_create
auto_outcome_eval: none # none | quantitative — quantitative layer outcome contracts auto-evaluate from telemetry (see reflect/references/telemetry-ingestion.md)
deploy_targets: # post-deploy dogfood targets (G4); omit → fall back to CI/deploy output
staging_url: null
production_url: null
dogfood: # host-aware post-deploy validation (see executor/references/dogfood-validation.md)
method: auto # auto | agent-browser | codex-native | playwright
post_deploy_env: none # none | staging | production
on_issue: create_story # create_story | escalate
post_merge_guard: # G4a — watch merged stories' deploy health (see autopilot/references/post-merge-guard.md)
window_min: 15
auto_revert: false # conservative default: warn + escalate only; true = auto `gh pr revert` on confirmed regression
health_signals: [] # e.g. ["ci_status", "error_rate", "health_endpoint"]
telemetry_sources: [] # G5 — read-only signal sources. Detected by /aep-scaffold audit (or set by hand); /aep-map binds each needed quantitative success_metric + health_signal via metric_map (coverage rule: reflect/references/telemetry-ingestion.md §1.5). token_env only — never embed secrets.
# - { kind: error_stream, endpoint: "https://…?since={since}", token_env: SENTRY_TOKEN, metric_map: { error_rate: "<query>" } }
watch: # G6 /aep-watch self-feeding discovery
sources: []
interval: 30m
auto_create: false # surface proposed stories for confirmation; true (or full_auto) = auto-create + dispatch
handoffs:
- from: implementer
to: evaluator
trigger: implementation_complete # Phase 4 done, Phase 5 starts — evaluator runs BEFORE PR creation
payload: "eval-request.md + code diff + contracts.md + feature-verification.json"
validation:
- "All tasks committed on the feature branch"
- "Dev server running and accessible"
- "eval-request.md created with round number and change summary"
on_validation_failure: "reject handoff, notify source agent"
# ─── LAYER GATES ─────────────────────────────────────────────
# Integration tests that verify a completed layer works as a whole.
# Must pass before advancing to the next layer.
layer_gates:
- layer: 0
status: not_started # not_started | running | passed | failed
test_definition: "End-to-end user journey from Layer 0 MVP contract"
results: # structured test results
tests_run: 0
tests_passed: 0
tests_failed: 0
failures: [] # [{ test: "...", error: "...", boundary: "module_a → module_b" }]
completed_at: null
# ─── WAVES (/aep-map Step 3) ─────────────────────────────────────
# Groups stories by layer + wave for batch dispatch.
# Computed by /aep-map from the dependency DAG. Wave 1 has no in-layer
# dependencies; Wave 2 depends on Wave 1 completing; etc.
# User-facing term: "Wave". YAML field: stories[].slice.
waves:
- layer: 0
wave: 1
stories: [] # story IDs in this wave
theme: "Walking skeleton foundation"
- layer: 0
wave: 2
stories: []
theme: "Walking skeleton integration"
# ─── COST TRACKING ───────────────────────────────────────────
# Accumulated cost data. Updated by /aep-build after each story.
# Reviewed by /aep-reflect for optimization opportunities.
cost:
total_usd: 0
by_layer: {} # { "0": 12.50, "1": 8.30 }
by_module: {} # { "auth": 5.20, "api": 7.30 }
by_story: {} # { "PROJ-001": 3.10 }
alerts: # structured anomaly records
- story_id: null
type: cost_exceeded # cost_exceeded | retry_concentration | timeout_pattern
detail: "Description of the anomaly"
threshold: null
actual: null
timestamp: null
# ─── CHANGELOG ───────────────────────────────────────────────
# Semantic history of how the product context evolved.
# git log shows file diffs; changelog shows why things changed.
# Appended by every skill that modifies this file.
changelog:
- date: <ISO date>
type: initial # initial | envision_update | map_update | dispatch | reflection | outcome_evaluation | build | wrap | layer_gate_pass | layer_gate_fail | architecture_review
author: human # human | agent
summary: "What changed and why"
sections_changed:
- <section-name>
# Only for type: reflect
feedback:
bugs: []
refinements: []
discoveries: []
opportunity_shifts: []
Symphony SPEC.md — Specification Writing Reference
This document extracts reusable documentation patterns from OpenAI's Symphony SPEC.md, a ~15,000-word language-agnostic specification for a coding agent orchestration service. Symphony's spec is notable because it is precise enough that any coding agent can implement it in any programming language without clarifying questions.
Use this reference when writing specifications for systems with protocol-level complexity. The patterns here are the standard to aim for.
---
Source Structure
Symphony's SPEC.md has 15 sections. Each maps to an AEP template:
| # | Symphony Section | What It Does | AEP Template |
|---|---|---|---|
| 1 | Problem Statement | Defines what the service IS, what problems it solves, what it is NOT | context-document.md (Problem Statement) |
| 2 | Goals and Non-Goals | Behavior-observable goals, explicit non-goals with reasoning | context-document.md (Goals/Non-Goals) |
| 3 | System Overview | Named components + abstraction layers + external deps | system-map.md (Modules) |
| 4 | Core Domain Model | Typed entity fields, defaults, normalization rules, stable identifiers | system-map.md (Domain Model) |
| 5 | Workflow Specification | Repository contract with schema, validation, dynamic reload | technical-spec.md (Configuration) |
| 6 | Configuration Specification | Source precedence, typed getters, dynamic reload, config cheat sheet | technical-spec.md (Configuration) |
| 7 | Orchestration State Machine | Named states, transition triggers, idempotency rules | technical-spec.md (State Machines) |
| 8 | Polling, Scheduling, Reconciliation | Poll loop, candidate selection, concurrency, retry/backoff | technical-spec.md (Protocol Specs) |
| 9 | Workspace Management and Safety | Filesystem lifecycle, hooks, safety invariants | technical-spec.md (Security) |
| 10 | Agent Runner Protocol | Launch contract, handshake with JSON transcripts, streaming | technical-spec.md (Protocol Specs) |
| 11 | Issue Tracker Integration | Adapter contract, query semantics, normalization rules | system-map.md (Interface Contracts) |
| 12 | Prompt Construction | Template rendering, retry semantics, failure handling | technical-spec.md (Protocol Specs) |
| 13 | Logging, Status, Observability | Structured logs, runtime snapshots, optional HTTP API | technical-spec.md (Observability) |
| 14 | Failure Model | 5 failure classes, per-class recovery, restart recovery | context-document.md (Failure Model) / technical-spec.md |
| 15 | Security and Operational Safety | Trust boundaries, filesystem safety, secret handling | context-document.md (Security Model) / technical-spec.md |
---
Extracted Patterns
Pattern 1: Problem-First Framing
Symphony opens with a single sentence saying what the service IS, then lists exactly 4 operational problems it solves, then states what it is NOT.
How Symphony does it:
"Symphony is a long-running automation service that continuously reads work from an issue tracker, creates an isolated workspace for each issue, and runs a coding agent session for that issue inside the workspace."
>
The service solves four operational problems: [enumerated list]
>
Important boundary: Symphony is a scheduler/runner and tracker reader. Ticket writes are performed by the coding agent.
Why it works: An agent reading this spec knows in 3 paragraphs exactly what to build and — critically — what NOT to build. The "important boundary" prevents the most common scope creep.
Use in AEP: context-document.md Problem Statement section. Ensure every problem statement includes a "what it is NOT" boundary.
---
Pattern 2: Behavior-Observable Goals with Explicit Non-Goals
Goals are statements that can be verified by observing the running system. Non-goals are things a reasonable person might expect but the system deliberately excludes.
How Symphony does it:
Goals:
- "Poll the issue tracker on a fixed cadence and dispatch work with bounded concurrency."
- "Create deterministic per-issue workspaces and preserve them across runs."
- "Recover from transient failures with exponential backoff."
Non-Goals:
- "Rich web UI or multi-tenant control plane."
- "General-purpose workflow engine or distributed job scheduler."
Why it works: Each goal is testable — you can observe the system and confirm it does (or doesn't do) the thing. Non-goals prevent agents from gold-plating.
Use in AEP: context-document.md Goals/Non-Goals section. Distinct from "In Scope / Out of Scope" — scope defines what the system does, goals define how you know it's working.
---
Pattern 3: Typed Entity Definitions with Normalization Rules
Every domain entity has typed fields with defaults, and normalization rules that prevent ambiguity.
How Symphony does it:
Issue:
id (string) — Stable tracker-internal ID
identifier (string) — Human-readable ticket key (example: ABC-123)
priority (integer or null) — Lower numbers are higher priority
labels (list of strings) — Normalized to lowercase
blocked_by (list of blocker refs) — Each contains id, identifier, stateNormalization rules:
- "Workspace Key: Derive from issue.identifier by replacing any character not in [A-Za-z0-9._-] with \_"
- "Normalized Issue State: Compare states after lowercase"
Why it works: No ambiguity about what a field contains, what type it is, what the default is, or how to compare values. A coding agent in any language can implement this without guessing.
Use in AEP: system-map.md Domain Model section. Replace unstructured "Key internal concepts" bullet lists with typed field tables.
---
Pattern 4: State Machine Documentation
Stateful entities get explicit state diagrams with named states, transition triggers, and recovery rules.
How Symphony does it:
Orchestration states (distinct from tracker states):
1. Unclaimed — not running, no retry scheduled 2. Claimed — reserved to prevent duplicate dispatch 3. Running — worker task exists 4. RetryQueued — worker not running, retry timer exists 5. Released — claim removed
Transition triggers: Poll Tick, Worker Exit (normal), Worker Exit (abnormal), Retry Timer Fired, Reconciliation State Refresh, Stall Timeout.
"Important nuance: A successful worker exit does not mean the issue is done forever."
Why it works: Every state is named, every transition has a trigger, and easy-to-miss nuances are called out explicitly.
Use in AEP: technical-spec.md State Machines section. Also useful in system-map.md when a module owns a state machine.
---
Pattern 5: Protocol Specs with Illustrative JSON Transcripts
Multi-step interactions get exact handshake sequences with example payloads.
How Symphony does it:
{"id":1,"method":"initialize","params":{"clientInfo":{"name":"symphony","version":"1.0"},"capabilities":{}}}
{"method":"initialized","params":{}}
{"id":2,"method":"thread/start","params":{"approvalPolicy":"...","sandbox":"...","cwd":"/abs/workspace"}}
{"id":3,"method":"turn/start","params":{"threadId":"<thread-id>","input":[{"type":"text","text":"<rendered prompt>"}]}}Each step includes: what to send, what to expect back, timeout behavior, error mapping.
Why it works: An implementor can literally trace through the JSON transcript to verify their implementation. No prose interpretation needed.
Use in AEP: system-map.md Protocol Sequences section and technical-spec.md Protocol Specifications section.
---
Pattern 6: "Important Boundary" / "Important Nuance" Callouts
Inline blockquote markers flag precision points that are easy to miss.
How Symphony does it:
Important boundary: Symphony is a scheduler/runner and tracker reader. Ticket writes are typically performed by the coding agent.
Important nuance: A successful worker exit does not mean the issue is done forever. The orchestrator schedules a short continuation retry.
Why it works: Agents scan documents for actionable information. These callouts are semantic anchors that prevent the most common implementation mistakes. They stand out visually and can be grep'd.
Use in AEP: Convention across all templates — system-map.md, technical-spec.md, and anywhere precision matters.
---
Pattern 7: Agent-Friendly Redundancy (Config Cheat Sheet)
Symphony includes a section explicitly labeled "intentionally redundant" that summarizes all configuration in one flat table.
How Symphony does it:
"This section is intentionally redundant so a coding agent can implement the config layer quickly."
>
-tracker.kind: string, required, currentlylinear
-polling.interval_ms: integer, default30000
-workspace.root: path, default<system-temp>/symphony_workspaces
[... all fields in one list]
Why it works: Agents pay a cognitive/context tax for cross-referencing. A redundant summary eliminates cross-referencing entirely for the most common implementation task (reading config).
Use in AEP: story-spec.md Implementation Cheat Sheet section. Applied at the story level (not product level) because the relevant subset varies per story.
---
Pattern 8: Enumerated Failure Classes with Per-Class Recovery
Failures are taxonomized into classes, each with detection method and recovery behavior.
How Symphony does it:
5 failure classes:
1. Workflow/Config Failures — missing files, invalid YAML, missing credentials 2. Workspace Failures — directory creation, hook timeout, invalid paths 3. Agent Session Failures — handshake failure, turn timeout, subprocess exit 4. Tracker Failures — API errors, non-200 status, malformed payloads 5. Observability Failures — snapshot timeout, dashboard render errors
Each class has explicit recovery: "Dispatch validation failures: Skip new dispatches. Keep service alive. Continue reconciliation."
Why it works: Undocumented failure modes become undocumented bugs. By enumerating failure classes early, every implementor handles the same set of failure scenarios the same way.
Use in AEP: context-document.md Failure Model section (product-level) and technical-spec.md Failure Model section (implementation-level).
---
Pattern 9: Trust Boundary Documentation
Security is not a checklist — it's a boundary declaration about what is trusted and what is not.
How Symphony does it:
"Each implementation defines its own trust boundary."
>
"Implementations should state clearly whether they are intended for trusted environments, more restrictive environments, or both."
>
"Hooks are fully trusted configuration. Hooks run inside the workspace directory."
Filesystem safety is mandatory:
- Workspace path must remain under configured workspace root
- Coding-agent cwd must be the per-issue workspace path
- Workspace directory names must use sanitized identifiers
Why it works: Rather than prescribing specific security controls, Symphony forces each implementation to explicitly declare its trust posture. This prevents the worst outcome: implicit trust assumptions that no one documented.
Use in AEP: context-document.md Security Model section (trust boundaries and auth) and technical-spec.md Security section (filesystem safety, secret handling).
---
Key Principle
The overarching principle from Symphony's approach:
Define WHAT the system does and HOW it behaves under all conditions. Let implementors decide the programming language, framework, and internal architecture.
This is what separates a specification from documentation. Documentation describes what was built. A specification defines what must be built — precisely enough that the builder needs no clarifying questions.
Story Specification Template
The atomic unit of work for the execution plane. A well-written story spec gives an agent everything it needs to implement, verify, and submit a PR without asking questions.
Quality bar: a single-responsibility agent reading only this spec, the Context Document, and the relevant System Map slice should produce correct, mergeable code.
---
Metadata
- Story ID: [Unique identifier, e.g.,
SANDBOX-001] - Title: [Short, descriptive]
- Layer: [0, 1, 2… — which development layer]
- Module: [Primary module, as defined in System Map]
- Activity: [Which user activity this enables, from
product.activities. Null for infrastructure stories that don't directly serve a user journey step.] - Wave: [Which wave (execution slice) within the layer this belongs to]
- Dependencies: [Story IDs that must complete before this starts]
- Estimated complexity: [S / M / L]
---
Description
What changes when this story is complete
[Observable difference in the system. Focus on behavior, not implementation. The agent decides how; this spec defines what.]
Why this story exists
[Connect to the Context Document. Which layer of the MVP contract does this serve? Why this layer and not a later one?]
---
Acceptance Criteria
Each must be automatable as a test. If it cannot be automated, it is too vague or belongs in manual review.
1. [Criterion — specific, observable, testable] 2. [Criterion] 3. [Criterion]
---
Interface Obligations
If this story touches a module boundary:
- Implements: [Endpoints/APIs created or modified, referencing System Map contracts]
- Consumes: [Other module APIs called, referencing System Map contracts]
- Contract tests required: [Yes/No]
---
Technical Notes
[Optional. Only include guidance that prevents known pitfalls. Do not over-specify — let the agent choose its approach.]
---
Implementation Cheat Sheet
[Optional. Intentionally redundant summary of everything an implementer agent needs from the Context Document and System Map, copied here so the agent doesn't need to cross-reference. Include only when the story touches 2+ modules or has complex interface obligations.
This section trades DRY for agent effectiveness — a coding agent implementing this story can work from this section alone without searching other documents.]
- Stack: [relevant subset]
- Module: [name] — [one-line responsibility]
- Key types: [TypeScript/schema definitions the agent will need]
- Adjacent interfaces: [endpoints this story calls or implements, with shapes]
- Conventions: [naming, file structure, error handling patterns in this codebase]
---
Files Likely Affected
[Optional. Helps orchestrator detect conflicts between parallel stories.]
---
Verification Strategy
- Unit tests: [What to unit test]
- Integration tests: [Cross-module interaction tests, if applicable]
- Contract tests: [Interface compliance tests, if applicable]
---
Definition of Done
All of the following must be true:
1. All acceptance criteria pass as automated tests 2. All relevant contract tests pass 3. Code follows project conventions 4. PR submitted with description linking to this Story ID 5. No regressions in existing tests 6. Structured status report produced
System Map Template
Defines the architecture at the module level. Serves two functions: (1) establishes module boundaries so decomposition agents work independently, (2) defines interface contracts so parallel implementation stays compatible.
A module boundary drawn wrong costs more to fix than any implementation bug. Review carefully before proceeding to story decomposition.
Callout Conventions
Use these blockquote markers throughout this document to flag precision points that agents must not miss:
Important boundary: Where a responsibility stops and another begins
Important nuance: Easy-to-miss detail that changes implementation
Important constraint: Hard limit that shapes design choices
---
System Overview
Architecture style: [e.g., microservices, modular monolith, serverless functions]
High-level description: [2–3 sentences on structure and why this architecture was chosen, referencing Context Document constraints.]
---
Modules
[Module Name]
Responsibility: [What this module does and does not do. The "does not" part defines the boundary.]
Owns: [Data, state, or resources this module is the authority on. No other module directly modifies these.]
Depends on: [Other modules this one calls or consumes from.]
Technology: [If different from default stack.]
Key internal concepts: [Domain objects, patterns, or abstractions implementers need to understand.]
[Repeat for each module]
---
Domain Model
Domain entities that span module boundaries or require precise typing. Module-specific concepts remain in the Modules section above. Each entity has typed fields so implementers in any language know exactly what to build. See references/symphony-spec-reference.md Pattern 3 for the standard.
[Entity Name]
Purpose: [One sentence — what this entity represents in the system.]
Fields:
| Field | Type | Default | Required | Notes |
|---|---|---|---|---|
id | string | — | yes | Stable across restarts. Derived from [rule]. |
status | enum | pending | yes | See state machine if applicable. |
Normalization rules:
- [e.g., "All identifiers are lowercased and slugified"]
- [e.g., "Replace characters not in [A-Za-z0-9._-] with \_"]
Invariants:
- [Conditions that must always hold, e.g., "A completed entity always has a non-null completed_at timestamp"]
[Repeat for each domain entity]
---
Interface Contracts
For every module-to-module connection. These will be enforced by automated contract tests in Phase 4. An undefined interface is a guaranteed integration failure.
[Module A] → [Module B]
Protocol: [HTTP REST, gRPC, message queue, function call, etc.]
Endpoint / Channel: [Specific API path, queue name, or function signature.]
Request shape:
[Exact data structure — TypeScript types, JSON Schema, or equivalent. Specify required vs optional, types, constraints.]Response shape:
[Same specificity as request.]Error contract:
[What errors can be returned, their shape, what the caller should do for each.]SLA: [Expected latency, throughput, availability. "TBD" is acceptable if noted as open question.]
---
Protocol Sequences
For interface contracts that involve multi-step interactions (handshakes, streaming, request-response chains), document the sequence here. Simple request-response contracts don't need this — use it when the interaction has ordering, state, or timing constraints. See references/symphony-spec-reference.md Pattern 5 for the standard.
[Protocol Name]: [Module A] <> [Module B]
Trigger: [What initiates this protocol]
Sequence:
1. [Module A] sends [message type]:
{ "type": "init", "payload": { "..." } }2. [Module B] responds with [message type]:
{ "type": "ack", "session_id": "..." }3. [Steady-state interaction description]
Timeout behavior: [What happens if step N takes too long] Error behavior: [What happens if step N fails]
Important nuance: [Easy-to-miss detail about this protocol]
---
Data Flow
For each primary user journey in the Layered MVP Contract, trace the data path:
[Journey Name]
User → [Module] → action → [Module] → action → responseShow which module handles each step, what data passes between them, where state is persisted.
---
Third-Party Boundaries
[Service Name]
Provides: [Specific capability used.] Integration point: [Which module, how.] Failure mode: [Behavior when service is down — graceful degradation or hard fail?] Limitations: [Rate limits, quotas, latency.]
---
Deployment Topology
Environments: [Local dev, staging, production.] Module → Runtime mapping: [Which modules run where.] Persistence: [Databases/storage, which modules own them.]
---
Architecture Decision Records
ADR-001: [Title]
Context: [What prompted this decision.] Decision: [What was decided.] Reasoning: [Why, over alternatives.] Consequences: [What this enables and constrains.]
---
Amendment Log
During story decomposition, agents may discover boundary or contract issues. Collected here, reviewed in batch.
| Proposed By | Module Affected | Proposed Change | Reasoning | Status |
|---|---|---|---|---|
| pending/accepted/rejected |
Trigger Architecture Review when: 3+ pending amendments, or any single amendment affects an interface contract.
Technical Specification Template: [System Name]
A production-grade system specification for protocol-heavy systems. Use this template when the Context Document and System Map don't capture enough behavioral detail for agents to implement without ambiguity — typically when the system has multi-step protocols, multiple state machines, or complex failure/recovery semantics.
Important boundary: This template is opt-in. Most projects go directly from context-document to system-map. Use this only when the system has protocol-level complexity that those templates don't capture.
When to use this template: During /aep-map, if the System Map reveals 3+ interface contracts requiring protocol sequences, 2+ distinct state machines, explicit failure classes with different recovery behaviors, or trust boundaries crossing module lines.
Reference exemplar: See references/symphony-spec-reference.md for an annotated analysis of OpenAI's Symphony SPEC.md — the standard this template is modeled after.
Quality standard: every statement must be convertible into a verification condition. If it cannot be tested, it is not precise enough.
---
1. Service Identity
[System name] is [one sentence defining what this service IS — what it does, for whom, in what context].
The service solves [N] operational problems:
- [Problem 1 — concrete operational pain point this service eliminates]
- [Problem 2]
- [Problem 3]
Important boundary: [What this service is NOT. State the most likely misunderstanding about scope. Example: "Symphony is a scheduler/runner and tracker reader. Ticket writes are performed by the coding agent."]
Trust Posture
[State explicitly whether this service is intended for trusted environments, restricted environments, or both. This shapes every downstream security and approval decision.]
---
2. Goals and Non-Goals
2.1 Goals
[Behavior-observable statements. Each must be verifiable by observing the running system. Not aspirations — observable behaviors.]
- [Goal — e.g., "Poll the issue tracker on a fixed cadence and dispatch work with bounded concurrency."]
- [Goal — e.g., "Recover from transient failures with exponential backoff."]
- [Goal — e.g., "Support restart recovery without requiring a persistent database."]
2.2 Non-Goals
[Things a reasonable person might expect this system to do, but it deliberately will NOT. Each explains why.]
- [Non-goal — e.g., "Rich web UI or multi-tenant control plane."]
- [Non-goal — e.g., "General-purpose workflow engine or distributed job scheduler."]
---
3. System Overview
3.1 Components
[Named components, each with a one-line responsibility. Number them for cross-referencing.]
1. [Component Name]
- [One-line responsibility. What it does and — if ambiguous — what it does NOT do.]
2. [Component Name]
- [One-line responsibility.]
3.2 Abstraction Layers
[Group components into named layers. This makes the system easier to port and reason about.]
1. [Layer Name] ([layer purpose])
- [What lives in this layer]
2. [Layer Name] ([layer purpose])
- [What lives in this layer]
3.3 External Dependencies
- [Dependency — what it provides, how failure is handled]
- [Dependency]
---
4. Domain Model
4.1 Entities
[Entity Name]
[One sentence — what this entity represents in the system.]
Fields:
| Field | Type | Default | Required | Notes |
|---|---|---|---|---|
id | string | — | yes | Stable identifier. Derived from [rule]. |
status | enum | pending | yes | See state machine in Section 5. |
created_at | timestamp | — | no | ISO-8601. |
[Entity Name]
[Repeat for each domain entity.]
4.2 Normalization Rules
[How values are compared, derived, and sanitized across the system.]
[Identifier Type]— [Derivation rule, e.g., "Replace any character not in [A-Za-z0-9._-] with \_"][State comparison]— [e.g., "Compare states after lowercase"]
4.3 Invariants
[Conditions that must always hold across the system, not just within one entity.]
- [Invariant — e.g., "A running entity always has a non-null started_at timestamp"]
- [Invariant — e.g., "At most max_concurrent entities may be in Running state"]
---
5. State Machines
[Stateful Entity] States
[These are the system's internal states, which may differ from external/user-visible states.]
1. [State Name] — [When the entity is in this state, what is true about it] 2. [State Name] — [Description] 3. [State Name] — [Description]
Important nuance: [Easy-to-miss detail about state semantics, e.g., "A successful exit does not mean the entity is done forever."]
Transition Triggers
| Trigger | From State(s) | To State | Side Effects |
|---|---|---|---|
| [Event] | [State] | [State] | [What happens — cleanup, notifications, retries] |
Idempotency and Recovery Rules
- [Rule — e.g., "Claimed checks are required before launching any worker"]
- [Rule — e.g., "Restart recovery is tracker-driven, no durable DB required"]
---
6. Configuration Specification
6.1 Source Precedence
[Where configuration comes from, in priority order.]
1. [Highest priority — e.g., CLI arguments] 2. [e.g., Configuration file values] 3. [e.g., Environment variable indirection] 4. [Lowest priority — built-in defaults]
6.2 Dynamic Reload Semantics
[Can config change at runtime? What happens when it does?]
- [e.g., "Watch config file for changes. Re-apply without restart."]
- [e.g., "Invalid reloads must not crash the service. Keep last known good config."]
6.3 Validation Rules
[What must be true before the system starts dispatching work?]
- [e.g., "Config file can be loaded and parsed"]
- [e.g., "API key is present after environment variable resolution"]
6.4 Config Cheat Sheet
This section is intentionally redundant so a coding agent can implement the config layer quickly.
| Key | Type | Default | Notes |
|---|---|---|---|
[key.path] | string | [default] | [What it controls] |
[key.path] | integer | [default] | [What it controls] |
---
7. Protocol Specifications
[Protocol Name]: [Participant A] <> [Participant B]
Purpose: [What this protocol accomplishes]
Compatibility note: [What must be preserved for interoperability vs. what can vary]
Launch Contract
- Command:
[how the subprocess/service is started] - Working directory: [where it runs]
- Communication: [stdio, HTTP, gRPC, etc.]
Startup Handshake
[Illustrative transcript showing the exact message sequence. Equivalent payload shapes are acceptable.]
{"id":1,"method":"initialize","params":{...}}
// wait for response
{"method":"initialized","params":{}}
{"id":2,"method":"[next step]","params":{...}}1. [Step 1 — what is sent, what to expect back, timeout] 2. [Step 2] 3. [Steady-state interaction begins]
Streaming / Turn Processing
[How the steady-state interaction works.]
- [e.g., "Read line-delimited JSON from stdout"]
- [e.g., "Buffer partial lines until newline"]
- [e.g., "Stderr is diagnostics only, not protocol"]
Completion conditions:
- [e.g., "turn/completed → success"]
- [e.g., "subprocess exit → failure"]
- [e.g., "turn timeout → failure"]
Timeout and Error Mapping
| Timeout | Default | Applies To |
|---|---|---|
[name] | [value] | [which phase of the protocol] |
| Error Category | Cause | System Response |
|---|---|---|
[error_name] | [what triggers it] | [what the system does] |
---
8. Failure Model
8.1 Failure Classes
| # | Class | Examples | Detection | Recovery | Escalation |
|---|---|---|---|---|---|
| 1 | [Name] | [What triggers it] | [How the system detects it] | [Automatic recovery] | [When/how human is notified] |
| 2 | [Name] | [Triggers] | [Detection] | [Recovery] | [Escalation] |
8.2 Partial State Recovery (Restart)
[What state survives a restart and what must be reconstructed?]
- [e.g., "No retry timers are restored from prior process memory"]
- [e.g., "Service recovers by fresh polling of active items and re-dispatching eligible work"]
8.3 Operator Intervention Points
[How operators control behavior without code changes.]
- [e.g., "Edit config file — changes detected and re-applied automatically"]
- [e.g., "Change entity states in external system — running sessions stopped when reconciled"]
- [e.g., "Restart service — for process recovery or deployment"]
---
9. Security and Operational Safety
9.1 Trust Boundaries
[What is trusted and what is not. Be explicit.]
- [e.g., "The config file is fully trusted configuration"]
- [e.g., "External tracker data is not assumed trustworthy"]
9.2 Filesystem Safety
[Mandatory filesystem invariants.]
- [e.g., "Working paths must remain under configured root"]
- [e.g., "Directory names must use sanitized identifiers"]
9.3 Secret Handling
- [e.g., "Support $VAR indirection in config"]
- [e.g., "Do not log API tokens or secret values"]
- [e.g., "Validate secret presence without printing them"]
9.4 Script/Hook Safety
[If the system executes user-provided scripts or hooks:]
- [e.g., "Hooks are fully trusted configuration"]
- [e.g., "Hook output should be truncated in logs"]
- [e.g., "Hook timeouts are required to avoid hanging the system"]
---
10. Observability
10.1 Logging Conventions
Required context fields:
- [e.g.,
entity_id,session_id]
Message format:
- [e.g., "Stable key=value phrasing"]
- [e.g., "Include action outcome: completed, failed, retrying"]
- [e.g., "Avoid logging large raw payloads"]
10.2 Runtime Snapshot
[If the system exposes a monitoring interface, define the snapshot shape.]
{
"running": [],
"retrying": [],
"totals": { "input_tokens": 0, "output_tokens": 0, "seconds_running": 0 }
}10.3 Optional HTTP API
[If applicable — endpoints, response shapes, error envelopes.]
---
11. Operational Boundaries
Important boundary: [System-level boundary that shapes all design decisions]
Important constraint: [Hard limit — resource, scaling, or architectural]
Resource Limits and Backpressure
- [e.g., "Maximum N concurrent workers"]
- [e.g., "Backoff formula: min(10000 * 2^(attempt-1), max_backoff_ms)"]
Scaling Constraints
- [e.g., "Single-process, in-memory state — no distributed coordination"]
- [e.g., "Horizontal scaling requires partitioning by project"]