按源仓库内容呈现,保留标题、案例、代码、表格、链接以及原文引用的演示图片。
Watch
Self-feeding work discovery. /aep-watch is a continuous/scheduled monitor: it pulls from configured sources (bug trackers, error streams, telemetry), classifies each finding with the same classifier as `/aep-reflect`, dedupes against the backlog, and writes new bug/refinement stories into product-context.yaml — which then flow into /aep-dispatch (or autopilot picks them up), closing the loop so the system keeps finding work 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 classifier you run after shipping; /aep-watch is its always-on sibling — same logic, no human prompting each finding — the thing that makes the loop continuous. It feeds the same `stories` section `/aep-dispatch` reads, so discovered work re-enters the /aep-envision → /aep-map → /aep-dispatch → … → /aep-wrap → /aep-reflect cycle.
Session: 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).
Orchestrator Boundary
/aep-watch runs from the main workspace only and is an orchestrator, not an executor: the orchestrator boundary stated in /aep-autopilot applies here unchanged. It reads only:
- the configured sources (via their APIs/feeds — see Step 1),
product-context.yaml(to dedupe and to write stories).
If a finding needs code investigation, that happens inside a workspace agent after the story is dispatched.
# 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 per /aep-executor (scripts/spawn-liveness-probe.sh <name> <agent_id>). If the probe fails, tear the spawn down and retry once; if the retry also fails, run the fetch + classify inline in the watch session for this tick (degraded but still signals-only). The watch session itself does not read workspace code.
Config
Watch is driven entirely by topology.routing.watch in product-context.yaml. Each sources[] entry names a source type (its adapter and finding shape live in references/telemetry-ingestion.md, which is where the types are defined):
topology:
routing:
full_auto: false # master switch (see below)
watch:
sources: # source types + adapters: references/telemetry-ingestion.md
- type: bug_tracker # 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 # dogfood findings (local / post-deploy / standalone)
glob: ".dev-workflow/dogfood-*.md" # default; see telemetry-ingestion.md adapter
- type: distillation # layer distillations (proposal-only synthesis from /aep-wrap)
glob: "lessons-learned/distillations/*.yaml" # 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 (conservative by default): auto-create a story only when full_auto: true (master switch) OR watch.auto_create: true (per-watch opt-in, narrower than the master switch). Otherwise surface a proposal: write the story object to a watch_proposals block under topology.routing.watch and print it; nothing enters the stories section until a human approves (via /aep-reflect or inline). Under full_auto: true, watch writes straight into stories and /aep-dispatch / /aep-autopilot pick them up on the next tick.
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, so a watch that covers nothing says so. 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) →
report the gap rather than the 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.
Postcondition: every covered source resolves to a wired telemetry_sources entry with a metric_map (file-glob sources dogfood_report/distillation are self-describing and exempt), or the tick surfaced the incomplete-binding message.
Step 1: Pull from Sources
For each entry in watch.sources, pull findings created/updated since watch.since, reducing each to the finding record and using the per-source adapters in references/telemetry-ingestion.md (→ The /aep-watch finding record; Dogfood-report adapter; Distillation adapter), which define the finding shape. Advance watch.since to the newest last_seen only after the tick completes successfully (a failed tick re-pulls rather than dropping findings).
- File-glob sources (`dogfood_report`, `distillation`) carry no per-item
timestamp, so watch.since does not advance for them; re-scanning the glob each tick is harmless because Step 3 dedupes on each adapter's stable external_id (dogfood:<report>:<hash> / distillation:<layer>:<hash>). Being self-describing, neither is gated by Step 0's coverage_check.
- `distillation` extra rule: items mapped to
process(skill_amendments)
are never auto-created as stories — they surface to a human as proposed amendments regardless of full_auto/auto_create.
Postcondition: each configured source yielded zero or more finding records; watch.since is unchanged until the tick completes successfully.
Step 2: Classify Each Finding
Classify every finding with the exact same classifier as `/aep-reflect` Step 2 (bug / refinement / discovery / opportunity shift / process) — apply /aep-reflect "Classify Each Observation" (the canonical 5-category logic; if it changes, it changes there).
Watch acts autonomously on only the two categories it can safely turn into work: bug → a bug story (Step 4), refinement → a refinement story in the next layer (Step 4). Discovery, opportunity shift, calibration, and process findings are never auto-created regardless of full_auto — they change product intent or workflow, so they always surface to a human (via /aep-reflect; opportunity shifts escalate because they change the bet).
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 is completed/closed and the issue has recurred (new occurrences after completed_at), add a note and surface it as a regression for human attention — a recurrence is new information about old work, not new work.
Postcondition: every surviving finding has no matching open story and no prior watch_origin.{source, external_id}.
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 routes these through /aep-design first.
Then, per the confirmation policy (Config):
- Auto-create (
full_auto: trueORwatch.auto_create: true): append the
story to the stories section — a normal pending story /aep-dispatch scores and /aep-autopilot picks up on the next tick.
- Surface (default): append the story object to
topology.routing.watch.watch_proposals and print it; a human runs /aep-reflect (or confirms inline) to promote proposals into stories.
Validate + commit (same guardrails as reflect/dispatch — run the validation command in references/yaml-guardrails.md):
npx js-yaml product-context.yaml > /dev/null && echo "YAML OK"
# Resolve $BASE (integration branch) per /aep-git-ref "Integration Branch".
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. Postcondition: npx js-yaml product-context.yaml exits 0 and the commit is pushed to $BASE.
Driver
/aep-watch is a continuous/scheduled monitor on the same driver matrix as /aep-autopilot: resolve the host's driver with executor detect() and the driver × backend matrix in /aep-executor references/backends.md, using watch.interval as the interval.
- Claude Code — `/loop <interval>` (long-lived, in-session):
/loop 30m /aep-watch tick.
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 (launchd StartInterval, cron, or a while … sleep loop), one cheap one-shot per tick, with OS-bound workers (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).
Cross-References
/aep-reflect— Step 2 classifier (bug / refinement / discovery / …),
reused here; the human-in-the-loop counterpart to watch.
references/telemetry-ingestion.md— the finding record + per-source adapters
used by Step 1 (shared with /aep-reflect Step 1).
/aep-dispatch— consumes the stories watch creates (scoring, readiness, WIP)./aep-autopilot— the orchestrator pattern, driver matrix, liveness probe, and
main-workspace boundary watch mirrors; picks up watch-created stories next tick.

