You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 5f4d81a
Browse filesBrowse the repository at this point in the historyBrowse files
- The completion guard says 'not closed' explicitly instead of an
undefined 'open' status.
- D recovery keeps task storage whole (volumes and keeper container)
until F exports and removes it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/implementation/runner-lifecycle.md
+3-2Lines changed: 3 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -104,7 +104,7 @@ Any change not in this table is illegal. The `Store` refuses it.
104
104
|`pending`|`stale`| Context changed before launch | Same attempt ID; state is `pending`; first reason is `stale`, or no first reason and the context is no longer current |
105
105
|`pending`|`failed`| Preparation or launch error | Same attempt ID; state is `pending`; **no first reason recorded; captured context still current** (otherwise the row takes `stale`) |
106
106
|`pending` → `pending`, `running` → `running`| same state, first reason set | A stop request ("Stopping") | Same attempt ID; state unchanged; **first reason is null**. It sets the first reason, and the attempt's state stays the same. The state version increases. This is how rule 1 of "Rules for the running state" becomes durable. |
107
-
|`running`|`completed`| Validated result | Attempt ID is current; state is `running`; no first reason recorded; captured context still current; **task status is open (not closed) and no cancel-task request is pending**|
107
+
|`running`|`completed`| Validated result | Attempt ID is current; state is `running`; no first reason recorded; captured context still current; **task status is not closed (neither `merged` nor `cancelled`) and `cancel_requested`is null**|
108
108
|`running`|`failed`| Settled with an error or invalid output | Attempt ID is current; state is `running`; **no first reason recorded; captured context still current** (otherwise the row takes `stale`) |
109
109
|`running`|`cancelled`| Settled after a user, hard-stop, shutdown or time-limit reason | Attempt ID is current; state is `running`; first reason is `cancelled`, `shutdown` or `time-limit`|
110
110
|`running`|`stale`| Settled after the context changed | Attempt ID is current; state is `running`; first reason is `stale`, **or** no first reason is recorded and the context is no longer current (a change made by another transaction that the runner had not yet seen) |
@@ -247,7 +247,7 @@ The server computes `retryable` and sends it to the UI. The UI never works it ou
247
247
| Runner owner token | F | A random ID of exactly 32 lowercase hexadecimal characters (`^[0-9a-f]{32}$`), stored in the database (`app_settings`). A stored value that does not match is refused before it is used as a label or in any path, and startup exits. It is stored together with the database file's device and inode numbers. It is read, or created, in startup recovery step 2, after the lock is taken and the `Store` is opened. If the stored device and inode don't match the open file (the database was copied), F creates a new token. So a copy never shares a token with its original. |
248
248
| Token in every request | D contract |`InvocationInput` gains a `runnerOwner` field, **and so does every allocator that runs before an invocation exists**: `prepareTaskFilesystems` (which today takes no owner, `agents/container/storage.ts`) and vendor network creation. Every resource D creates carries the label `io.codeboost.runner=<token>`: containers, networks, egress proxies, task-storage volumes and allocations. |
249
249
| Recovered storage handles | D | D tracks task-storage ownership in a process-local `WeakMap`, which is empty after a restart. So `recoverLeftovers` returns, for each kept volume set, an **authenticated recovery handle** that D registers in its ownership map after checking the volumes' runner and allocation labels. **Binding to an attempt:** task-storage volumes also carry `io.codeboost.attempt=<attemptId>`, set by `prepareTaskFilesystems` from a new `attemptId` parameter, and F saves the returned allocation ID as `attempts.allocation_id` in the same turn. The handle carries both IDs. F uses a handle only if its attempt ID and allocation ID both match one attempt row of this database. A handle that matches no row, or disagrees with the row, is reported and listed by `--list-unowned-agent-resources` for the user to remove by hand; it is never exported or removed automatically. `exportTaskDiff` and `removeTaskFilesystems` accept these handles. F never builds a `TaskFilesystems` value from report fields. |
250
-
| Scoped recovery | D |`recoverLeftovers(runnerOwner): Promise<RecoveryReport>` acts only on resources whose `io.codeboost.runner` label equals the token. It stops and removes containers, egress proxies and networks, and resolves only when they are gone. **It keeps task-storage volumes** and lists them in the report by allocation, so F can export partial output first. F then calls `removeTaskFilesystems` for each one (startup recovery step 4). It rejects with a bounded diagnostic if anything cannot be stopped or removed. |
250
+
| Scoped recovery | D |`recoverLeftovers(runnerOwner): Promise<RecoveryReport>` acts only on resources whose `io.codeboost.runner` label equals the token. It stops and removes agent containers, egress proxies and networks, and resolves only when they are gone. **It keeps taskstorage whole: the volumes and their keeper container**, which it recognises by its task-storage labels. The recovery handle depends on that keeper and lists them in the report by allocation, so F can export partial output first. F then calls `removeTaskFilesystems` for each one (startup recovery step 4). It rejects with a bounded diagnostic if anything cannot be stopped or removed. |
251
251
| Unowned resources | D and F | Resources with codeboost labels but no `io.codeboost.runner` label (from builds before this change) fail closed. The report lists them, and F refuses to open admission while any exist. **codeboost never removes them itself**, because it cannot prove that no older-build runner is alive: older builds take no lock this build can see, and an idle older runner may have no container running at the moment of a check. So `--list-unowned-agent-resources` prints each resource and the exact `docker rm` / `docker network rm` / `docker volume rm` commands. The user stops every older codeboost process, runs those commands, and starts again. There is no machine-wide lock. |
252
252
| When it runs | F | Only while holding this database's single-runner lock (startup recovery step 1 comes first). |
253
253
@@ -504,6 +504,7 @@ Each case needs a test that fails before the fix and passes after it. Each test
504
504
| Legacy resources are never removed automatically (review rounds 27–28) | Unlabelled resources exist → startup refuses admission and lists them with removal commands; no codeboost code path deletes them; after manual removal, the next start opens admission |
505
505
| Ledger write in the publication transaction (review round 27) | Completed `execute` → the step-3 guard passes → `completed` and `recordHistory` in one transaction → generation +1. A stale variant → refused, no ledger entry, the commit is discarded with the storage |
506
506
| D timeout, then shutdown, then crash (review round 28) | (a) Row with a saved `stop_reason` of `timeout` and first reason `shutdown` → restart → `failed`, not requeued. (b) D timed out, then shutdown, then a crash before settlement (no `stop_reason` saved) → restart → `cancelled` "Stopped by shutdown" and requeued once; the requeued attempt has a fresh finite deadline |
507
+
| Recovery keeps the keeper (review round 29) | Crash with task storage → `recoverLeftovers` → the agent container and network are gone, but the volumes and their keeper container remain → export succeeds → `removeTaskFilesystems` removes both |
507
508
| Old attempt settles after a retry (D/F contract) | Attempt A cancelled and settled → retry B admitted → a late publish from A is refused → B's row and the visible status are unchanged |
0 commit comments