memorysaver/agentic-engineering-patterns

aep-watch

- Ingests bug trackers, errors, and telemetry, then dedupes them into backlog stories.

View source
Original skill document

Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.

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.

bash
# 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):

yaml
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:

yaml
- 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`):

  • Bugpriority: 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).

  • Refinementstatus: pending in the next layer.
  • Leave module / files_affected unset 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: true OR watch.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):

bash
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-reflectStep 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.

from this repository

More skills

All skills