Skip to content

Commit 832bc1e

Browse files
alari76claude
andcommitted
feat(loops): Loops 2.0 Phase 1 — durable engine core, v1 deleted
Implements Phase 1 of docs/LOOPS-REWRITE-SPEC.md and removes the unused v1 Goal Runs slice outright (replace, don't migrate — spec §12). Engine core: - loop-recipe: recipe v2 format (apiVersion codekin.dev/v2), strict validation (unknown fields fail), normalization + content hash frozen per run, built-in + per-repo discovery, provider resolution (auto / different-from-maker) - loop-store: event-sourced schema in the shared runs.db — loop_runs (state and outcome stored separately), loop_stages, loop_attempts, loop_events (append-only, per-run monotonic sequence), loop_checkpoints, loop_evaluations, loop_artifacts, loop_interventions; drops the v1 goal_runs tables on open - loop-artifacts: content-addressed evidence bodies on disk - loop-evaluators: command evaluator (shell string or argv, timeout, environment-vs-code classification, normalized failure fingerprints) plus rubric prompt/verdict helpers ported from the v1 checker - loop-engine: deterministic decision node (spec §7 order), checkpoints after every decided turn, pause/resume/steer/cancel as auditable events, interventions (completion approval in guided mode, budget extension, escalation), no-progress detection, wall-time budget, and startup recovery that resumes interrupted runs at a stage boundary in their surviving worktree instead of blanket-failing them - loop-finalizer: idempotent commit/push/PR landing (re-finalize after a crash recovers the existing PR) - loop-routes: /api/loops REST surface incl. recipes/validate, runs preflight, resumable event stream (?after=seq), artifact bodies, and all controls v1 removal and rewiring: - deleted goal-run-{store,controller,routes,finalizer}, loop-loader, verifier-runner, LoopRunsView, goalRunApi (+ their tests) - built-in templates rewritten natively in recipe v2 - rewired ws-server (recovery instead of failInterrupted, event bridge), orchestrator-monitor (handleLoopEvent), unified-runs (state/outcome folding, 'paused' added / 'aborted' dropped from the lifecycle vocabulary), runs-routes, MCP tools (start_loop takes recipeId), relay allowlist (/api/loops), interim LoopsView tab in Automations - docs: LOOPS.md rewritten for v2, /api/loops section in API-REFERENCE Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent ea934d1 commit 832bc1e

55 files changed

Lines changed: 6126 additions & 3913 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ codekin uninstall # Remove Codekin entirely
5353
- **Multi-provider AI** — Use Claude Code, [OpenCode](https://github.com/nicepkg/opencode), or [OpenAI Codex](https://github.com/openai/codex) as the backend per session. OpenCode enables any LLM provider (OpenAI, Gemini, etc.) through a single interface; Codex unlocks ChatGPT-subscription OpenAI models — all with full streaming, tool events, plan mode, and permission control
5454
- **Multi-session terminal** — Open and switch between multiple coding sessions, one per repo
5555
- **Agent Joe** — AI orchestrator agent that spawns and manages up to 5 concurrent child sessions, with a dedicated chat UI, welcome screen, and color-coded sidebar status indicators. Resilient by design: realtime blocked-child notifications, a persistent notification outbox that replays when the orchestrator returns, pausable child timeouts, and ground-truth completion verification
56-
- **Goal Runs** — Durable act→verify→continue loops that run a coding agent against a goal until a *deterministic* verifier passes (your own build/test/lint commands, judged by exit code), under turn and cost budgets. An optional second provider reviews the diff before it lands, every turn is recorded in an evidence ledger, and a verified run is committed, pushed and opened as a PR by Codekin itself. Ships with CI Autorepair, Coverage Increase, and Dependency Upgrade templates
56+
- **Loops** — Durable, event-sourced outcome loops that run a coding agent until *deterministic* evaluators pass (your own build/test/lint commands, judged by exit code), under turn/cost/wall-time budgets with no-progress detection. An independent second provider reviews the diff before it lands, every transition is an auditable event with retained evidence artifacts, runs survive server restarts (pause/resume/steer included), and a passing run is committed, pushed and opened as a PR by Codekin itself. Ships with CI Autorepair, Coverage Increase, and Dependency Upgrade recipes
5757
- **Git worktrees** — Isolate sessions in dedicated worktree directories, with mid-session creation, auto-enable setting, and session context preservation
5858
- **Session archive** — Full retrieval and re-activation of archived sessions
5959
- **Repo browser** — Auto-discovers local repos and GitHub org repos

docs/API-REFERENCE.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,90 @@ Remove a repo workflow configuration.
553553

554554
---
555555

556+
## Loops
557+
558+
Loops 2.0 — durable, event-sourced outcome loops (see [LOOPS.md](./LOOPS.md)
559+
and [LOOPS-REWRITE-SPEC.md](./LOOPS-REWRITE-SPEC.md)). All routes are mounted
560+
at the `/api/loops/` prefix and require the master Bearer token.
561+
562+
### `GET /api/loops/recipes`
563+
564+
List recipes visible to a repo (built-ins plus `{repo}/.codekin/loops/*.md`
565+
overrides). Query: `repoPath` (optional).
566+
567+
**Response:** `{ "recipes": LoopRecipeInfo[] }`
568+
569+
### `POST /api/loops/recipes/validate`
570+
571+
Validate recipe markdown without saving it.
572+
573+
**Request:** `{ "content": string }`
574+
**Response:** `{ "valid": true, "recipe": LoopRecipe }` or `{ "valid": false, "error": string }`
575+
576+
### `POST /api/loops/runs/preflight`
577+
578+
Resolve the exact effective run configuration (frozen recipe, resolved
579+
provider, default branch, outcome) without starting anything.
580+
581+
**Request:** `{ "recipeId": string, "repo": string, "branch"?: string, "goal"?: string }`
582+
**Response:** `{ "effective": { recipe, repo, branch, goal, provider, model } }`
583+
584+
### `POST /api/loops/runs`
585+
586+
Start a run. `branch` defaults to `loop/<recipeId>-<timestamp>`; `goal`
587+
defaults to the recipe's outcome prompt.
588+
589+
**Request:** same shape as preflight.
590+
**Response:** `{ "run": LoopRun }`
591+
592+
### `GET /api/loops/runs`
593+
594+
List runs. Query: `state`, `repo`, `active=1`, `limit`.
595+
596+
**Response:** `{ "runs": LoopRun[] }`
597+
598+
### `GET /api/loops/runs/:id`
599+
600+
One run plus its stages, evaluations, interventions, artifact metadata, and
601+
the current event-sequence cursor.
602+
603+
**Response:** `{ "run": LoopRunDetail }`
604+
605+
### `GET /api/loops/runs/:id/events?after=<sequence>`
606+
607+
The append-only event log — the source of truth clients reconcile against
608+
after a WS reconnect. Events carry `{ runId, sequence, type, at, actor,
609+
stageId?, attemptId?, payload }`.
610+
611+
**Response:** `{ "events": LoopEvent[], "lastSequence": number }`
612+
613+
### `GET /api/loops/runs/:id/artifacts/:artifactId`
614+
615+
An artifact body (evaluator output, review text) as `text/plain`, with
616+
`X-Artifact-Kind` / `X-Artifact-Label` headers.
617+
618+
### `POST /api/loops/runs/:id/pause` · `/resume` · `/cancel`
619+
620+
Pause after the current safe boundary / resume a paused run in its surviving
621+
worktree / stop now (worktree kept). `409` when the run is not in an eligible
622+
state.
623+
624+
### `POST /api/loops/runs/:id/steer`
625+
626+
Queue an operator instruction, delivered to the maker at the next safe
627+
boundary.
628+
629+
**Request:** `{ "instruction": string }`
630+
631+
### `POST /api/loops/runs/:id/interventions/:interventionId/resolve`
632+
633+
Resolve a pending intervention. `choice` must be one of the intervention's
634+
offered options; `note` becomes guidance to the maker where applicable.
635+
636+
**Request:** `{ "choice": string, "note"?: string }`
637+
638+
---
639+
556640
## Orchestrator (Agent Joe)
557641

558642
All orchestrator routes are mounted at the `/api/orchestrator/` prefix.

docs/HOSTED-RELAY-IMPLEMENTATION-PLAN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Facts the plan builds on, from a code audit (2026-08-08):
1616
- The WebSocket half is a clean seam: `wsUrl()` in `src/lib/ccApi.ts:461` is the only WS URL
1717
construction, called from exactly one place (`src/hooks/useWsConnection.ts:86`).
1818
- The REST half is **not** centralized: four `BASE` constants (`src/lib/ccApi.ts:10`,
19-
`src/lib/workflowApi.ts:7`, `src/lib/goalRunApi.ts:8`, `src/hooks/useDocsBrowser.ts:10`) plus
19+
`src/lib/workflowApi.ts:7`, `src/lib/loopsApi.ts`, `src/hooks/useDocsBrowser.ts:10`) plus
2020
four raw `fetch('/cc/...')` calls (`src/hooks/useRepos.ts:42`,
2121
`src/components/NewSessionButton.tsx:69`, `src/components/RepoSelector.tsx:50`,
2222
`src/components/OrchestratorView.tsx:55`).

docs/LOOPS.md

Lines changed: 131 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,128 +1,150 @@
1-
# Loop Runs (Goal Runs)
1+
# Loops
22

3-
A loop run wraps a coding session in a durable **act → verify → continue/stop**
4-
loop: an agent (the *maker*) works toward a goal in an isolated worktree, a
5-
deterministic verifier runs shell commands after every turn, and the loop
6-
continues — feeding failures back to the maker — until the verifier passes,
7-
a budget is exhausted, or a human needs to decide. Every step is recorded in an
8-
evidence ledger, so a run is auditable after the fact.
3+
Loops are Codekin's durable control plane for outcome-driven agent work: you
4+
state an outcome and acceptance criteria (a **recipe**), and Codekin runs a
5+
coding agent in checkpointed stages until the criteria pass, a human decision
6+
is needed, or a budget boundary is reached. The full design rationale lives in
7+
[LOOPS-REWRITE-SPEC.md](./LOOPS-REWRITE-SPEC.md); this page documents what is
8+
implemented today (Phase 1: durable engine core).
99

10-
Loop runs differ from [AI Workflows](WORKFLOWS.md) in shape: a workflow is a
11-
scheduled one-shot session that produces a report; a loop run is goal-driven
12-
and iterates until a machine-checkable condition holds.
10+
## Core loop
1311

14-
## Lifecycle
15-
16-
```
17-
queued → running ⇄ verifying ⇄ checking → succeeded
18-
⇅ failed
19-
blocked aborted
20-
awaiting_human
12+
```text
13+
preflight → act → evaluate → (review) → decide → … → finalize
2114
```
2215

23-
| Status | Meaning |
24-
|---|---|
25-
| `queued` | Created; maker session not yet started. |
26-
| `running` | The maker is working a turn. |
27-
| `verifying` | The verify commands are executing against the worktree. |
28-
| `checking` | The checker (second provider) is reviewing the diff. |
29-
| `blocked` | A maker/checker tool call is waiting on human approval or a question. Non-terminal: answer the prompt (open the session from the sidebar) and the loop resumes; unanswered prompts are denied by the router timeout and the loop continues on the denial. |
30-
| `awaiting_human` | Escalated to a human checkpoint — repeated readonly violations, a checker `escalate` verdict, or an unparseable verdict. Terminal. |
31-
| `succeeded` | Verifier green (and checker approval, if configured); changes landed per the completion policy. |
32-
| `failed` | Turn/cost budget exhausted, unrecoverable error, or the run was interrupted by a server restart. |
33-
| `aborted` | Cancelled by the user. |
34-
35-
**Restarts.** In-flight runs do not survive a server restart: at boot, any run
36-
persisted in a non-terminal status is marked `failed` with a
37-
"interrupted by a server restart" row in its ledger. (Reattaching to a live
38-
maker session after a restart is future work — until then the ledger is honest
39-
rather than optimistic.)
40-
41-
## Turn mechanics
42-
43-
Each maker turn ends with a session result, which triggers:
44-
45-
1. **Budgets** — the run fails once `maxTurns` or `maxCostUsd` is reached.
46-
2. **Readonly enforcement** — files matching `readonly` globs must not change;
47-
a violation re-prompts the maker, and repeated violations escalate.
48-
3. **No-change nudge** — a clean tree is not success; the maker is re-prompted.
49-
4. **Verify (debounced)** — the `verify` commands run in order in the worktree
50-
(10 min per command); the run skips re-verifying when the diff is unchanged.
51-
Failures are fed back to the maker as the next turn's input.
52-
5. **Checker review (optional)** — when the spec names a `checker`, a second
53-
provider reviews the diff read-only and must end its reply with
54-
`VERDICT: approve | request_changes | escalate`.
55-
6. **Finalization** — on success Codekin (not the agent) commits the verified
56-
tree and, per `completionPolicy`, pushes and opens a PR. Auto-merge is never
57-
performed.
58-
59-
## Tool allowlists
60-
61-
Maker sessions are created with the shared headless-agent allowlist
62-
(`server/agent-allowlist.ts`) — git, gh, package managers, build/test tools,
63-
non-destructive file operations. Destructive commands (`rm`, `sudo`,
64-
`git push --force`, …) still require approval; a run waiting on one shows as
65-
`blocked`. Checker sessions get a read-only subset (no Write/Edit) — a reviewer
66-
that needs to write has left its mandate.
67-
68-
## Templates
69-
70-
A loop template is a markdown file with YAML frontmatter (spec) and a body
71-
(default goal text):
72-
73-
```markdown
16+
- **act** — a maker session (claude / codex / opencode) works in an isolated
17+
git worktree on the run's branch.
18+
- **evaluate** — command evaluators (your own build/test/lint commands, judged
19+
by exit code) run in order after every maker turn. Failures are fed back to
20+
the maker; transient environment errors (timeout, spawn failure) retry per
21+
the recipe's `retry` policy.
22+
- **review** — rubric evaluators put an independent model — always a
23+
*different provider* than the maker — over the diff. It answers with
24+
`approve` / `request_changes` / `escalate`; an unparseable verdict escalates
25+
rather than silently passing.
26+
- **decide** — deterministic code, not the model. The maker never decides
27+
whether its own acceptance criteria passed. The decision order is: user
28+
cancel/pause → budgets → protected paths → no-change nudge → evaluation →
29+
no-progress detection → review → completion.
30+
- **finalize** — Codekin itself commits the verified tree and, per the
31+
completion action, pushes and opens a PR. Auto-merge does not exist.
32+
33+
## Durability
34+
35+
Every transition is an append-only row in `loop_events` (monotonic sequence
36+
per run) and orchestration counters are checkpointed after every decided
37+
turn. On restart Codekin reconciles instead of failing runs:
38+
39+
- `paused` and `awaiting_approval` runs are left waiting (they hold no
40+
process);
41+
- in-flight runs resume at a stage boundary — a fresh session in the
42+
surviving worktree with a regenerated context prompt (provider sessions are
43+
not resumed in-provider);
44+
- a run whose worktree is gone fails honestly with a reason.
45+
46+
Execution **state** and terminal **outcome** are separate fields: a run ends
47+
`done` + `completed` / `completed_with_warnings` / `failed` / `canceled`.
48+
Waived or failed-optional evaluators qualify the outcome — a run never shows
49+
an unqualified green with a skipped check.
50+
51+
## Controls
52+
53+
Every control appends an auditable event:
54+
55+
- **Pause** — parks the run durably at the next safe boundary; **Resume**
56+
continues in the same worktree with a fresh session.
57+
- **Stop** — cancels now; the worktree is kept for inspection.
58+
- **Steer** — queue an operator instruction; it reaches the maker at the next
59+
safe boundary (mid-turn injection is not attempted).
60+
- **Interventions** — when the run cannot decide safely it parks in
61+
`awaiting_approval` with a pending intervention card: completion approval
62+
(guided mode), budget extension (extend adds 50% of the original budget),
63+
or escalation (repeated protected-path violations, no-progress, reviewer
64+
escalation). Resolving the card continues or ends the run.
65+
66+
## Budgets and no-progress
67+
68+
`budgets.turns` and `budgets.costUsd` are hard caps; `budgets.wallTime` is
69+
optional. At a boundary the run *asks* for a bounded extension (guided /
70+
guarded modes) or stops with a partial result (autonomous mode). The
71+
no-progress detector compares diff summaries and normalized failure
72+
fingerprints across evaluate cycles — producing more text is not progress —
73+
and escalates after `budgets.noProgressAttempts` identical failures.
74+
75+
## Recipes
76+
77+
A recipe is Markdown + YAML frontmatter, reviewable in git:
78+
79+
```yaml
7480
---
75-
kind: flaky-e2e
76-
name: Flaky E2E Quarantine
77-
maker:
78-
provider: claude
79-
checker: # optional — omit for a single-provider loop
80-
provider: opencode
81-
verify:
82-
- npm test
83-
- npm run lint
84-
readonly: # optional
85-
- .github/workflows/**
86-
maxTurns: 12
87-
maxCostUsd: 5
88-
completionPolicy: pr # pr | merge | commit-only (defaults to pr)
81+
apiVersion: codekin.dev/v2
82+
kind: LoopRecipe
83+
metadata:
84+
id: ci-autorepair
85+
name: CI Autorepair
86+
agent:
87+
provider: auto # resolves at run start; recorded on the run
88+
workspace:
89+
strategy: worktree
90+
protectedPaths: [".github/workflows/**"]
91+
evaluators:
92+
- id: tests
93+
type: command
94+
command: npm test # shell string (trusted repo code) or argv array
95+
timeout: 15m
96+
retry: { maxAttempts: 2 }
97+
- id: review
98+
type: rubric
99+
provider: different-from-maker
100+
budgets:
101+
turns: 12
102+
costUsd: 5
103+
wallTime: 90m
104+
policy:
105+
mode: guarded # guided | guarded | autonomous
106+
completion:
107+
action: pull-request # or commit-only; auto-merge does not exist
89108
---
90-
Find the flaky e2e test on this branch, fix the root cause...
109+
The outcome prompt (markdown body) goes here.
91110
```
92111

93-
Templates are read from two places:
112+
Validation is strict — unknown fields fail. The parsed recipe is normalized,
113+
content-hashed, and frozen into every run, so editing the file never changes
114+
what a past run claims it executed. Recipes load from:
94115

95-
- **Built-ins** shipped with the package (`server/loops/*.md`):
96-
`ci-autorepair`, `coverage-increase`, `dependency-upgrade`.
97-
- **Per-repo templates** in `{repo}/.codekin/loops/*.md`. A repo template with
98-
the same `kind` overrides the built-in; a repo template with a **new kind is
99-
a first-class loop** — kinds are an open set, validated only as lowercase
100-
slugs (letters, digits, `.`, `_`, `-`, max 64 chars).
116+
- built-ins shipped with the package: `server/loops/*.md`
117+
(`ci-autorepair`, `coverage-increase`, `dependency-upgrade`);
118+
- per-repo overrides: `{repo}/.codekin/loops/*.md` (same id wins).
101119

102-
## API
120+
Evaluator types beyond `command` and `rubric` (test-report, diff-policy,
121+
artifact, ci, human, composite) arrive with the Phase 3 evaluator platform
122+
and are rejected at validation until then.
103123

104-
All endpoints require the master Bearer token.
124+
## Evidence
125+
126+
Full evaluator output is retained as content-addressed artifacts
127+
(`~/.codekin/loop-artifacts/`), referenced from structured `loop_evaluations`
128+
rows; the maker sees only a tail as feedback. Reviews are artifacts too.
129+
130+
## API
105131

106-
| Endpoint | Description |
107-
|---|---|
108-
| `GET /api/goal-runs/templates?repoPath=` | Available templates (built-ins + repo). |
109-
| `GET /api/goal-runs/runs?kind=&status=&limit=` | List runs, newest first. |
110-
| `GET /api/goal-runs/runs/:id` | One run plus its turn-by-turn evidence ledger. |
111-
| `POST /api/goal-runs/runs` | Start a run: `{ kind, repo, branch, goal? }`. `goal` overrides the template's default goal text. |
112-
| `POST /api/goal-runs/runs/:id/abort` | Abort an in-flight (or restart-orphaned) run. |
132+
Mounted at `/api/loops` (master Bearer token). See
133+
[API-REFERENCE.md](./API-REFERENCE.md#loops) for the endpoint list. Live
134+
updates ride the shared `workflow_event` WS channel (`engine: 'loop'`) as
135+
pings; clients reconcile against `GET /runs/:id/events?after=<sequence>`.
113136

114137
## UI
115138

116-
The **Loop Runs** sidebar entry (`/loops`) lists runs with live status, spend
117-
vs budget, and turn count; a run's detail view shows the evidence ledger. The
118-
maker and checker are ordinary sessions (`source: agent`) and appear in the
119-
sidebaropen one to answer a `blocked` prompt or watch the agent work.
139+
The Loops tab in Automations is an interim run list + control surface (start,
140+
pause/resume/stop, steer, intervention cards, evaluator scorecard). The full
141+
control plane — wizard with repo/branch pickers, four-tab run workspace,
142+
timelineis Phase 2 of the spec.
120143

121144
## Storage
122145

123-
SQLite at `~/.codekin/runs.db` (WAL, `0600`), shared with the workflow engine —
124-
one runs database for all background automation. Rows from the pre-unification
125-
`~/.codekin/goal-runs.db` are copied over automatically on first boot (the
126-
legacy file is left in place). Tables: `goal_runs` (one row per
127-
run) and `goal_run_turns` (the evidence ledger — diff stat, verify command,
128-
exit code, output tail, checker verdict, cost per action).
146+
Tables in the shared `~/.codekin/runs.db`: `loop_runs`, `loop_stages`,
147+
`loop_attempts`, `loop_events`, `loop_checkpoints`, `loop_evaluations`,
148+
`loop_artifacts` (metadata), `loop_interventions`. The v1 `goal_runs` /
149+
`goal_run_turns` tables are dropped on first open — v1 had no users and no
150+
history worth preserving (spec §12).

server/agent-allowlist.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* Shared tool allowlists for autonomous agent sessions.
33
*
4-
* Both orchestrator child sessions and goal-run maker sessions run headless:
4+
* Both orchestrator child sessions and loop-run maker sessions run headless:
55
* a tool call that falls through to manual approval blocks the session until a
66
* human notices. These curated lists cover standard dev operations without
77
* granting arbitrary shell access, so a headless agent can do real work while
@@ -39,7 +39,7 @@ export const AGENT_ALLOWED_TOOLS = [
3939
]
4040

4141
/**
42-
* Allowed tools for review-only agent sessions (e.g. a goal-run checker).
42+
* Allowed tools for review-only agent sessions (e.g. a loop-run rubric reviewer).
4343
* Reading and inspection only — a reviewer that suddenly needs Write has left
4444
* its mandate, and that is exactly the moment a human should be asked.
4545
*/

0 commit comments

Comments
 (0)