docs(as-built): verify lifecycle, packaging and content-library pages against a350c59b (#781)

Re-verifies `docs/as-built/architecture/lifecycle-snapshot-restore.md`, `packaging-bootstrap-bundles.md` and `plugin-agent-image-context-pack.md` against main `a350c59b` and restamps them. Corrects restore-check's verdicts (an `unknown` branch and a `restore-preconditions` check), where a pod-aware bundle's content is routed (inside bootstrap, before any seat launches) and how guided compaction holds `/compact` behind a restore map, and adds the features since the old stamps, including topology rosters, restore receipts, `rig seat continue`, seat handover, GitHub folder links, `rig bundle check`, configurations and package identity, System World, recaps, skill loadouts, `rig compact` and `rig fork`. Part of the 0.6.6 developer documentation refresh. Text only.
This commit is contained in:
Mike Schwarz authored and GitHub committed 2026-10-05 03:14:04 -07:00
1 parent fcaf1f8ee8
commit aa4ee793cd
3 files changed
+452 -165

No files matched your search

@@ -11,8 +11,8 @@ applies-when: |
and the CLI restore-packet command work.
siblings: [daemon-core.md, agent-spec-and-startup.md, transport-and-transcripts.md]
prerequisite-reads: [../README.md, agent-spec-and-startup.md]
last-verified-against-source: 254122872cf477511514979a4300b695d77cd1f7
last-updated: 2026-10-03
last-verified-against-source: a350c59b5a5fb37ee4a21b1026b595068b603df1
last-updated: 2026-10-05
---
# Lifecycle — Snapshot, Restore, Continuity
@@ -22,7 +22,7 @@ The durable-state half of the core product loop:
captures serialized rig state; restore replays it honestly (no silent
fresh-fallback); restore-check is a separate read-only readiness probe.
> Verified against source at main `254122872cf477511514979a4300b695d77cd1f7`. Each count below sits beside the
> Verified against source at main `a350c59b5a5fb37ee4a21b1026b595068b603df1`. Each count below sits beside the
> command that produces it; run the command from the repository root to refresh
> it.
@@ -31,51 +31,55 @@ fresh-fallback); restore-check is a separate read-only readiness probe.
All in `packages/daemon/src/domain/types.ts`. The spec/projection types live in
`agent-spec-and-startup.md`; the snapshot/restore types live here.
- **NodeRestoreOutcome** (`types.ts:501`) — the per-node restore outcome
- **NodeRestoreOutcome** (`types.ts:500`) — the per-node restore outcome
projected into `rig ps`. **9** values
(`grep '^export type NodeRestoreOutcome' packages/daemon/src/domain/types.ts | grep -o '"[^"]*"' | wc -l`):
`resumed`, `rebuilt`, `fresh`, `fresh-primed`, `awaiting-decision`,
`failed`, `attention_required`, `operator_recovered`, `n-a`. The restore
result itself, `RestoreNodeResult.status` (`types.ts:476`), has the same
result itself, `RestoreNodeResult.status` (`types.ts:475`), has the same
values without `n-a`: **8**
(`grep -E '^ status: "resumed"' packages/daemon/src/domain/types.ts | grep -o '"[^"]*"' | wc -l`).
Where `restore-orchestrator.ts` sets each one:
- `resumed` — `baseStatus = "resumed"` after a legacy resume (`:1212`); the
status is returned only by `finishJoinedResume` (`:1499`, `:1561`) after
it has rebound and verified the pane, otherwise that becomes
`attention_required` (`:1533`).
- `rebuilt` — a checkpoint file was written (`:1272`).
- `resumed` — `baseStatus = "resumed"` after a legacy resume (`:1031`); the
status is returned only by `finishJoinedResume` (defined `:1319`, called
`:1245` and `:1312`, returning `resumed` at `:1381`) after it has rebound
and verified the pane; otherwise that becomes `attention_required`
(`:1353`, `:1377`).
- `rebuilt` — a checkpoint file was written (`:1091`).
- `fresh-primed` — the default for a deliberate non-resume launch
(`:1194`).
(`:988`).
- `fresh` — only the skip path for a pod node whose live continuity state
is already `restoring` (`:992`).
is already `restoring` (`:786`).
- `awaiting-decision` — the seat could not resume and nothing is running:
before launch (`:1027`), or after launch once the blank session is rolled
back (`:1208`, `:1230`, `:1243`, `:1259`, `:1417`).
before launch (`:821`), or after launch once the blank session is rolled
back (`:1027`, `:1049`, `:1062`, `:1078`, `:1237`).
- `attention_required` — a live session waiting on a runtime prompt
(`:1221`, `:1443`) or a harness that never started (`:1307`).
- `failed` — occupant ambiguity (`:979`), launch failure (`:1118`), a
checkpoint that cannot be delivered (`:1268`, `:1274`), a missing
required startup file (`:1343`), or a startup error.
(`:1040`, `:1263`) or a harness that never started (`:1126`).
- `failed` — occupant ambiguity (`:773`, `:970`), launch failure (`:912`),
a checkpoint that cannot be delivered (`:1087`, `:1093`), a missing
required startup file (`:1162`), or a startup error (`:1272`, `:1283`,
`:1293`, `:1301`).
- `operator_recovered` — only from later reconciliation,
`reconcileNodeRuntimeTruth` (`:1759`, event at `:1902`).
- **RestoreRigResult** (`types.ts:445`) — the rig-level rollup, **4** values
`reconcileNodeRuntimeTruth` (`:1587`, event at `:1724`).
- **RestoreRigResult** (`types.ts:444`) — the rig-level rollup, **4** values
(`grep '^export type RestoreRigResult' packages/daemon/src/domain/types.ts | grep -o '"[^"]*"' | wc -l`):
`fully_restored`, `partially_restored`, `failed`, `not_attempted`
(`rollupRestoreRigResult`, `restore-orchestrator.ts:82`).
- **SnapshotData** (`types.ts:352`) — the serialized snapshot payload. It has
`fully_restored`, `partially_restored`, `failed`, `not_attempted`.
`rollupRestoreRigResult` (`restore-orchestrator.ts:82`) produces only the
first three; `not_attempted` is set when pre-restore validation fails
(`:282`).
- **SnapshotData** (`types.ts:351`) — the serialized snapshot payload. It has
**7** optional fields, kept optional so older snapshots still load
(`sed -n '/^export interface SnapshotData/,/^}/p' packages/daemon/src/domain/types.ts | grep -c '?:'`):
`activeSessionIdByNode?`, `activeOccupantsByNode?`, `topologyRoster?`,
`pods?`, `continuityStates?`, `nodeStartupContext?`, `envReceipt?`.
- **NodeStartupSnapshot** (`types.ts:345`) — persisted restore replay input:
- **NodeStartupSnapshot** (`types.ts:344`) — persisted restore replay input:
classification-free projection entries, resolved startup files, startup
actions, runtime.
- **PersistedProjectionEntry** (`types.ts:333`) — the classification-free
- **PersistedProjectionEntry** (`types.ts:332`) — the classification-free
restore replay seam: persists only entry identity + source metadata, NOT
stale `classification`, `conflicts`, or `noOps` (Architecture Rule 10).
Restore rebuilds each entry as `safe_projection` with empty `conflicts` and
`noOps` (`restore-orchestrator.ts:1352`, `:1357`, `:1358`).
`noOps` (`restore-orchestrator.ts:1171`, `:1176`, `:1177`).
## 2. Snapshot, restore, continuity domain services
@@ -93,9 +97,18 @@ All under `packages/daemon/src/domain/`:
accept only snapshots that pass `isRestoreUsableSnapshotData` (`:283`).
- `restore-orchestrator.ts` — resume, checkpoint delivery, startup replay,
live continuity consultation, topology ordering (`computeRestorePlan`,
`:876`, ordering only `delegates_to` and `spawned_by` edges, `:72`), and
service boot gating before agent restore (`serviceOrchestrator.boot`,
`:328`).
`:670`, ordering only `delegates_to` and `spawned_by` edges, `:72`, among
the roster's intended nodes, `:671`), and service boot gating before agent
restore (`serviceOrchestrator.boot`, `:332`).
- `restore-preconditions.ts` — `validatePreRestore` (`:7`), the one
pre-restore validation shared by restore and restore-check (§3, §5.1).
- `restore-topology.ts` — `resolveSnapshotRestoreTopology` (`:12`), which
splits a snapshot's nodes into intended and excluded (§3).
- `restore-attempt-receipt.ts` — `deriveRestoreAttemptReceipt` (`:42`), the
read-only per-attempt receipt (§4).
- `rehydrate-eligibility.ts` — `snapshotMatchesCurrentOccupants` (`:20`) and
`assessCurrentStateRehydrateEligibility` (`:66`), shared by `routes/up.ts`
and `routes/restore-check.ts`.
- `active-occupant.ts` — the shared rule for which snapshot session row is a
node's occupant (`resolveActiveSnapshotSession`, `:74`).
@@ -103,62 +116,97 @@ All under `packages/daemon/src/domain/`:
Restore behavior, each point checked in `restore-orchestrator.ts`:
- Selects the snapshot with `selectRestoreUsable` even when a snapshot id is
given (`:252–253`), so wrong-rig and unusable snapshots are refused before
any mutation. The selection (`RestoreSnapshotSelection`, `types.ts:396`)
records whether it was explicit or automatic, why, and any newer usable
alternative; it is carried on the result and on `restore.started`.
- Restores only the nodes the snapshot's topology roster intends
(`SnapshotData.topologyRoster`, `types.ts:370`). The roster comes from the
latest `topology.roster_recorded` event (`snapshot-capture.ts:171`), from
`rig snapshot <rigId> --intended-seats` (`packages/cli/src/commands/snapshot.ts:20`),
or, for older snapshots, all current nodes. `resolveSnapshotRestoreTopology`
(`restore-topology.ts:12`) reports every other node as excluded with reason
`historical_not_in_intended_roster` (`:27`); the result carries
`intendedRoster` and `excludedNodes` (`types.ts:440–441`).
- Resolves each node's occupant from the snapshot's recorded active-occupant
maps, not by picking the newest session row
(`resolveActiveSnapshotSession`, `active-occupant.ts:74`, called at
`restore-orchestrator.ts:974` and `:1171`). An ambiguous occupant fails the
`restore-orchestrator.ts:768` and `:965`). An ambiguous occupant fails the
node loudly (`activeOccupantAmbiguityError`, `active-occupant.ts:97`).
- Consults live `continuity_state`; preserves state when a node is already
`restoring` (`:985–998` — `SELECT status FROM continuity_state …` at
`:987`; if `status === "restoring"` the node is skipped with a warning and
reported as `fresh`, `:990–992`; `degraded` only adds a warning).
`restoring` (`:778–792` — `SELECT status FROM continuity_state …` at
`:781`; if `status === "restoring"` the node is skipped with a warning and
reported as `fresh`, `:784–786`; `degraded` only adds a warning).
- Reuses the seat's durably bound tmux session name when the prior binding
has a usable one (`:841`), and otherwise falls back to the derived name
with a warning (`:870`).
- Replays restore-safe startup using persisted startup context;
prefilters missing optional artifacts into warnings; **hard-fails a node if
a required startup file is missing** (`:1343` — status `"failed"`, error
a required startup file is missing** (`:1162` — status `"failed"`, error
"Missing required startup files: …"). Before any mutation,
`validatePreRestore` (`:585`) already blocks the whole restore when a node
`validatePreRestore` (`restore-preconditions.ts:7`, called at
`restore-orchestrator.ts:272`) already blocks the whole restore when a node
that will consume replay is missing a required startup file
(`required_startup_file_missing`, `:729`; outcome
`pre_restore_validation_failed`, `:290`). A node that resumes its exact
native session replays no startup files, actions, guidance or skills
(`replayContained`, `:1288`); its saved Claude activity-hook selection is
reapplied before native resume. A skipped hook reapply adds a warning without
changing the restore outcome.
(`required_startup_file_missing`, `restore-preconditions.ts:151`; outcome
`pre_restore_validation_failed`, `restore-orchestrator.ts:292`). Its other
blockers are `invalid_snapshot_data` (`restore-preconditions.ts:27`),
`invalid_topology_roster` (`:78`), `checkpoint_missing_node_cwd` (`:91`),
`startup_owner_root_missing` (`:139`), `service_rig_root_missing` (`:187`)
and `service_compose_file_missing` (`:197`). The `/api/up` route returns
that outcome as HTTP 409 with `status: "not_attempted"`
(`routes/up.ts:161`). A node that resumes its exact native session replays
no startup files, actions, guidance or skills (`replayContained`,
`restore-orchestrator.ts:1108`); its saved Claude activity-hook selection
is reapplied before native resume. A skipped hook reapply adds a warning
without changing the restore outcome.
- Writes a **transcript boundary marker before re-launch**
(`writeBoundaryMarker`, `:1100`, called before `launchNode`).
- Refuses to restore over live sessions (`:258–261` — `rig_not_stopped`: "Rig …
has live sessions. Stop the rig with 'rig down' before restoring"). A tmux
probe that fails also blocks it (`classifyRunningSessions`, `:812`).
(`writeBoundaryMarker`, `:894`, called before `launchNode` at `:905`) when
the transcript store is enabled.
- Refuses to restore over live sessions (`:260–263` — `rig_not_stopped`: "Rig …
has live sessions. Stop the rig with 'rig down' before restoring, or use the
latest auto-pre-down snapshot."). A tmux probe that fails also blocks it
(`classifyRunningSessions`, `:606`).
- Checks that the harness actually resumed. The resume adapters judge the
pane with `assessNativeResumeProbe` (`domain/native-resume-probe.ts:84`). A
pod-aware resume counts as resumed only when startup reports `resumed`
continuity or the launched session row carries the snapshot's resume type
and token (`launchedSessionMatchesSnapshotResume`,
`restore-orchestrator.ts:1564`); a reported `fresh` continuity without that
proof rolls back to `awaiting-decision` (`:1405–1419`).
`restore-orchestrator.ts:1384`); a reported `fresh` continuity without that
proof rolls back to `awaiting-decision` (`:1222–1239`).
- Saves a `rig up --non-interruptive` or `--no-non-interruptive` choice on
the rig only after the live-session check and pre-restore validation pass
(`:302`); see [adapters-and-runtimes.md](adapters-and-runtimes.md) for the
launch flags.
`launchNodeSubset` (`:393`) and `launchSingleNode` restore chosen seats rather
than the whole rig; unlike a full restore, a failed tmux probe there proceeds
with a `liveness_probe_unknown` warning (`:511`).
**Restore-honesty rules** (texts in `architecture-rules-and-event-system.md`;
cited here by number with the code that carries them):
- Rule 7 — `RESTORE_POLICY_LEVEL`
(`packages/daemon/src/domain/profile-resolver.ts:103`) orders the three
policies, and `resolveRestorePolicy` (`profile-resolver.ts:488`) rejects a
(`packages/daemon/src/domain/profile-resolver.ts:105`) orders the three
policies, and `resolveRestorePolicy` (`profile-resolver.ts:526`) rejects a
profile or member value that broadens it.
- Rule 14 — the outcome set is `RestoreNodeResult.status` (`types.ts:476`,
- Rule 14 — the outcome set is `RestoreNodeResult.status` (`types.ts:475`,
§1); `rebuilt` is set only when a checkpoint is written
(`restore-orchestrator.ts:1272`).
(`restore-orchestrator.ts:1091`).
- Rule 15 — in `restore-orchestrator.ts`, a `resume_if_possible` seat with a
session but no token stops as `awaiting-decision` before launch unless
`--fresh` names it (`:1020–1029`); a failed resume kills the blank session
(`rollbackToZeroSession`, `:1140`) and returns `awaiting-decision`
(`:1230`); pod-aware resume passes
`allowFreshFallback: !(isPodAware && resumeRequested)` (`:1399`). Fresh
launch is explicit: `rig up --existing <rig> --fresh <seat...>`
(`packages/cli/src/commands/up.ts:85`).
`--fresh` names it (`:806–825`); a failed resume kills the blank session
(`rollbackToZeroSession`, `:934`) and returns `awaiting-decision`
(`:1049`); pod-aware resume passes
`allowFreshFallback: !(isPodAware && resumeRequested)` (`:1218`). Fresh
launch is explicit: `rig up --existing <rig> --fresh <seats...>`
(`packages/cli/src/commands/up.ts:92`). On a terminal, `rig up` asks
yes or no for each `awaiting-decision` seat and re-posts with the accepted
seats as fresh (`up.ts:618`).
- Rule 16 — `rig down` prints the snapshot id and a restore command
(`packages/cli/src/commands/down.ts:236–246`); `rig up` on an existing rig
prints per-node statuses and the attach command
(`packages/cli/src/commands/up.ts:540–560`).
(`packages/cli/src/commands/up.ts:583–616`).
(The full rule list lives in `architecture-rules-and-event-system.md`.)
@@ -166,17 +214,22 @@ cited here by number with the code that carries them):
- `rig down <rig>` (name or id, `packages/cli/src/commands/down.ts:104`)
auto-captures an `auto-pre-down` snapshot before teardown when the rig has
live sessions (`packages/daemon/src/domain/rig-teardown.ts:116`; an
already-stopped rig returns at `:89–109` without one).
live sessions (`packages/daemon/src/domain/rig-teardown.ts:116`, capture
at `:127`, after refreshing resume metadata at `:118`; an already-stopped
rig returns at `:93–114` without one).
- `rig up <rig-name>`: a source with no `/` and no
`.yaml`/`.yml`/`.rigbundle`/`.rigtopology` extension is a rig name
(`packages/daemon/src/domain/up-command-router.ts:49–50`). The CLI first
checks the spec library unless `--existing` is given, and refuses a name
that matches both (`packages/cli/src/commands/up.ts:287–302`). The daemon
finds the rig by name (`packages/daemon/src/routes/up.ts:222`) and
restores from `selectRestoreUsable` (`routes/up.ts:105`), which prefers the
newest `auto-pre-down` or `auto-periodic` snapshot, then the newest other
usable one (`snapshot-repository.ts:75`).
(`packages/daemon/src/domain/up-command-router.ts:49–50`). The CLI refuses
a name that matches only archived rigs, with `rig unarchive` guidance
(`packages/cli/src/commands/up.ts:290`). It then checks the spec library
unless `--existing` is given, and refuses a name that matches both
(`up.ts:338–353`). The daemon finds the rig by name, preferring unarchived
rigs (`packages/daemon/src/routes/up.ts:222–225`), and restores from
`selectRestoreUsable` (`routes/up.ts:105`), which prefers the newest
`auto-pre-down` or `auto-periodic` snapshot, then the newest other usable
one (`snapshot-repository.ts:75`).
- `rig up <rig-name> --plan` is a read-only restore preview that never
captures a snapshot (`routes/up.ts:126`).
- If no usable snapshot exists, or the chosen one names an older occupant
(`snapshotMatchesCurrentOccupants`, `routes/up.ts:109`), `rig up` captures
an `auto-rehydrate` snapshot of current DB state when that state is
@@ -187,77 +240,124 @@ cited here by number with the code that carries them):
`To restore: rig up <name>` (or `rig restore <snapshotId> --rig <rigId>`
when the name is not unique); `up` output includes node statuses and an
`Attach:` command (anchors under Rule 16 above).
- `rig restore status <attemptId> --rig <rigId>`
(`packages/cli/src/commands/restore.ts:120`) reads
`GET /api/rigs/:rigId/restore/status/:attemptId`
(`packages/daemon/src/routes/snapshots.ts:76`). `deriveRestoreAttemptReceipt`
(`restore-attempt-receipt.ts:42`) folds that attempt's events into a
per-node view without writing anything.
### Single-seat recovery and handover
- **`rig seat continue <seat>`** — when a fresh launch stops at a native
prompt before its startup context is delivered, the startup orchestrator
fails it as `attention_required` and adds "run: rig seat continue
<session>" (`startup-orchestrator.ts:600`). The command
(`packages/cli/src/commands/seat.ts:632`) calls
`POST /api/seat/continue/:seatRef` (`routes/seat.ts:293`), which runs
`continueFreshStartup` (`seat-lifecycle-service.ts:395`): it re-checks the
binding, pane and runtime, then delivers the pending context to the same
conversation without relaunching. In a restore, a pod-aware seat in this
state reports `attention_required` (`restore-orchestrator.ts:1259`). See
[agent-spec-and-startup.md](agent-spec-and-startup.md) for the startup side.
- **Per-seat startup decision** — `POST /api/startup/:rigId/:logicalId`
(`routes/startup.ts:142`) takes `resume`, `start`, `fresh` or `continue`
for one seat. `resume` captures an eligibility-checked `auto-rehydrate`
snapshot when none exists and goes through `launchSingleNode`.
- **Seat handover** — `rig seat handover <seat> --source
fresh|rebuild|fork:<id>|discovered:<id> [--dry-run]`
(`packages/cli/src/commands/seat.ts:363`) calls
`POST /api/seat/handover/:seatRef` (`routes/seat.ts:94`), which runs
`SeatHandoverService.handover` (`seat-handover-service.ts:267`).
`SuccessorSessionLauncher` (`successor-session-launcher.ts:90`) respawns
the same pane in place, so the session name stays the same. The fresh
source delivers a daemon-built restore packet (`buildRestorePacket`,
`seat-handover-service.ts:1146`), which is separate from the CLI
`rig restore-packet` below. The rebuild source primes from the seat's
durable chain (`buildRebuildPrimingChain`, `rebuild-priming-chain.ts:22`).
## 5. Daemon-side restore-check and CLI restore-packet
### 5.1 `rig restore-check` — readiness probe
`GET /api/restore-check?rig=<name>&noQueue=true&noHooks=true&compact=1&ready=1`
(`routes/restore-check.ts:205–206`). The route calls
`createRestoreCheckService` (`:149`), which assembles a framework-free
`RestoreCheckDeps` (`:153–201`) over existing daemon projections —
`listRigs` from `rigRepo` (`:172–175`), `getNodeInventory` (joining
`node_id` by `logical_id`, `:24–29`, `:176–182`), `getStartupContext` (reads
(`routes/restore-check.ts:218–219`). The route calls
`createRestoreCheckService` (`:150`), which assembles a framework-free
`RestoreCheckDeps` (`:154–214`) over existing daemon projections —
`listRigs` from `rigRepo` (`:173–176`), `getNodeInventory` (joining
`node_id` by `logical_id`, `:25–30`, `:177–183`), `getStartupContext` (reads
`node_startup_context`, parses `projection_entries_json` /
`resolved_files_json` / `startup_actions_json`, `:47–141`), `hasSnapshot`
/ `getLatestSnapshot` from `snapshotRepo` (`:186–192`), `probeQueueStore`
(`:155–167`), `getClaudeActivityHookEvents` (`:168–171`), and
`probeDaemonHealth` (self-evident: "We're inside the daemon — if this route
is responding, daemon is healthy", `:193–196`). It then runs
`service.check(...)` (`:216–217`).
`resolved_files_json` / `startup_actions_json`, `:48–142`), `hasSnapshot`
/ `getLatestSnapshot` from `snapshotRepo` (`:187–193`), `getRestoreInputs`
(`:194–205`), `probeQueueStore` (`:156–168`), `getClaudeActivityHookEvents`
(`:169–172`), and `probeDaemonHealth` (self-evident: "We're inside the
daemon — if this route is responding, daemon is healthy", `:206–209`). It
then runs `service.check(...)` (`:229–230`).
`RestoreCheckService.check()` (`restore-check-service.ts:243`) layers:
`RestoreCheckService.check()` (`restore-check-service.ts:254`) layers:
1. **Host checks** — `checkDaemonReachable` (probe-throw → `verdict:
unknown`, NOT `not_restorable`, `:256–263`), `checkStateDirWritable`
(`:265`), `checkHostInfraDeclaration` (`:266`, impl `:432`).
2. **Rig enumeration** — `listRigs()` throw → `buildUnknown` (`:271–278`);
`--rig` filter; unknown rig → red `rig.<name>.exists` (`:280–287`).
3. **Per-rig checks** — `checkSnapshot` (`:294`, impl `:701`),
`checkSpecPresent` (`:299`, impl `:1064`).
4. **Per-seat checks** — `checkSeatReadiness` (`:315`), `checkStartupContext`
(`:329`, impl `:789`; `unknownChecks` → `buildUnknown`, `:330–335`),
`checkTranscript` (`:351`), `checkResumePath` (`:355`), and unless opted
unknown`, NOT `not_restorable`, `:265–275`), `checkStateDirWritable`
(`:277`, impl `:493`), `checkHostInfraDeclaration` (`:278`, impl `:510`).
2. **Rig enumeration** — `listRigs()` throw → `buildUnknown` (`:281–290`);
`--rig` filter; unknown rig → red `rig.<name>.exists` (`:292–300`).
3. **Per-rig checks** — `checkSnapshot` (`:306`, impl `:779`),
`checkSpecPresent` (`:311`, impl `:1142`), and
`rig.<name>.restore-preconditions` (`checkRestorePreconditions`, `:333`,
impl `:417`). The last runs the same `validatePreRestore` that restore
runs (`:441`) on the inputs from `getRestoreInputs`: red on a blocker,
yellow on a warning, green otherwise. Without a usable snapshot it is
yellow when current-state rehydrate is eligible and red when it isn't;
when the inputs can't be read it is yellow and the rig's rollup becomes
`unknown` (see Verdict). The rig-status route sets
`recoveryOnly` (`routes/rigs.ts:272`), which skips this check when every
seat is ready (`restore-check-service.ts:332`).
4. **Per-seat checks** — `checkSeatReadiness` (`:341`), `checkStartupContext`
(`:355`, impl `:867`; `unknownChecks` → `buildUnknown`, `:356–361`),
`checkTranscript` (`:377`), `checkResumePath` (`:381`), and unless opted
out: the daemon SQLite `queue_items` availability probe `checkQueueStore`
(`:360`, gated by `--no-queue`; impl `:910`) and `checkHooks` (`:365`,
gated by `--no-hooks`; impl `:933`). Queue continuity is represented by
the shared daemon store; an empty queue is valid
(`routes/restore-check.ts:159–160`). Claude hook readiness follows the
(`:385–389`, gated by `--no-queue`; impl `:988`) and `checkHooks`
(`:390–394`, gated by `--no-hooks`; impl `:1011`). Queue continuity is
represented by the shared daemon store; an empty queue is valid
(`routes/restore-check.ts:158–161`). Claude hook readiness follows the
persisted `claude_activity_hooks` runtime-resource selection
(`restore-check-service.ts:957`) and the adapter's activity-relay
projection in the seat CWD (`restore-check-service.ts:974–975`). A seat
(`restore-check-service.ts:1034–1041`) and the adapter's activity-relay
projection in the seat CWD (`restore-check-service.ts:1052–1053`). A seat
that deliberately omits that resource is not applicable. In compact mode
without `ready=1`, a seat whose readiness check is green gets only the
startup-context check (`restore-check-service.ts:327`, `:347–349`); that
check still counts toward the verdict (`:337–340`).
5. **Verdict** — `buildResult` (`:1106`) aggregates: any red →
`not_restorable`; any yellow → `restorable_with_caveats`; else
`restorable` (`:1121–1128`); probe-uninspectable → `unknown`
(`buildUnknown`, `:1137`). Plus a `RecoveryPlan` (`buildRecovery`,
`:1241`) and a `RepairStep[]` packet (`buildRepairPacket`, `:1475`;
`null` when fully restorable, `:1476`).
startup-context check (`restore-check-service.ts:353`, `:363–375`); that
check still counts toward the verdict.
5. **Verdict** — `buildResult` (`:1186`) aggregates: any red →
`not_restorable`; else any rig rollup with status `unknown` → `unknown`
(`:1204–1205`); else any yellow → `restorable_with_caveats`; else
`restorable` (`:1201–1210`). A rig rollup is `unknown` when its restore
inputs could not be read (`:1530–1532`). A probe that can't be inspected
at all also gives `unknown` (`buildUnknown`, `:1219`). Plus a
`RecoveryPlan` (`buildRecovery`, `:1323`) and a `RepairStep[]` packet
(`buildRepairPacket`, `:1562`; `null` when fully restorable, `:1563`).
The result shape is `RestoreCheckResult` (`restore-check-service.ts:144–156`):
The result shape is `RestoreCheckResult` (`restore-check-service.ts:147–159`):
`verdict`, `readiness`, `continuity`, `rigs[]`, `hostInfra`, `recovery`,
`counts {red,yellow,green}`, `classCounts`, `checks[]`, `repairPacket`. With
`compact=1` the route returns a reduced body (`routes/restore-check.ts:219–245`).
`compact=1` the route returns a reduced body (`routes/restore-check.ts:232–259`).
**Honest-error design:** a daemon-probe *exception* produces
`verdict: unknown` (uninspectable state), distinct from a daemon
definitely-down state which is `red` / `not_restorable`
(`restore-check-service.ts:253–263`, `checkDaemonReachable` `:393–413`). The
(`restore-check-service.ts:265–275`, `checkDaemonReachable` `:471–491`). The
route's catch-all returns the same `unknown`-shaped body with HTTP 500 + a
`probe.error` red check (`routes/restore-check.ts:249–297`).
`probe.error` red check (`routes/restore-check.ts:262–310`).
`CheckEntry.remediationSafe` defaults to `false` (conservative —
unclassified remediations are NOT auto-execution-safe,
`restore-check-service.ts:19–24`, applied at `:1490`).
`restore-check-service.ts:22–27`, applied at `:1577`).
CLI surface (`../cli-reference.md` `### rig restore-check`; options in
CLI surface (`../cli-reference.md` `### restore-check`; options in
`packages/cli/src/commands/restore-check.ts:222–227`):
`rig restore-check [--rig <name>] [--full] [--ready] [--no-queue] [--no-hooks] [--json]`.
Output is compact unless `--full` (`:313`). Exit codes: `0` restorable (or
with caveats), `1` not restorable (red), `2` unknown / probe error
(`:214–217`).
(`:214–217`). When the daemon is down, the CLI builds a local
`not_restorable` result and exits 1 (`:269`).
### 5.2 `rig restore-packet` — cross-runtime restore packet
@@ -265,7 +365,7 @@ CLI-side, no restore-packet daemon route. `commands/restore-packet.ts`
(**539** lines, `wc -l < packages/cli/src/commands/restore-packet.ts`)
implements **3** subcommands
(`grep -c 'cmd.command(' packages/cli/src/commands/restore-packet.ts`;
`../cli-reference.md` `### rig restore-packet`): `write [options]` (`:216`;
`../cli-reference.md` `### restore-packet`): `write [options]` (`:216`;
generate a packet directory from a source session or JSONL file, with
`omitted-records` accounting; `--source-session` reads the transcript from the
daemon's `/api/transcripts/<session>/full`, `:156`), `read <packet-dir> [--json]`
@@ -284,6 +384,8 @@ via runtime parsers + redaction (`packages/cli/src/restore-packet/`).
- `transport-and-transcripts.md` — transcript boundary markers written on
restore.
- Source roots: `packages/daemon/src/domain/{restore-orchestrator,
snapshot-capture,snapshot-repository,checkpoint-store,active-occupant,
restore-check-service}.ts`, `packages/daemon/src/routes/{restore-check,up}.ts`,
`packages/cli/src/commands/{restore-packet,restore-check}.ts`.
restore-preconditions,restore-topology,restore-attempt-receipt,
rehydrate-eligibility,snapshot-capture,snapshot-repository,checkpoint-store,
active-occupant,restore-check-service}.ts`,
`packages/daemon/src/routes/{restore-check,up,snapshots}.ts`,
`packages/cli/src/commands/{restore-packet,restore-check,restore}.ts`.
@@ -9,14 +9,14 @@ applies-when: |
source routing, bootstrap plan/apply, or the retained package install engine.
siblings: [agent-spec-and-startup.md, plugin-agent-image-context-pack.md]
prerequisite-reads: [../README.md, agent-spec-and-startup.md]
last-verified-against-source: 254122872cf477511514979a4300b695d77cd1f7
last-updated: 2026-10-03
last-verified-against-source: a350c59b5a5fb37ee4a21b1026b595068b603df1
last-updated: 2026-10-05
---
# Packaging, bootstrap and bundles
This module describes source at main commit
`254122872cf477511514979a4300b695d77cd1f7`. Source paths below are repository-relative.
`a350c59b5a5fb37ee4a21b1026b595068b603df1`. Source paths below are repository-relative.
An npm CLI artifact and a `.rigbundle` have different builders and consumers; neither
source verification nor archive integrity establishes that a daemon has adopted an artifact.
@@ -59,30 +59,90 @@ from live state.
| Pod-aware rig spec | `packages/daemon/src/domain/pod-bundle-assembler.ts`: `PodBundleAssembler.assemble()` produces schema-version-2 metadata, vendors resolved agent trees, and rewrites agent references to local paths. |
| Legacy rig spec | `packages/daemon/src/domain/bundle-assembler.ts`: `LegacyBundleAssembler` (imported as `BundleAssembler` by the route) collects the referenced legacy packages and produces the legacy manifest shape. |
The pod builder collects culture, docs and startup material, preserves file bytes and available
mode metadata, and skips vendoring the terminal sentinel as an agent. Required document
collection failures stop assembly; missing declared skills can instead produce warnings.
The pod builder collects culture, docs and startup material, preserves file bytes, and skips
vendoring the terminal sentinel as an agent. A copied file keeps its source mode with owner
read and write added. Required document collection failures stop assembly; missing declared
skills can instead produce warnings, which are also written into the provenance notes.
Inspect the builder's collection helpers when changing these distinctions.
Both route branches can consume a source-root `bundle.yaml` to carry declared skills,
plugins, workflow specs, context packs and agent images into staging before computing
integrity. Manifest types, validation and serialization are in
plugins, workflow specs, context packs, agent images and preconditions into staging before
computing integrity. Manifest types, validation and serialization are in
`packages/daemon/src/domain/bundle-types.ts`. Provenance and compatibility are metadata;
provenance is not an author signature.
Preconditions are carried as data. Commands containing shell operators produce a warning,
inspect reports the block, and the behaviour view lists it under needs. The install and
bootstrap code do not reference preconditions.
Two more inputs are accepted only for a pod-aware spec; the legacy branch refuses them with
a 400:
- `--context-pack <dir>` (`vendorContextPackDir()` in
`packages/daemon/src/domain/bundle-carried-context-pack.ts`) carries a pack's
`manifest.yaml` and its declared files to `context-packs/<manifest name>/`. The pack may
sit outside the rig folder.
- `--project-dir <dir>` (`vendorProjectDir()` in
`packages/daemon/src/domain/bundle-carried-project.ts`) carries a folder with a
`project.yaml` to `project/`.
Create also refuses content it must not ship. `computeIntegrity()` in
`packages/daemon/src/domain/bundle-integrity.ts` throws on sensitive paths (`.env` files,
`*.pem`, `*.key`, `*.p12`, `credentials.*`, `tokens.*`, `.git/` and `node_modules/`).
`assertShippableSubstance()` in `packages/daemon/src/domain/agent-resolver.ts` runs on
vendored content and on the final staging tree, and refuses text containing
`substrate/shared-docs/` and files declaring `taxonomy: lore`.
### Configurations and package identity
A bundle folder may declare alternate team configurations in `configurations.yaml` beside
`rig.yaml` (`packages/cli/src/lib/bundle-configuration.ts`). `rig bundle configurations
<spec>` lists them. `rig bundle create --preset <name>` or `--seat <member=runtime>` resolves
one and stages a copy of the rig folder in a temporary directory with the chosen runtimes and
profiles, so the author's folder is never changed.
`packages/daemon/src/domain/bundle-identity.ts` defines the two identity values:
- `configurationId()`: every member's `pod.member=runtime`, sorted and joined with `,`.
Create computes it from the normalized spec and returns a 400 if the client sent a
different one.
- `packageDigest()`: SHA-256 over the sorted `integrity.files` entries (coverage label
`openrig.package-digest/v1`). It covers every packaged file's bytes; it does not cover
`bundle.yaml`, the junk files the integrity walk skips, or file modes.
Create and inspect responses carry the source, configuration ID, package digest, archive
hash and assembler version.
### Archive checks
`packages/daemon/src/domain/bundle-archive.ts` owns `pack()` and `unpack()`;
`packages/daemon/src/domain/bundle-integrity.ts` owns the per-file integrity map.
Packing sorts entries, uses portable tar metadata and a fixed tar timestamp, and writes a
sibling SHA-256 file. Unpacking checks that digest, rejects archive links and unsafe entry
paths before extraction, requires `bundle.yaml` and its integrity section, and verifies
the extracted file inventory. Hashes establish consistency with the supplied manifest and
digest; they do not authenticate whoever supplied both.
Packing requires a `.rigbundle` output path, sorts entries, uses portable tar metadata, a
fixed tar timestamp and gzip level 9, and writes a sibling SHA-256 file. Unpacking checks that
digest, rejects archive links and unsafe entry paths before extraction, requires
`bundle.yaml` and its integrity section, and verifies the extracted file inventory. Hashes
establish consistency with the supplied manifest and digest; they do not authenticate
whoever supplied both.
The inspect handler unpacks through this same path before reporting the appropriate manifest
shape. A syntactically valid manifest alone is not an inspected archive.
The inspect handler does not call `unpack()`, because it must report a broken bundle rather
than refuse it. It reports a missing or mismatched digest as `digestValid: false`, applies the
same unsafe-entry pre-scan (`collectUnsafeArchiveEntries()`) and extraction filter, and runs
`verifyIntegrity()` to report an `integrityResult`. A syntactically valid manifest alone is
not an inspected archive.
Inspect also returns a behaviour view, `describeBundleBehaviour()` in
`packages/daemon/src/domain/bundle-behaviour.ts` (schema `openrig.bundle-behaviour/v1`). It
reads the extracted archive only, through `bundle-behaviour-inspect.ts` (regular UTF-8 files,
at most 1 MiB each and 8 MiB in total, opened without following links). It lists the team,
each seat's permission posture, the files seats are told to read, what else runs, where
content is written, literal outside addresses, needs, and what can't be known before launch.
A schema-1 archive gets `state: not_generated`. Posture comes from the member's or rig's
`permission_policy`: `builtin:yolo` is full bypass and `builtin:auto` is auto, both by launch
flag; `builtin:locked`, `builtin:standard` and `builtin:open` are configuration; a policy file
is read from the archive or marked unresolved; no policy means the product default. The CLI
prints the view before `rig bundle install` and `rig up` as diagnostics only; it never changes
the request or the exit code.
## Source routing and bootstrap
@@ -95,13 +155,14 @@ extensionless paths also have content detection.
| Input | Consumer |
|---|---|
| Existing rig name | Existing-rig restore path. |
| Existing rig name | Existing-rig restore path; unarchived rigs of that name are preferred. |
| Single rig spec or bundle | `BootstrapOrchestrator.bootstrap()` in `packages/daemon/src/domain/bootstrap-orchestrator.ts`. |
| Topology manifest | `MultiRigLauncher`, with a single-rig bootstrap or remote-up leaf per entry. |
Topology plan mode is rejected. Placement belongs on the individual topology entries; a
top-level host flag is rejected. The selected topology parser/route also rejects nested
topologies and existing-rig-name entries. This is narrower than the single-rig `up` surface.
topologies, existing-rig-name entries and `.rigbundle` entries: topology entries are spec
paths only. This is narrower than the single-rig `up` surface.
Bootstrap distinguishes pod-aware and legacy specs, and inspects a bundle manifest to choose
`PodBundleSourceResolver` or `LegacyBundleSourceResolver` from
@@ -115,7 +176,10 @@ the created rig identity rather than claiming every member launched.
### Durable install target
Bundle apply requires an explicit target root at the HTTP boundary. For a pod bundle,
Bundle apply requires an explicit target root at the HTTP boundary. The CLI supplies the
current directory as the default for `rig up <file>.rigbundle` and for a GitHub link through
`rig up` or `rig bundle install`. `rig bundle install` with a local `.rigbundle` sends no
default, so apply without `--target` gets a 400. For a pod bundle,
`materializePodBundle()` copies the verified extraction into that durable root before
instantiation, so local agent references and relative working directories survive removal
of the extraction directory. It checks destination conflicts before copying, preserves
@@ -125,6 +189,30 @@ Pod bundles containing service definitions are refused by bootstrap; their servi
requires a stable spec directory. Direct spec bootstrap has separate service prelaunch
handling. See [agent-spec-and-startup.md](agent-spec-and-startup.md) for instantiation.
## GitHub folder links and the bundle check
`packages/cli/src/lib/bundle-source.ts` lets `rig up`, `rig bundle create`, `inspect` and
`install` take a GitHub folder link (`isGitHubBundleLink()`). Only a credential-free
`https://github.com/owner/repo[/tree/<ref>/<folder>]` link is accepted. The ref is resolved to
the longest matching branch or tag, or taken as a full commit hash. The import:
- needs a healthy local daemon whose `selfHostId` matches the local origin;
- runs Git with credential helpers, hooks and non-HTTPS protocols disabled;
- shallow-fetches the exact commit into `bundle-imports/` under the OpenRig home, requires a
`rig.yaml` in the folder, and refuses symlinks or agent and package references that resolve
outside the checkout;
- posts the folder to `/api/bundles/create` with the source recorded in provenance, refuses a
response without a package digest or with a different source commit, and writes
`source.json` and `build.json` in the import folder.
`rig bundle check <folder>` (`checkBundleFolder()` in `packages/cli/src/lib/bundle-check.ts`)
is an advisory, file-read-only check against `openrig.bundle-standard/v1`; it makes no daemon,
launch or provider call. Its rules are `pod_aware_rig`, `readme_in_docs`, `referenced_files`,
`portable_agents`, `minimum_versions`, `configurations` and `credential_paths` (a filename scan
bounded at 10,000 entries that skips `.git`, `node_modules` and symlinks). `readme_coverage`
and `embedded_secrets` are always reported as not checked, and host-resolved resources are
reported as checked at launch (`host_resources`). Any finding sets exit code 1.
## Install checks and content routing
The install handler in `packages/daemon/src/routes/bundles.ts` obtains a source-path
@@ -137,22 +225,36 @@ It is not a complete agent, port or filesystem collision audit; a missing rig na
no name comparison. Target-file conflicts are handled separately by materialization.
Skipping these prechecks does not skip archive validation in bootstrap.
After a completed bundle install, the route separately unpacks and routes declared content:
Declared content is routed by `routeBundleContents()` in
`packages/daemon/src/domain/bundle-content-routing.ts`, which unpacks the bundle once and
routes each kind independently. When it runs depends on the bundle schema:
| Manifest content | Destination selected by the route |
- **Pod-aware (schema 2):** bootstrap runs it as `PodRigInstantiator`'s prelaunch hook, after
the rig record exists and before any seat launches, so every seat's first turn can see the
content.
- **Legacy (schema 1):** there is no hook, so the install route routes after bootstrap, and
only when the status is `completed`.
| Manifest content | Destination |
|---|---|
| `skills` | The OpenRig `packages` cache, stripping the legacy `packages/` prefix; this does not import a complete harness skill into the managed skill catalog. |
| `plugins` | The OpenRig `plugins` root, through local plugin references. |
| `workflow_specs` | `workflows/` beneath resolved `workspaceSpecsRoot`; unresolved settings skip this routing. |
| `workflow_specs` | `workflows/` beneath the resolved `workspaceSpecsRoot`. If that setting is empty, this kind is recorded as a routing failure. |
| `context_packs` | The configured `context.root`, matching context-library discovery. |
| `agent_images` | The OpenRig `agent-images` root; declarations address image directories containing a manifest. |
| `project` | Registered in the workspace catalog (`workspace.catalog_path`) with the rig associated, and its files placed under `workspace.projects_root/<id>` (`registerBundleProject()` in `packages/daemon/src/domain/workspace/project-registration.ts`). A conflict changes nothing and is a routing failure. |
The individual implementations are the `bundle-skills-router.ts`,
`bundle-plugins-router.ts`, `bundle-workflow-specs-router.ts`,
`bundle-context-packs-router.ts` and `bundle-agent-images-router.ts` files under
`packages/daemon/src/domain/`. The route treats these post-bootstrap calls as best effort:
their failure does not roll back or turn an already completed rig install into failure.
Inspect their returned results and warnings separately from bootstrap success.
Context packs are never merged into an installed pack of the same name: an identical one is
reported as `already_installed`, a different one is kept unchanged as `kept_existing`. After
routing, the live context library rescans, and a routed pack it can't load becomes a routing
failure.
The per-kind implementations are the `bundle-skills-router.ts`, `bundle-plugins-router.ts`,
`bundle-workflow-specs-router.ts`, `bundle-context-packs-router.ts` and
`bundle-agent-images-router.ts` files under `packages/daemon/src/domain/`. Routing is best
effort in both cases: the hook always lets the launch continue, and a failure never rolls back
or fails the install. Failures appear in the result's `routingFailures` and `warnings`, in a
`route_bundle_contents` stage for the hook, and in the install audit record.
Install audit records are appended best effort to `bundle-audit.jsonl` under the OpenRig
home; the history endpoint reads that audit. Bootstrap run state and action journals are
@@ -168,9 +270,12 @@ bootstrap calls `PackageInstallService` in
The source chain is package resolution/manifest validation, planning and conflict policy,
then apply/verification with repository records:
- `packages/daemon/src/domain/package-resolver.ts` and `package-manifest.ts`.
- `resolvePackage()` in `packages/daemon/src/domain/package-resolve-helper.ts`, which reads
and validates through `package-manifest.ts`. `package-resolver.ts` supplies the types; its
`PackageResolver` class has no production caller.
- `packages/daemon/src/domain/install-planner.ts`, `conflict-detector.ts` and
`install-policy.ts`.
`install-policy.ts`. The planner refuses any planned destination outside the install
target.
- `packages/daemon/src/domain/install-engine.ts`, `install-verifier.ts`,
`install-repository.ts` and `package-repository.ts`.
@@ -9,14 +9,14 @@ applies-when: |
agent-image capture/fork/protection, or Claude guided-compaction behavior.
siblings: [packaging-bootstrap-bundles.md, agent-spec-and-startup.md]
prerequisite-reads: [../README.md, agent-spec-and-startup.md]
last-verified-against-source: 254122872cf477511514979a4300b695d77cd1f7
last-updated: 2026-10-03
last-verified-against-source: a350c59b5a5fb37ee4a21b1026b595068b603df1
last-updated: 2026-10-05
---
# Content libraries and compaction
This module describes source at main commit
`254122872cf477511514979a4300b695d77cd1f7`. Source paths below are repository-relative.
`a350c59b5a5fb37ee4a21b1026b595068b603df1`. Source paths below are repository-relative.
Context packs, plugins and agent images have filesystem-backed content and daemon-side
discovery. Their consumers can read database identity, mutate files, deliver messages or
launch sessions; the whole layer is not a read-only catalog.
@@ -30,10 +30,13 @@ the entry ID is `context-pack:<ref>`.
### Discovery and mutation
Startup in `packages/daemon/src/startup.ts` configures the shipped context-pack root,
the resolved `context.root` and its system root, followed by an existing workspace-local
`.openrig/context-packs` root. The library scans recursively, stops descending when it
finds a pack manifest, and does not follow directory symlinks.
Startup in `packages/daemon/src/startup.ts` configures the shipped context-pack root (when
it exists), the resolved `context.root` and its `system` root, followed by the
workspace-local `.openrig/context-packs` root. The workspace root is registered even before
it exists, so a folder created later is found by the next sync; it is left out only when it
is the same path as one of the other two. The library scans recursively, stops descending
when it finds a pack manifest, skips `.tmp-add-*` staging folders, and does not follow
directory symlinks.
The first configured root to encounter a physical pack owns that directory. For distinct
physical packs with the same ref, the later root wins. This distinction prevents overlapping
@@ -66,7 +69,9 @@ Situation composition selects fresh atoms for `fresh`, fresh plus handover atoms
selection is Claude or Codex, with `any` atoms applicable to either. Required atoms join
the selection; missing dependencies or runtime-incompatible dependencies fail composition.
The output is ordered by atom order, then ID. Profile-only atoms are excluded from ordinary
situation selection.
situation selection, but the dependency closure doesn't check that flag, so one can still join
as a dependency of a selected atom. Post-compaction composition skips an absent
`seat:RECAP.md` and reports it as skipped instead of failing.
The profile route resolves configured tree sources through
`packages/daemon/src/domain/context-packs/profile-source-resolver.ts`.
@@ -85,11 +90,42 @@ reports overage and drop candidates without silently truncating the profile. Ato
`/api/context-packs`. Its library handlers cover list, sync, compose, read/delete by ref,
preview, pieces, addressed reads and profile composition.
`packages/cli/src/commands/context.ts` implements `rig context`, including
`get`, `profile`, `compose`, `add`, `rm` and `sync`, plus work-install,
trace, source and recap operations. The library read/compose routes do not themselves send
a pack to a running seat. Delivery and installation consumers have their own behavior;
inspect the selected CLI verb before treating a context command as read-only.
`packages/cli/src/commands/context.ts` implements `rig context`, including `list`, `show`,
`preview`, `get`, `profile`, `compose`, `add`, `rm` and `sync`, plus `work-install`, `trace`,
`source inspect|update` and `recap-write`. The library read/compose routes do not themselves
send a pack to a running seat. Several verbs write: `add` installs into `context.root` (from
a folder, a URL, or Git with `--git`), `source update` fetches and merges a Git-installed pack
and resyncs, `work-install --apply-skills` reconciles skill projections, and `recap-write`
writes a seat recap. Inspect the selected CLI verb before treating a context command as
read-only. `rig context work-install` resolves a project position and its declared context
without the daemon; [workspace-primitive.md](workspace-primitive.md) covers project
resolution.
On a profile read, the route adds each piece's `sha256` and provenance, with a warning when a
piece's bytes come from outside the granted folder. When rig and seat are given, it adds the
seat's `LEARNED.md` as an implicit atom.
### System World, seat recaps and skill loadouts
`packages/daemon/src/domain/system-world.ts` reads the System World manifest (schema
`openrig.system-world/v0alpha1`): an id, a version, the context packs every seat starts from,
and system skills. The `context.system_world` setting selects the default manifest at
`system/system-world.yaml` under `context.root`, disables it, or names another manifest file.
The default (`openrig-default`) names the `onboarding-width` and `world-public` packs and no
skills; it is seeded when missing.
`rig context recap-write --rig <rig> --seat <seat> --file <file>` writes the seat's
`RECAP.md` through `packages/daemon/src/domain/context-packs/seat-recap-store.ts`. The previous
recap is kept byte-for-byte under `recap-superseded/`. A recap that isn't addressable Markdown
is refused; the other content checks are advisory.
`packages/daemon/src/domain/skill-catalog.ts` resolves a seat's skill loadout from the
catalog at `skills.root` (`catalog.yaml`, schema `openrig.skill-catalog/v1`) and the system,
topology and project selections. It projects skills into `<cwd>/.claude/skills` or
`<cwd>/.agents/skills` and tracks what it owns in
`<cwd>/.openrig/skill-loadouts/<runtime>.json`. Each projection reports `current`, `missing`,
`shadowed`, `stale` or `conflicting`. `rig skill loadout|audit` reads and reconciles it, and
launch applies it per seat.
## Agent images and forks
@@ -128,9 +164,18 @@ contribute protection reasons. The route applies the guard to deletion/pruning u
`force` is explicit. Prune defaults to dry-run. Its protection view depends on the supplied
spec roots; it is not an inventory of every reference anywhere on the host.
The guard keys its result by image name, not version: every version of one name gets the
status computed for the last-listed version, carrying that version's image ID. The delete
route looks up by image ID, so on delete only that last-listed version of a name is matched,
and its other versions are deleted without `force` even when pinned. `rig agent-image prune`
sends a dry run unless `--force` is given, and `--force` also overrides the guard, so a
guarded real prune is reachable only through the HTTP route.
The routes mount at `/api/agent-images` and expose list/sync, snapshot/fork,
entry/preview, pin/unpin, delete and prune.
`packages/cli/src/commands/agent-image.ts` owns the image library commands.
`packages/cli/src/commands/agent-image.ts` owns the image library commands. The top-level
`rig fork` verb (`packages/cli/src/commands/fork.ts`) drives `POST /fork`; `--keep-image`
also captures a pinned image.
## Plugins
@@ -153,7 +198,9 @@ harness loading a plugin.
The service constructor is not read-only: it retires obsolete refocus hook registrations
from the older bundled plugin and rejects duplicate vendored hook registrations across
providers. Cached plugin versions are excluded from that duplicate-provider check.
providers. Cached plugin versions are excluded from that duplicate-provider check. Startup
constructs the service while building the daemon's dependencies and doesn't catch that
rejection, so a duplicate vendored hook stops daemon creation.
`packages/daemon/src/routes/plugins.ts` exposes GET-only inspection at `/api/plugins`:
list, detail, used-by and file list/read. The file routes use the discovered plugin root as
@@ -167,11 +214,14 @@ At this pin, the route's source filter accepts vendored, Claude-cache and Codex-
`ensureVendored()` seeds absent plugins or advances an older numeric manifest version.
Equal/newer installed versions preserve installed bytes; equal versions can reconcile file
modes on byte-identical files. Missing target version authority is preserved rather than
overwritten; invalid or conflicting manifest versions are errors.
overwritten; invalid or conflicting manifest versions are errors. Advancing a version writes
changed files but never deletes files the new version dropped.
`ensureSkillGlobally()` projects a named plugin skill using a vendor-version marker.
An existing unversioned global skill remains externally owned. Startup wires this service
and its filesystem implementation; inspect that wiring when changing projection roots.
and its filesystem implementation; inspect that wiring when changing projection roots. At
startup only `openrig-core` is vendored, and it is projected globally as `openrig-skills` into
`~/.claude/skills` and `~/.agents/skills`.
`ensureLatest()` performs local vendoring before `attemptAutoFetch()`. The latter
requests a release asset with a bounded timeout and logs failures; even its successful
@@ -190,11 +240,40 @@ and the manual-compaction route consumers.
`packages/daemon/src/domain/user-settings/settings-store.ts` resolves the
`policies.claude_compaction.*` settings; automatic compaction defaults to disabled.
`maybeAutoCompact()` applies the delivery guard when available. Its implementation
requires Claude Code, observed usage and a valid integer threshold. Above-threshold
automatic work additionally checks enablement, deduplication, cooldown and the
already-triggered latch. It sends prep, then compact, and queues the below-threshold
continuation: turn boundary, restore prompt, then compliance prompt.
`maybeAutoCompact()` applies the delivery guard when one exists and the runtime is Claude
Code. Its implementation requires Claude Code, observed usage and a valid integer threshold
(default 80). Above-threshold automatic work additionally checks enablement, the
post-restore cooldown (10 minutes), the deduplication window (60 seconds) and the
already-triggered latch; an active manual attempt also blocks it.
`/compact` is held behind a preparation attempt:
1. The first above-threshold tick starts an attempt with its own ID, the seat's occupant
generation, a restore-map path and a completion marker, then sends only the prep prompt.
An unknown occupant generation stops the attempt at once.
2. Later ticks return `preparation_pending` until the attempt's `RESTORE-MAP.md` exists and
ends with that exact marker. The map path is under the seat's working directory
(`.openrig/compaction/preparation/<session>/<attempt>/`, with a `.gitignore` of `*`) when
that is writable, otherwise under `compaction/preparation/` in the OpenRig home.
3. Only then is `/compact` sent, with the map path appended to the compact instruction.
4. The continuation then runs below the threshold: turn boundary, restore prompt, then a
read-depth audit prompt (each item marked `FULL`, `PARTIAL` or `NOT_READ`).
An automatic attempt has a 25-minute ceiling that starts once the prep is delivered. A stale
occupant generation, the policy becoming disabled, or the deadline passing stops it. A prep
that clearly wasn't sent is retried at most three times; an uncertain prep or `/compact` is
never replayed.
The restore prompt names a restore-pending marker at
`compaction/restore-pending/<session>.json` in the OpenRig home, which the `openrig-core`
PreCompact hook writes. A per-seat `compaction/post-compact-extra/<session>.md` is preferred
over the global extra file, and an extra file that declares a different seat is refused.
`rig compact <session>` (`packages/cli/src/commands/compact.ts`) runs the same sequence
manually through `/api/compaction/trigger`, `cancel` and `state`, behind the bearer token.
`--skip-map` skips the map requirement once, `--cancel` ends the preparation, and `--state`
shows the attempt, its expected map and its deadline. A manual attempt has 120 seconds for
preparation and the idle wait.
The below-threshold continuation checks policy enablement too, except for an explicitly
operator-initiated manual sequence. A known occupant-generation mismatch invalidates the
@@ -202,7 +281,8 @@ queued stages; an unknown generation does not prove a match. Failed or retained
do not advance the stage. The manual trigger shares this continuation while having its
own initiation checks.
Stage, deduplication and cooldown maps are in memory. Startup supplies live generation
Stage, deduplication, cooldown and preparation-attempt maps are in memory; the restore maps
are files. Startup supplies live generation
resolution and a post-restore callback that records a managed-width receipt, so the wider
operation also has persistent effects. Do not infer durable lifecycle completion from
a library read or one successfully queued message.