carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

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 string

Where the code differs from the spec, and what the plans do

Each of these is decided here once; the plans follow it.

  1. 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 from cmd/console/main.go at about :424 to :467). Plan 2 adds a worker, StartRunSweep, every 30 seconds.
  2. Reconcile does not run at agent start (spec §5.2 "at agent start"). instancesLoop sleeps a minute before its first pass (internal/host/agent.go:2816) and fastConvergeLoop only seeds its serial at arming. Plan 3 adds a boot pass at fastConvergeLoop'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 a RunSeen hook, as the spec says.
  3. The record has no caller field on RunState, but refusal 7 names the open run's caller. Plan 2 adds By string \json:"by,omitempty"\`` (additive).
  4. The wake error has no field in result.json. Plan 2 adds Detail string \json:"detail,omitempty"\` for the member-safe wake error (memberSafeWakeReason, internal/activator/logsink.go:49`).
  5. Signal is the decimal signal number as a string ("9"), so the CLI can exit 128 + n; the spec shows the field but not its form.
  6. 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.
  7. --follow needs --app and --instance (spec §3 shows carlos 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).
  8. The console spools each file part to a temp file before the bucket write. release.PutObjectFrom needs an io.ReadSeeker and a size (internal/release/store.go:266); the bytes still go only through the console (F8). The 64 MB cap is enforced while spooling.
  9. 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 new runs window of 600 a minute.
  10. The existing 429 text has an em dash (mainGateCopy().tooMany, internal/console/apicli.go:203), so the run door has its own (RUN-C15).
  11. 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 while CARLOS_MOVES_PHASE1 is 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.
  12. 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 adds cmd to the reservation protocol.
  13. Files are written 0600 in a 0700 directory (spec §5.2 says "mode 0700" for the files): they are data, not programs.
  14. The import abort door does not exist yet. Plan 2 gives the seeded plan (*RunState).Open() and provision.ErrRunOpen; its abort transition must refuse with them (spec §5.1 "an import's abort is refused while ActiveRun is non-terminal"). SeedSource here has Kind and State only, with the seeded spec's json tags; the seeded plan extends it.

Handed to other work

  • #250 (claim activation): add cmd to the reservation protocol beside main, seed and sidecar, settle the cmd unit's producer before a generation is published, and lift the RUN-C42 refusal and the box's refused: "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). Extend provision.SeedSource; make the import abort transition refuse with provision.ErrRunOpen; keep (*Record).ImportUncommitted()'s meaning (an import whose state is not committed).
  • The moves series: the Phase 1 door's wording for a run that is open (item 11).