carlos / platform Public
carlos run Implementation Plans: Index
> For agentic workers: this file is the map. Each plan it names is a > self-contained implementation plan with the superpowers header; execute > them in the order below with superpowers:subagent-driven-development or > superpowers:executing-plans. Do not start a plan whose dependencies are > not merged on main.
Goal: Build carlos run (amadan carlos/platform #259): a member runs one of their app's commands against one platform-run instance, as that instance, with its output streamed back through the console and its record kept.
Spec: docs/superpowers/specs/2026-10-08-carlos-run-design.md rev 4 (Astra-accepted in CODEX-FINDINGS-RUN-AND-SEEDED-R4.md, approved by Paul). Every accepted finding in its §11 to §13 is a requirement; the plans name the finding beside the task that answers it. The failure modes behind them are in CODEX-FINDINGS-RUN-AND-SEEDED-R1.md to R4.md.
Built against: main at 4a51a6da. Every name the plans cite was read there (file:line given where it matters).
Copy: 2026-10-08-carlos-run-copy.md is the deck: every user-facing string of the series, 83 rows, all pending. Plan 1 Task 1 puts the gate in place and gets Paul's review before any other task writes a string.
The series
| # | plan | produces | depends on | |---|---|---|---| | 1 | 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 | | 2 | 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) | | 3 | 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 | | 4 | 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 |
Plans 1 and 2 have no dependency on each other's code, but Plan 2 uses approved copy, so it starts after Plan 1 Task 1 is merged. Plan 4's code needs only Plan 2's console; it can be built beside Plan 3.
main (4a51a6da) ─► Plan 1 ─► Plan 2 ─► Plan 3 ─► Plan 4 (dev-box checks)
│ └──────────────► Plan 4 (code)
└─ Task 1: copy review (Paul) gates every stringWhere the code differs from the spec, and what the plans do
Each of these is decided here once; the plans follow it.
- No "deadline-sweep loop" exists in the console (spec §5.8). The console's background passes are claim workers (
registerClaimWorkers,internal/console/claim_workers.go:5, started fromcmd/console/main.goat about :424 to :467). Plan 2 adds a worker,StartRunSweep, every 30 seconds. - Reconcile does not run at agent start (spec §5.2 "at agent start").
instancesLoopsleeps a minute before its first pass (internal/host/agent.go:2816) andfastConvergeLooponly seeds its serial at arming. Plan 3 adds a boot pass atfastConvergeLoop's arming that reads every record once (provision.ListAllRecords) and acts on runs for rows this box owns; later passes ride reconcile's own reads through aRunSeenhook, as the spec says. - The record has no caller field on
RunState, but refusal 7 names the open run's caller. Plan 2 addsBy string \json:"by,omitempty"\`` (additive). - The wake error has no field in
result.json. Plan 2 addsDetail string \json:"detail,omitempty"\`for the member-safe wake error (memberSafeWakeReason,internal/activator/logsink.go:49`). Signalis the decimal signal number as a string ("9"), so the CLI can exit 128 + n; the spec shows the field but not its form.- A cancelled run's exit code is not in the spec's table. The CLI exits 130 (the shell's Ctrl-C status) for a run that was cancelled, before or after launch, and for the second Ctrl-C. Docs row RUN-C75 says so.
--followneeds--appand--instance(spec §3 showscarlos run --follow <id>alone). Every console route is under/api/cli/apps/{account}/{app}/instances/{host}/, and a bare run id carries none of that; the CLI prints the full follow command when a run starts (RUN-C54).- The console spools each file part to a temp file before the bucket write.
release.PutObjectFromneeds anio.ReadSeekerand a size (internal/release/store.go:266); the bytes still go only through the console (F8). The 64 MB cap is enforced while spooling. - The instance verbs' window is 60 a minute (
cliInstanceBurst,internal/console/authcli.go:101), which a once-a-second poll would exhaust. Only the create door spends it (spec refusal 3); the follow routes spend a newrunswindow of 600 a minute. - The existing 429 text has an em dash (
mainGateCopy().tooMany,internal/console/apicli.go:203), so the run door has its own (RUN-C15). - The Phase 1 move door says its generic sentence for "a run is open".
phase1Conflict(internal/console/apiinstancemove.go:895) is inside the moves copy scan (movesScanFiles), so a new sentence there needs a moves record row. The legacy door, the one live whileCARLOS_MOVES_PHASE1is off, answers the provision error, which carries RUN-C43. The moves series adds the Phase 1 wording when its gate task next runs; this series does not touch the moves record. - A host-claim console refuses every run route with RUN-C42. Boxes with claim control armed refuse runs anyway (spec §3.1), and the claim-invariant census (
claim_invariant_test.go:258) needs every route to be a door, a read or a listed refusal; a refusal is the honest one until #250 addscmdto the reservation protocol. - Files are written 0600 in a 0700 directory (spec §5.2 says "mode 0700" for the files): they are data, not programs.
- The import abort door does not exist yet. Plan 2 gives the seeded plan
(*RunState).Open()andprovision.ErrRunOpen; its abort transition must refuse with them (spec §5.1 "an import's abort is refused while ActiveRun is non-terminal").SeedSourcehere hasKindandStateonly, with the seeded spec's json tags; the seeded plan extends it.
Handed to other work
- #250 (claim activation): add
cmdto the reservation protocol besidemain,seedandsidecar, settle the cmd unit's producer before a generation is published, and lift the RUN-C42 refusal and the box'srefused: "claim". Post a note on #250 when Plan 1 merges. - The seeded-instances plan: its record plan (being written beside this series) defines the full
SeedSource; whichever lands second extends the other rather than redefining it (Plan 2 Task 1 says how). Extendprovision.SeedSource; make the import abort transition refuse withprovision.ErrRunOpen; keep(*Record).ImportUncommitted()'s meaning (an import whose state is notcommitted). - The moves series: the Phase 1 door's wording for a run that is open (item 11).