
Aep Watch
- 30 installs
- 14 repo stars
- Updated July 31, 2026
- memorysaver/agentic-engineering-patterns
Helps with ai & agent building tasks.
About
aep-watch is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- aep-watch
- AI & Agent Building
- AI-coding skill
Aep Watch by the numbers
- 30 all-time installs (skills.sh)
- +1 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #9,316 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-watchAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 30 |
|---|---|
| repo stars | ★ 14 |
| Last updated | July 31, 2026 |
| Repository | memorysaver/agentic-engineering-patterns ↗ |
What it does
Helps with ai & agent building tasks.
Files
Watch
Self-feeding work discovery. /aep-watch is a continuous/scheduled monitor that discovers new work: it pulls from configured sources (bug trackers, error streams, telemetry), classifies each finding with the same classifier as `/aep-reflect`, dedupes against the existing backlog, and writes new bug/refinement stories into product-context.yaml. Those stories then flow into /aep-dispatch (or autopilot picks them up) — closing the loop so the system keeps finding work to do without a human running /aep-envision or /aep-reflect by hand.
sources → [ /aep-watch: pull → classify → dedupe → write stories ] → product-context.yaml
│
▼
/aep-dispatch (or /aep-autopilot)/aep-reflect is the human-in-the-loop feedback classifier you run after shipping. /aep-watch is its always-on sibling: same classification logic, no human prompting each finding — it is what makes the loop _continuous_.
Where this fits:
/aep-envision → /aep-map → /aep-validate
→ /aep-watch (continuous monitor — discovers + ingests new work)
→ /aep-dispatch → … → /aep-wrap → /aep-reflect → loop
▲ /aep-watch feeds the same stories section /aep-dispatch readsSession: Main workspace only (like /aep-autopilot) — respects the orchestrator boundary. Driver: /loop <interval> (Claude Code) or codex exec cron/launchd (Codex). Input: Sources configured in topology.routing.watch. Output: New bug / refinement stories appended to the stories section of product-context.yaml (or surfaced as proposals for confirmation — see Config).
---
STOP — Orchestrator Boundary
/aep-watch runs from the main workspace only and is an orchestrator, not an executor. Like /aep-autopilot, it never reads, reviews, edits, or evaluates workspace code. It only reads:
- the configured sources (via their APIs/feeds — see Step 1),
product-context.yaml(to dedupe and to write stories).
If a finding needs investigation that requires reading code, that happens inside a workspace agent after the story is dispatched — never in the watch session.
# Main workspace guard
pwd | grep -q '.feature-workspaces' && echo "ABORT: Run /aep-watch from main workspace only" && exit 1
[ -f product-context.yaml ] || echo "ABORT: Run /aep-envision and /aep-map first"Any worker /aep-watch spawns (e.g. a cheap CHECK delegate to fetch + classify a batch) is a `native-bg-subagent` on Claude Code, gated by the standard post-spawn liveness probe (scripts/spawn-liveness-probe.sh): confirm the agent exists AND shows activity before counting it; on failure, tear down and fall back to native-bg-subagent. The watch session itself does not read workspace code.
---
Config
Watch is driven entirely by topology.routing.watch in product-context.yaml:
topology:
routing:
full_auto: false # A1 master switch (see below)
watch:
sources: # what to pull from — see references/telemetry-ingestion.md
- type: bug_tracker # e.g. github_issues, linear, jira, sentry, datadog, log_stream
query: "is:open label:bug"
- type: error_stream
dsn: "<sentry/rollbar/...>"
- type: telemetry
metric: "error_rate"
threshold: 0.02
- type: dogfood_report # ingest dogfood findings (local / post-deploy / standalone)
glob: ".dev-workflow/dogfood-*.md" # default; see telemetry-ingestion.md adapter
interval: 30m # poll cadence for the /loop or cron driver
auto_create: false # write stories directly vs. surface proposals
since: null # high-water mark — last ingested timestamp (watch maintains this)Confirmation policy (default conservative):
- `full_auto: false` (default) — watch surfaces proposed stories for human
confirmation. It writes them to a watch_proposals block (under topology.routing.watch) and prints them; nothing enters the stories section until the human approves. auto_create: true lets watch write stories directly even when full_auto is off (a per-watch opt-in, narrower than the master switch).
- `topology.routing.full_auto: true` (A1 master switch) — watch **auto-creates
AND lets dispatch run** without confirmation: it writes new stories straight into the stories section, and /aep-dispatch / /aep-autopilot pick them up on the next tick. No human gate per finding.
Resolution: auto-create whenfull_auto: trueORwatch.auto_create: true;
otherwise surface proposals. When in doubt, surface — recreating noise as stories
is worse than a confirmation prompt.
---
The Watch Loop
Each tick runs the same four-step body. Idempotent — re-running with no new source data produces no new stories (the dedupe + since high-water mark guarantee it).
⓪ PRECHECK → verify the /aep-map telemetry binding is complete (coverage_check)
① PULL → fetch new findings from each configured source (since high-water mark)
② CLASSIFY → run each finding through the /aep-reflect Step 2 classifier
③ DEDUPE → drop findings that already map to an existing story
④ WRITE → create bug/refinement stories (or surface proposals)Step 0: Precondition — verify the map binding
/aep-watch consumes telemetry sources, so first confirm /aep-map actually bound them — don't silently watch nothing. Run coverage_check() (the helper in references/telemetry-ingestion.md §1.5) over the signals this watch needs: each topology.routing.watch.sources[] entry (and any metric/error_stream it relies on) must resolve to a wired topology.routing.telemetry_sources entry with a metric_map.
- Covered → proceed to Step 1.
- Not covered (sources empty, or a referenced metric has no
metric_map) →
do not claim auto-coverage. Surface: "telemetry binding incomplete for <missing> — run /aep-map (Telemetry Binding step) before /aep-watch can ingest it", skip the uncovered sources, and (if nothing is covered) stop the tick with that message. A missing binding blocks; it never silently no-ops.
Step 1: Pull from Sources
For each entry in watch.sources, pull findings created/updated since watch.since. Reuse the ingestion format and per-source adapters defined in `references/telemetry-ingestion.md` (the same source contract /aep-reflect Step 1 draws on) — do not invent a new finding shape here. This includes the `dogfood_report` adapter (telemetry-ingestion.md → Dogfood-report adapter): parse each ## finding in the configured glob (default .dev-workflow/dogfood-*.md) into the record below, with external_id = the adapter's deterministic dogfood:<report>:<hash> key so Step 3 dedupes re-runs of the same dogfood. A dogfood_report source is a self-describing file glob, so Step 0's coverage_check does not gate it. Each finding normalizes to:
- source: "sentry"
external_id: "ISSUE-4821" # stable id used for dedupe
title: "TypeError in checkout flow"
detail: "..." # stack/message/metric summary
signal: error_stream # bug_tracker | error_stream | telemetry | dogfood
count: 142 # occurrences / affected users (priority input)
first_seen: "<ISO8601>"
last_seen: "<ISO8601>"Advance watch.since to the newest last_seen only after the tick completes successfully (so a failed tick re-pulls rather than dropping findings). Exception — `dogfood_report`: the unified report carries no per-finding timestamp, so count/first_seen/last_seen are unset and watch.since does not advance for this source; re-scanning the glob each tick is harmless because Step 3 dedupes on the adapter's stable external_id (priority comes from the finding's Severity, not count). See references/telemetry-ingestion.md → Dogfood-report adapter.
Step 2: Classify Each Finding
Classify every finding using the exact same classifier as `/aep-reflect` Step 2 — bug / refinement / discovery / opportunity shift / process. Do not duplicate that logic here; apply /aep-reflect's "Classify Each Observation" rules (see ../reflect/SKILL.md → Step 2). Watch only acts autonomously on the two categories it can safely turn into work:
| Classification | Watch action |
|---|---|
| Bug | Create a bug story (Step 4). |
| Refinement | Create a refinement story in the next layer (Step 4). |
| Discovery | Do NOT auto-create. Surface for /aep-reflect → /aep-envision//aep-map. |
| Opportunity shift | Do NOT auto-create. Always escalate to a human — this changes the bet. |
| Process / Calibration | Do NOT auto-create. Surface for /aep-reflect. |
Discoveries, opportunity shifts, calibrations, and process findings always go to a human regardless of full_auto — they change product intent or workflow, which watch must never decide autonomously.
Step 3: Dedupe Against Existing Stories
Before creating anything, check the finding against the current stories section of product-context.yaml (and existing watch_proposals). Skip a finding when:
- a story already records this
source+external_id(watch stamps
watch_origin: { source, external_id } on every story it creates), or
- an open story's
title/description clearly covers the same issue
(same error signature, same endpoint, same metric).
If a matching story exists but is completed/closed and the issue has recurred (new occurrences after completed_at), do not silently recreate — add a note and surface as a regression for human attention. Never recreate work.
Step 4: Write Stories (or Surface Proposals)
For each surviving bug / refinement finding, build a story:
- id: "watch-<source>-<external_id>"
title: "<finding title>"
description: "<finding detail> (auto-discovered by /aep-watch from <source>)"
type: bug # or refinement
status: pending
priority: high # bugs: high; tune by count/severity (see below)
layer: <active_layer> # bug → current layer; refinement → next layer
module: <best-effort or unset> # leave unset if the source doesn't localize it
watch_origin:
source: "<source>"
external_id: "<external_id>"
discovered_at: "<ISO8601>"Priority / layer rules (mirror `/aep-reflect`):
- Bug →
priority: high,status: pending, in the current/active layer
(escalate to critical when count or severity is high, e.g. crash affecting many users / error_rate over threshold).
- Refinement →
status: pendingin the next layer. - Leave
module/files_affectedunset when the source can't localize them;
dispatch's readiness score will route these through /aep-design first.
Then, per the confirmation policy:
- Auto-create (
full_auto: trueORwatch.auto_create: true): append the
story to the stories section. It is now a normal pending story — /aep-dispatch scores it and /aep-autopilot picks it up on the next tick.
- Surface (default): append the story object to
topology.routing.watch.watch_proposals
instead, and print it. The human runs /aep-reflect (or confirms inline) to promote proposals into stories.
Validate + commit (same guardrails as reflect/dispatch — see ../reflect/references/yaml-guardrails.md):
npx js-yaml product-context.yaml > /dev/null && echo "YAML OK"
# Resolve $BASE (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}
git pull --ff-only origin "$BASE"
git add product-context.yaml
git commit -m "chore: watch — auto-discovered N stories from <sources>"
git push origin "$BASE"Append a changelog entry (type: watch) summarizing findings ingested, classified, deduped, and created vs. proposed.
---
Driver
/aep-watch is a continuous/scheduled monitor — the same driver matrix as /aep-autopilot (executor detect() + the driver × backend matrix in .claude/skills/aep-executor/references/backends.md):
- Claude Code — `/loop <interval>` (long-lived, in-session):
/loop 30m /aep-watch tickUse watch.interval for <interval>. The session stays alive, so any spawned CHECK delegate is a session-bound native-bg-subagent.
- Codex — `codex exec` cron/launchd (ephemeral, OS-scheduled): schedule
/aep-watch tick externally (e.g. launchd StartInterval, cron, or a while … sleep loop), one cheap one-shot per tick. Workers must be OS-bound (codex-exec). AEP prints the snippet; it does not install the scheduler.
/aep-watch tick runs one pass of the four-step loop and exits. /aep-watch stop cancels the driver (/loop cancel, or remove the cron/launchd job).
---
Guardrails
- Main workspace only — refuse to run if
pwdcontains.feature-workspaces. - Never read workspace code — watch reads sources +
product-context.yamlonly;
any code investigation happens inside a dispatched workspace agent.
- Reuse, don't duplicate, the reflect classifier — Step 2 applies
/aep-reflect Step 2; if classification logic changes, it changes there.
- Conservative by default — surface proposals unless
full_auto: true(A1)
or watch.auto_create: true. When in doubt, surface.
- Only bugs and refinements are auto-creatable — discoveries, opportunity
shifts, calibrations, and process findings always go to a human.
- Always dedupe — never recreate work that already has a story; stamp
watch_origin so future ticks recognize it.
- Spawned workers are native-bg-subagent + liveness probe — never trust
"state says active"; confirm via the probe, fall back on failure.
- Advance the high-water mark only on success — a failed tick re-pulls.
---
Cross-References
../reflect/SKILL.md— Step 2 classifier (bug / refinement / discovery / …),
reused here verbatim; the human-in-the-loop counterpart to watch.
references/telemetry-ingestion.md— source adapters + normalized finding format
used by Step 1 (shared with /aep-reflect Step 1).
../dispatch/SKILL.md— consumes the stories watch creates (scoring, readiness, WIP).../../patterns/autopilot/SKILL.md— the orchestrator pattern, driver matrix,
liveness probe, and main-workspace boundary watch mirrors; autopilot picks up watch-created stories on its next tick.
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