Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .claude/loop-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@ this repo from the "loop engineering" loop-design checklist
**5. State / memory**
- [ ] Run state documented (`.claude/memory/loop-run-log.md`)
- [ ] Prior state read at every iteration start
- [ ] Outcomes + timestamps written; resolved items pruned; human overrides recorded
- [ ] Outcomes + timestamps appended to the run-log (append-only — never pruned there);
resolved *plan nodes* pruned only in `loop-plan.md`; human overrides recorded
- [ ] Durable **plan/DAG** (`.claude/memory/loop-plan.md`) read at iteration start; every
L2/L3 action traces to a **ready node** (no action without a node)
- [ ] **Re-plan is an explicit step run before dispatch**; the empty/first-iteration plan
bootstraps a node rather than deadlocking

**6. Human handoff**
- [ ] Escalation triggers explicit (max attempts, risk paths, ambiguity)
Expand Down
78 changes: 57 additions & 21 deletions .claude/loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,32 @@ budget, denylist, escalation, stop conditions). Before running, fix the loop's
- **L3 — Unattended.** May act without a human watching; requires every section of
`.claude/loop-checklist.md` to be satisfied first.

## The plan (`.claude/memory/loop-plan.md`) — the DAG every action traces to
The loop's watchlist is a **durable, mutable plan** of typed work nodes with `after:`
dependency edges, kept in `.claude/memory/loop-plan.md` (schema + ready-node rule are
documented there). Two disciplines follow from it:
- **No action without a node.** Every L2/L3 fix the loop dispatches MUST be a **ready
node** in the plan (its `args.items` `id` = the node `id`). An action with no backing
node is not taken — the re-plan step creates the node first. Readiness does **not**
bypass the denylist or trust level; those gates still apply on top.
- **Re-plan is a first-class step** that runs *before* dispatch (step 9 below), not an
afterthought — the loop consults the fresh DAG before every action.
The plan is the concrete realization of "the watchlist" referenced throughout this file;
`loop-run-log.md` remains the separate, append-only *history*.

