carlos / platform Public

spec: record the final review's lifecycle rulings

§7.2 gains the fired-timer consumption rule, the exit-status reading of a
hibernating worker's exit, and what "the record exists ⇒ the process runs"
costs an always-on one (the sweep's start arm, and max_run spent as a
recycle). §7.4 scopes the seed-don't-replay rule to arming and says why a
route first seen afterwards is woken. §8 states most-restrictive placement.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WvcQVWggXkE5BAkjRDgWF
Paul Campbell pushed by paul@keymail.dev 32eb6a3cde6777d153a876e99043996282ff773b parent eb5a1a8
1 file changed, +38 −2
  • docs/superpowers/specs/2026-08-17-sidecar-members-design.md +38 −2
diff --git a/docs/superpowers/specs/2026-08-17-sidecar-members-design.md b/docs/superpowers/specs/2026-08-17-sidecar-members-design.md
index 0064c4889..75184b49e 100644
--- a/docs/superpowers/specs/2026-08-17-sidecar-members-design.md
+++ b/docs/superpowers/specs/2026-08-17-sidecar-members-design.md
@@ -361,6 +361,21 @@ as it refuses `StartSidecar` today.
backoff (2s → 1m, reset after a healthy minute) — the constants the
co-spawn path already uses, now applied to a standalone member.
+A hibernating worker's exit is read by **exit status**, the only thing that
+separates the two meanings it can carry (final review, 2026-08-17): a clean
+exit is "my work is done" and parks; a non-zero exit is a crash and restarts
+with that same backoff, exactly as an always-on worker's does. Without the
+distinction, a bot that fell over before setting a next-due slept forever —
+nothing can dial one.
+
+A **fired** due time is consumed by the wake it causes, never by the sleep
+that follows (final review, 2026-08-17): a worker's stop writes no
+`next_due` at all, so otherwise the passed value is re-served on every sweep
+and the sidecar wakes, idles, parks and re-wakes forever. The consumption is
+conditional on the row still holding the value that fired, so a next-due the
+child posted during its own wake survives — and a worker that sleeps BEFORE
+its timer fires keeps its schedule untouched.
+
**Sleep**, triggered by `/v1/idle`, by `max_run`, or by the idle timer
(no `/v1/heartbeat` and no agent-socket call within `idle`):
SIGTERM (5s grace, then SIGKILL) → record `next_due` from whatever the
@@ -370,7 +385,14 @@ sidecar's next-due arrived over the socket while it was alive, which is
strictly better than asking a process we are about to kill.
**Always-on** is `hibernate: false`: the record exists ⇒ the process runs,
-same supervision, no idle clock, no lease churn.
+same supervision, no idle clock, no lease churn. Two consequences of that
+rule, both from the final review (2026-08-17): the sweep starts an always-on
+worker that has no live entry — otherwise one created while the agent was
+running never ran at all, since the boot pass runs once, at arming — and
+`max_run` applies to it too (it is the wedged-reconnect-loop guard, and a
+long-lived worker is exactly that shape), but is spent there as a RECYCLE of
+its child on the same entry, keeping the lease, because stopping an
+always-on worker leaves nothing to start it again.
### 7.3 Where this lives: inside the activator, off its request path
@@ -414,6 +436,16 @@ on someone else's.
serving that app notices within seconds (the `carlos restart` pattern
verbatim) and wakes the named sidecar. Nothing is pushed at a box —
which is what lets the box live in a private subnet.
+
+ The box's etag memory is seeded once, at arming, and that seeding is
+ what keeps an agent restart from replaying every wake in history. It is
+ scoped to arming precisely (final review, 2026-08-17): a sidecar route
+ seen for the first time AFTER that baseline has just been minted, and a
+ wake object already standing at it is a wake somebody asked for and the
+ CLI already reported as accepted — the create-then-wake flow, which is
+ the feature's primary one, since an operator wake is a hibernating
+ sidecar's only first-run mechanism. A spurious extra wake is idempotent
+ and cheap; a lost one is a bot that never runs.
3. **Boot** — a non-hibernating sidecar starts with the agent.
### 7.5 Delivery wake — designed, not built
@@ -447,7 +479,11 @@ apps-domain-style gate this repo already uses. On a `none` box:
**The record asks.** `placement: "no-inbound"` on the manifest/record makes
the sidecar reconciler mint **only** on boxes declaring `CARLOS_INBOUND=none`.
-`placement: "any"` (default) places by pool/region alone.
+`placement: "any"` (default) places by pool/region alone. Where the two
+disagree, the **most restrictive wins** (final review, 2026-08-17): a record
+may narrow an app that asks for nothing, but a record stating `"any"` under a
+`no-inbound` manifest does not widen it — that is the direction the console
+door already refuses, and a direct bucket write must not reach around it.
The two are independent on purpose: `none` is a property of the box's
network, `no-inbound` is a requirement of the workload. A member who wants