| 1 | # `carlos run` Implementation Plans: Index |
| 2 | |
| 3 | > **For agentic workers:** this file is the map. Each plan it names is a |
| 4 | > self-contained implementation plan with the superpowers header; execute |
| 5 | > them in the order below with superpowers:subagent-driven-development or |
| 6 | > superpowers:executing-plans. Do not start a plan whose dependencies are |
| 7 | > not merged on `main`. |
| 8 | |
| 9 | **Goal:** Build `carlos run` (amadan carlos/platform #259): a member runs one |
| 10 | of their app's commands against one platform-run instance, as that instance, |
| 11 | with its output streamed back through the console and its record kept. |
| 12 | |
| 13 | **Spec:** `docs/superpowers/specs/2026-10-08-carlos-run-design.md` rev 4 |
| 14 | (Astra-accepted in `CODEX-FINDINGS-RUN-AND-SEEDED-R4.md`, approved by Paul). |
| 15 | Every accepted finding in its §11 to §13 is a requirement; the plans name |
| 16 | the finding beside the task that answers it. The failure modes behind them |
| 17 | are in `CODEX-FINDINGS-RUN-AND-SEEDED-R1.md` to `R4.md`. |
| 18 | |
| 19 | **Built against:** `main` at 4a51a6da. Every name the plans cite was read |
| 20 | there (file:line given where it matters). |
| 21 | |
| 22 | **Copy:** `2026-10-08-carlos-run-copy.md` is the deck: every user-facing |
| 23 | string of the series, 83 rows, all pending. Plan 1 Task 1 puts the gate in |
| 24 | place and gets Paul's review before any other task writes a string. |
| 25 | |
| 26 | ## The series |
| 27 | |
| 28 | | # | plan | produces | depends on | |
| 29 | |---|---|---|---| |
| 30 | | 1 | [`2026-10-08-carlos-run-1-box-foundation.md`](2026-10-08-carlos-run-1-box-foundation.md) | the copy gate and Paul's review; `instancectl.RoleCmd` and `carlos-instance-cmd@.service` (equal to the seed template outside the named lines); `POST /cmd` and the `GET /cmd/{host}` long-poll; `instancefs.RemoveAllBeneath`; `cmdVars`, `launch.cmd.json`, the two stream listeners and `ExecBackend.StartCmd`; stop reasons on every stop path; the activator entry's `cmd` field, `Service.RunCmd`, the stop ordering in `finishStop` and the idle hold | `main` | |
| 31 | | 2 | [`2026-10-08-carlos-run-2-record-and-console.md`](2026-10-08-carlos-run-2-record-and-console.md) | `provision.RunState` and `Record.ActiveRun` (carried by ordinary writers), the minimal `SeedSource`, the run transitions and the move transitions' refusal; `internal/runs` (ids, keys, wire shapes, validation, reasons); the console's five run routes in spec order, the run-end audit, the run sweep worker; the claim-invariant fixtures | Plan 1 merged (Task 1's gate and approved copy) | |
| 32 | | 3 | [`2026-10-08-carlos-run-3-box-pickup-and-output.md`](2026-10-08-carlos-run-3-box-pickup-and-output.md) | `runs.Chunker` and `runs.StageFiles`; reconcile's `RunSeen` hook; the box's runs worker: pickup CAS, incarnation and import checks, agent-restart recovery, the boot sweep of `.carlos-run`; one run's life from wake to `result.json` and the `done` CAS; the agent wiring | Plans 1 and 2 merged | |
| 33 | | 4 | [`2026-10-08-carlos-run-4-cli-and-docs.md`](2026-10-08-carlos-run-4-cli-and-docs.md) | `carlos run` and `--follow`, interleaving, the 60-second expiry, the ack, every exit code, Ctrl-C once and twice; `docs/site/cli.md`'s `carlos run` section and the contract section; the closing copy check; the dev-box checklist | Plan 2 merged for code; Plan 3 merged before the dev-box checks | |
| 34 | |
| 35 | Plans 1 and 2 have no dependency on each other's code, but Plan 2 uses |
| 36 | approved copy, so it starts after Plan 1 Task 1 is merged. Plan 4's code |
| 37 | needs only Plan 2's console; it can be built beside Plan 3. |
| 38 | |
| 39 | ``` |
| 40 | main (4a51a6da) ─► Plan 1 ─► Plan 2 ─► Plan 3 ─► Plan 4 (dev-box checks) |
| 41 | │ └──────────────► Plan 4 (code) |
| 42 | └─ Task 1: copy review (Paul) gates every string |
| 43 | ``` |
| 44 | |
| 45 | ## Where the code differs from the spec, and what the plans do |
| 46 | |
| 47 | Each of these is decided here once; the plans follow it. |
| 48 | |
| 49 | 1. **No "deadline-sweep loop" exists in the console** (spec §5.8). The |
| 50 | console's background passes are claim workers (`registerClaimWorkers`, |
| 51 | `internal/console/claim_workers.go:5`, started from `cmd/console/main.go` |
| 52 | at about :424 to :467). Plan 2 adds a worker, `StartRunSweep`, every 30 |
| 53 | seconds. |
| 54 | 2. **Reconcile does not run at agent start** (spec §5.2 "at agent start"). |
| 55 | `instancesLoop` sleeps a minute before its first pass |
| 56 | (`internal/host/agent.go:2816`) and `fastConvergeLoop` only seeds its |
| 57 | serial at arming. Plan 3 adds a boot pass at `fastConvergeLoop`'s arming |
| 58 | that reads every record once (`provision.ListAllRecords`) and acts on |
| 59 | runs for rows this box owns; later passes ride reconcile's own reads |
| 60 | through a `RunSeen` hook, as the spec says. |
| 61 | 3. **The record has no caller field on `RunState`**, but refusal 7 names the |
| 62 | open run's caller. Plan 2 adds `By string \`json:"by,omitempty"\`` |
| 63 | (additive). |
| 64 | 4. **The wake error has no field in `result.json`.** Plan 2 adds |
| 65 | `Detail string \`json:"detail,omitempty"\`` for the member-safe wake |
| 66 | error (`memberSafeWakeReason`, `internal/activator/logsink.go:49`). |
| 67 | 5. **`Signal` is the decimal signal number as a string** ("9"), so the CLI |
| 68 | can exit 128 + n; the spec shows the field but not its form. |
| 69 | 6. **A cancelled run's exit code is not in the spec's table.** The CLI |
| 70 | exits 130 (the shell's Ctrl-C status) for a run that was cancelled, before |
| 71 | or after launch, and for the second Ctrl-C. Docs row RUN-C75 says so. |
| 72 | 7. **`--follow` needs `--app` and `--instance`** (spec §3 shows |
| 73 | `carlos run --follow <id>` alone). Every console route is under |
| 74 | `/api/cli/apps/{account}/{app}/instances/{host}/`, and a bare run id |
| 75 | carries none of that; the CLI prints the full follow command when a run |
| 76 | starts (RUN-C54). |
| 77 | 8. **The console spools each file part to a temp file before the bucket |
| 78 | write.** `release.PutObjectFrom` needs an `io.ReadSeeker` and a size |
| 79 | (`internal/release/store.go:266`); the bytes still go only through the |
| 80 | console (F8). The 64 MB cap is enforced while spooling. |
| 81 | 9. **The instance verbs' window is 60 a minute** (`cliInstanceBurst`, |
| 82 | `internal/console/authcli.go:101`), which a once-a-second poll would |
| 83 | exhaust. Only the create door spends it (spec refusal 3); the follow |
| 84 | routes spend a new `runs` window of 600 a minute. |
| 85 | 10. **The existing 429 text has an em dash** (`mainGateCopy().tooMany`, |
| 86 | `internal/console/apicli.go:203`), so the run door has its own |
| 87 | (RUN-C15). |
| 88 | 11. **The Phase 1 move door says its generic sentence for "a run is open".** |
| 89 | `phase1Conflict` (`internal/console/apiinstancemove.go:895`) is inside |
| 90 | the moves copy scan (`movesScanFiles`), so a new sentence there needs a |
| 91 | moves record row. The legacy door, the one live while |
| 92 | `CARLOS_MOVES_PHASE1` is off, answers the provision error, which carries |
| 93 | RUN-C43. The moves series adds the Phase 1 wording when its gate task |
| 94 | next runs; this series does not touch the moves record. |
| 95 | 12. **A host-claim console refuses every run route** with RUN-C42. Boxes with |
| 96 | claim control armed refuse runs anyway (spec §3.1), and the |
| 97 | claim-invariant census (`claim_invariant_test.go:258`) needs every |
| 98 | route to be a door, a read or a listed refusal; a refusal is the honest |
| 99 | one until #250 adds `cmd` to the reservation protocol. |
| 100 | 13. **Files are written 0600 in a 0700 directory** (spec §5.2 says "mode |
| 101 | 0700" for the files): they are data, not programs. |
| 102 | 14. **The import abort door does not exist yet.** Plan 2 gives the seeded |
| 103 | plan `(*RunState).Open()` and `provision.ErrRunOpen`; its abort |
| 104 | transition must refuse with them (spec §5.1 "an import's abort is |
| 105 | refused while ActiveRun is non-terminal"). `SeedSource` here has `Kind` |
| 106 | and `State` only, with the seeded spec's json tags; the seeded plan |
| 107 | extends it. |
| 108 | |
| 109 | ## Handed to other work |
| 110 | |
| 111 | - **#250 (claim activation):** add `cmd` to the reservation protocol beside |
| 112 | `main`, `seed` and `sidecar`, settle the cmd unit's producer before a |
| 113 | generation is published, and lift the RUN-C42 refusal and the box's |
| 114 | `refused: "claim"`. Post a note on #250 when Plan 1 merges. |
| 115 | - **The seeded-instances plan:** its record plan (being written beside this |
| 116 | series) defines the full `SeedSource`; whichever lands second extends the |
| 117 | other rather than redefining it (Plan 2 Task 1 says how). Extend |
| 118 | `provision.SeedSource`; make the |
| 119 | import abort transition refuse with `provision.ErrRunOpen`; keep |
| 120 | `(*Record).ImportUncommitted()`'s meaning (an import whose state is not |
| 121 | `committed`). |
| 122 | - **The moves series:** the Phase 1 door's wording for a run that is open |
| 123 | (item 11). |
| 124 | |