## Per-iteration process (act as Lead Agent)
1. **Read state first:** open `.claude/memory/loop-run-log.md` (prior iterations,
outcomes, human overrides) and check **remaining budget** against the per-run cap
(turns or tokens — see *Budget & limits*) before doing anything; stop and
escalate if it is already exhausted. **If no per-run cap was declared, default to
25 turns and record that you used the default** in the run-log entry.
2. Identify the current active task from the conversation or run-log. **If no active
task is identifiable, treat the watchlist as empty and self-stop (per *Stop when*)
rather than inventing a task.**
1. **Read state first:** open **`.claude/memory/loop-plan.md`** (the current plan/DAG)
and `.claude/memory/loop-run-log.md` (prior iterations, outcomes, human overrides),
and check **remaining budget** against the per-run cap (turns or tokens — see
*Budget & limits*) before doing anything; stop and escalate if it is already
exhausted. **If no per-run cap was declared, default to 25 turns and record that you
used the default** in the run-log entry.
2. **Orient on the plan:** note its current ready nodes (shorthand: `status` not
done/dropped/escalated/in-progress AND all `after:` deps `done` — illustrative only;
always defer to the plan's ready-node rule for the exact formula). Node *selection*
is deferred to step 10
(after re-plan) so dispatch always consults the freshest DAG — never act on a raw
finding here.
3. Check git status and changed files.
4. Start or reuse the dev server.
5. Open the relevant localhost page in Chrome or Playwright MCP.
Expand All @@ -31,18 +48,35 @@ budget, denylist, escalation, stop conditions). Before running, fix the loop's
Render the returned `confirmed` matrix.
8. **Maker/checker:** the pass that verifies a fix must be *separate* from the one
that made it — the implementer never marks its own work "done".
9. Act **only within the trust level** (**if no level was explicitly declared for
this run, treat it as L1 — never infer L2/L3 from context**):
- L1: record findings only.
- L2/L3: fix the confirmed blockers + directly-related important findings via the
**loop-iteration Workflow** (`.claude/workflows/loop-iteration.js`,
`args.items` = the triaged, denylist-cleared items): an implementer subagent
fixes each in an isolated worktree and a *separate* verifier reviews the real
diff and approves/rejects (a hard denylist gate also forces escalation). For
each **approved** item, apply its returned `diff` patch and re-run the smallest
relevant check before committing; escalate `rejected`/`escalate` items. Never
fix nice-to-haves automatically; never start unrelated refactors.
10. **Append a run entry** to `.claude/memory/loop-run-log.md` (see format below).
9. **Re-evaluate & re-plan** (a first-class step, not an afterthought) — run this
**before** any dispatch so the DAG is fresh when you act. Rewrite
`.claude/memory/loop-plan.md`: mark nodes verified done in a prior iteration as
`done` (unblocking their dependents), **add new nodes** for this run's confirmed
findings (with `after:` deps + `origin`), re-order by priority, `drop` obsolete
nodes, and recompute which nodes are `ready`. New evidence may re-shape the DAG here
— that is the point. Also **repair broken states** (see the plan's rules): reset any
stale `in-progress` node left by an interrupted prior run, and **cascade-resolve** any
node whose `after:` dep became `dropped`/`escalated` (drop, re-point, or escalate it)
so nothing is left silently `blocked`. Node creation is the *only* loop action exempt
from "no action without a node"; it is the sanctioned bootstrap, so an
empty/first-iteration plan is populated here rather than deadlocking. Record the
re-plan delta in the run-log entry.
10. **Select & act — only within the trust level** (**if no level was explicitly
declared for this run, treat it as L1 — never infer L2/L3 from context**). Pick the
highest-priority **ready node(s)** from the just-updated plan:
- L1: record findings only (no dispatch).
- L2/L3: fix the selected nodes via the **loop-iteration Workflow**
(`.claude/workflows/loop-iteration.js`, `args.items` = those **ready plan nodes**,
each `id` = the node `id`; **no action without a node**): an implementer subagent
fixes each in an isolated worktree and a *separate* verifier reviews the real diff
and approves/rejects (a hard denylist gate also forces escalation — readiness does
not bypass it). For each **approved** item, apply its returned `diff` patch, re-run
the smallest relevant check, then mark that node `done` in the plan; escalate
`rejected`/`escalate` items (set the node `escalated`). Never fix nice-to-haves
automatically; never start unrelated refactors.
- **If no node is ready** after re-plan, do not invent work — **self-stop** (per
*Stop when*).
11. **Append a run entry** to `.claude/memory/loop-run-log.md` (see format below).

## Denylist — never touch autonomously (escalate instead)
Auth, payments, secrets/`.env`, infrastructure/deploy, CI workflow config, and
Expand Down Expand Up @@ -73,7 +107,7 @@ Notify only when action is needed — do not ping on a no-op run.
a token ceiling — and record remaining budget in every run-log entry; stop and
escalate when reached.
- Max iterations per item per run: small (e.g. 3). Max auto-PRs per day: small.
- A loop with an empty watchlist should self-stop, not spin.
- A loop with an empty watchlist **MUST** self-stop, not spin.

## Stop when
- No blockers remain, browser verification passes, relevant tests pass, and the
Expand All @@ -82,8 +116,10 @@ Notify only when action is needed — do not ping on a no-op run.
## Run-log entry (append to `.claude/memory/loop-run-log.md`)
```
## [YYYY-MM-DD HH:MM] L<level> | <task>
- node: <plan-item id acted on, or "none — report-only / no ready node">
- found: <n blockers / n important / n nice-to-have>
- actions: <fixes applied, or "report only">
- re-plan: <nodes added / marked done / dropped, or "none">
- budget: <used>/<cap> (turns or tokens)
- escalations: <none | reason>
- evidence: <tests/browser>
Expand Down
5 changes: 4 additions & 1 deletion .claude/memory/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ The **always-loaded catalog** of the project knowledge wiki (Andrej Karpathy's
"LLM wiki" pattern). `CLAUDE.md` imports **only this file**, so it loads every
session and must stay small. Detailed knowledge lives in topic pages under
`.claude/memory/topics/` and is read **on demand** — never all at once. History
lives in `.claude/memory/log.md`.
lives in `.claude/memory/log.md`. Loop **state** (not knowledge) lives beside it:
`loop-plan.md` is the loop's current plan/DAG (the mutable watchlist every loop action
traces to) and `loop-run-log.md` is the append-only run history — neither is a topic
page; both are read only by a running `/loop`.

> Links below are plain markdown (not `@imports`), so topic pages are NOT
> auto-loaded. Open the ones relevant to the current task.
Expand Down
79 changes: 79 additions & 0 deletions .claude/memory/loop-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Loop Plan — the DAG every loop action traces to

The loop's **durable, mutable plan**: an ordered set of typed work nodes with explicit
`after:` dependency edges. This is the HTDAG "no action without a plan node" surface —
every L2/L3 fix the loop dispatches must correspond to a **ready node** here.

This is **distinct from `loop-run-log.md`**:
- `loop-plan.md` (this file) = the *current* plan — what remains, mutable, re-planned each iteration.
- `loop-run-log.md` = append-only *history* — what happened, never rewritten.

> Not `@imported`, so it costs nothing until a loop opens it. The loop reads it at
> iteration start (step 1) and rewrites it in the re-plan step (see `.claude/loop.md`).

## Node schema
One row per node in the table below:

| Field | Meaning |
|---|---|
| `id` | Stable node id, `LP-NNN` (never reused after `done`/`dropped`). |
| `description` | One line — the bounded unit of work. |
| `after` | Comma-separated `id`s this node depends on; empty = no deps. |
| `status` | `ready` · `blocked` · `in-progress` · `done` · `escalated` · `dropped`. |
| `origin` | Where it came from: a confirmed critic finding, a re-plan, or a human. |
| `notes` | Evidence / decision trail (attempt count, escalation reason). |

**Status meaning**
- `ready` — all `after:` deps are `done`; eligible for dispatch this iteration.
- `blocked` — has an unmet `after:` dep (compute, don't hand-set, when deps are open).
- `in-progress` — dispatched to the loop-iteration Workflow this iteration.
- `done` — implemented **and** verified by the separate checker; deps unblock.
- `escalated` — handed to a human (denylist hit, max attempts, ambiguity).
- `dropped` — obsoleted by re-plan; kept as a row for the decision trail.

## Ready-node rule (what the loop dispatches next)
A node is **ready** iff `status ∉ {done, dropped, escalated, in-progress}` **and** every
`id` in its `after:` is `done`. The loop picks the highest-priority ready node (topmost
first). Denylist and trust-level gates still apply **on top** of readiness — a ready node
whose fix would touch a denylist path is escalated, never auto-applied.

**Stale `in-progress` recovery:** `in-progress` means "dispatched this iteration" and
normally resolves to `done`/`escalated` in the same iteration. A node still `in-progress`
at the *start* of a later iteration means the prior run was interrupted (crash / timeout /
budget exhaustion) — it is stale. The re-plan step MUST resolve it (reset it to `ready`
or `blocked` per its current `after:` deps, with a note) before it can be selected again;
never dispatch a leftover `in-progress` node as-is (it may be mid-flight or orphaned).

## No action without a node
Every L2/L3 dispatch (`loop-iteration.js` `args.items`) MUST be ready nodes from this
table — each item's `id` is its node `id`. An action with no backing node is **not
taken**; instead the re-plan step creates the node first (see below). Node creation is
the *only* loop action not itself gated by an existing node — it is the sanctioned way
to bootstrap, so an empty plan never deadlocks:

- **Empty / first-iteration plan:** after triage, the re-plan step creates nodes from
this run's *confirmed* findings, then dispatch acts on the now-ready nodes. If triage
+ re-plan yield zero ready nodes, the loop **self-stops** (empty-watchlist rule in
`loop.md`) — it does not invent work.
- **Cascading drop / escalation** (a dependency never reaching `done`): since readiness
requires every `after:` id to be `done`, a node whose dependency becomes `dropped` or
`escalated` can never become ready and would sit `blocked` forever. The re-plan step
MUST resolve such a node the same iteration — never leave it silently stuck:
- dependency **`dropped`** → drop the dependent too (`origin: cascade from <id>`) if now
obsolete, or re-point its `after:` to a live node;
- dependency **`escalated`** → do **not** re-wire to un-block it (its prerequisite work
is unresolved) — set the dependent `escalated` too, with a note.

## Example (illustrative — not a live row)
```
| LP-001 | Fix hydration warning on / | — | done | critic:frontend | verified 2026-… |
| LP-002 | Add E2E for the cleared-name path | LP-001 | ready | critic:qa | |
| LP-003 | Rework auth token refresh | — | escalated | critic:security | denylist: auth |
```

## Plan
_No nodes yet. The first L2/L3 loop run bootstraps nodes here from its confirmed
findings during the re-plan step._

| id | description | after | status | origin | notes |
|---|---|---|---|---|---|
6 changes: 5 additions & 1 deletion .claude/memory/loop-run-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ Entry format (see `.claude/loop.md`):

```
## [YYYY-MM-DD HH:MM] L<level> | <task>
- node: <plan-item id acted on, or "none — report-only / no ready node">
- found: <n blockers / n important / n nice-to-have>
- actions: <fixes applied, or "report only">
- re-plan: <nodes added / marked done / dropped, or "none">
- budget: <used>/<cap> (turns or tokens)
- escalations: <none | reason>
- evidence: <tests/browser>
Expand All @@ -18,7 +20,9 @@ Entry format (see `.claude/loop.md`):

Rules:
- One entry per loop iteration; record outcomes, timestamps, and any human override.
- Prune resolved watchlist items; keep the decision trail.
- The **plan** (`.claude/memory/loop-plan.md`) holds the *current* watchlist/DAG (mutable,
re-planned each iteration); this run-log is the *append-only history*. Prune resolved
nodes in the plan, not here; keep the decision trail in both.
- **Rotation:** when this file passes **500 lines**, roll older entries into
`.claude/memory/loop-run-log/<YYYY>.md` — **create that directory/file on demand**
if absent (same rule as `log.md`); the file is not `@imported`, so this is about
Expand Down
10 changes: 10 additions & 0 deletions .claude/memory/topics/workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@
`.claude/memory/loop-run-log.md`, and obeys the denylist + red-flag stop
conditions in `.claude/loop.md`. Raise a level only after `.claude/loop-checklist.md`
passes. The verifier pass is always separate from the implementer (no self-approve).
- **The loop is HTDAG-disciplined:** it traces every action to a durable plan/DAG in
`.claude/memory/loop-plan.md` — typed nodes with `after:` dependency edges and a
`ready` rule (deps `done`). Three disciplines: (1) **persistent plan** the loop reads
at iteration start (step 1) and rewrites in the re-plan step; (2) **re-plan is a
first-class step** that runs *before* dispatch (step 9) so the loop consults the fresh
DAG every iteration; (3) **no action without a node** — every L2/L3 dispatch is a ready
plan node, and node creation is the *only* action exempt (the sanctioned bootstrap, so
an empty plan populates rather than deadlocks). Readiness never bypasses the denylist
or trust level — those gates stay on top. `loop-plan.md` = current mutable plan;
`loop-run-log.md` = append-only history — keep them distinct.
- Custom commands live in `.claude/commands/` (each with a `description` for
routing); the grouped "when to use" catalog is `.claude/skills-index.md`. A
catalog file must NOT live in `.claude/commands/` — anything there auto-registers
Expand Down
5 changes: 3 additions & 2 deletions .claude/skills-index.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ catalog so both you and Claude can route to the right command.
- **Capture/curate what was learned** → `/dream`, `/memory-audit`
- **Improve the system's own prompts/skills/workflows** → `/improve-skills`
- **Keep watching localhost on an interval** → `/loop` (uses `.claude/loop.md`;
gate trust level with `.claude/loop-checklist.md`, state in
`.claude/memory/loop-run-log.md`)
gate trust level with `.claude/loop-checklist.md`; state in
`.claude/memory/loop-plan.md` (current plan/DAG — every L2/L3 action traces to a ready
node here) and `.claude/memory/loop-run-log.md` (append-only history))

## Development loop

Expand Down
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ building. Triggers: "write me the `/goal` for this", "turn this into a `/loop`",
For recurring/watchdog work, the standing loop prompt is `.claude/loop.md`
(used by `/loop`). Loops are **hardened** ("loop engineering"): declare a trust
level — **L1 report-only → L2 assisted → L3 unattended** — and stay within it; read
`.claude/memory/loop-plan.md` (the durable plan/DAG — every L2/L3 action must trace to a
ready node, and re-plan runs as a first-class step before each dispatch) and
`.claude/memory/loop-run-log.md` at the start of each iteration and append an entry
per iteration; respect the denylist (auth, payments, secrets, infra, CI config, migrations),
the escalation triggers, and the red-flag stop conditions. Before raising a loop's
Expand Down
2 changes: 1 addition & 1 deletion docs/workflow-graphs.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ flowchart LR

```mermaid
flowchart LR
items([triaged, denylist-cleared items]) --> budget{budget floor?}
items([ready plan nodes — triaged, denylist-cleared]) --> budget{budget floor?}
budget -- "under floor" --> esc0[▫️ escalate all]
budget -- ok --> P
subgraph P["pipeline — per item, independent"]
Expand Down
Loading