# Aep Watch

> Ingests bug trackers, errors, and telemetry, then dedupes them into backlog stories. Use for "monitor for new work" or telemetry-driven stories.

- Skill: `memorysaver/aep-watch` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add memorysaver/aep-watch`
- Raw SKILL.md: https://api.skillmd.com/api/skills/memorysaver/aep-watch/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Product & Planning
- Author: memorysaver (https://skillmd.com/u/memorysaver)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/memorysaver/aep-watch

---


# 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`):**

- **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: 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-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.

