* fix(cli): replace proof artifacts without writing through links
`rig proof add --replace` opened the existing artifact name with the "w"
flag. That follows a symlink and truncates a shared inode, so replacing
an artifact name that was a symlink or hard link overwrote the other
path's bytes, including files outside the slice and proof media.
--replace now writes a uniquely named staging file next to the artifact,
created exclusively, and renames it over the name. The old link or inode
is never opened for writing, so other paths keep their bytes. A failed
write or rename leaves the target as it was and removes only the staging
file this call created; the original error is reported. A self-sourced
replace still works because the source is read before the write. The
non-replace path keeps its exclusive-create refusal.
The replaced artifact is a new inode with default permissions. There is
no fsync, so this is a directory-entry swap, not a crash-durability
guarantee.
* fix(cli): keep artifact permissions and the first failure on proof replace
An explicit --replace of an existing regular artifact now carries its
permission bits onto the staging file before the swap, so a replacement
never broadens access (a 0600 artifact stays 0600; a read-only artifact
is replaced and stays read-only). A symlinked or new name still gets
default permissions.
The write and close failures are collected separately: the first one is
reported and a later close failure is a warning. A close failure on its
own still refuses the rename, and the target keeps its bytes either way.
* fix(cli): create the proof replace staging file no wider than the artifact
The exclusive staging open now passes the replaced artifact's permission
bits (or the default 0666 for a symlinked or new name) as its creation
mode, so the staging file is never created wider than the artifact it
replaces. The fchmod still sets the exact bits regardless of umask.
---------
Co-authored-by: OpenRig contributors <noreply@openrig.dev>
The file title sent with files.completeUploadExternal was the queue summary
(or the file name) as is, while the message text carrying the same summary
already went through redactSecrets. A secret in a row summary was therefore
hidden in the text but shown as the uploaded file's title. The title now
goes through the same redaction. Slack does not parse file titles as mrkdwn,
so the text's control-syntax escaping is not applied to it.
Fixes#300
Claude-Session: https://claude.ai/code/session_01V8RzdrMhGkYLV4rn96kNMN
Co-authored-by: v-openrig-build <v-openrig-build@users.noreply.github.com>
The daemon no longer serves the web UI's pages, assets and SPA routes, or
registers the web-terminal WebSocket, unless `ui.enabled` is true. Turn them
on with `rig config set ui.enabled true`, then stop and start the daemon.
While the UI is off, a page request answers 404 with those steps, and
`rig ui open` asks the running daemon and prints the same steps instead of
opening the page.
The /api routes, /healthz and the HTTP terminal routes (open, views, preview,
status) answer as before. The guard that ran in front of the HTTP terminal
routes through the WebSocket route still runs when the WebSocket is absent.
Co-authored-by: OpenRig contributors <noreply@openrig.dev>
The README opening now says what OpenRig is in one sentence (open-source
software for building and running your own network of agents) and closes
with the line about the AI civilization experiments. The npm package
description and the Context7 description use the same short summary:
"Build your own network of agents from Claude Code, Codex and Pi:
long-running teams with roles, shared context and owned work."
Co-authored-by: v-openrig-build <v-openrig-build@users.noreply.github.com>
Onboarding piece 01 told only half of the doghouse story: the build that
over-grows (now a light that needs power, not a lock) and then asked "How
big is the dog?", which belongs to the other half. It now tells both: the
moon base, stopped by "does the thing actually need this?", and the tidy
doghouse whose door the dog can't fit through, stopped by "how big is the
dog?". A short paragraph adds that shared understanding of the goal, not
tighter instructions, is what prevents both.
Piece 02's ending becomes "When you need more": the capability map is read
when needed rather than by every seat, a world pack is for planning and
routing work, and the world template is pointed to for people setting one up.
The delegating-work skill gains "Hand over intent, not instructions".
Co-authored-by: v-openrig-build <v-openrig-build@users.noreply.github.com>
A mutating `rig seat handover` (and `rig handover`, which shares it) waits up to
120 s for the daemon (#260). When the client reaches that bound the daemon may
still be working, so the handover's outcome is unknown, but the CLI surfaced only
a raw transport timeout (reported in korallis/agent-stack#32).
The CLI now prints "handover outcome unknown" in text and JSON, exits 1, and
makes no second request. It tells the user to inspect the seat before any new
handover, and that a result shown there may belong to an earlier attempt.
A completed or refused handover, a connection failure, and dry-run behave as
before.
Co-authored-by: OpenRig contributors <noreply@openrig.dev>
* docs: add contributor maps and a developing-openrig skill
Add ARCHITECTURE.md (packages, request path, counts with the commands that
produce them, where to add a command, route, migration, adapter, skill,
context pack or scenario), docs/as-built/arteries.md (high-impact areas,
their dependents and past regressions) and docs/as-built/test-layers.md
(what to run before a pull request and what each layer proves).
Add a repo-local developing-openrig skill for Claude Code (.claude/skills)
and Codex (.agents/skills) that routes to these maps, with narrow .gitignore
exceptions so only that skill is tracked. Point CONTRIBUTING.md at the maps
and correct its skill-mirror instructions. Replace the private-host paragraph
in the shipped openrig-skills router with a pointer to the repository skill.
* docs: correct six factual points from review
Newest as-built markers; skills may have one or several copies; the host
scenario runner drops rather than refuses TMUX and daemon variables;
scenario-10 declares its own normaliser; the managed Claude launch applies
only with an explicit permission mode; Dockerfile.scenarios is layered by
run-pr-scenarios.sh. Also tighten several wordings the review flagged.
---------
Co-authored-by: v-openrig-build <v-openrig-build@users.noreply.github.com>
* fix(daemon): deliver built-in startup files from the running install (#261)
Built-in startup files (CULTURE-default.md, openrig-start.md and the two
onboarding files) are stored per seat as absolute paths inside the install
that created the seat. After an upgrade removes that install (mise and other
versioned managers), every fresh launch rolled back with ENOENT, and restore
blocked on the missing required file. While the old install remained, seats
kept receiving the old version's shipped guidance.
Fresh launch (SeatLifecycleService.readStartupContext) and restore
(pre-validation and replay) now re-anchor a stored file to the running
daemon's assets when it is a recognized built-in: one of the four logical
names, stored at exactly <ownerRoot>/<known relative path>, with ownerRoot a
daemon/assets directory. Custom rig and agent files, including a same-named
CULTURE-default.md, are delivered exactly as stored. Required/appliesOn/
delivery metadata is unchanged, a missing current built-in still fails
honestly, and a same-native resume still replays nothing.
Refs #261
* fix(daemon): project shipped-spec resources from the running install (#261)
Kernel and library agents ship inside the install, so their stored projection
entries (agent guidance, shared runtime settings/MCP fragments) also point
into the install that created the rig. After that install is removed, a fresh
launch still rolled back in projection, before startup-file delivery.
Fresh launch and restore (pre-validation drift checks and replay) now
re-anchor a stored projection entry to the running install's daemon/specs when
both its sourcePath and its resource path lie under the same OpenRig install's
daemon/specs (packaged @openrig/cli/daemon/specs or a packages/daemon/specs
checkout). A resource stored elsewhere, such as the openrig-core plugin under
~/.openrig/plugins, user specs, or a folder merely named daemon/specs, is
projected exactly as stored. Identifiers, category, target, merge behavior and
the existing skill exclusion are unchanged; a resource missing from the
running version keeps the existing failure or warning.
Refs #261
* fix(daemon): own-key built-in lookup; restore-check judges the running paths (#261)
The built-in name lookup used a plain object, so a custom startup file named
constructor, toString or __proto__ matched an inherited key and fresh launch
threw on a valid seat (review finding on be599a64). Use a Map.
RestoreCheckService.checkStartupContext checked and reported the stored
paths directly, so a seat whose old install was removed showed missing
required inputs that replay no longer uses, and named stale paths in its
evidence and remediation. It now applies the same resolvers to the inspected
context and uses the resolved paths in both the predicates and the reported
text. The red/yellow rule, genuinely missing current or custom files, and
missing/malformed/probe-error handling are unchanged. The route's context
reader passes ownerRoot and sourcePath through so the resolvers can apply.
Refs #261
* fix(daemon): shipped-spec startup files follow the running install (#261)
Kernel and library seats also store their rig culture and agent startup files
(culture/CULTURE.md, guidance/role.md, startup/context.md) as absolute paths
inside the install's daemon/specs. With the projection fixed, a kernel created
by a removed install still rolled back on the culture file, and its post-launch
role/context would have followed.
The startup-file resolver now also re-anchors a stored startup file whose
ownerRoot and absolutePath both lie under the same recognized install's
daemon/specs, using the same mapping as projection entries. It applies through
fresh launch (pre- and post-launch delivery), restore replay and
pre-validation, and restore-check. Custom, external and plugin paths, and all
required/applicability/delivery metadata, are unchanged; a file missing from
the running version still fails or warns honestly.
Refs #261
* fix(daemon): re-anchor dev-checkout paths only when missing (#261)
A daemon running from one install remapped every recognized shipped path,
including ones stored from a developer checkout (packages/daemon/...). Those
files can be newer than, or absent from, the running install, so a present
file was remapped to a missing running path and the launch rolled back
(review finding, confirmed by both reviewers).
Packaged installs (@openrig/cli/daemon/...) still re-anchor even while the
stored file exists, so seats never keep an old version's shipped content.
Dev-checkout files re-anchor only when the stored file is missing. Recognition
is limited to those two layouts for built-in assets too. Each consumer passes
its own existence check (restore fsOps, restore-check deps; fresh launch uses
the filesystem).
Refs #261
* fix(daemon): leave legacy persisted startup shapes as stored (#261)
Snapshots and contexts persisted by older versions can lack ownerRoot on
startup files or sourcePath on projection entries. The #261 resolvers called
path.resolve on those fields, which threw, so restore reported restore_error
instead of its real outcome; restore pre-validation walks projection entries
even for an exact resume (CI: restore-honesty-d1-d6 D6a, 4 failures).
Entries without a string root or path are now returned exactly as stored,
which is their pre-#261 behavior.
Refs #261
---------
Co-authored-by: v-openrig-build <v-openrig-build@users.noreply.github.com>
* fix(cli): use current home daemon endpoint for local clients
* fix(cli): retain recorded endpoint after daemon exit
---------
(cherry picked from commit cdf5fff724)
Co-authored-by: dev-driver <dev-driver@openrig-build>
A managed Claude launch with an explicit permission mode first runs
`claude --help` to read the supported modes. The query was killed after one
second, so a valid but slow help refused the launch with "capability query
failed". The daemon now allows five seconds; a hung query still ends at that
bound with the same refusal. `rig seat set-permissions` waits up to 10 seconds
and a mutating `rig seat handover` gets the existing 120-second launch window,
so the daemon's answer arrives before the client deadline.
Port of #271 from release/0.6.4 (squash bfa66821). Original author:
dev50-driver; one-line S03 test finisher: dev60-driver.
Refs #260
Co-authored-by: OpenRig contributors <noreply@openrig.dev>
Bundle create read every vendored agent-package file, and the declared
culture, docs and startup files, as UTF-8 text and wrote it back as UTF-8.
Any byte sequence that is not valid UTF-8 became U+FFFD, and the integrity
manifest was hashed from the changed copy, so inspect and install verified
the corrupted bytes as intact.
Copy those files as bytes: the pod assembler reads them with readFileBuffer
and writes the same bytes, and the install-side skills and workflow-spec
routers copy with fs.copyFileSync. Rewriting rig.yaml and agent.yaml import
refs stays a text operation.
Port of #257 from release/0.6.4 (squash 2174b11e). The async test change in
that squash is already on main via #268 and is not repeated here.
Fixes#245
Co-authored-by: OpenRig contributors <noreply@openrig.dev>
Stopping the server's last session ends tmux's server, so launchFresh's
later probes saw transport_unavailable and refused with tmux_probe_failed
after the occupant was already stopped. After its own successful stop,
launchFresh now restores an empty server with the existing startServer()
(a no-op while the server is up; no session is invented), so the
classified probes get a positive answer. Transport, permission,
collision, claimed-seat and wrong-pane refusals are unchanged.
Port of #267 from release/0.6.4 (squash fa770114), without its RC-only
CHANGELOG notes.
Refs #265
Co-authored-by: OpenRig contributors <noreply@openrig.dev>
The npm page for @openrig/cli shows "No README data found" because the
published package root has no README.md. The CLI build already copies the
root LICENSE into the package; it now copies the root README.md the same
way, and the generated copy is ignored like LICENSE. The packaging test
now also checks that npm pack carries README.md byte-for-byte.
Co-authored-by: v-openrig-build <v-openrig-build@users.noreply.github.com>
* fix(daemon): prove managed Claude identity during restore
* fix(daemon): sample Claude lineage only for shell labels
* test(daemon): distinguish bare shells from unusable Claude panes
* fix(identity): retain unavailable final Claude command observation
---------
Co-authored-by: dev-driver <dev-driver@openrig-build>
The three anti-sync discriminators in list-processes-async.test.ts raced a
0ms timer against the sampler's result. Timer order is not a valid guarantee
of async execution: in a local probe, a 70ms synchronous stall after spawning
a single-PID `ps eww` let the result settle before the 0ms timer in most
trials. The CI failure of this test is consistent with that race; the CI
timing itself was not measured.
Assert instead that the returned promise is still pending after draining 20
microtask ticks: an async execFile cannot settle without an event-loop turn,
while a synchronous ps inside the call settles within a few ticks. The real
rows and HOME assertions are unchanged.
Refs #245
Co-authored-by: OpenRig contributors <noreply@openrig.dev>
client.test.ts expected only MAJOR.MINOR.PATCH, so the 0.6.4-rc.1 candidate failed
Tests run 36798254083. Allow an optional pre-release suffix and keep the anchors and the
optional (commit[, dirty]) stamp. Test-only.
Co-authored-by: OpenRig contributors <noreply@openrig.dev>
* test: bind scenario seeds and exercise the library baton in CI
* test: run named scenarios through a remote Docker executor
* test: bound and clean up the testbed image-load check
---------
Co-authored-by: dev-driver <dev-driver@openrig-build>
Co-authored-by: dev-qa <dev-qa@openrig-build>
* fix(daemon): keep a blocked row's park timer through a seat handover
At a seat swap, the occupant invalidator stops every watchdog job the retiring
generation registered. A park timer (rig queue block --wake-after, with or without
--wake-max) is registered by the parking seat, so a handover stopped it too. Nothing
re-armed it, and the row stayed blocked with no timer, which is the strand reported
in korallis/agent-stack#61.
The rig's parked-owner consumer eventually noticed the dead wake and sent one generic
wake once the new occupant was idle. The recorded wake time, message and backoff
cadence were still lost.
A blocked row's current queue-generated timer wakes the seat that owns the row, and
the successor inherits that row, so the swap now keeps it. The queue lists those
timers: each is still the row's current armed wake, is still active, and still
targets the row's destination. The watchdog drop leaves them running.
Every other job the retiring generation registered still stops: operator-attached
watchdogs, superseded or stale timers, timers of rows rerouted to another seat, and
occupant-only jobs. Re-parking still retires the kept timer, so no duplicate is
created. A failed handover never reaches the invalidator, as before.
* fix(daemon): retire a park timer when its row is rerouted
routeToFallback moved a blocked row to a new owner but left the row's park timer
active and aimed at the old owner, so it could fire to the old owner. The
pre-delivery check only refuses timers whose rows are all terminal. That was already
reachable on main (park, reroute, fire), and keeping park timers through a handover
also let it happen after a swap (review-r2).
The reroute now retires the row's current park-generated timer through the existing
retireParkGeneratedTimer path, reason park_rerouted, the same one re-park and exit
use. The new owner has no dedicated wake from a reroute, as before; the retired timer
leaves the row's wake dead, so the parked-owner backstop covers it.
* fix(daemon): leave a repeating queue wait running when its row is rerouted
The previous commit retired every park timer on reroute. A repeating queue wait
already delivers to the row's current owner (evaluateQueueWait sends to view.owner),
so it woke the new owner correctly, and retiring it took that wake away. Only a
periodic park timer is aimed at the old owner, so only that is retired now.
Tests now fire through the engine with resolveQueueWait wired, as startup.ts wires
it. After a periodic reroute the row shows no live park timer, and the new owner's
parked diagnosis sees it as unhealthy, so the backstop covers the new owner. A
repeating wait survives the reroute, with or without swaps, and delivers only to the
new owner.
---------
Co-authored-by: OpenRig contributors <noreply@openrig.dev>
createHerdrSocketRpc decoded each socket chunk on its own, so a
multi-byte character split across two chunks became U+FFFD while the
JSON line still parsed. Setting the socket encoding to UTF-8 uses Node's
streaming decoder, which carries an incomplete character into the next
chunk. Newline framing, id matching and error, end and timeout handling
are unchanged.
The new test drives the real RPC over a private unix socket and splits
the reply inside 2-, 3- and 4-byte characters, plus a case where
another id's line arrives first.
Co-authored-by: OpenRig contributors <noreply@openrig.dev>
Both native process listers run `ps` with an `lstart` column and parse it with
an English date pattern. `ps` formats that column through the locale, so a
daemon that inherited a non-English locale (for example LANG=de_DE.UTF-8,
LC_TIME=fr_FR.UTF-8 or LC_ALL=ja_JP.UTF-8) saw every row fail the pattern:
process detection and resume metadata silently came back empty.
Each `ps` call now runs with a child-only `LC_ALL=C`, spread over the existing
environment. The parent environment, the column sets, the parsers and every
identity requirement are unchanged.
On macOS, the C locale also makes `ps` print non-ASCII bytes in the command
column as escapes (for example `\240`) instead of raw UTF-8. Token boundaries
and every ASCII identity token (executable name, session and resume ids) are
unchanged.
Claude-Session: https://claude.ai/code/session_01V8RzdrMhGkYLV4rn96kNMN
Co-authored-by: v-openrig-build <v-openrig-build@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(daemon): rig remove never kills another rig's live seat; seat refs skip archived rigs (#174)
An archived duplicate of a live rig shares its name and its seats'
canonical session names.
- `rig remove <archived-rigId> <node>` killed the tmux session by name,
so it could end the live rig's seat. Removal now asks whether another
node owns that session name, using the handover's live-ownership check,
now shared as session-owner.ts. If another node owns it, removal
leaves the session running, doesn't block on or reroute the queue
work addressed to it, and reports the owner as `sessionKeptFor`. The
node itself is still removed.
- Seat refs (`member@rig`) for seat launch, stop, model and handover
resolved rigs by name including archived ones, so they failed with
"matched multiple nodes". They now resolve unarchived rigs only, as
default reads do since migration 042. Other findRigsByName callers
are unchanged.
* fix(daemon): archived rigs don't claim a live seat's session or input target (#174)
Review follow-up. Archiving a rig keeps its bindings, so the archived twin
still names the live seat's session:
- Removing the live node found the archived twin as the "other owner",
kept the session and removed the node's open work. Removal now ignores
owners in archived rigs, so the live node kills its own session and
still refuses open work without --fallback. The handover check is
unchanged.
- The delivery guard needs exactly one match for a target, and the twin's
binding made it two, so delivery to the live seat failed. Unarchived
matches now win, and archived ones count only when nothing else
matches. Exactly one match is still required, so a truly ambiguous
live target still refuses.
- `rig remove` and `rig shrink` human output now says when a session was
kept, and for whom.
* fix(daemon): a YAML import archives the stopped same-name rig it replaces (#141)
After the seats' tmux server died, `rig up <same rig.yaml>` created a
second, unarchived rig with the same name. Every seat name then matched
two nodes, so launches and kills were refused.
When an explicit YAML import (PodRigInstantiator.instantiate) finds an
unarchived rig with the same name, it first confirms that rig is stopped:
- no running session rows (the existing running-name guard); and
- for every session name it used, this daemon's tmux reports positive
absence or "no server running".
It then archives that rig in the same database transaction that creates
the replacement. The earlier rig's records are kept, and the output names
it with `rig unarchive <id>`. If the rig can't be confirmed stopped, the
import creates nothing and says why.
If creating the replacement fails, the service hook refuses it, or every
replacement node fails and the replacement is removed, the earlier rig
is un-archived. An adapter that cannot probe keeps today's behavior.
* fix(daemon): one YAML import per rig name while it may archive a generation (#141)
Review follow-up to the stopped-generation archive.
- Concurrency. The stopped check awaits tmux, and a second same-name
import could run during that wait. It could replace the same old rig
too, or archive the first import's in-progress replacement, which has
no running sessions yet. The YAML import now takes a per-database,
per-name in-flight claim, and a same-name import arriving meanwhile is
refused (generation_unconfirmed). Unrelated names are unaffected. The
create transaction also rechecks that the same-name set is the one it
checked, and that each archive changed its row, so a rollback restores
only archives this import made.
- Responses. generation_unconfirmed is now a 409 conflict with its
message on POST /api/up and POST /api/rigs/import, not a 500. The
archive notice also rides the attention_required outcome, where the
replacement is kept.
* fix(cli): print the #141 archive notice on the attention 409 and the generation_unconfirmed message
rig up returned from its status >= 400 block before either warnings loop, so the
archive notice (old rig id and its unarchive command) on an attention 409 reached
--json only. Print the response warnings after the attention nodes; that path returns
early, so nothing prints twice. Render generation_unconfirmed verbatim like
rig_name_running instead of the validate-your-spec fallback. JSON output and exit
codes are unchanged.
* fix(daemon): archived generations don't block the replacement's handover or pod shrink
A YAML re-import archives the stopped earlier generation, which keeps its bindings
to the session names the live replacement now uses.
Handover: a fresh, fork or rebuild successor reuses the seat's own session name,
so the archived binding made finalize return successor_already_managed after the
successor had replaced the process. The owner check skips archived rigs for
composer-launched successors, as removeNode does. A discovered successor is still
blocked by any other owner.
Pod shrink: shrinkPod counted and, with --fallback, rerouted queue work addressed
to member session names that another unarchived node owns. It now applies
removeNode's ownership rule first, so shrinking the archived pod neither stops on
nor moves the replacement's work.
* fix(daemon): keep a shrink target's own shared-name work in the up-front check
C3's shrinkPod ownership filter excluded only the node being checked, so when two
members of the pod being shrunk shared a session name, each counted the other as its
owner. Their open queue work then dropped out of the up-front check. Without
--fallback the shrink removed one member and failed on the last. With --fallback the
work wasn't rerouted.
findOtherSessionOwner gains an optional excludeNodeIds, applied in both the binding
and the session-row lookups before the first match, and its default is unchanged.
shrinkPod passes the whole target, so only an unarchived owner outside the target
takes a name's work out of the check. Before C3, shrinking such a pod refused up front
or rerouted with --fallback, and that behavior is restored.
---------
Co-authored-by: OpenRig contributors <noreply@openrig.dev>
* fix: provider-aware Codex readiness for non-OpenAI providers (#194)
`rig setup` and the kernel runtime probe treated `codex login status` as the
only Codex authentication check. A user whose Codex selects a custom provider
with `requires_openai_auth = false` and an `env_key` (the reported Amazon
Bedrock bearer-token provider) failed setup and was never offered a Codex
kernel, even with the provider's credential set.
Both checks now read the provider selected in `$CODEX_HOME/config.toml`. For
an explicit provider entry with `requires_openai_auth = false` and an
`env_key`, a nonblank variable counts as local credential availability; the
kernel probe still requires the Codex executable. Every other case keeps the
OpenAI login check, including a missing file, a parse error, a legacy
top-level `profile`, and built-in or undeclared providers, so an unresolved
config never earns readiness. The setup message says this is a local
credential check, not remote authentication or inference.
`AWS_BEARER_TOKEN_BEDROCK` joins the known provider-auth names, so an operator
who names it in `recovery.provider_auth_env_allowlist` can forward it to
managed seats. Nothing is forwarded without that opt-in.
Other Codex config layers (project, system or managed config, requirements,
`-c`, `--profile`) are not resolved.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V8RzdrMhGkYLV4rn96kNMN
* docs: narrow the #194 unresolved-config wording
An unresolved Codex config does not take the new env-key shortcut; readiness
still follows the existing login result. The comment and one test title now say
that, instead of implying OpenRig validates every configuration. No behavior
change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V8RzdrMhGkYLV4rn96kNMN
---------
Co-authored-by: v-openrig-build <v-openrig-build@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Adds a 'Before a security or integration PR' section to CONTRIBUTING.md, a
security question block to the PR template, and a trust-model bullet to
SECURITY.md's scope notes. Additive only; existing scope text is unchanged.
Co-authored-by: v-openrig-build <v-openrig-build@users.noreply.github.com>
* fix(daemon): rig remove never kills another rig's live seat; seat refs skip archived rigs (#174)
An archived duplicate of a live rig shares its name and its seats'
canonical session names.
- `rig remove <archived-rigId> <node>` killed the tmux session by name,
so it could end the live rig's seat. Removal now asks whether another
node owns that session name, using the handover's live-ownership check,
now shared as session-owner.ts. If another node owns it, removal
leaves the session running, doesn't block on or reroute the queue
work addressed to it, and reports the owner as `sessionKeptFor`. The
node itself is still removed.
- Seat refs (`member@rig`) for seat launch, stop, model and handover
resolved rigs by name including archived ones, so they failed with
"matched multiple nodes". They now resolve unarchived rigs only, as
default reads do since migration 042. Other findRigsByName callers
are unchanged.
* fix(daemon): archived rigs don't claim a live seat's session or input target (#174)
Review follow-up. Archiving a rig keeps its bindings, so the archived twin
still names the live seat's session:
- Removing the live node found the archived twin as the "other owner",
kept the session and removed the node's open work. Removal now ignores
owners in archived rigs, so the live node kills its own session and
still refuses open work without --fallback. The handover check is
unchanged.
- The delivery guard needs exactly one match for a target, and the twin's
binding made it two, so delivery to the live seat failed. Unarchived
matches now win, and archived ones count only when nothing else
matches. Exactly one match is still required, so a truly ambiguous
live target still refuses.
- `rig remove` and `rig shrink` human output now says when a session was
kept, and for whom.
---------
Co-authored-by: OpenRig contributors <noreply@openrig.dev>
* fix(daemon): a snapshot refresh probe never leaves its tmux session behind (#188)
`rig down` probes Claude resumability in a private `rigged-refresh-*`
tmux session. Two paths left it running, and every retry added another:
- After a seats' tmux server restart, the new server reuses pane ids,
so a stale seat binding could name the probe's brand-new pane. The
probe's kill then asked the guard to resolve the helper by name, which
threw, and the unawaited write let the throw escape: the snapshot
failed ("Cannot establish managed input target rigged-refresh-...")
and the helper was never killed.
- When the new pane could not be proven, the probe returned failure
without removing the session it had just created.
createProbeSession now refuses to use a probe whose new pane is named by
a managed record, or whose pane it cannot prove. It removes that helper
immediately, by the name this call just created. The probe write is
awaited, so a later refusal returns as a result instead of throwing.
Managed or changed targets are still never killed.
* fix(daemon): roll back an unusable probe only by the id its create returned (#188)
Review follow-up. The allocation rollback killed the helper by name after
awaiting creation and pane observation, which could kill a session that a
managed record had adopted meanwhile, or a different session that had taken
the name.
The probe is now created with `new-session -P -F '#{session_id}'`, and the
rollback kills only that session id. It leaves the helper in place, and
says so, when the id is unknown or a managed record now claims the helper's
name. A binding that names only the reused pane id under another session
name is stale and does not block the rollback. A cleanup failure is
reported in the result. The awaited probe write is unchanged.
* fix(daemon): check the helper's identity in the same tmux command that removes it (#188)
Review follow-up. A tmux session id is unique only for one server's
lifetime. If the server restarted between creating the refresh helper and
rolling it back, `kill-session -t $N` could remove an unrelated session
that the new server gave the same id.
The helper's create now returns its id and creation time. The rollback
runs `if-shell -F -t $N` with a check on the session's name and creation
time, and kills only when both match, in the same tmux command. A
mismatch, or an id that no longer resolves, leaves everything in place
and says so in the result.
---------
Co-authored-by: OpenRig contributors <noreply@openrig.dev>
When a seat's agent runtime has failed or exited, its pane shows a bare
shell, and any text typed there runs as shell commands. The
parked-owner watchdog wake was delivered anyway (`zsh: bad pattern:
[OpenRig`).
The transport now refuses a send when the pane's foreground is a shell
and the seat's runtime is an agent runtime, before anything is written.
It uses the shared shell classifier. A terminal node's shell is its
runtime and still receives text. An unknown or unreadable pane stays
advisory, as before.
The watchdog records the refusal as a failed delivery on the open row
and sends no second wake that episode. A running runtime still gets its
wake.
Co-authored-by: OpenRig contributors <noreply@openrig.dev>
* fix(bundle): resolve bundle paths on the client and launch v2 installs from --target
`rig bundle create/inspect/install` sent spec, output, bundle and target paths
unresolved, so the daemon resolved them against its own cwd. The CLI now
resolves them against the operator's cwd (files are still read on the daemon's
host; nothing is uploaded).
A schema-version-2 bundle apply launched from the temp extraction dir, which is
removed right after instantiate, and ignored targetRoot: members with cwd "."
and the spec/agent refs pointed at a deleted directory. Apply with a targetRoot
now copies the verified extraction into the target and launches from there.
A target with different content at any bundle path is refused with
target_conflict and nothing written; identical files are accepted so a
reinstall works. Plan mode, explicit cwdOverride, legacy v1 bundles and
applies without a targetRoot are unchanged.
Docs: rig-bundle.md install/inspect/--plan/--target/path behavior.
* fix(cli): resolve local rig up --target and align its help with the v2 target contract
On the local route, `rig up <bundle> --target <rel>` now resolves the
target against the client's cwd before sending it, matching `rig bundle
install`. The --target help now states the accepted contract: a v2 bundle
is materialized into the target (default: current directory), relative
member cwds resolve against it, and --cwd still overrides the launch cwd.
Remote --host and topology routing are unchanged: --host sends --target
as given, and it must exist on that host.
Docs: drop the "pass an absolute --target" workaround for the local route.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: v-openrig-build <v-openrig-build@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
#107 refuses to report a fresh pod-aware launch as fresh-primed without the
node's startup context and a runtime adapter. Two fixtures still expected a
successful launch without either, and failed on main:
- launch-node-subset: the single-node launch now seeds the target's startup
context and passes a ready claude-code adapter, and asserts the harness
was launched. The non-target assertions are unchanged.
- sessions-routes: the launch-subset success case seeds both nodes' startup
contexts and opts into route runtime adapters, as startup wires them.
createTestApp gains an opt-in wireRuntimeAdapters option. It is off by
default, so every other route test keeps no route adapters. No production
change.
Co-authored-by: OpenRig contributors <noreply@openrig.dev>
Carries the tests and scope note from #137, which was folded into #135:
- round trip with the stampSelfHostSuffix producer;
- 4-segment names, @local, a near-miss host id, legacy flat names and
virtual refs pass through unchanged;
- the origin-unknown marker and the P18 wire-over-body rule hold after
the strip, for both sender helpers.
The comment records that only the sender is canonicalized: the CLI
cannot fix this at the source, and self-suffixed destinations keep the
C4 refusal. No behavior change.
Co-authored-by: OpenRig contributors <noreply@openrig.dev>
Co-authored-by: Yi-111-a <153097222+Yi-111-a@users.noreply.github.com>
* docs(readme): link the guide, help, Q&A and updates on X near the top
OpenRig is on GitHub's daily trending list. A stranger reading the first
screen found no link to the getting-started guide, the help guide, the Q&A
start-here thread or where updates and demos are posted. One line after the
introduction adds all four.
* docs(readme): offer the next walkthrough to visitors not installing today
For trending visitors who can't stop to install now: a secondary call to action
near the top and again after the getting-started steps, pointing to the
existing openrig.dev/follow signup. Nothing about installing the software is
gated.
* docs(readme): show the demo and a Start here link before the install steps
Trending visitors read the install prerequisites before seeing what OpenRig
looks like. Move the existing 'See it running' recording above the install
section, add a Start here link to the guided first-use path, and keep the
follow line after it. The install section, its requirements and the
disclosure of what OpenRig changes are unchanged and in the same order.
---------
Co-authored-by: v-openrig-build <v-openrig-build@users.noreply.github.com>
In a linked git worktree <cwd>/.git is a file, and passing it to Codex as
--add-dir breaks the Linux sandbox for every command. The fresh launch now
resolves the git metadata directories: an ordinary repository keeps
<cwd>/.git; a .git file resolves through git rev-parse to the worktree git
dir and the common git dir (existing directories only); anything
unresolvable adds nothing. A missing .git keeps the previous argument.
Resume and fork are unchanged.
Reported with a reproduction and the suggested rev-parse approach by
mgall-ibizdigital in #121.
Co-authored-by: orch-advisor <orch-advisor@openrig-build>
Reported-by: Hexgunner69 (issue #116, recognition portion). Includes screen-derived regressions contributed by Aummadour in PR #120; the admitted generic recognizer covers these cases without widening the unguarded legacy branch.
Co-authored-by: OpenRig Contributors <contributors@openrig.dev>
spike/tui-drivability was the Phase-0 agent-drivability harness from
2026-08-02: ten self-contained files importing only each other and Node
built-ins. No test command, config, workflow or published package
reaches it. The Context7 'spike' exclusion stays because
packages/tui/spike still holds a tracked fixture.
Co-authored-by: orch-advisor <orch-advisor@openrig-build>
* ci: add installed baton scenario and regression control to PR tests
* ci: retain healthy return after the durability fault control
* Bind seeded failure to queue observation and propagate pack errors
* test: repair hosted fixture drift and bound temporary IPC
* ci: record a bounded self-process probe before package suites
* test: keep hosted process and transport probes confined
---------
Co-authored-by: dev-driver <dev-driver@openrig-build>
Co-authored-by: dev-qa <dev-qa@openrig-build>
* docs: point agents to the OpenRig help route when OpenRig misbehaves
Add a short pointer to https://www.openrig.dev/help/agents in the
managed start guidance every seat reads, the openrig-skills index (all
three identical copies, digests regenerated) and the README 'For agents'
line. The route sends a symptom to version-matched docs and known
issues and explains how to reach the team. rig --version and rig doctor
help when available, but a broken install is not a reason to skip the
page. No new command.
* docs: one help guide for agents, served by rig context get help
Agent help now lives in one file, docs/reference/help.md. It ships with every
install, so it matches the installed version, and Context7 indexes it. A new
static context pack, help, serves the same file through `rig context get help`
by symlinking to it. The pack build copies the link's target, so installs get a
real file.
The pack generator accepts a static-pack symlink only when it points to a file
under docs/reference/. Any other link fails the build before anything is
written. Tests cover the help pack's bytes and both symlink cases.
The startup guidance, the openrig-skills index (all three copies), the
onboarding capability map, README and SUPPORT now point to `rig context get
help`, falling back to the installed file or openrig.dev/help/agents (the same
text) when `rig` won't run.
* docs(help): say contact options, not a contact form
The website's help page lists email and public Q&A; it has no contact form.
* docs(help): load linked guides on demand; correct the installed fallback path
F1: `rig context get help` served help.md alone, so its links to
getting-started, instance-layout and rig-spec had nothing to resolve against.
A new static pack, reference, symlinks exactly those three files from
docs/reference/. help.md keeps its source-relative links and adds the
`rig context get reference/<file>[#section]` address beside each, so a guide
loads only when it's needed and a help read stays small.
F2: the broken-rig fallback named docs/reference/help.md "in the installed
package". The package build copies these files to daemon/docs/reference/ in
@openrig/cli. Corrected in help.md, the startup guidance and the openrig-skills
index (all three copies, digests regenerated).
Tests: the reference pack ships real files equal to their sources; every
address help.md teaches resolves through the daemon's address route, section
and whole-file; the help preview contains none of the three manuals; a fixture
proves the address check fails on a missing target; source links and the
installed-path text match build-package.sh.
---------
Co-authored-by: v-openrig-build <v-openrig-build@users.noreply.github.com>
.evidence held 23 internal build receipts (test runs, spec drafts, fix-round
notes, 156 KB) committed during 0.5.4 work on 2026-08-26. Nothing in the
product or package uses them. Remove the folder, ignore it at the repo root,
and drop its now-unneeded Context7 exclude.
Co-authored-by: v-openrig-build <v-openrig-build@users.noreply.github.com>
* fix(specs): give the vault-user skill the frontmatter every shipped skill needs (OPR.0.6.1.6)
vault-user/SKILL.md was the only shipped SKILL.md without a name/description
frontmatter block, so it failed the daemon's own parseSkillFrontmatter
contract and external skill syncers rejected it. Adds name + description and
leaves the body byte-identical.
Adds a regression check that runs parseSkillFrontmatter over every SKILL.md
under packages/daemon/specs and packages/daemon/assets, the two skill roots
scripts/build-package.sh copies into the npm artifact.
* test(daemon): correct the shipped-skill check's context-packs scope note (OPR.0.6.1.6)
The projection's default source is packages/daemon/specs/agents/shared/skills,
already inside the checked specs root. Manifest validation is not SKILL.md
frontmatter validation. Comment only; test behavior unchanged.
---------
Co-authored-by: v-openrig-build <v-openrig-build@users.noreply.github.com>
Bring the shipped macOS arm64/Node 24 SQLite caveat (0.5.15 known
compatibility limitation, repeated in the 0.5.17 notes) to the install
line, the Requirements list and Getting Started. State the platform
posture: macOS or Linux; native Windows is not supported yet; WSL2 has
not been tested. The declared Node range is unchanged.
Co-authored-by: v-openrig-build <v-openrig-build@users.noreply.github.com>
* fix(codex): launch Codex seats with --no-daemon when supported (#69)
Before each managed Codex launch, run `codex --help` once, asynchronously, in the
seat's cwd with its launch PATH. Codex usage listing --no-daemon: fresh, fork,
pod resume and restore resume start with `codex --no-daemon`. Codex usage
without it: the command is unchanged. Failure, timeout or non-Codex output:
refuse the launch and send nothing. Suggested manual resume commands are
unchanged.
Reported by @reisalbuquerque.
* Bound Codex capability probe decision time (#69)
---------
Co-authored-by: OpenRig contributors <noreply@openrig.dev>
* release: prepare 0.5.17
Set the version to 0.5.17 and add the changelog entry and release notes for
installing with Bun (#66, #68).
* docs: attribute the Bun verification to the installation fix
---------
Co-authored-by: OpenRig contributors <noreply@openrig.dev>
* fix(package): install without the unpublished @openrig/daemon (#66)
The CLI package declared the private @openrig/daemon in dependencies and
shipped it as a bundled dependency. npm used the bundle, but package managers
that resolve every dependency from the registry, such as Bun, failed with a
404.
The package already ships the daemon under daemon/. The package build now
rewrites the compiled CLI and TUI imports of @openrig/daemon/<subpath> to that
copy, using the daemon's exports map, and fails on any unmapped or unstaged
subpath or any daemon import left afterwards. The dependency, the bundled
copy and their lockfile entries are removed. Source imports and development
resolution are unchanged.
Reported by @drewpayment.
* fix(package): rewrite daemon imports from the syntax tree, not text
Parse each staged file with the TypeScript compiler and rewrite only real
import/export specifiers and literal import()/require() arguments. Text in
comments, strings and templates is no longer changed, and imports that carry
comments or use template literals are no longer missed. Unparseable files and
non-literal module arguments naming the package now fail the build.
* docs: note Bun installation and its postinstall limit
Bun can install the CLI, but OpenRig still runs on Node.js and Bun may
block the package's postinstall check.
* fix(package): run the daemon import rewrite from any checkout path
The direct-run check compared import.meta.url with a hand-built file:// URL,
which never matched when the path needed percent-encoding (spaces, #) or went
through a symlink. The script then exited 0 without rewriting anything. Compare
the decoded module path with the real path of argv[1] instead.
---------
Co-authored-by: OpenRig contributors <noreply@openrig.dev>
* feat(daemon): let a rig choose CLAUDE.local.md for Claude managed blocks (#25)
A rig spec can now declare
managed_blocks:
claude-code: CLAUDE.local.md
so OpenRig writes its managed instruction blocks for Claude Code members to
CLAUDE.local.md instead of a git-tracked CLAUDE.md. The default stays
CLAUDE.md and Codex members stay on AGENTS.md.
- Validation accepts only the claude-code key with CLAUDE.md or
CLAUDE.local.md, and rejects anything else before a member launches.
- The selection is stored on the rig (migration 085) and bound once in
StartupOrchestrator.startNode, so launch, restore replay, relaunch,
continue and added members all write the same file. Startup
guidance_merge, profile managed_block projection and the conflict
target all use it.
- Teardown cleans only the selected file. The other file is never edited,
moved or deleted; blocks already written to CLAUDE.md stay until the
user removes them, as the reference docs explain.
- Export and the bundle rig.yaml rewrite round-trip the field.
Reported and proposed by @hvpaiva.
* docs(daemon): say accurately which paths deliver the Claude managed-block file
Handover launches the successor directly and writes no guidance, so the
startNode and migration comments no longer list it among the delivery paths.
Comment-only change.
* release: prepare 0.5.16
Set the version to 0.5.16 and finalize the changelog and release notes for
the Claude instruction-file option (#25), the advisory portability report (#56)
and the README update (#54).
* docs: keep uncommitted edits safe when removing old Claude blocks
Recommend deleting only the managed sections by hand, and warn that
git restore CLAUDE.md discards every unstaged change to the file. Say that
portability findings never fail the check while operational errors still do.
---------
Co-authored-by: OpenRig contributors <noreply@openrig.dev>
* ci: add a portability report for pull requests
* ci: word the empty portability report as a scan result
---------
Co-authored-by: orch-advisor <orch-advisor@openrig-build>
Delta commits 3c24b69a0 ('docs: make shipped references self-contained')
and d83f84d70 ('fix(s12): classify packaged derived artifacts') modified
packages/daemon/policies/builtin/standard.policy.md and yolo.policy.md
content but did not update the corresponding entries in the packing-
policies test's AUTHORITY_SHA256 constant. Aligns the test's expected
hashes with the actual shipped-content hashes so the T7 byte-equal
check reflects the current shipped surface.
No product code change; no policy-content change; only the test's
expectation is being brought forward to match commits already in the
delta.
v0.5.6 — world-building as context engineering. Three themes: the context
system (mission install, lore routing, taxonomy, verifiable public world
pack, complete skill index, substance gate); continuity (compaction_strategy
policy, apprentice-handover, managed compaction with honest restores,
usage-threshold watchdog primitive); the human layer (destination resolver,
delivery receipts on the row, notification levels + dials, delivery-rules
engine, Slack files as work). Plus operational honesty fixes and the SDLC
conventions component menu.
Three new migrations: 074_context_usage_watchdog +
075_context_usage_watchdog_generation + 076_owner_notification_levels
(head advances 073 to 076); 41 shipped context packs (up from 39).
Bounded version bump across the release-owned files (root package.json,
packages/cli|daemon|ui/package.json, and the matching root+workspace entries
in package-lock.json). packages/tui remains at its independent 0.1.0.
Bounded version bump across the release-owned files (root package.json,
packages/cli|daemon|ui/package.json, and the matching root+workspace entries
in package-lock.json). packages/tui remains at its independent 0.1.0.
The honesty release: classified transport resolution, delivery verification by effect, supported single-seat lifecycle controls, and state-preserving truth surfaces.
Bounded version bump across the release-owned files (root package.json,
packages/cli|daemon|ui/package.json, and the matching root+workspace entries
in package-lock.json). packages/tui remains at its independent 0.1.0.
The slice-09 packaging guard at scripts/check-cli-daemon-freshness.test.mjs
still asserted existence of vendored routes/rig-policy.js and registration
of rigPolicyRoutes + /api/rig-policy after the rig-policy -> rig-mode
rename (verb, API path, identifiers) landed in the 0.5.3 delta. Renames
the assertions to match the shipped route name, so the guard reflects the
current packaging contract.
Companion to the delta's rig-mode rename commit. No product code change.
The 'context release': rig context get/list/profile/recap-write. Address
grammar for context, ask-then-ref discovery, durable seat recap, refocus
resolves refs. Composition by reference, not by copy.
No new migrations (head stays at 071). No CLI surface removals or renames.
Bounded version bump across the release-owned files (root package.json,
packages/cli|daemon|ui/package.json, and the matching root+workspace entries
in package-lock.json). packages/tui remains at its independent 0.1.0.
Formal CHANGELOG [0.5.2] entry + docs/releases/v0.5.2.md file, both
authored from the VM-side closing paper (RELEASE-0.5.2-CLOSING-PAPER.md
+ ADDENDUM-1) — not derived from 74 commit subjects. Content structure
matches prior release-notes shape.
Known-limits section carries all 5.3 inheritances the closing paper
lists as user-visible; internal debt items summarized. Migration
wording precise: no migrations added in this release (v0.5.1 shipped
068-071 with 068+071 as a create/drop-if-exists pair; v0.5.2 stays
at 071).
Companion to the Release 0.5.2 version-bump commit below; GH Release
body will use docs/releases/v0.5.2.md via gh release create
--notes-file at ceremony time.
A test system for the product — self-driving scenarios that prove the
product's behaviour through the same commands a person uses. This
release exercises that system for real and ships the reliability fixes
that fell out of doing so.
The headline is B1: the crash-cart fleet-restore conductor. Type bare
`rig` at a dead daemon and reach a truthful cockpit; one Enter restores
the fleet kernel-first with surviving tmux panes adopted (never
clobbered); the triage carries exact per-seat remediation walkable in
one keystroke; cancel is honest; destructive restore is only offered
on positive evidence that adoption cannot succeed. Ten build rounds,
four independent gates.
Alongside: the daemon event-loop no-longer-hangs-under-load fix (with
its measurement); unattended Claude seat handover (outgoing seat writes
a recap and submits its packet automatically, door-proven); `rig
policy` handles adversarial input honestly (three review rounds);
messages carry their machine-of-origin end-to-end; a detector for a
class of Codex model-config drift that caught real cases on landing;
test-integrity work (runner refuses a dirty tree; hermetic test roots;
flaky fixtures isolated); governance-in-code (plan locks are chosen,
not inherited; wake is opt-in); the container tar-file hang cause
eliminated at the seam.
v0.5.2 contains v0.5.1 in full. One lineage, no divergence. No new
migrations in this release (v0.5.1 was 067 → 071 already; v0.5.2 stays
at 071). API surface preserved.
Formal CHANGELOG entry + docs/releases/v0.5.2.md follow in a small
docs commit on top of this release commit.
Formal CHANGELOG [0.5.1] entry + docs/releases/v0.5.1.md file, both
authored from the intent-vs-delivered trace at
openrig-work/missions/release-0.5.1 (not derived from 362 commit
subjects). Content structure matches prior release-notes shape.
Known-limits wording carried verbatim per the trace: container mode is
scaffolded but not driven (scoped forward); three of the intended
seed scenarios run green, the tenth cannot be built without two
capabilities that do not yet exist (scoped forward).
Companion to the Release 0.5.1 version-bump commit below; GH Release
body will use docs/releases/v0.5.1.md via gh release create
--notes-file at ceremony time.
A test system for the product, plus reliability fixes shipped through it.
Scenarios drive the real product through the same commands a person uses,
so it can prove its own behaviour without a human checking by hand. The
reliability fixes of this window ship through the test system — proving
its value on its first cycle.
v0.5.1 contains v0.5.0 in full. One lineage, no divergence, no dual
maintenance. Migrations are additive-only; existing v0.5.0 databases
upgrade by running rig daemon start on the new daemon.
TEST SYSTEM
- Stub runtime for tests: a fake agent runtime that scenarios can drive.
Lets scenarios exercise the daemon end-to-end without needing a real
Claude or Codex process.
- Scenario format + runner: a text-based format and an executor that
runs scenarios against the real product. Includes a real fix that
surfaced from writing the runner: text-matching against a pane or
transcript could never match its two intended surfaces; now it does.
- Seed scenarios: three seed scenarios that run green end-to-end against
the real product. The intended tenth cannot be built without two
capabilities that do not yet exist in the product (a scenario cannot
mutate the running system's shape; the terminal interface exposes
navigation state rather than fleet inventory). Both capabilities are
scoped forward.
CONTAINER TEST BED — SCAFFOLDING
Scaffolding for container mode ships: argument builders, image-identity
stamping, runbooks, and a unit suite. The runner does not yet execute
in containers — this is scaffolding you will see in the tree but that
no test currently drives. Container-mode execution is scoped forward.
RELIABILITY FIXES SHIPPED THROUGH THE TEST SYSTEM
- Honest render for daemon transport failures: the operator sees the
actual failure rather than a silent no-op or generic error.
- Queue persistence: two fixes ensuring queue records reflect what
actually happened after they were written.
- Cross-machine send regression: found and fixed during verification.
The test system doing its job — proving product behaviour across
machines and catching a regression before release.
SMALL FEATURES
- Per-agent model from spec: each agent's model comes from its spec
rather than a fleet-wide default, so different agents can run on
different providers or models.
- Token usage tracked over time: the daemon records token use so
operators can see trends and predict when a seat is approaching a
usage limit.
- Machine-of-origin on messages: messages carry the machine they came
from, so multi-host coordination is legible.
OTHER
- UI timing hardening: UI tests no longer fail on load-timing flakes.
- Queue records tell the truth after they are written: a class of
wrote-it-and-it-did-not-stick issues closed.
- Unreached-message visibility: show messages that never reached an
agent. On landing, this surfaced nine real handoffs that had been
silently lost since 8 August.
BEHAVIOR + COMPATIBILITY
- Migrations: additive-only.
- API surface preserved: no CLI-command or flag removals or renames
from v0.5.0.
- v0.5.1 contains v0.5.0 in full.
KNOWN LIMITS (carried into release notes verbatim)
- Container mode is scaffolded but not driven. Scoped forward.
- Seed scenarios: three of the intended set run green; the tenth
cannot be built without two capabilities that do not yet exist.
Scoped forward.
Formal CHANGELOG entry + docs/releases/v0.5.1.md follow in a small
docs commit on top of this release commit.
Reconciles origin/main forward-only into the 0.5.1 release branch so the
push is a fast-forward and does not drop the two 2026-08-07 docs commits
(CHANGELOG entries + docs/releases/v0.4.7.md, v0.4.8.md, v0.5.0.md).
Zero-conflict merge — the 362 scrubbed VM commits did not touch
CHANGELOG.md or docs/releases/*.md. Tree-identity of those 362 scrubbed
commits is preserved (first-parent reachable, SHAs unchanged).
Per §6a step 2 standing pattern: every release's public tip back-merges
into the working lineage promptly post-release. Doing it here at the
last honest moment; the release-ceremony discipline says next time this
happens right after each ship, not four days later.