From 6cff73a0c4dff89e4d4674c08117fbfaea3dec7d Mon Sep 17 00:00:00 2001 From: Jiri Puc Date: Sun, 12 Jul 2026 23:13:02 +0200 Subject: [PATCH 1/6] docs: add decision log + AGENTS.md/CLAUDE.md working agreement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adopt the ultimate-memory house style (terse canonical decision log + non-negotiable doc rules) so the deliberate design choices are legible to future agents and humans who were not in the conversation. - design/decisions.md — Architecture Decision Log (D1-D6): continuity in files/git (D1); single-worker is deliberate (D2); iteration success = "ran" not "good" (D3); LLM-as-judge, no agent-authored deterministic checks (D4); full autonomy with unresolvable_error as the only last-resort human escape hatch, no preferred human gate (D5); large multi-phase targets driven by the planner/dispatcher double loop from the start (D6). Each entry is self-contained (Decision / Context / Consequences) and anchors claims in code. - AGENTS.md — working agreement: Rule 1 respect the deliberate decisions (don't "fix" D2/D3/D4); Rule 2 autonomy is the goal, the unresolvable_error escape hatch is a LAST resort and no preferred human gate (D5); Rule 3 design/decision docs must be understandable cold by future agents AND humans. - CLAUDE.md — short pointer to AGENTS.md plus the two things that most trip up a change (the deliberate decisions and the autonomy escape hatch), so the two files never diverge. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01PN8aFwwxA8FzMy9LgpbQFt --- AGENTS.md | 80 ++++++++++++++++++++++ CLAUDE.md | 17 +++++ design/decisions.md | 163 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 260 insertions(+) create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 design/decisions.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..466380b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,80 @@ +# AGENTS.md — working agreement for this repo + +`loopy-loop` runs long-running AI agent workflows inside a repository. A FastAPI +**coordinator** owns durable loop state in files and picks the next workflow; a single +**worker** runs each assignment through `team-harness`. Continuity lives in files and git, +not in a chat transcript. Read `README.md` for the user-facing model, and +`design/decisions.md` for *why the system is the way it is*. + +This file is the working agreement for any agent (or human) changing this repo. Three +things are non-negotiable. + +## Rule 1 — Respect the deliberate decisions; don't "fix" them + +`design/decisions.md` is the canonical Architecture Decision Log (D1, D2, …). **Several +decisions there look like defects if you only skim the code, and are recorded precisely so +they don't get "fixed" by accident.** Before changing behavior in these areas, read the +relevant decision (and its companion design doc): + +- **D3 — iteration success ≠ "work is good."** `IterationResult.success` means only that + the harness ran without erroring. Do not make it consult worker exit codes / infer + semantic success. The eval layer (`control.json` + `goal_check.json`) decides quality. +- **D4 — evaluation is LLM-as-judge; agents don't author deterministic checks.** Do not + "add deterministic checks" to the stock `inner_outer_eval` eval workflows. (A target + repo that owns its own test suite is a different case — see D4.) +- **D2 — single worker is deliberate.** Do not add parallel loopy workers as a scaling + feature. +- **D5 — full autonomy with a last-resort escape hatch.** See Rule 2. + +If you believe a decision is genuinely wrong, propose amending `design/decisions.md` +(state what changes and why) — do not silently contradict it in code. + +## Rule 2 — Autonomy is the goal; the human escape hatch is a LAST resort (D5) + +This system is meant to run **fully autonomously, with no human in the loop**. Human +involvement is a last resort, never a normal step. + +- **The one sanctioned escape hatch already exists.** When a workflow hits a *genuinely + terminal* blocker — a decision only a human can make, a missing credential, a + billable/destructive action it isn't permitted to take — it writes the session + `control.json` with: + ```json + {"state": "stopped", "reason": "", "stop_reason": "unresolvable_error", "schema_version": 1} + ``` + This stops the loop as terminal, with a recorded reason. That is the entire + human-in-the-loop mechanism, and it is enough. +- **Exhaust autonomous options first.** Re-scope, retry with a better child goal, route + around the blocker. Reach for `unresolvable_error` only when a blocker is genuinely + unavoidable without a human. It should fire rarely. +- **Make the give-up legible.** The recorded blocker is the only thing a human sees when a + run stops — state exactly what was missing and what was tried. +- **Do NOT build or assume a preferred human gate.** No `paused` / `waiting_for_human` + state, no `gate_request.json`, no external-action approval flow. That was considered and + rejected (D5; `design/designs/improvement-proposals.md` P0.2). Do not pause a run to ask + a question when you could either solve it autonomously or stop cleanly with + `unresolvable_error`. + +## Rule 3 — Design and decision docs must be understandable cold, by future agents AND humans + +A design or decision doc is read by someone who was **not** in the conversation that +produced it — a future agent with no memory of this session, or a human who is not a +specialist. Write for them. + +- **Explain, don't just name.** Naming a mechanism ("the ping-pong protocol", "cadence + scheduling", "the double loop") is not explaining it. State, in plain language, *what it + is, what problem it solves, and why we chose it*, with a concrete example where it helps. +- **The reasoning lives in the doc, not in your head.** Don't rely on a future reader + re-deriving the rationale. A decision-log entry may state the conclusion tersely; the + companion design section must be self-contained. +- **Anchor claims in the code.** Reference the file/function (e.g. `coordinator_app.py`, + `harness_runner.py`) so a reader can verify — but lead with the plain-English meaning. +- **Keep `design/` honest about status.** `design/designs/` is binding; `design/analysis/` + is working notes (may be superseded); `improvement-proposals.md` is *proposed, not + decided*. Don't cite a proposal as if it were a decision. + +When in doubt on any rule, favor the version a stranger could read cold and fully +understand. + +--- + +Note: `CLAUDE.md` points here. Keep this file as the single source; don't fork the two. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..fb18f45 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,17 @@ +# CLAUDE.md + +The working agreement for this repo lives in **[`AGENTS.md`](./AGENTS.md)** — read it first. +It is the single source; this file only points to it so the two never diverge. + +Quick orientation for what most often trips up a change here: + +- **`design/decisions.md`** is the canonical decision log. Several decisions look like + defects if you only skim the code and are recorded so they don't get "fixed" by accident + (notably **D3** iteration-success semantics and **D4** LLM-as-judge evaluation). +- **Autonomy is the goal; the human escape hatch is a last resort (D5).** The system runs + unattended. When a workflow hits a genuinely terminal blocker it stops by writing + `control.json` with `stop_reason: "unresolvable_error"` and a specific recorded reason — + that is the entire human-in-the-loop mechanism. Do **not** build or assume a preferred + `paused` / `waiting_for_human` gate; exhaust autonomous options first, then stop cleanly. + +See `AGENTS.md` Rules 1–3 for the full agreement. diff --git a/design/decisions.md b/design/decisions.md new file mode 100644 index 0000000..c5eb412 --- /dev/null +++ b/design/decisions.md @@ -0,0 +1,163 @@ +# Architecture Decision Log + +Decisions made while building and reviewing loopy-loop, recorded with the context and +rationale that a future reader — a human implementer, or an agent with **no memory of +the conversation that produced them** — needs to understand each one cold. + +Companion docs: +- `design/designs/success-semantics-and-evaluation.md` — the self-contained, detailed + form of **D3** and **D4** (this log states the conclusion; that doc makes it complete). +- `design/designs/improvement-proposals.md` — forward-looking changes we are *considering* + (not decided; do not treat as binding). +- `design/analysis/` — the July 2026 review that produced several of these decisions + (working notes; may be messy or superseded). +- `README.md` — the user-facing behavior these decisions implement. + +Each entry states the **Decision** (the conclusion, plainly), the **Context** (what +problem it solves or why the question arose), and the **Consequences** (what follows). +A `**Refined by**` line records later decisions that modify an earlier one. + +> **Several of these are deliberate choices that read like defects to someone skimming +> the code.** They are recorded here precisely so a future agent does not "fix" them by +> accident. If you are about to change behavior related to an entry below, read the +> entry — and its companion design section — first. + +--- + +## D1. Continuity lives in files and git, not in a chat transcript + +**Decision.** All durable state — loop dispatch state, the goal, each iteration's +rendered prompt and result, evidence, and the stop switch — lives as inspectable files +under `.loopy_loop/sessions//`, alongside the target repo's normal git +history. A session is a directory you can read, pause, resume, and audit; it is **not** +a growing chat conversation. + +**Context.** The common "one agent solves a large task in one long chat" approach hides +all state inside a transcript: you cannot inspect it, resume it after a crash, diff it, +or audit why it did what it did. loopy-loop exists to replace that with durable, +external structure. + +**Consequences.** Every mechanism in this log assumes files are the source of truth. +Crash recovery reads artifacts on disk (e.g. `pending_finished_request.json`, +`result.json`), never in-memory conversation. A finished run is legible after the fact. +This is the premise the rest of the log depends on. + +## D2. The single-worker model is deliberate + +**Decision.** The coordinator drives exactly **one** worker at a time, over a +two-endpoint ping-pong API (`POST /register`, `POST /finished`). v0.2.0 deliberately +removed leases, polling, and worker identity to get here. Several recovery paths in +`coordinator_app.py` are correct **only** under this single-worker assumption, and say +so in comments. + +**Context.** loopy-loop coordinates stateful mutations to a single working checkout. +Multiple concurrent workers editing one checkout would need worktree/branch isolation, a +merge coordinator, and conflict recovery — a large, error-prone machine. Intra-iteration +parallelism already exists one layer down, inside `team-harness` (a coordinator LLM can +spawn several worker CLIs at once). + +**Consequences.** Do **not** reintroduce parallel loopy workers as a "scaling" feature; +it is a redesign of the state machine, not a flag, and it is explicitly deferred (see +`improvement-proposals.md`). Recovery logic may rely on "at most one task is live." +Child sessions are depth-first, one at a time (see D6). + +## D3. Iteration success means "the assignment ran," not "the work is good" + +**Decision.** `IterationResult.success` is `True` whenever a `team-harness` run returns +normally, and `False` only when the harness itself raises. It is **not** a judgment +about whether the requested work was accomplished, and the per-worker exit codes in +`TeamHarnessResult.agents` are intentionally not consulted. Whether the work was any +*good* is decided by the evaluation layer via `control.json` (the stop switch) and +`goal_check.json` (evidence). + +**Context.** `team-harness`'s coordinator is an orchestrator, not a build system: it can +legitimately return a normal result after a worker failed (synthesize an answer, route +around a dead worker). Worker exit codes are therefore a *noisy* proxy for real success +— a non-zero worker can accompany a good outcome, and all-green workers can accompany a +useless one. Mapping them to a boolean would manufacture false precision. This has been +the behavior since the first commit of `harness_runner.py`; it is original intent, not +drift. + +**Consequences.** The evaluation layer, not the harness return value, is the real +arbiter of completion. The scheduler keys cadence off mechanical success, so a +harness-completed-but-worker-failed run still advances `run_every`/`must_follow` +counters — an accepted, bounded inaccuracy, because `control.json`/`goal_check.json` +remain the true gates. Full reasoning and alternatives: +`design/designs/success-semantics-and-evaluation.md` (Decision 1). + +## D4. Evaluation is LLM-as-judge; agents do not author deterministic checks + +**Decision.** In the packaged `inner_outer_eval` workflow set, the eval workflows create +**only** `harness_judge` (LLM-as-judge) checks that describe desired *outcomes*. +Authoring deterministic checks is explicitly forbidden in the stock template. + +**Context.** This is a lesson from experience, not theory. When agents were allowed to +*author* deterministic checks, they produced brittle, wrong-target, gameable ones — the +implementer inventing its own pass/fail criteria let it game itself. Judging a described +outcome removes that failure mode. **Important boundary:** the thing that failed was +*agent-authored* checks, not deterministic checks as a category. Running a check the +repo *already owns* (`pytest`, `import-linter`, `alembic upgrade`, exit codes) is +deterministic but is not that failure mode. + +**Consequences.** The stock "deterministic forbidden" rule is correct for generic target +repos, where the only deterministic checks would be agent-invented. For a target that +already owns a trustworthy contract-test suite, the right configuration is *both* — the +judge for qualitative outcomes, plus a deterministic backstop that shells out to the +repo's own suite — via a dedicated child workflow set, not by loosening the stock +template. Judge with a different model family than the implementer where practical. A +single judge pass is evidence, not a hard gate for a high-stakes stop. Full reasoning: +`design/designs/success-semantics-and-evaluation.md` (Decision 2). + +## D5. Full autonomy, with `unresolvable_error` as the only, last-resort human escape hatch + +**Decision.** The design goal is to run **fully autonomously, with no human in the +loop**. Human involvement is a *last resort*, never a step in the normal flow. The one +sanctioned escape hatch is already built: a workflow that hits a genuinely terminal +blocker writes `control.json` with `stop_reason: "unresolvable_error"`, which stops the +session as terminal and leaves a recorded reason. We deliberately **do not** build a +preferred, resumable "pause and wait for a human to answer" gate. + +**Context.** An autonomous long-horizon loop will occasionally hit something it truly +cannot do alone — a decision only a human can make, a credential it lacks, a +billable/destructive action it is not permitted to take. It must not silently guess, and +it must not stall. But making a human-answered gate a *normal* step would defeat the +whole autonomy goal. The escape hatch already exists end to end and needs no new +machinery: +- `ControlSignal.stop_reason` is `Literal["goal_met", "unresolvable_error"]` + (`models.py`). +- Writing it flows through `_apply_session_control` → `_apply_stop_precedence` in + `coordinator_app.py`, which stops the loop. +- The stock planner prompt already instructs it: *"If no useful work remains and the + blocker is genuinely terminal, update the Session control path with stop_reason + `unresolvable_error` and record the exact blocker."* + +**Consequences.** Workflows must **exhaust autonomous options first** — re-scope, retry +with a better child goal, route around the blocker — and reach for `unresolvable_error` +only when a blocker is genuinely unavoidable without a human. When they do stop, the +recorded blocker must be specific enough that a human reading it later understands +exactly what was missing and what was tried, because that report is the entire +human-facing surface. Do **not** add a `paused` / `waiting_for_human` state, a +`gate_request.json` flow, or an external-action approval gate; that proposal was +considered and rejected (see `improvement-proposals.md` P0.2). This decision is the +mechanism `AGENTS.md` and `CLAUDE.md` point agents at. + +## D6. Large multi-phase targets are driven by the planner/dispatcher double loop from the start + +**Decision.** For a large, multi-phase target project, the intended execution shape is +the **planner/dispatcher double loop** (the `pm_planner_dispatcher` workflow set) from +day one — a parent "planner" session that maintains PM state and selects one unit of +work, a "dispatcher" that spawns a child implementation session per unit, and a review +of the child's evidence — rather than starting with a single flat loop and adding the +double loop later. + +**Context.** A single `inner_outer_eval` loop pointed at "build the whole thing" drowns +in context; the double loop keeps each unit small (a fresh child context scoped to one +work package) while the parent carries durable cross-cutting state. Committing to it from +the start avoids re-architecting mid-project. + +**Consequences.** The parent/child machinery is on the critical path from day one, so it +must be hardened *first* — durable active-child crash recovery, per-child budgets, and a +PM template that is runnable from a clean init are prerequisites, not later polish (see +`improvement-proposals.md` P0). Child sessions remain depth-first and one-at-a-time +(consistent with D2). The planner drives the target's *own* authoritative plan; it does +not invent a parallel backlog. From ebeeefb233a9668170d11b301b6287a7e9367b30 Mon Sep 17 00:00:00 2001 From: Jiri Puc Date: Sun, 12 Jul 2026 23:29:45 +0200 Subject: [PATCH 2/6] docs(design): align D6 with P0.3 withdrawal + state-not-processes recovery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop "per-child budgets" from D6's prerequisites (withdrawn as P0.3), and note that double-loop crash recovery is of session/child STATE from files, not of running agent processes — re-adopting a crashed worker's agent subprocesses is not feasible, so a hard worker crash is cleanup, not adoption (proposals P2.5). Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01PN8aFwwxA8FzMy9LgpbQFt --- design/decisions.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/design/decisions.md b/design/decisions.md index c5eb412..884c7b7 100644 --- a/design/decisions.md +++ b/design/decisions.md @@ -156,8 +156,11 @@ work package) while the parent carries durable cross-cutting state. Committing t the start avoids re-architecting mid-project. **Consequences.** The parent/child machinery is on the critical path from day one, so it -must be hardened *first* — durable active-child crash recovery, per-child budgets, and a -PM template that is runnable from a clean init are prerequisites, not later polish (see -`improvement-proposals.md` P0). Child sessions remain depth-first and one-at-a-time +must be hardened *first* — durable active-child crash recovery and a PM template that is +runnable from a clean init are prerequisites, not later polish (see +`improvement-proposals.md` P0). Note that this recovery is of session/child *state* from +files, not of running agent processes: re-adopting a crashed worker's agent subprocesses +is not feasible, so a hard worker crash is handled by cleanup, not adoption +(`improvement-proposals.md` P2.5). Child sessions remain depth-first and one-at-a-time (consistent with D2). The planner drives the target's *own* authoritative plan; it does not invent a parallel backlog. From 48829c94a4f74511c526200b778a3e1c5fafb28f Mon Sep 17 00:00:00 2001 From: Jiri Puc Date: Sun, 12 Jul 2026 23:49:20 +0200 Subject: [PATCH 3/6] =?UTF-8?q?docs(design):=20add=20D7=20=E2=80=94=20cros?= =?UTF-8?q?s-repo=20process-lifecycle=20ownership?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Record the split we settled on now that we own team-harness: team-harness owns the agent-CLI processes (own process group, persisted pid/pgid/starttime, a reap op — TH-D5), and loopy-loop owns its worker process (pid + heartbeat, verify-dead before reclaim, call reap on recovery). Makes D6 state-recovery safe rather than optimistic; the reaping is a designed team-harness feature we consume, not a /proc-scraping hack. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01PN8aFwwxA8FzMy9LgpbQFt --- design/decisions.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/design/decisions.md b/design/decisions.md index 884c7b7..34bc31b 100644 --- a/design/decisions.md +++ b/design/decisions.md @@ -164,3 +164,31 @@ is not feasible, so a hard worker crash is handled by cleanup, not adoption (`improvement-proposals.md` P2.5). Child sessions remain depth-first and one-at-a-time (consistent with D2). The planner drives the target's *own* authoritative plan; it does not invent a parallel backlog. + +## D7. Process-lifecycle ownership is split: team-harness owns agent processes, loopy-loop owns the worker + +**Decision.** Responsibility for OS process lifecycle is split cleanly across the two repos we +own: +- **team-harness owns the agent-CLI processes** it spawns. It launches each in its own process + group, persists their identity (pid/pgid/starttime) in its worker-session manifest, and + provides a **reap** operation to kill leftover groups. (team-harness `design/decisions.md` + TH-D5; `design/designs/process-lifecycle-and-reaping.md`.) +- **loopy-loop owns its own `loopy worker` process.** It records the worker's pid and a + heartbeat in the session directory, and on crash recovery it (a) uses worker liveness to tell + "still running" from "dead" before reclaiming a task — closing the duplicate-work window on a + second `/register` — and (b) calls team-harness **reap** for the interrupted run to kill any + orphaned agent processes before starting fresh. + +**Context.** loopy-loop runs the harness synchronously inside its worker; the agent CLIs are +children of that worker, not of loopy-loop's coordinator, and loopy-loop tracks no process +identity of its own today. A hard worker crash therefore orphans agent processes that keep +spending money and writing to the checkout, and loopy-loop cannot currently distinguish a live +worker from a dead one. Neither problem is solvable by re-adopting processes (impossible — see +D6 and team-harness TH-D2); both are solvable by *tracking identity + reaping*, and the natural +split is "each layer owns the processes it spawns." + +**Consequences.** The agent-reaping mechanism is a team-harness feature (we own it — it is not a +`/proc`-scraping hack bolted onto loopy-loop); loopy-loop consumes it. loopy-loop's own new work +is small: persist the worker pid + heartbeat, and call reap on recovery. This makes D6's +state-recovery *safe* (verify-dead-before-reclaim) rather than optimistic. See +`improvement-proposals.md` P0.1 (worker liveness) and P2.5 (reaping), and team-harness TH-D5. From 349e7d1630d2e54e1614f43b9d52306797f89432 Mon Sep 17 00:00:00 2001 From: Jiri Puc Date: Mon, 13 Jul 2026 00:09:58 +0200 Subject: [PATCH 4/6] docs(design): D7 recovery default is bounded drain, reap the escape Align D7 with TH-D5 / P2.5: team-harness provides liveness + drain/reap/ignore ops; loopy's default recovery policy is bounded drain (finish + harvest within a timeout), with reap as the escape. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01PN8aFwwxA8FzMy9LgpbQFt --- design/decisions.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/design/decisions.md b/design/decisions.md index 34bc31b..ccf5e09 100644 --- a/design/decisions.md +++ b/design/decisions.md @@ -171,21 +171,25 @@ not invent a parallel backlog. own: - **team-harness owns the agent-CLI processes** it spawns. It launches each in its own process group, persists their identity (pid/pgid/starttime) in its worker-session manifest, and - provides a **reap** operation to kill leftover groups. (team-harness `design/decisions.md` - TH-D5; `design/designs/process-lifecycle-and-reaping.md`.) + provides a durable liveness check plus **drain / reap / ignore** policy operations over those + groups. (team-harness `design/decisions.md` TH-D5; + `design/designs/process-lifecycle-and-reaping.md`.) - **loopy-loop owns its own `loopy worker` process.** It records the worker's pid and a heartbeat in the session directory, and on crash recovery it (a) uses worker liveness to tell "still running" from "dead" before reclaiming a task — closing the duplicate-work window on a - second `/register` — and (b) calls team-harness **reap** for the interrupted run to kill any - orphaned agent processes before starting fresh. + second `/register` — and (b) applies a policy per orphaned agent for the interrupted run. + **Default: bounded drain** — let an in-flight agent finish within a timeout and harvest its + output (fits loopy's git-is-truth, cost-conscious profile; no concurrent-writer problem + because it runs during recovery), with **reap** as the escape for force-stop / hung-past-timeout + / unsafe-to-finish. **Context.** loopy-loop runs the harness synchronously inside its worker; the agent CLIs are children of that worker, not of loopy-loop's coordinator, and loopy-loop tracks no process identity of its own today. A hard worker crash therefore orphans agent processes that keep spending money and writing to the checkout, and loopy-loop cannot currently distinguish a live worker from a dead one. Neither problem is solvable by re-adopting processes (impossible — see -D6 and team-harness TH-D2); both are solvable by *tracking identity + reaping*, and the natural -split is "each layer owns the processes it spawns." +D6 and team-harness TH-D2); both are solvable by *tracking identity, then draining or reaping*, +and the natural split is "each layer owns the processes it spawns." **Consequences.** The agent-reaping mechanism is a team-harness feature (we own it — it is not a `/proc`-scraping hack bolted onto loopy-loop); loopy-loop consumes it. loopy-loop's own new work From 030aadac0ce3731ad9b37f687cc5f914475352a6 Mon Sep 17 00:00:00 2001 From: Jiri Puc Date: Mon, 13 Jul 2026 00:17:58 +0200 Subject: [PATCH 5/6] =?UTF-8?q?docs(design):=20D7=20consequences=20?= =?UTF-8?q?=E2=80=94=20recovery=20policy=20(drain=20default),=20not=20reap?= =?UTF-8?q?-only?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Last stale reap-only phrasing: loopy's new work is "apply a recovery policy per orphan (default bounded drain, reap as escape)", not "call reap on recovery". Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01PN8aFwwxA8FzMy9LgpbQFt --- design/decisions.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/design/decisions.md b/design/decisions.md index ccf5e09..1fc3ad2 100644 --- a/design/decisions.md +++ b/design/decisions.md @@ -191,8 +191,10 @@ worker from a dead one. Neither problem is solvable by re-adopting processes (im D6 and team-harness TH-D2); both are solvable by *tracking identity, then draining or reaping*, and the natural split is "each layer owns the processes it spawns." -**Consequences.** The agent-reaping mechanism is a team-harness feature (we own it — it is not a -`/proc`-scraping hack bolted onto loopy-loop); loopy-loop consumes it. loopy-loop's own new work -is small: persist the worker pid + heartbeat, and call reap on recovery. This makes D6's +**Consequences.** The process-lifecycle mechanism (liveness + drain/reap/ignore) is a +team-harness feature (we own it — it is not a `/proc`-scraping hack bolted onto loopy-loop); +loopy-loop consumes it. loopy-loop's own new work is small: persist the worker pid + heartbeat, +and apply a recovery policy per orphan (default bounded drain, reap as escape). This makes D6's state-recovery *safe* (verify-dead-before-reclaim) rather than optimistic. See -`improvement-proposals.md` P0.1 (worker liveness) and P2.5 (reaping), and team-harness TH-D5. +`improvement-proposals.md` P0.1 (worker liveness) and P2.5 (recovery policy), and team-harness +TH-D5. From 9ba293ac3f3028e02855b37849e216a19c4ce92c Mon Sep 17 00:00:00 2001 From: Jiri Puc Date: Mon, 13 Jul 2026 00:30:35 +0200 Subject: [PATCH 6/6] =?UTF-8?q?docs(design):=20D7=20=E2=80=94=20drained=20?= =?UTF-8?q?iterations=20re-run;=20salvage.json=20records=20provenance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Align D7 with the pinned-down drain semantics: drain never synthesizes the dead coordinator's result (D3 false-closure trap); its yield is the agents' completed repo edits (git, D1) plus an explicit salvage record (salvage.json + abandoned_after_drain) so surviving edits are auditable. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01PN8aFwwxA8FzMy9LgpbQFt --- design/decisions.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/design/decisions.md b/design/decisions.md index 1fc3ad2..0239be3 100644 --- a/design/decisions.md +++ b/design/decisions.md @@ -178,10 +178,15 @@ own: heartbeat in the session directory, and on crash recovery it (a) uses worker liveness to tell "still running" from "dead" before reclaiming a task — closing the duplicate-work window on a second `/register` — and (b) applies a policy per orphaned agent for the interrupted run. - **Default: bounded drain** — let an in-flight agent finish within a timeout and harvest its - output (fits loopy's git-is-truth, cost-conscious profile; no concurrent-writer problem - because it runs during recovery), with **reap** as the escape for force-stop / hung-past-timeout - / unsafe-to-finish. + **Default: bounded drain** — let an in-flight agent finish within a timeout (fits loopy's + git-is-truth, cost-conscious profile; no concurrent-writer problem because it runs during + recovery), with **reap** as the escape for force-stop / hung-past-timeout / unsafe-to-finish. + A drained iteration is **still re-run**: its `result.json` never existed and is never + synthesized from drained outputs (that would fabricate a result the dead coordinator never + produced — the false-closure trap D3 prevents). The drain's yield is the agents' completed + repo edits (preserved by git, D1) plus an explicit **salvage record** — `salvage.json` in the + interrupted iteration's directory and an `abandoned_after_drain` history code — so the + provenance of the surviving edits is auditable rather than a mystery diff. **Context.** loopy-loop runs the harness synchronously inside its worker; the agent CLIs are children of that worker, not of loopy-loop's coordinator, and loopy-loop tracks no process