Compare commits

...
6 Commits
Author SHA1 Message Date
dev-driver ddb9d25be7 docs: teach what 0.7.9 changed in the map, references and three help texts (#2571)
The packaged capability map takes in 0.7.9's changes, and its header marker moves to capability-delta-v0.7.9. Two lines 0.7.9 made stale are corrected: an emit now opens or notes an intake row for queue.worker, and any registered person can settle an ask asked of someone else, recorded as themselves. It also covers indeterminate receipts, --closure-required-at, guidance placement for fresh Pi and Codex 0.164.0+ seats with the cases that still use the repository's AGENTS.md, what rig down takes back and keeps, the rebuild for wrapper and symlink Claude launches, and the daemon HTTP client reference. rig-spec's managed_blocks and guidance_merge rows and agent-spec's target row say where guidance normally goes. Help text: rig stream emit names the intake row and how to keep emits passive, rig send --verify says exit 1 can mean unconfirmed, and rig down says what it takes back and what it keeps. Docs and help text only.

(cherry picked from commit 73cf8138e7)
2026-11-05 17:33:38 +00:00
Mike Schwarz 4f257ef1cf docs(capability-map): teach what 0.7.8 added and drop three sentences it made false (#2568)
The packaged capability map, which agents read on every install, drops three sentences 0.7.8 made untrue (mission following isn't included; a community harness gets no approvals; a later hold notice can show a generic reason) and adds what 0.7.8 shipped: the answered runner permission path with rig seat set-permissions-to, rig environment (with its 0.7.8 not-usable caveat), mission following, rig host edit and remove, terminal.tmux_server isolated, claude_mcp: none, doctor's port-owner warning, rig daemon status's pending-start line, the stopped-seat token switch, and the finished-map expiry states for automatic compaction. Docs only.

(cherry picked from commit 781b5fbf64)
2026-11-05 14:48:13 +00:00
dev60-review 3c09264112 docs(release): 0.7.9's permit records open no intake row 2026-11-05 13:00:36 +00:00
Mike Schwarz 6d0bc07df2 fix(daemon): stream intake reads past default-permit records (#2563)
Stream intake reads past OpenRig's record of a default permit (the runner-permission-permit stream item): it advances its position with no intake row, note or wake, on live intake and after a restart, so the record stays pull-only and wakes no one. Ordinary observations, coalescing and the default permit are unchanged. Refs #2361. Refs #2321.

(cherry picked from commit e0a285968d)
2026-11-05 12:59:03 +00:00
dev60-review f3020ad9fb docs(release): describe 0.7.9 2026-11-05 12:00:50 +00:00
dev60-review 2681074bf9 chore(release): prepare 0.7.9 2026-11-05 11:51:59 +00:00
19 changed files with 852 additions and 225 deletions

No files matched your search

+307
View File
@@ -8,6 +8,313 @@ deprecations, and behavioral changes. Breaking changes are called out explicitly
---
## [0.7.9]
OpenRig 0.7.9 follows through on 0.7.8. A person's typed words on a runner
seat's permission ask now reach the agent, each permit OpenRig grants a runner
by default is recorded, and a runner can keep a long, quiet turn reading
working. New stream observations go to the queue worker by default. Fresh Pi
seats, and fresh Codex seats on Codex 0.164.0 or later, can keep OpenRig's
guidance out of your repository's `AGENTS.md`. There are fixes across delivery,
asks, handover, install and recovery. It includes a repair for the OpenShell
worker that wasn't usable in 0.7.8, but that repair hasn't run end to end in our
release testing: see [the OpenShell worker](#the-openshell-worker-hasnt-run-end-to-end-in-079).
### Before you upgrade
- **Restart the daemon after installing:**
```bash
npm install -g @openrig/cli@0.7.9
rig daemon stop && rig daemon start
```
Four migrations (146 to 149) record a Pi seat's guidance file, what OpenRig
last wrote in each managed guidance block, an index for reading a rig's
outbox, and the stream intake position.
([#2432](https://github.com/mvschwarz/openrig/pull/2432), [#2461](https://github.com/mvschwarz/openrig/pull/2461), [#2496](https://github.com/mvschwarz/openrig/pull/2496), [#2321](https://github.com/mvschwarz/openrig/pull/2321))
- **Stream intake is on by default.** Each `rig stream emit` now opens an
intake row for the kernel queue worker (`queue.worker`), or adds a note to the
one still unclaimed. The row wakes the queue worker like any queue row, and
earlier stream history isn't replayed. **To keep 0.7.8's behaviour,** with no
intake rows, run this after installing: `rig config set stream.intake off`.
([#2321](https://github.com/mvschwarz/openrig/pull/2321))
- **The bundled openrig-core plugin advances from 0.1.24 to 0.1.25.** Its
seat-continuity-and-handover skill changed. Managed refresh preserves replaced
files under `$OPENRIG_HOME/backups/managed-refresh/<id>/`; reapply local edits
you still need from that copy. ([#2420](https://github.com/mvschwarz/openrig/pull/2420), [#2554](https://github.com/mvschwarz/openrig/pull/2554))
- **If you run an OpenShell environment, rebuild its guest image** with this
release's helper (`daemon/assets/openshell/README.md`). An older helper
rejects the new startup step, and the error names the rebuild. ([#2418](https://github.com/mvschwarz/openrig/pull/2418))
### Runner seats
- **A person's typed words reach the agent, and the ask says what they do.** A
reply of just yes or no counts as the agent's allow or reject. "Any other
words choose nothing": OpenRig's default permits the request when the agent
offered an allow option, and the words come back whole in the read result as
`said`. Section 7 of `docs/reference/harness-runner-contract.md` lets a runner
queue them for the agent "as an ordinary prompt" after the current turn:
"They're never the answer." A runner that drops them still keeps the contract.
([#2363](https://github.com/mvschwarz/openrig/pull/2363), [#2561](https://github.com/mvschwarz/openrig/pull/2561))
- **Each permit OpenRig grants by default is recorded,** once, as a stream item
from the seat: when no one can be asked, and when an ask is closed without a
choice (a row passed to an agent seat included). The record names the tool
call, why no person answered (for a closed ask, who closed which row and how),
the option the agent got with its kind, and the conversation and request or
the ask. Read the records with `rig stream list --tag runner-permission-permit`,
or `--source <seat>`. A record has no destination, and one that can't be
written doesn't change the permit. Stream intake reads past it, so it opens no
intake row and wakes no one. ([#2361](https://github.com/mvschwarz/openrig/pull/2361), [#2563](https://github.com/mvschwarz/openrig/pull/2563))
- **A working beat keeps a long, quiet turn reading working.** A runner may post
`active` / `liveness` while a prompt is outstanding and its agent is alive
(section 4); one that sends no beats behaves as before. The Pi runner and the
dsh example post it. ([#2370](https://github.com/mvschwarz/openrig/pull/2370), [#2336](https://github.com/mvschwarz/openrig/pull/2336), [#2399](https://github.com/mvschwarz/openrig/pull/2399), [#2388](https://github.com/mvschwarz/openrig/pull/2388))
- **The conformance check** covers the beat, and no longer falsely fails a
correct runner on a busy machine (cases 1, 2 and 4b).
([#2368](https://github.com/mvschwarz/openrig/pull/2368), [#2375](https://github.com/mvschwarz/openrig/pull/2375), [#2388](https://github.com/mvschwarz/openrig/pull/2388), [#2530](https://github.com/mvschwarz/openrig/pull/2530))
- **Section 8 names ACP's `session/resume`,** and the dsh example reads
`DSH_SEAT_BIN` and `DSH_SEAT_HOME`, which a member's `env:` can set, and
reports dsh's real version. ([#2364](https://github.com/mvschwarz/openrig/pull/2364), [#2414](https://github.com/mvschwarz/openrig/pull/2414))
### The OpenShell worker hasn't run end to end in 0.7.9
0.7.9 includes a repair for the OpenShell worker that wasn't usable in 0.7.8,
but it hasn't run end to end in our release testing. Don't rely on this path in
0.7.9. What the repair changes:
- **Startup reaches the guest through a hash-checked first-input file.** Nothing
is typed through the host viewer, so startup text can't reach the host shell
if the viewer exits.
- **Readiness checks the guest's own execution,** and opening the viewer does
one identity check instead of re-checking every projected file.
- **A failed owned delete reports the stage that failed,** and whether its
dispatch is unknown.
- **The README** names the kernel operator `operator-agent@kernel`.
([#2418](https://github.com/mvschwarz/openrig/pull/2418), [#2404](https://github.com/mvschwarz/openrig/pull/2404))
### Assistants connected through a grant service (experimental)
**Experimental and unsupported. Off by default. Use at your own risk.** This path
needs a grant service that you supply and run; ordinary use of OpenRig doesn't
need one. With `rig mcp serve --http --grant-service <url>`, a connected
assistant can now claim, update and park the rows addressed to its own seat, and
see its team with `rig_team`, within the scope its existing tools have. Serving
MCP over HTTP behind your own tunnel "is like opening a port in your firewall."
See `docs/reference/hosted-members.md`. ([#2384](https://github.com/mvschwarz/openrig/pull/2384))
### For client authors
- **New reads:** `GET /api/gateway/participants` (people and the default
person), `GET /api/queue/open-human-updates` (a person's open updates, without
Slack), `GET /api/queue/outbox/list?rig=<rig>` (a rig's messages in one
request), and `GET /api/gateway/slack/channels` and `/users` (a page of public
channels or of people, without the client holding the bot token).
([#2495](https://github.com/mvschwarz/openrig/pull/2495), [#2497](https://github.com/mvschwarz/openrig/pull/2497), [#2496](https://github.com/mvschwarz/openrig/pull/2496), [#2501](https://github.com/mvschwarz/openrig/pull/2501))
- **Settle exactly the decision a person reviewed:** `GET /api/queue/:qitemId`
returns `decisionRevision`, and answer, resolve, approve and deny take an
optional `expectedRevision`. A stale one gets 409 `decision_revision_changed`
with nothing written; a caller that sends none acts as before. ([#2448](https://github.com/mvschwarz/openrig/pull/2448))
- **A client reference:** `docs/reference/daemon-http-clients.md`, with a recipe
for people, open updates and a rig's outbox. ([#2490](https://github.com/mvschwarz/openrig/pull/2490), [#2524](https://github.com/mvschwarz/openrig/pull/2524))
### Seats, delivery and the TUI
- **Delivery:**
- a Claude prompt suggestion isn't taken for a person's draft;
- a long line Claude wraps short of the box is still submitted;
- a guarded wake recovers its own paste instead of holding later deliveries;
- a wake isn't typed into Claude's open Theme picker, or refused because a
reply quotes a permission question;
- a seat that starts, or takes over a seat, with held messages is told about
them; each watchdog job holds one reminder, and handoffs pass a full held
quota;
- a reminder that couldn't reach its seat, for example after a reboot, stays
owed and retries;
- a stalled tmux client is ended after 30 seconds.
([#2435](https://github.com/mvschwarz/openrig/pull/2435), [#2436](https://github.com/mvschwarz/openrig/pull/2436), [#2447](https://github.com/mvschwarz/openrig/pull/2447), [#2514](https://github.com/mvschwarz/openrig/pull/2514), [#2520](https://github.com/mvschwarz/openrig/pull/2520), [#2437](https://github.com/mvschwarz/openrig/pull/2437), [#2442](https://github.com/mvschwarz/openrig/pull/2442), [#2438](https://github.com/mvschwarz/openrig/pull/2438), [#2478](https://github.com/mvschwarz/openrig/pull/2478), [#2452](https://github.com/mvschwarz/openrig/pull/2452))
- **Status and receipts:**
- an idle Claude with newer footer rows reads idle, and a busy Codex with a
quiet window reads working;
- window samples older than their bound no longer decide a seat's activity, so
a seat whose pane can't be read reads unknown, not its last verdict;
- `rig send --verify` and `rig walk` report unconfirmed when they can't see the
result, and uncertain sends and broadcast recipients read `indeterminate`;
- a broadcast that reached nobody doesn't say "Delivered";
- replies to `human@kernel`, `human@host` and people forwarded from another
host get a usable `rig queue create` hint.
([#2470](https://github.com/mvschwarz/openrig/pull/2470), [#2500](https://github.com/mvschwarz/openrig/pull/2500), [#2558](https://github.com/mvschwarz/openrig/pull/2558), [#2509](https://github.com/mvschwarz/openrig/pull/2509), [#2510](https://github.com/mvschwarz/openrig/pull/2510), [#2505](https://github.com/mvschwarz/openrig/pull/2505), [#2507](https://github.com/mvschwarz/openrig/pull/2507), [#2512](https://github.com/mvschwarz/openrig/pull/2512), [#2359](https://github.com/mvschwarz/openrig/pull/2359), [#2493](https://github.com/mvschwarz/openrig/pull/2493), [#2504](https://github.com/mvschwarz/openrig/pull/2504))
- **Guidance and handover:**
- an edit to a culture or startup file reaches the next start, and a block you
edited by hand is kept and named;
- a shared `CLAUDE.md` or `AGENTS.md` stops growing by a line each launch;
- a Claude seat loads openrig-core's skills once;
- a fresh or rebuild handover successor gets its guidance back for every
runtime and a list of its startup files, role first, and is marked when a
required file is missing;
- launched successors can be stopped, restarted and cleaned, and the rebuild
receipt reports its lineage record and the old process truthfully;
- a Claude launched through the native installer's symlink can be rebuilt, a
slow classic resume under a non-exec launch shell no longer lands in
attention, and a `local:` Claude seat gets its activity hooks back on a
contained resume; restores use the seat's launch directory.
([#2441](https://github.com/mvschwarz/openrig/pull/2441), [#2443](https://github.com/mvschwarz/openrig/pull/2443), [#2432](https://github.com/mvschwarz/openrig/pull/2432), [#2461](https://github.com/mvschwarz/openrig/pull/2461), [#2376](https://github.com/mvschwarz/openrig/pull/2376), [#2433](https://github.com/mvschwarz/openrig/pull/2433), [#2457](https://github.com/mvschwarz/openrig/pull/2457), [#2387](https://github.com/mvschwarz/openrig/pull/2387), [#2402](https://github.com/mvschwarz/openrig/pull/2402), [#2474](https://github.com/mvschwarz/openrig/pull/2474), [#2458](https://github.com/mvschwarz/openrig/pull/2458), [#2466](https://github.com/mvschwarz/openrig/pull/2466), [#2440](https://github.com/mvschwarz/openrig/pull/2440), [#2420](https://github.com/mvschwarz/openrig/pull/2420), [#2371](https://github.com/mvschwarz/openrig/pull/2371), [#2518](https://github.com/mvschwarz/openrig/pull/2518), [#2479](https://github.com/mvschwarz/openrig/pull/2479), [#2373](https://github.com/mvschwarz/openrig/pull/2373))
- **Asks and answers:**
- in a shared workspace, any registered person settles another's ask as
themselves, and the first answer wins everywhere;
- a person may have no connector binding, and is then asked on Steering and in
the Feed only;
- a click on a re-parked Slack ask records a new answer and wakes its asker;
- a Slack ask's post shows it was handled once its row settles;
- For You no longer offers Approve on an ask with questions.
([#2386](https://github.com/mvschwarz/openrig/pull/2386), [#2419](https://github.com/mvschwarz/openrig/pull/2419), [#2421](https://github.com/mvschwarz/openrig/pull/2421), [#2472](https://github.com/mvschwarz/openrig/pull/2472), [#2415](https://github.com/mvschwarz/openrig/pull/2415))
- **Compaction:** managed compaction reads a small Claude pane at a readable
size; a manual `rig compact` that times out says whether the restore map was
ready; a Pi seat shows a failed or cancelled compaction as such.
([#2445](https://github.com/mvschwarz/openrig/pull/2445), [#2400](https://github.com/mvschwarz/openrig/pull/2400), [#2537](https://github.com/mvschwarz/openrig/pull/2537))
- **Install, hosts and setup:**
- the openrig-upgrade skill backs up without the sqlite3 CLI when Node has
`node:sqlite`;
- preflight warns when a Codex seat's login shell can't find `rig`;
- the experiment config can be written where Node has no `process.getuid`;
- bundle unpack restores archived owner bits;
- the Pi Node check is bounded, and `rig doctor` no longer hangs on a port held
by a listener that never replies;
- a scoped launch on tmux 3.4 keeps its default command;
- the kernel's template survives a daemon restart;
- `rig host ls` reads your ssh config with `ssh -G`, which runs any
`Match exec` command in it, and `rig host pair` names the bearer token the
target needs;
- the install docs describe the installer's step-4 outcome and the first
kernel's provider choice.
([#2394](https://github.com/mvschwarz/openrig/pull/2394), [#2522](https://github.com/mvschwarz/openrig/pull/2522), [#2449](https://github.com/mvschwarz/openrig/pull/2449), [#2475](https://github.com/mvschwarz/openrig/pull/2475), [#2451](https://github.com/mvschwarz/openrig/pull/2451), [#2549](https://github.com/mvschwarz/openrig/pull/2549), [#2557](https://github.com/mvschwarz/openrig/pull/2557), [#2471](https://github.com/mvschwarz/openrig/pull/2471), [#2488](https://github.com/mvschwarz/openrig/pull/2488), [#2491](https://github.com/mvschwarz/openrig/pull/2491), [#2551](https://github.com/mvschwarz/openrig/pull/2551), [#2560](https://github.com/mvschwarz/openrig/pull/2560))
- **The CLI:**
- unknown `--state`, `--kind` and `--priority` values warn and still run;
- `rig workspace validate` prints a readable refusal, and `rig seat set-runtime`
says what to do for a seat with no pane;
- a cut `rig queue show` body says how to get the rest;
- `rig queue update --closure-required-at` moves a claimed row's deadline;
- `rig seat set-typing-guard` applies from an ordinary terminal, recorded as
unattributed;
- `rig scope` tells shipped, superseded and closed slices apart, flags a slice
name two missions share, and skips fenced examples;
- `rig context add <url>` installs into a new nested name;
- a retained stuck-sweep recovery names its current reason, and a refused
context-pressure setting says what it compared.
([#2357](https://github.com/mvschwarz/openrig/pull/2357), [#2365](https://github.com/mvschwarz/openrig/pull/2365), [#2358](https://github.com/mvschwarz/openrig/pull/2358), [#2367](https://github.com/mvschwarz/openrig/pull/2367), [#2383](https://github.com/mvschwarz/openrig/pull/2383), [#2422](https://github.com/mvschwarz/openrig/pull/2422), [#2460](https://github.com/mvschwarz/openrig/pull/2460), [#2351](https://github.com/mvschwarz/openrig/pull/2351), [#2434](https://github.com/mvschwarz/openrig/pull/2434), [#2492](https://github.com/mvschwarz/openrig/pull/2492), [#2395](https://github.com/mvschwarz/openrig/pull/2395), [#2546](https://github.com/mvschwarz/openrig/pull/2546), [#2552](https://github.com/mvschwarz/openrig/pull/2552), [#2553](https://github.com/mvschwarz/openrig/pull/2553))
- **Launch options:** `specs.builtins_visible: false` leaves built-in specs out
of the inventory (`rig specs ls --include-builtins` still lists them).
`plugin_channels` passes a Claude member's own plugin channels to
`--channels`; beside `claude_mcp: none` it warns that no channel could
register, and the member still launches. We haven't run it with a real channel
plugin. ([#2379](https://github.com/mvschwarz/openrig/pull/2379), [#2439](https://github.com/mvschwarz/openrig/pull/2439))
- **The TUI and web UI:**
- body text reads on a light terminal, and the selected row shows in
16-colour mode;
- JUST FINISHED and RECENTLY FINISHED show what closed last, in the configured
time zone, even on a busy fleet;
- a fresh seat opened in tiles is sized to its tile, and grid fillers open your
login shell;
- in the web UI, the Topology filter no longer hangs, and the kernel card and
maintenance notice display correctly.
([#2499](https://github.com/mvschwarz/openrig/pull/2499), [#2506](https://github.com/mvschwarz/openrig/pull/2506), [#2392](https://github.com/mvschwarz/openrig/pull/2392), [#2393](https://github.com/mvschwarz/openrig/pull/2393), [#2459](https://github.com/mvschwarz/openrig/pull/2459), [#2464](https://github.com/mvschwarz/openrig/pull/2464), [#2465](https://github.com/mvschwarz/openrig/pull/2465), [#2411](https://github.com/mvschwarz/openrig/pull/2411), [#2413](https://github.com/mvschwarz/openrig/pull/2413), [#2412](https://github.com/mvschwarz/openrig/pull/2412))
- **Also:**
- transcript search scans a long line once instead of once per chunk, with the
same results;
- a Slack transport error has the bot token removed (see Known limits);
- the hook-review check recognises a kernel Codex pane that runs under a shell;
- the reference docs cover a Slack private-channel route, missed-message
recovery, checked stops and slice status words, the spec scan, bundle hashes
and the Pi selector path.
([#2397](https://github.com/mvschwarz/openrig/pull/2397), [#2503](https://github.com/mvschwarz/openrig/pull/2503), [#2416](https://github.com/mvschwarz/openrig/pull/2416), [#2494](https://github.com/mvschwarz/openrig/pull/2494), [#2401](https://github.com/mvschwarz/openrig/pull/2401), [#2489](https://github.com/mvschwarz/openrig/pull/2489), [#2462](https://github.com/mvschwarz/openrig/pull/2462), [#2543](https://github.com/mvschwarz/openrig/pull/2543), [#2477](https://github.com/mvschwarz/openrig/pull/2477), [#2369](https://github.com/mvschwarz/openrig/pull/2369), [#2403](https://github.com/mvschwarz/openrig/pull/2403))
### Behaviour that changed
- **Stream intake is on by default** (above). `off` keeps 0.7.8's behaviour.
- **Fresh Pi seats, and fresh Codex seats on 0.164.0 or later, get OpenRig's
guidance outside the repository `AGENTS.md`:** Pi in its own agent folder
(unless that file or folder is a link), Codex as launch developer
instructions. Codex still uses the repository file for a resume, fork,
restore or handover successor, an older or unknown version, or a config layer
that sets `developer_instructions` or can't be read. If only your shell
startup sets `CODEX_HOME`, OpenRig reads `~/.codex`, so a
`developer_instructions` in your home is replaced for that session; declare
the same absolute path as `CODEX_HOME` in the member's or rig's `env:` to have
it checked.
- **`rig down`** removes OpenRig's own additions from a working folder once no
live seat uses them.
- **After upgrading, the Slack connector edits asks that ended earlier** to show
they were handled, at Slack's edit rate: about an hour for 450 asks.
`rig slack status` shows pending edits.
- **A later answer to a settled ask** gets 409 `qitem_already_terminal` and
records nothing.
- **`rig send --verify`** reports unconfirmed and exits 1, not delivered, when it
can't see what its Enter did.
- **`rig export` with no `-o`** writes `rig-export.yaml` beside an existing
`rig.yaml` instead of replacing it. ([#2356](https://github.com/mvschwarz/openrig/pull/2356))
- **`rig workspace validate`** exits 1 for malformed optional frontmatter.
([#2396](https://github.com/mvschwarz/openrig/pull/2396))
- **An active rig name that matches more than one rig** makes `rig env down`,
`status` and `logs` list the matching rig IDs and stop, and
`rig doctor --spec` report conformance as unknown, instead of using the first
match. ([#2548](https://github.com/mvschwarz/openrig/pull/2548), [#2550](https://github.com/mvschwarz/openrig/pull/2550))
- **New default kernels** start in `<workspace.root>/kernel`. Existing kernels
stay where they are; `docs/reference/agent-startup-guide.md` describes the
optional move.
### Known limits
- **OpenShell:** the repair hasn't run end to end in our release testing
(above). 0.7.8's requirements, cost note and cleanup limits still apply,
including "Never grant the guest access to the daemon's main API port
(normally 7433)." A same-generation reconciled launch opens no viewer, so run
`rig environment connect` yourself; non-text startup actions aren't supported
on environment seats.
- **Grant-service assistants aren't woken:** "an item stays queued until its
receiver claims it."
- **Open updates for a person** read a 1,001-row window, so a page can come back
short, marked `truncated`.
- **Slack:** a private channel needs the documented route, which we don't test,
and direct messages aren't supported. The channel and people reads haven't
been run against a live Slack workspace. Redaction of the bot token from a
transport error can still leave a malformed token fragment shorter than six
characters, so review an error before sharing it.
- **SSH host status** reads `unknown` behind a ProxyJump or ProxyCommand; that
isn't proof the connection is broken.
- **Shared asks:** "a click's option written before its answer row … can stand
beside or inside the closer's answer without the first clicker's authorship."
- **Permit records** last as long as stream retention keeps them. A permit a
runner makes on its own, when it can't reach OpenRig, is in its pane only. For
an ask opened before 0.7.9, a record may omit the option's kind.
- **Existing seats** pick up Codex launch instructions and the single skill load
at their next fresh launch. A `CLAUDE.md` or `AGENTS.md` that already grew
isn't trimmed.
- **Codex hooks after an upgrade:** Codex sessions kept open across the 0.7.8
upgrade showed "Hook failed — plugin contents changed; hook approval
required". The 0.7.9 behaviour, and hook recovery after a relaunch, haven't
been verified.
- **With Node outside the login shell's PATH,** Codex seats can still report
`rig: command not found`; preflight now warns, and the getting-started guide
gives the `~/.profile` fix.
- **Fixed or changed from 0.7.8's limits:** the dsh example's `env:` names, the
conformance check's false failures, a person's typed words, the README's
operator address, a rebuild behind a symlink, the rebuild receipt, a click on
a re-parked ask, and recording a permit made when no one can be asked: a
person's answer is recorded on the ask, and a default permit as the stream
item above.
- **The other 0.7.8 limits, and the 0.7.7 and 0.7.6 limits, below still apply.**
### Credits
Thanks to @porterchild, @Mohsintl, @Nikhi00718, @oodadoudou, @AdityaxDeore,
@w4rc0n, @genius8267, @Aschenbath, @DeryFerd, @mohitdixit02, @Vaishnavi220506,
@Nikhila-Dhevi-R, @natefaerber, @dajiaohuang, @Aummadour, @KM199 and @Totopo27
for merged contributions, and to @todie and @seanmcn, whose fork work is carried
in [#2439](https://github.com/mvschwarz/openrig/pull/2439) and [#2400](https://github.com/mvschwarz/openrig/pull/2400). Thanks to @ziadeh, @billbenetti, @eisenbergjerry,
@ch-davidriott, @puniserv, @oneandonlydean, @sabnanikl-dev, @gifflen,
@pvl-agent, @EZotoff, @adam-nectar, @bpamiri, @tgrundtvig, @aanestesia,
@AdamWhitehurst and @eisenrigbot for reports carried into this release. Thanks
also to @z4cc, @hitianqi, @jimallen, @m3ac-AllbrittenJ and @lkraider for reports
and field feedback.
## [0.7.8]
Published 2026-11-05 as `@openrig/[email protected]`, built from the commit tagged
+5 -5
View File
@@ -26,9 +26,9 @@ use:
```sh
# Claude Code
npx skills add https://github.com/mvschwarz/openrig/tree/v0.7.5/skills/rigs --skill rigs --agent claude-code
npx skills add https://github.com/mvschwarz/openrig/tree/v0.7.9/skills/rigs --skill rigs --agent claude-code
# Codex
npx skills add https://github.com/mvschwarz/openrig/tree/v0.7.5/skills/rigs --skill rigs --agent codex
npx skills add https://github.com/mvschwarz/openrig/tree/v0.7.9/skills/rigs --skill rigs --agent codex
```
Then type `/rigs` in Claude Code, or `$rigs` in Codex, and say what you want built. The skill has your agent tell
@@ -68,11 +68,11 @@ Requires Node.js 22 or 24 and tmux, on macOS or Linux, and one working Claude Co
### Install it yourself
This runs OpenRig's install script from the `v0.7.5` release. It checks Node.js and npm, installs the latest published `@openrig/cli` with `npm install -g`, runs the Node.js and SQLite check, then runs `rig setup --dry-run` and `rig setup`. The first line prints that plan and changes nothing:
This runs OpenRig's install script from the `v0.7.9` release. It checks Node.js and npm, installs the latest published `@openrig/cli` with `npm install -g`, runs the Node.js and SQLite check, then runs `rig setup --dry-run` and `rig setup`. The first line prints that plan and changes nothing:
```bash
curl -fsSL https://raw.githubusercontent.com/mvschwarz/openrig/v0.7.5/scripts/install.sh | sh -s -- --dry-run
curl -fsSL https://raw.githubusercontent.com/mvschwarz/openrig/v0.7.5/scripts/install.sh | sh
curl -fsSL https://raw.githubusercontent.com/mvschwarz/openrig/v0.7.9/scripts/install.sh | sh -s -- --dry-run
curl -fsSL https://raw.githubusercontent.com/mvschwarz/openrig/v0.7.9/scripts/install.sh | sh
```
If the last step isn't ready, the installer reports `INSTALLED, SETUP NOT COMPLETE` and points to the details from `rig setup` above. Follow that output and any reported Fix, then rerun `rig setup`; the CLI does not need reinstalling. If the only remaining failures are provider sign-ins, sign in to each selected provider as below and continue.
+5
View File
@@ -462,6 +462,11 @@ do not establish connectivity to a live peer.
| `rig stream read` | — | `--limit <n>`<br>`--reader <session>`<br>`--json` |
| `rig stream act <streamItemId>` | — | `--work <qitemId>`<br>`--decline <reason>`<br>`--duplicate <id>`<br>`--reader <session>`<br>`--json` |
Stream intake: the `stream.intake` setting (default `queue.worker`, or a canonical `<seat>@<rig>`) receives each emitted item.
It opens an intake row for that seat, which wakes it, or adds a note to that seat's intake row still pending; a `critical`
or interrupting item makes the row urgent. `rig config set stream.intake off` keeps emits passive. A runner's
default-permit record (tag `runner-permission-permit`) opens no row.
### queue
| Invocation | Aliases | Declared options |
+1 -1
View File
@@ -440,7 +440,7 @@ guidance:
|-------|------|----------|---------|-------------|
| `id` | string | yes | — | Resource identifier. A `rig-role` guidance resource is never merged; deliver a role with a `send_text` startup file. |
| `path` | string | yes | — | Relative path to the guidance file. |
| `target` | string | no | — | Parsed but not used: the runtime decides the file. Claude merges into the rig's `managed_blocks` file (`CLAUDE.md` by default, or `CLAUDE.local.md`); Codex, Pi and OMP merge into `<cwd>/AGENTS.md`. |
| `target` | string | no | — | Parsed but not used: the runtime decides the file. Claude merges into the rig's `managed_blocks` file (`CLAUDE.md` by default, or `CLAUDE.local.md`); Codex, Pi and OMP merge into `<cwd>/AGENTS.md`, except that a fresh Pi seat normally uses its own agent folder's `AGENTS.md` and a fresh Codex 0.164.0+ seat normally gets launch developer instructions; the conditions are in [Codex guidance at launch](agent-startup-guide.md#codex-guidance-at-launch) and [Runtime Differences](agent-startup-guide.md#runtime-differences). |
| `merge` | string | no | `managed_block` | Only `managed_block` is applied. Any other value, such as `append`, merges nothing. |
---
+2 -2
View File
@@ -201,11 +201,11 @@ You can also read [`skills/rigs/SKILL.md`](../../skills/rigs/SKILL.md) directly.
script will do and changes nothing:
```sh
curl -fsSL https://raw.githubusercontent.com/mvschwarz/openrig/v0.7.5/scripts/install.sh | sh -s -- --dry-run
curl -fsSL https://raw.githubusercontent.com/mvschwarz/openrig/v0.7.9/scripts/install.sh | sh -s -- --dry-run
```
The same command without `--dry-run`,
`curl -fsSL https://raw.githubusercontent.com/mvschwarz/openrig/v0.7.5/scripts/install.sh | sh`, installs the
`curl -fsSL https://raw.githubusercontent.com/mvschwarz/openrig/v0.7.9/scripts/install.sh | sh`, installs the
latest published `@openrig/cli` with `npm install -g`,
runs the Node.js and SQLite check, then `rig setup --dry-run` and `rig setup`. `rig setup` checks both Claude Code and
Codex and may install a missing one, as described below. A failed step prints
+1 -1
View File
@@ -17,7 +17,7 @@ npx skills add https://github.com/mvschwarz/openrig/tree/<release-tag>/skills/ri
npx skills add https://github.com/mvschwarz/openrig/tree/<release-tag>/skills/rigs --skill rigs --agent codex
```
For a new install, use the latest release's tag, for example `v0.7.5`. If OpenRig is already installed, use the tag
For a new install, use the latest release's tag, for example `v0.7.9`. If OpenRig is already installed, use the tag
that matches `rig --version`, or upgrade first. If the command fails with `npx: command not found`, Node.js isn't
installed or isn't on your PATH yet (see [What you need](#what-you-need)). Then type `/rigs` in Claude Code, or `$rigs` in Codex, and say what
you want built.
+2 -2
View File
@@ -180,7 +180,7 @@ edges:
| `non_interruptive` | boolean | no | — | Pod-aware rig launch-warning default. With full-bypass Claude/Codex seats, true accepts or hides supported harness warnings using launch flags. Explicit CLI choices override this field; this field overrides the machine default. It does not grant permissions, change native settings files, or bypass login. |
| `permission_policy` | string | no | — | Permission policy attached to the rig. Either a built-in (`builtin:locked`, `builtin:standard`, `builtin:open`, `builtin:yolo`, `builtin:auto`) or a safe relative path to a custom policy file (resolved from this spec's directory; no `..`, no absolute, no empty segments, each segment `[A-Za-z0-9][A-Za-z0-9._-]*`), or `none`, a recorded choice of the floor. Absent uses the default floor with the scoped team launch allowances described below. A bare built-in name such as `yolo` is refused ("use 'builtin:yolo'"), and so is an explicit `null`. A custom file that is missing, unreadable or invalid isn't a validation error: it resolves to the floor, and preflight warns. A member may set its own `permission_policy` (not on a terminal member), which takes precedence over the rig-level one; there is no pod level. See "Attaching a permission policy" below. |
| `env` | map | no | — | Launch environment for every seat in the rig: `NAME: value` pairs. A member's own `env` overrides it name by name. Names match `^[A-Z_][A-Z0-9_]*$`, names starting `OPENRIG_` are reserved, and values must be non-empty strings (quote numbers and booleans). Values are not secret. See "Launch environment" below. |
| `managed_blocks` | map | no | `CLAUDE.md` | File that receives OpenRig's managed instruction blocks for Claude Code members. Only the `claude-code` key is accepted, with `CLAUDE.md` or `CLAUDE.local.md`. Codex, Pi and OMP members use `AGENTS.md`. See "Choosing the Claude instruction file" below. |
| `managed_blocks` | map | no | `CLAUDE.md` | File that receives OpenRig's managed instruction blocks for Claude Code members. Only the `claude-code` key is accepted, with `CLAUDE.md` or `CLAUDE.local.md`. Codex, Pi and OMP members use `AGENTS.md`, except that a fresh Pi seat normally uses its own agent folder's `AGENTS.md` and a fresh Codex 0.164.0+ seat normally gets launch developer instructions; a link, a conflicting or unreadable Codex config layer, or a resume keeps the repository file. See [Codex guidance at launch](agent-startup-guide.md#codex-guidance-at-launch) and [Runtime Differences](agent-startup-guide.md#runtime-differences). See "Choosing the Claude instruction file" below. |
| `workspace` | object | no | — | The rig's workspace: `workspace_root` (required), `repos[]` of `{name, path, kind}` with `kind` one of `user`, `project`, `knowledge`, `lab` or `delivery` and unique names, an optional `default_repo` naming one of them, and an optional `knowledge_root`. Relative repo paths resolve against `workspace_root`. |
| `docs` | Doc[] | no | — | Documentation files that should travel with the rig. Included in rig bundles. Each entry has a `path` field (safe relative path). The engine does not consume these — they are for humans and agents setting up the environment before launch. |
| `startup` | StartupBlock | no | — | Rig-level startup files and actions. Applied to all members via the startup layering model. |
@@ -1077,7 +1077,7 @@ file; missing optional files do not trigger that warning.
| Hint | Behavior |
|------|----------|
| `auto` | System chooses based on file type and context. |
| `guidance_merge` | Merged as a managed block into the runtime's guidance file: for Claude, the `managed_blocks` file (`CLAUDE.md` or `CLAUDE.local.md`); for Codex, Pi and OMP, `AGENTS.md`. Delivered before harness boot. |
| `guidance_merge` | Merged as a managed block into the runtime's guidance file: for Claude, the `managed_blocks` file (`CLAUDE.md` or `CLAUDE.local.md`); for Codex, Pi and OMP, `AGENTS.md`, except that a fresh Pi seat normally uses its own agent folder's and a fresh Codex 0.164.0+ seat normally gets launch developer instructions (the conditions are in [Codex guidance at launch](agent-startup-guide.md#codex-guidance-at-launch) and [Runtime Differences](agent-startup-guide.md#runtime-differences)). Delivered before harness boot. |
| `skill_install` | Installed as a skill in the runtime's skill directory. Delivered before harness boot. |
| `send_text` | Sent as text to the agent's terminal after the harness is ready. Requires the agent TUI to be active. |
+314 -178
View File
@@ -1,192 +1,328 @@
# What changed in OpenRig 0.7.1
# What changed in OpenRig 0.7.9
This note is for an agent on an install that was just upgraded to 0.7.1. It lists only what behaves differently from
0.7.0 and what to do about it. For everything OpenRig can do, read the capability map:
`rig context get onboarding-width/public-what-you-can-do.md`. Each release replaces this file, so
`rig context get reference/whats-new.md` describes the installed version once the upgraded daemon is running. Without
a running daemon, read `$OPENRIG_HOME/reference/whats-new.md`. The daemon refreshes that copy when it starts, so right
after an upgrade it can still hold the previous version's note.
This note is for an agent on an install that was just upgraded to 0.7.9. OpenRig
0.7.9 records default runner permits, passes a person's typed words to the
agent, turns stream intake on by default, and moves guidance for fresh Pi seats,
and fresh Codex seats on 0.164.0 or later, out of the repository's `AGENTS.md`.
If you upgraded from 0.7.7 or
earlier, also read "What changed in OpenRig 0.7.8" below. For the full
capability map, read `rig context get onboarding-width/public-what-you-can-do.md`.
After the upgraded daemon starts, `rig context get reference/whats-new.md`
describes this version. Without a running daemon, read the packaged
`daemon/docs/reference/whats-new.md`; `$OPENRIG_HOME/reference/whats-new.md` can
still be the previous copy until startup.
## After the upgrade
- **Restart the daemon** (`rig daemon stop`, then `rig daemon start`). Eight migrations run on the first start (124, 125,
127, 128, 129, 130, 131 and 135). Nine settings are added: `policies.claude_compaction.preparation_root` (unset),
`skills.router_line.enabled` (off), `decisions.enabled` (false), `decisions.consumers`, `decisions.template_dir`,
`retention.stream_days` (30), `retention.stream_max_items` (20000), `provider.selected` (empty) and
`daemon.db_conflict` (`warn`). None is removed.
- **Stream retention is on.** Stream items emitted after the upgrade are deleted after 30 days, or beyond the newest
20,000, unless a record names them (a classification, duplicate link, attempt or disposition). History from before the
upgrade is kept. Raise `retention.stream_days` or `retention.stream_max_items` to keep more.
- **The bundled openrig-core plugin goes from 0.1.17 to 0.1.18.** Startup replaces the installed plugin's changed files:
its Claude hook definitions, three hook scripts and the pre-compaction hook (so hooks start on a seat whose PATH has
no Node), and the `openrig-user`, `messaging-the-human` and `forming-an-openrig-mental-model` skills. As in
0.7.0, it first copies each file it replaces under `$OPENRIG_HOME/backups/managed-refresh/<id>/`, edited or not, and
logs `[plugin-vendor] preserved previous contents of <file> at <copy> before managed refresh`. A copy doesn't mean
anyone edited the file; if someone did, reapply the change from the copy.
- **Claude seats need Claude Code 2.1.280 or newer to see their team skills,** which now arrive as a per-seat plugin
through `CLAUDE_CODE_PLUGIN_DIRS` (below). OpenRig doesn't check the version. A running seat gets the new layout at its
next launch, restore or handover.
- **If a worktree's `.claude/settings.local.json` says `"defaultMode": "acceptEdits"` and the person didn't choose it,**
an earlier OpenRig wrote it when it added or freshly launched a managed Claude seat. 0.7.1 no longer writes it but
doesn't repair it; ask the person which mode they want and set it once.
- **Restart the daemon** to load the new code: `rig daemon stop`, then
`rig daemon start`.
- **Migrations 146 to 149** record a Pi seat's guidance file, what OpenRig last
wrote in each managed guidance block, an index for reading a rig's outbox,
and the stream intake position.
- **Tell the person that stream intake is on.** Each `rig stream emit` now opens
an intake row for the kernel queue worker (`queue.worker`), or adds a note to
the one still unclaimed, and the row wakes the queue worker like any queue row.
Earlier stream history isn't replayed. If they want 0.7.8's behaviour, with no
intake rows, run `rig config set stream.intake off`.
- **The bundled openrig-core plugin goes from 0.1.24 to 0.1.25.** Its
seat-continuity-and-handover skill changed. Managed refresh preserves replaced
files under `$OPENRIG_HOME/backups/managed-refresh/<id>/`; reapply local edits
you still need from the preserved copy.
- **Where seat guidance goes now:**
- a fresh Pi seat gets OpenRig's guidance in its own agent folder's
`AGENTS.md`, unless that file or folder is a link;
- a fresh Codex seat on Codex 0.164.0 or later gets it as launch developer
instructions, not in the repository `AGENTS.md`, when no config layer
OpenRig reads sets `developer_instructions`;
- a Codex resume, fork, restore or handover successor, an older or unknown
Codex, and a config layer that sets `developer_instructions` or can't be read
still use the repository `AGENTS.md`;
- if only the person's shell startup sets `CODEX_HOME`, OpenRig reads
`~/.codex`, so a `developer_instructions` set in their home is replaced for
that session. Declare the same absolute path as `CODEX_HOME` in the member's
or rig's `env:` to have it checked. `docs/reference/agent-startup-guide.md`
("Codex guidance at launch") is the rule;
- a managed block a person edited by hand is kept and named
(`guidance_block_edited`), not replaced. Read the warning before you change
the block.
- **Slack:** on the first start after upgrading, the connector edits the asks
that ended earlier to show they were handled, at Slack's edit rate: about an
hour for 450 asks. `rig slack status` shows pending edits.
- **OpenShell:** rebuild a guest image built for 0.7.8 with this release's
helper; an older helper rejects the new startup step, and the error names the
rebuild.
## New things you can do
## Runner seats
- **Choose how much automatic input reaches a seat:** `rig seat set-typing-guard <seat> --mode <mode> --reason "…"`, where
`<mode>` adds `openrig-urgent` (OpenRig's own notices and anything urgent) and `urgent-only` between `off` and `hold`.
Mark a message urgent with `rig send --urgent`, or create the row at priority `urgent` or `critical`. Held input stays
readable (`rig seat held-messages`) and is never replayed.
- **Choose how far OpenRig and peers may act on a seat:** `rig seat set-terminal-policy <seat> --preset
no-peer-interventions|help-within-instructions|full-control --reason "…"`, with `--helper <addresses…>`,
`--rig-default` (seats added later), and exceptions such as `--peer-compaction deny` or `--openrig-model allow`.
Managed compaction and saved model or effort changes are checked; a denial is `terminal_policy_refused`. Other
interventions are guidance only.
- **Read a seat's planned permissions:** `rig seat status <seat>` shows what its next fresh managed launch will pass and
where each part comes from, and its terminal policy. `rig setup --router-line yes|no` decides whether OpenRig adds its
one line to shared guidance files.
- **Let a person choose whether every ask reaches Slack:** `rig gateway human set <id> slack-subscription
every-ask|urgent-only|unset`. Every ask is on the TUI's Feed either way. Under `urgent-only`, only an ask
created or handed off with `--alert` posts. An ask marked `--alert` stands out in Slack (an interrupt with a mention)
under either setting, so use it only for something only they can do, or an approval whose wait costs now.
- **Give one Claude member its own setup** in the rig spec: `agent_file`, `append_system_prompt_file`, `plugin_dirs` and
`advisor` (an Advisor model). They're copied into that seat's storage, never the project.
- **See a seat's own skills:** `rig skill loadout --seat <session|self>`.
- **Reach the team from an assistant without a shell:** the MCP server has 34 tools, including `rig_whoami`,
`rig_restore_status`, `rig_team`, `rig_telemetry_events`, following a handoff, reading a finished item's artifact and
deciding inside a person's decision room. `rig mcp serve --http` serves them over HTTP; `rig mcp token add <name>
--seat <session>` gives each caller its own seat.
- **Hand a row's task to an outside agent:** `rig acp run <row>` for an ACP agent (Hermes, OpenClaw).
`docs/reference/a2a-agents.md` covers A2A agents.
- **Run a community harness that names, reports or reopens its conversations:** preset fields `launch_result`, `resume`,
`names_conversation` and `startup_assisted`, with examples for Cursor, Hermes and OpenClaw under
`docs/reference/community-harness-examples/`.
- **Change one edge on a running rig:** `rig edge add <rig> <from> <to> --kind <kind>` and `rig edge remove <rig>
<edge-id>` (`--plan`, `--json`). A `delegates_to` change says when it moves who work routes to.
- **Run one spec as several rigs:** `rig up <spec> --name <rig>`.
- **Retune a watchdog in place:** `rig watchdog update <jobId> [--interval-seconds n] [--message text] [--spec path]`
keeps its id and attachments. The context watch takes `--threshold-tokens` or `--threshold-percent`.
- **Put restore maps where seats may write:** `rig config set policies.claude_compaction.preparation_root <abs path>`.
- **Pick a Herdr session:** `rig terminal open <view> --provider herdr --session <name>`.
- **Close a row with its artifact:** `rig queue update <id> --state done --evidence-ref <path>`.
- **File and read subscription move reports:** `rig provider moves report` and `rig provider moves`. `--host <id>` and
`--codex-home <dir>` on `rig auth status|list|switch` and `rig seat restart` act on another machine's Codex home.
- **Route stream observations with typed decisions (opt-in, off by default):** once `decisions.enabled` is true,
`decisions.consumers` includes `stream` and the intake seat has `OPENROUTER_API_KEY`, `rig project wake --decide`
gives each eligible stream observation one bounded typed decision during that seat's wake: create a row for a supplied
owner, join supplied work, or record a duplicate or no action. Anything uncertain, refused or failed goes to the
intake seat as its own disposition, and the model isn't asked again. `rig project decisions status` shows the state
without making a call. Without `--decide`, or with decisions off, nothing changes; turning decisions off no longer
strands an accepted create or join whose effect hadn't landed.
- **Read the stream as a subscriber:** `rig stream subscribe <seat>` (`--tags`, `--type`; re-subscribing keeps the
place), then `rig stream read` returns the next bounded page for the calling seat (at most 20 items and 64 KiB, with
`more`, `unread` and any `gap`). `rig stream subscriptions` lists readers; `rig stream unsubscribe <seat>` stops one.
`rig stream show <id>` says what became of an observation.
- **Keep the kernel to the harnesses you chose:** `rig setup --providers <list>` is now saved as `provider.selected`, and
the kernel starts seats only for those.
- **Stop a second daemon on a live database:** `rig config set daemon.db_conflict stop` refuses a start whose database a
matching live daemon already holds (the default `warn` only warns). `rig daemon start --allow-db-conflict` allows one
deliberate start.
Section 7 of `docs/reference/harness-runner-contract.md` is the rule for
permissions, and section 4 for activity. Read them before writing or changing a
runner.
- **Typed words:** a reply of just yes or no counts as the agent's allow or
reject. Any other words choose nothing: OpenRig's default permits the request
when the agent offered an allow option, and the read result carries the words
whole as `said`. A runner may queue them for the agent as an ordinary prompt
after the current turn, never as the answer; one that drops them still keeps
the contract. The ask now tells the person this.
- **Permit records:** each permit OpenRig decides by default (no one to ask, or
an ask closed without a choice, including a row passed to an agent seat) is
recorded once, as a stream item from the seat, naming the tool call, why no
person answered and the option given. Read them with
`rig stream list --tag runner-permission-permit` or `--source <seat>`. A record
has no destination and never changes the permit. Stream intake reads past it, so
it opens no intake row and wakes no one.
- **The working beat:** while a prompt is outstanding and the agent is alive, a
runner may post `active` / `liveness` so a long call keeps reading working.
After delivering an answer or permit, it posts `active` /
`permission_resolved`. A runner that sends no beats behaves as before. The Pi
runner and the dsh example post it.
- **Check a runner** with
`node daemon/docs/reference/runner-conformance/check-runner.mjs --runner '<command with {agent}>' --name <harness>`.
It now covers the beat, and no longer falsely fails a correct runner on a busy
machine.
- **The dsh example** reads `DSH_SEAT_BIN` and `DSH_SEAT_HOME` from the member's
`env:`, and section 8 names ACP's `session/resume`.
## Behaviour that changed
## OpenShell workers
- **Team skills live in each seat's own storage** (`$OPENRIG_HOME/state/seats/<rig id>/<logical seat>/<runtime>/`), not
in the project's `.claude/skills` or `.agents/skills`. Claude seats get them as a generated plugin (`openrig:<id>`),
Codex seats as their own catalog. If a link sits in that path, the seat starts without them and the result names
what wasn't delivered.
- **Urgent wakes are labelled:** an urgent or critical row's wake starts with `Urgent (marked by …):` at every seat. A
seat on `hold` gets one held notice per wake instead of re-nudges, and lifecycle work is refused at the middle levels as
at `hold`.
- **A daemon restart picks up a managed compaction where it stopped.** An owed restore step resumes, a `/compact` that
may have been sent is never sent again, and `rig compact --state` shows anything stranded or owed.
- **A Claude seat whose startup turn is refused** (an expired login, say) holds the rest of its startup context as owed.
A startup input sent before the refusal is noticed can still be submitted (see Known issues in the
[0.7.1 release notes](https://github.com/mvschwarz/openrig/releases/tag/v0.7.1)).
- **`rig send` refuses with `target_needs_input`** when a Claude seat's hook has just reported it running and its screen
shows an MCP enablement menu, a question or a permission prompt.
- **`rig down` asks before stopping the caller's own rig.** Without a terminal it exits 1, stops nothing and prints the
`--yes` command.
- **`rig up --plan` doesn't start a stopped daemon:** it exits 1 with `daemon_not_running` and names `rig daemon start`
and `--no-kernel`.
- **`rig daemon start`** waits up to 20 seconds for a slow daemon, and starts with a warning when only a secondary
listener, such as Tailscale's, can't be verified.
- **A seat joined with `rig attach` as an outside app** (Claude Desktop) stays attached through `rig down` and restore
(`left_attached`). To replace it, `rig unclaim <name>`, then launch.
- **New seats get the daemon's `OPENRIG_DB` and `TMUX_TMPDIR`.**
- **A rebuilt Codex seat keeps its observed permissions,** and a read-only Codex profile launches without OpenRig's
writable roots.
- **A seat's own role** goes only to that seat, never into a shared `CLAUDE.md` or `AGENTS.md`, and `rig down` returns
guidance files byte for byte.
- **Workflow actions** (`project`, `route`, `resume`, `abort`) record the calling agent; a request with neither identity
nor actor gets the shared `actor_required` error.
- **`rig provider moves` is back** for subscription move reports. It switches nothing.
- **`--dangerously-interact` into a seat's input box stops first** when the box already holds typed or pasted text: it
shows that text (apart from suggestions) and types nothing. Clear the box and retry, or pass `--type-without-checking`,
which clears nothing. Keys for a recognized native menu are sent as before.
- **A terminal seat gets exactly the text sent to it,** without the From/To lines (the result says `envelopeOmitted`).
A queue wake to a terminal seat types nothing and fails `failed:terminal-seat`; the row stays queued.
- **A daemon restart that can't see the seats' tmux server** no longer detaches seats whose recorded process still
runs, so it doesn't relaunch them beside themselves.
- **Provider credentials and the activity-hook token stay off tmux, shell and `env` command lines** at launch and
handover. A launch whose staging fails stops instead of falling back.
- **An answer retried from Slack or Steering** after an interrupted close reuses the first reply and finishes the
request once.
- **One bad row no longer stops the stuck sweep** from flagging the others; **a malformed unrelated diagnosis row** no
longer stops health diagnoses from listing.
0.7.9 includes a repair for the OpenShell worker that wasn't usable in 0.7.8,
but it hasn't run end to end in our release testing. Don't rely on this path in
0.7.9. Startup now reaches the guest through a hash-checked first-input file,
and nothing is typed through the host viewer. Readiness checks the guest's own
execution, and a failed owned delete reports the stage that failed. If you set
one up anyway, follow `daemon/assets/openshell/README.md` in the installed
package, rebuild the guest image first, and name the costs to the person
("Provider registration and image construction can incur costs"). "Never grant
the guest access to the daemon's main API port (normally 7433)."
## Output that changed shape
## Assistants connected through a grant service (experimental)
- **`rig ps --nodes`** separates every cell with one space (the default row is 5 columns wider), adds an `ATTN` column
(`yes`, `no` or `?`) before `REASON`, and marks a `running` seat whose tmux couldn't be reached as unconfirmed.
- **`rig queue show --json`** no longer has `lastNudgeInputBox`, and send results no longer describe the input box.
- **`rig queue show` and `rig queue whoami`** print their human notices on stderr, so stdout is the JSON.
- **A claimed row whose owner waits on a person** reads pickup `needs-input`, with the reason.
- **`rig down --json`** adds `leftRunning`; `rig reconcile-session` says whether resume metadata was recorded.
- **`rig compact-plan`** labels its thresholds as its own and says whether automatic compaction is on.
- **A send held for a draft** in the seat's input box reports `inputPhase: "not_reached"`: it was never typed.
- **A stream body over 16 KiB** is stored truncated, ending `[truncated by OpenRig: kept N of M bytes]`, and reads mark it
`bodyTruncated`. `rig stream list --after <sortKey>` from a pruned item returns 410 `stream_cursor_expired`, naming
`--since` to resume, instead of an empty list.
Experimental, unsupported, off by default, and at the person's own risk. With
`rig mcp serve --http --grant-service <url>` and a grant service the person
supplies and runs, a connected assistant can claim, update and park the rows
addressed to its own seat, and see its team with `rig_team`, within the scope its
existing tools have. Nothing wakes it: an item stays queued until its receiver
claims it. Name the outside service, and the exposure of serving MCP over HTTP,
to the person before you set this up. `docs/reference/hosted-members.md` is the
rule.
## What to stop doing
## Other changes to act on
- **Don't read `lastNudgeInputBox`.** It's gone.
- **Don't parse `rig ps --nodes` by column position.** Use `--json`, with `--fields` to pick fields.
- **Don't look for a team seat's skills in the project folders.** Use `rig skill loadout --seat`.
- **Don't stop and re-register a watchdog to change it.** Use `rig watchdog update`.
- **Don't take a rig down to fix one edge.** Use `rig edge add` or `remove`.
- **Don't print `OPENRIG_ACTIVITY_*` values** while looking for identity. Use `rig whoami`.
- **Don't use `--dangerously-interact` to type over a half-written input box.** It stops and shows the text; choose
explicitly, or use `rig send --urgent` where the seat's interruption level allows it.
- **Held messages:** a seat that starts, or takes over a seat, with held
messages is told how many there are. Read them with
`rig seat held-messages <seat>`.
- **Unconfirmed sends:** `rig send --verify` now reports unconfirmed and exits 1
when it can't see what its Enter did, and `rig walk` reports unconfirmed
instead of lost. Inspect the recipient before any resend.
- **Deadlines:** to accept a re-estimate without unclaiming the work, run
`rig queue update <id> --closure-required-at <ISO>`.
- **Shared workspaces:** any registered person can settle another's ask as
themselves, and the first committed answer wins; a later answer gets 409
`qitem_already_terminal` and records nothing. A person registered with no
connector binding is asked on Steering and in the Feed only.
- **Handover:** a fresh or rebuild successor's first turn lists its startup
files, role first. A successor that started without a required startup file is
marked in the seat list.
- **Ambiguous rig names:** when an active rig name matches more than one rig,
`rig env down`, `status` and `logs` list the matching rig IDs and stop, and
`rig doctor --spec` reports conformance as unknown. Use the rig ID.
- **Seat activity:** window samples older than their bound no longer decide a
seat's activity, so a seat whose pane can't be read reads unknown rather than
its last verdict. Treat unknown as unknown, not idle.
- **Launch options:** `specs.builtins_visible: false` hides built-in specs
(`rig specs ls --include-builtins` still lists them); `plugin_channels` gives
one Claude member its own `--channels`, untested with a real channel plugin.
- **Files:** `rig down` removes OpenRig's own additions from a working folder
once no live seat uses them. `rig export` with no `-o` writes
`rig-export.yaml` beside an existing `rig.yaml`. `rig workspace validate` exits
1 for malformed optional frontmatter.
- **Kernels:** a new default kernel starts in `<workspace.root>/kernel`.
Existing kernels stay where they are. `docs/reference/agent-startup-guide.md`
explains the optional move; a fresh launch there starts a new native
conversation, so ask the person before moving.
- **PATH:** if preflight warns that a Codex seat's login shell can't find `rig`,
follow its fix. `getting-started.md` explains the `~/.profile` line and how to
check it with `bash -lc 'command -v node rig'`.
## Known gaps in 0.7.1
## Limits to keep in view
- **A new Claude seat's startup text can stay in its input box unsent.** If a fresh Claude seat sits idle with text at
its prompt, press Enter in its pane, or ask the person to.
- **Claude seats on a macOS native install can read as needing attention after a restore.** Check the pane before
acting on it.
- **A Slack channel-mapping change needs a daemon restart.** Don't use `rig slack disable` then `enable` for it: enabling
marks any alerts waiting to post to Slack as already seen.
- **Ubuntu 22.04's tmux 3.2a:** the query known to crash its server is gone, but a narrower race remains. Avoid 3.2a
(`tmux -V`).
- **`rig daemon start` can take 20 to 40 seconds** before its listener warning, longer than the TUI's 30-second "restore
everything" wait. On a slow host, start the daemon yourself first.
- **A launch that warns "Scoped tmux environment unavailable; retaining the previous environment"** didn't give that seat
the clean environment. Check `env` in the seat before relying on OpenRig's variables.
- **Going back to 0.7.0 with a Slack subscription set:** 0.7.0 can't read a person file that holds one, and its people
registry stops loading. Save the choice first (`rig gateway human show <id> --json > <id>.json`), then run
`rig gateway human set <id> slack-subscription unset` (`urgent-only` becomes every-ask, as on 0.7.0); set it again after
returning to 0.7.1. A person file still holding a `subscriptions:` entry fails on 0.7.0 too. Don't restore an older
gateway folder or database instead: that can lose newer people, bindings and delivery records.
- **A Herdr tile over SSH** doesn't come back by itself after a Herdr restart. Reopen it with `rig terminal open <view>
--provider herdr`.
- **Delegated decisions:** the ask is created before the decision, so without quiet hours it can post to the person
first. Pair a decision room with quiet hours, or decide before asking.
- **`restore-check` still treats a missing startup file as rig-wide.** Trust `rig up --existing`'s per-seat result.
- **Managed Claude launch requires `/usr/bin/env` support for `-0` and `-u`** (option support checked on GNU coreutils
and macOS 15; BusyBox and older macOS unverified). Without the required option support, launch stops instead of
falling back to credential-bearing command-line arguments.
- **In draft-aware mode, an agent's multi-line message to a Claude seat can remain unsubmitted when the paste changes
Claude's footer hint.** OpenRig withholds Enter, leaving the text in the composer and later sends waiting. Observed on
Claude 2.1.309 with both 0.7.0 and the 0.7.1 pre-cut build; #14 remains open.
- **A Claude seat signed in with a long-lived token (`claude setup-token`) gets no usage reading.** The reader accepts
only claude.ai subscription logins and reports `not_subscription_login`.
- **OpenShell** hasn't run end to end in our release testing (above). A
same-generation reconciled launch opens no viewer, so run
`rig environment connect` yourself; non-text startup actions aren't supported
on environment seats.
- **Open updates for a person** read a 1,001-row window, so a page can come back
short, marked `truncated`.
- **Slack:** a private channel needs the documented route, which isn't tested,
and direct messages aren't supported. The channel and people reads haven't
been run against a live Slack workspace. Redaction of the bot token from a
transport error can still leave a malformed token fragment shorter than six
characters, so review an error before sharing it.
- **SSH host status** reads `unknown` behind a ProxyJump or ProxyCommand; that
isn't proof the connection is broken. `rig host ls` reads the ssh config with
`ssh -G`, which runs any `Match exec` command in it.
- **Shared asks:** a click's option written before its answer row can stand
beside or inside the closer's answer without the first clicker's authorship.
- **Permit records** last as long as stream retention keeps them. A permit a
runner makes on its own, when it can't reach OpenRig, is in its pane only. For
an ask opened before 0.7.9, a record may omit the option's kind.
- **Existing seats** pick up Codex launch instructions and the single
openrig-core skill load at their next fresh launch. A `CLAUDE.md` or
`AGENTS.md` that already grew a line per launch isn't trimmed.
- **Codex hooks after an upgrade:** Codex sessions kept open across the 0.7.8
upgrade showed "Hook failed — plugin contents changed; hook approval
required". The 0.7.9 behaviour, and hook recovery after a relaunch, haven't
been verified.
- **0.7.8's limits** on the dsh `env:` names, conformance false failures, typed
words, the README's operator address, a symlinked rebuild, the rebuild receipt,
a re-parked click and unrecorded default permits are fixed or changed. The
other 0.7.8 and 0.7.7 limits below, and the
[0.7.6 limits](https://github.com/mvschwarz/openrig/releases/tag/v0.7.6), still
apply.
---
## What changed in OpenRig 0.7.8
The changes from 0.7.7 to 0.7.8, published on 2026-11-05, for an install
upgrading from 0.7.7 or earlier.
### After the upgrade
- **Restart the daemon** to load the new code: `rig daemon stop`, then
`rig daemon start`.
- **Migrations 142 to 145** store a Claude seat's MCP opt-out, seat activity
grants, OpenShell environment bindings and a seat's chosen permission person.
- **The bundled openrig-core plugin goes from 0.1.23 to 0.1.24.** One skill line
changed. Managed refresh preserves replaced files under
`$OPENRIG_HOME/backups/managed-refresh/<id>/`; reapply local edits you still
need from the preserved copy.
### Runner seats: a person answers permission requests
Section 7 of `docs/reference/harness-runner-contract.md` is the rule. Read it
before writing or changing a runner.
- **Who is asked:**
- **the default person** (`rig gateway default`, or the only registered
person) gets an ordinary ask in the Feed and on Slack;
- **a chosen person,** after
`rig seat set-permissions-to <seat> --to <person> --reason "…"`. `--clear`
returns to the default;
- **no one:** when no one can be asked, the request is "Permitted at once,
with the agent's own allow option";
- **refusal** is opt-in, with `--deny`. `rig seat status` shows the choice.
- **What a person's answer does:** a click gives that option, a plain yes gives
the allow, and a plain no gives the reject. "A person's no is never a grant."
An unanswered request stays pending until the agent's turn ends.
- **The runner's protocol:**
- `POST /api/activity/permission-requests` submits the request;
- `GET …/<askId>?waitSeconds=25` reads the answer (25 at most);
- `POST …/<askId>/withdraw` withdraws it at the turn's end.
Deliver an answer once, only to the live request.
- **Check a runner** with
`node daemon/docs/reference/runner-conformance/check-runner.mjs --runner '<command with {agent}>' --name <harness>`
from the installed package. It needs Node and tmux, and no daemon, model or
network.
- **The worked example** is the dsh runner under
`daemon/docs/reference/community-harness-examples/dsh/`.
- **A seat activity grant** lets a runner outside the host's hook setup report
activity for one seat and launch, without the install-wide hook token.
### OpenShell workers
0.7.8 includes `rig environment` (`setup`, `create`, `attach`) and a reference
for binding a seat to an NVIDIA OpenShell sandbox (`daemon/assets/openshell/README.md`).
**In our release testing the worker never became usable:** after the sandbox was
created and the native process started, `rig environment connect` failed when its
check timed out after 5 seconds, so no team work ran. Don't rely on this path in
0.7.8; 0.7.9's repair is described above.
**What it needs:**
- NVIDIA OpenShell 0.1.6;
- a Linux image;
- an API-backed provider using `ANTHROPIC_API_KEY`. Subscription or OAuth
credentials aren't supported.
"Provider registration and image construction can incur costs", so name them to
the person before you start. "Never grant the guest access to the daemon's main
API port (normally 7433)."
### Other changes to act on
- **Mission following:** `rig terminal open mission:<id> --follow --provider herdr`
follows a mission's current owners, with the same Herdr path as slices.
- **Launch choices:**
- `claude_mcp: none` on a Claude member launches it without MCP servers;
- `rig config set terminal.tmux_server isolated` gives this OpenRig home its
own persistent tmux server.
- **Hosts:** `rig host edit` and `rig host remove` correct or retire a host
entry. Use them instead of editing `hosts.yaml` by hand.
- **Another user's daemon:** when `rig doctor` warns that the port is held by
another OS user's daemon, follow its fix (`rig config set daemon.port <free-port>`),
or set `OPENRIG_URL` to share that daemon on purpose.
- **tmux control mode:** with `OPENRIG_TMUX_CONTROL=1`, OpenRig starts a control
client only on tmux 3.7c or newer, and uses spawned clients otherwise.
- **Read-only tmux clients:** a submit key refused because of one goes through
the paste route.
- **Slack:** a typed reply to a decision parked on its person is recorded as the
answer. Read the person's words; don't treat the close as approval.
- **The TUI:** agent pages now show health findings and recently finished items,
and an agent spec's "not loaded in this view" means it wasn't read, not that
nothing runs it.
### Limits to keep in view
- **No harness has run end to end under the runner contract yet.** The dsh
example and the conformance check were checked against stand-ins.
- **The answer owner is a registered person,** and a colleague's answer is
followed on this host only. A request handed to another host waits as needs
input until the turn ends.
- **Permits and answers aren't recorded** beyond the runner's pane and the ask.
If the agent offers only `allow_always`, a permit makes its harness remember a
permission nobody chose.
- **Coming as follow-ups:** recording permits made when no one can be asked, and
passing typed words that aren't a plain yes or no to the agent.
- **The dsh example keeps one hold per seat:** two asks in one turn can leave one
waiting after the hold is released.
- **An OpenShell worker's activity reads unknown,** sampled from the pane, never
idle.
- **The 0.7.7 limits below, and the [0.7.6 limits](https://github.com/mvschwarz/openrig/releases/tag/v0.7.6), still apply.**
---
## What changed in OpenRig 0.7.7
The changes from 0.7.6 to 0.7.7, a reliability patch for 0.7.6, published on
2026-11-02. All three fixes are in 0.7.8.
### The fixes
- A Claude seat at an empty prompt reads idle again when Claude prints
"✔ Update installed · /restart to apply" (or "Restart to apply") below its mode
bar after installing an update. Before, that line hid the mode bar, so the seat
read unknown and managed compaction waited for an idle prompt it could not see.
Both spellings are recognized; lines that only look similar still read unknown.
([#2311](https://github.com/mvschwarz/openrig/pull/2311))
- Managed compaction checks the input box before it pastes its preparation
prompt, `/compact` or a restore prompt, even with the typing guard off. Typed,
unreadable or changed input holds that step. A manual `rig compact` then says it
stopped to preserve the input box; read `rig compact <seat> --state` and retry
once the box is empty. A held restore prompt waits and goes ahead on its own once
the box is empty. An empty box or a high-confidence suggestion goes ahead.
Ordinary `rig send` and the typing-guard defaults are unchanged.
([#2347](https://github.com/mvschwarz/openrig/pull/2347))
- A Claude seat whose `claude` is a `#!` wrapper that starts the real Claude without
`exec` can be rebuilt again (`rig seat handover --source rebuild`): readiness also
accepts the wrapper's direct child when it names this launch's conversation token,
and refuses every other process as before. A rebuild that fails no longer leaves
the seat's resume target on the new, unprimed conversation.
([#2341](https://github.com/mvschwarz/openrig/issues/2341), [#2354](https://github.com/mvschwarz/openrig/pull/2354))
### Limits to keep in view
- Other reasons managed compaction can wait are unchanged.
- The input-box check is a read just before the paste, not a lock: typing that
starts in the same moment can still be appended to.
- If a seat's current occupant generation was never registered (an adopted Claude
with another generation, or a managed launch whose tenure record failed to write),
hooks don't move its resume token after the first one.
- The [0.7.6 limits](https://github.com/mvschwarz/openrig/releases/tag/v0.7.6) still apply.
+5 -5
View File
@@ -1,12 +1,12 @@
{
"name": "openrig",
"version": "0.7.1",
"version": "0.7.9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openrig",
"version": "0.7.1",
"version": "0.7.9",
"license": "Apache-2.0",
"workspaces": [
"packages/daemon",
@@ -7262,7 +7262,7 @@
},
"packages/cli": {
"name": "@openrig/cli",
"version": "0.7.1",
"version": "0.7.9",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
@@ -7300,7 +7300,7 @@
},
"packages/daemon": {
"name": "@openrig/daemon",
"version": "0.7.1",
"version": "0.7.9",
"license": "Apache-2.0",
"dependencies": {
"@hono/node-server": "^1.13.0",
@@ -7342,7 +7342,7 @@
},
"packages/ui": {
"name": "@openrig/ui",
"version": "0.7.1",
"version": "0.7.9",
"license": "Apache-2.0",
"dependencies": {
"@fontsource-variable/jetbrains-mono": "^5.2.8",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "openrig",
"version": "0.7.1",
"version": "0.7.9",
"private": true,
"license": "Apache-2.0",
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@openrig/cli",
"version": "0.7.1",
"version": "0.7.9",
"type": "module",
"description": "Build your own network of agents from Claude Code, Codex and Pi: persistent teams with roles, shared context and owned work.",
"keywords": [
+5
View File
@@ -147,6 +147,11 @@ Before running it, check who is working (rig ps --nodes --rig <rig>) and that th
don't stop a team you didn't start unless asked. OpenRig snapshots the rig first (best-effort), and
rig up <rig> --existing brings it back, resuming each seat's conversation where its harness allows. With
--delete the rig record is removed too, so it can't be brought back that way.
Once no live seat uses a working folder, rig down also takes back what OpenRig added there: its
guidance blocks, and in a Claude seat's folder the status line, activity hooks and context collector.
It keeps and lists an edited skills-line block, edited team skill files, and settings or a collector
that don't match what OpenRig wrote or that another seat shares by a link. Any other guidance block
goes even if someone edited it. rig up <rig> --existing puts the guidance back.
When your resolved seat belongs to this rig, confirm before stopping it. Without a terminal,
the command warns and exits without stopping; re-run with --yes to confirm explicitly.`);
const getDepsF = () => depsOverride ?? { lifecycleDeps: realDeps(), clientFactory: (url: string) => new DaemonClient(url) };
+1 -1
View File
@@ -242,7 +242,7 @@ export function sendCommand(depsOverride?: SendDeps): Command {
.option("--to <sessions>", "Multi-recipient: comma-list or repeated (--to a,b or --to a --to b)", collectSessions, [] as string[])
.option("--pod <name>", "Send to every seat in a pod (fan-out, per-recipient results)")
.option("--rig <name>", "Send to every seat in a rig (fan-out, per-recipient results)")
.option("--verify", "Verify pane only delivery by checking content after send")
.option("--verify", "Verify pane only delivery by checking content after send. Exit 1 can mean unconfirmed, not failed: the message may have arrived, so inspect with rig capture before any resend")
.option("--force", "Back-compat no-op: a mid-task/busy pane already sends-with-advisory by default; --force never bypasses the interactive-prompt/permission guard")
.option("--wait-for-idle <seconds>", "Wait until the target is explicitly idle before sending. If it's still busy, or its activity is unknown, when the wait ends, nothing is sent (exit 1) and nothing is kept to send later; put separate work in a queue row (rig queue create)")
.option("--raw", "Send exact text/keystrokes without the From/To messaging envelope (still guarded against interactive prompts)")
+5
View File
@@ -112,6 +112,11 @@ export function streamCommand(depsOverride?: StreamDeps): Command {
.option("--interrupt", "Mark item as interrupting")
.option("--id <streamItemId>", "Idempotent stream_item_id (skip if not provided)")
.option("--json", "JSON output for agents")
.addHelpText("after", `
Stream intake is on by default: the seat named by stream.intake (queue.worker unless set)
gets an intake row for each item, which wakes it, or a note on its intake row still pending.
--hint-urgency critical or --interrupt makes that row urgent. The reply names only the stream item.
To keep emits passive: rig config set stream.intake off`)
.action(async (opts: {
source: string;
body: string;
@@ -10,8 +10,8 @@ already does it, or two verbs crossed already answer it. **You cannot look up a
not know exists.** So the point of what follows is not skill. It is recognition: that when a task
lands, something rhymes, and you go check instead of building.
Ninety top-level verbs ship (capability canon refreshed through
`capability-delta-v0.7.6`). This marker describes the pack's teaching, not publication or live
Ninety-one top-level verbs ship (capability canon refreshed through
`capability-delta-v0.7.9`). This marker describes the pack's teaching, not publication or live
adoption. Read this once for shape, and let it make you suspicious that a thing already exists.
What changed in the installed version, and what to do about it, is one read away:
`rig context get reference/whats-new.md`.
@@ -108,7 +108,10 @@ unseen — which makes messages the one delivery channel that never gets skipped
- **`rig send`** — put words in front of one seat, several, a pod, or a rig. **`--verify` checks
pane-only delivery**: the text was staged at the far end, not that the agent consumed or acted
on it. Alternate-screen and queued-command cases can still yield false negatives, so
consequential delivery gets an effect check at the far end rather than a blind retry. A
consequential delivery gets an effect check at the far end rather than a blind retry.
`--verify` exits 1 when it can't confirm what its Enter did (`submission unconfirmed`, or text
staged but not submitted): the message may still have arrived, so inspect with `rig capture`
before any resend, and don't let a script resend on that exit code alone. A
producer-link advisory reading `no_activity_signal` means activity could not be determined;
confirm by effect, never relaunch on it. A send or wake to a seat whose latest hook is a
question waiting for a person is refused with `target_needs_input`, even when its screen
@@ -144,7 +147,7 @@ unseen — which makes messages the one delivery channel that never gets skipped
and the box was empty after Enter; it only reports.
- **`rig broadcast`** — one fact to everyone at once, rather than relaying it N times and getting
the wording wrong on the fourth. **Blast radius is real** — on a large rig this lands in every
seat's turn.
seat's turn. A broadcast that reached no one says so.
- **`rig capture <seat>`** — read what is on their screen right now. **`--rig` / `--pod`** captures
every seat in one call. `--lines N` is the last N lines (stderr says how many earlier lines were
cut); `--history-plus-pane` gives the older N history lines plus the whole pane. A short capture
@@ -171,8 +174,11 @@ unseen — which makes messages the one delivery channel that never gets skipped
connection refusals, and reset or closed connections are retried until that deadline; other errors,
including an error answer from the daemon such as a removed rig, still end the wait.
- **`rig stream emit` / `list` / `watch` / `archive`** — drop an observation somewhere the *next*
agent will find it. Emitting costs nothing and does not interrupt anyone; the value appears when
someone lists the stream before starting. **`rig stream show <id>`** says what became of one:
agent will find it. By default an emit also reaches the kernel's queue worker: it opens an
intake row for `queue.worker`, which wakes it like any queue row, or adds a note to that seat's
intake row still pending, and a `critical` or `--interrupt` item makes the row urgent.
`stream.intake` names the receiving seat; `rig config set stream.intake off` keeps emits passive.
A runner's default-permit record opens no row. **`rig stream show <id>`** says what became of one:
its routing, linked work and that work's state. Items emitted from 0.7.1 on are kept 30 days or
the newest 20,000 (`retention.stream_days`, `retention.stream_max_items`) unless a record names
them, and a body over 16 KiB is stored truncated.
@@ -235,11 +241,14 @@ unseen — which makes messages the one delivery channel that never gets skipped
New slice views default to four automatic tiles (`--max-tiles` overrides it). Known empty slices
stay following; incomplete reads keep prior viewers, and draft/focus protection still applies.
A join refused before a pane split can join on a later refresh. Hold notices keep the seat and phase.
The first notice keeps the original reason; later notices can show a generic reason, while the
original failure remains in the journal.
Later notices label the current confirmation apart from the first failure, also after the follower
is recreated, so the original reason stays in view.
An attach that ran and then exited to a shell is not restarted automatically; the whole slice view
can hold until that tile is resolved. No tested workaround is offered for that case.
Slice following targets the Linux Herdr 0.9.5 / protocol 22 path. Mission following is not included in 0.7.6.
**`rig terminal open mission:<id> --follow --provider herdr`** follows a mission's current owners the same way: the
union of active rows with that exact `mission:` tag and rows associated with its indexed child slices, each counted
once, with four automatic tiles by default. Flat legacy slices without that parent association aren't inferred into
the mission. Slice and mission following target the Linux Herdr 0.9.5 / protocol 22 path.
An unconfirmed pane is reported as `pane_unconfirmed`, not counted as opened. Inspect before
retrying: an unconfirmed result does not prove that nothing opened.
Add `--session <name>` to select a Herdr session for that request; `--session default`
@@ -259,7 +268,8 @@ unseen — which makes messages the one delivery channel that never gets skipped
piece and its matching Claude/Codex turn closure must arrive before pacing onward, including
the last piece. Initial missing evidence is explicitly unverified; a record lost or replaced
during verification stops the walk. Use `rig walk --help` for those receipts and bounds.
Delivery and turn completion do not prove comprehension.
A piece whose delivery or submission can't be confirmed reads unconfirmed, not lost: inspect
before resending. Delivery and turn completion do not prove comprehension.
- **`rig slack`** — inspect and manage one connector implementation. Use registered-human
readiness for the delivery decision and `queue create --verify` for its receipt; connector
setup or verification is not an outbound human message. After a connection gap the daemon
@@ -322,7 +332,9 @@ your context, your compaction and your replacement — and it is the only thing
`--note` does not reopen a terminal row; terminal-to-active repair requires explicit `--reopen`
with both `--state` and `--note`. `--summary` on its own corrects a row's subject in any state,
and a close with `--resolution "<outcome>"` stores the outcome on the row; `--state done
--evidence-ref <path>` also keeps the artifact's pointer on it.
--evidence-ref <path>` also keeps the artifact's pointer on it. To accept a re-estimate,
`--closure-required-at <iso>` on an in-progress row moves its deadline and keeps the claim,
recording the old and new time and who accepted it.
- **`rig queue handoff`** — pass work so the close and the create are **one transaction**, instead
of closing yours, failing to create theirs, and stranding the work in between. A standing sweep
checks recently closed rows (a 24-hour lookback) and flags a `handed_off_to` closure whose
@@ -364,6 +376,8 @@ your context, your compaction and your replacement — and it is the only thing
front of a seat that it can *refuse*. Mail, not assignment.
- **`rig queue outbox-record` / `outbox-list`** — what you dispatched, in a record that survives
your context. Stale requests you forgot are invisible to you and expensive to everyone else.
A receipt reads `indeterminate`, not delivered or failed, when the text may have reached the
seat: an unconfirmed send, or a write attempted before an error. Inspect before resending.
- **`rig queue fallback`** — reroute a row whose destination cannot receive it, without losing it
or rewriting its history.
@@ -513,6 +527,13 @@ not have to tear one down to change it, and you rarely have to start from nothin
rig. `rig up` with a pod-aware spec on a stopped team's name replaces that team (the earlier one
is archived), so check `rig ps --nodes` first. A rig name containing a dot is refused before
launch, because tmux would rename its sessions. Launch-created `AGENTS.md` or `CLAUDE` files now show in `git status`.
A fresh Pi seat normally keeps OpenRig's guidance in its own agent folder, and a fresh Codex
0.164.0+ seat normally gets it as launch developer instructions, so neither adds it to the
repository's `AGENTS.md`. The repository file is still used when the Pi folder or its `AGENTS.md`
is a link, when a Codex config layer sets `developer_instructions` or can't be read, and for a
Codex resume, fork, restore or handover successor, or an older or unknown Codex version
(`docs/reference/agent-startup-guide.md` has the rule). At
launch, a block a person edited is kept and named (`guidance_block_edited`), not replaced.
`rig up <rig> --existing` puts back the OpenRig guidance blocks `rig down` removed before a
resumed seat starts. A seat still missing one makes the rig `partially_restored` (its node lists
`guidanceGaps`) and `rig up` exits 1. A seat that wasn't running at the snapshot, but whose
@@ -532,6 +553,9 @@ not have to tear one down to change it, and you rarely have to start from nothin
`CODEX_HOME`, `OPENRIG_*` and the allowlisted provider auth variables) and its own `env`. Put an
account or key in the seat's `env` or `rig up --env`, or set the key to true. Running seats are
untouched, and `rig setup` warns when it sees a custom tmux configuration.
`rig config set terminal.tmux_server isolated` (default `inherit`) keeps managed seats on their own tmux server,
out of the person's `tmux ls`; it's read at daemon start, and `rig terminal attach '<session>'` reaches a seat. It
selects only the tmux server: home, credentials, filesystem and network stay shared.
**`rig up <spec> --name <rig>`** runs one spec as another rig under that name; the rig keeps the
name for `--existing`, snapshots and restore, and the spec file isn't changed.
- **A Claude member can bring its own setup** in the rig spec: `agent_file`,
@@ -539,7 +563,8 @@ not have to tear one down to change it, and you rarely have to start from nothin
that seat's own storage, never the project, so one seat in a shared repository can have its own
persona, prompt or plugins. A bundle's before-install view lists them. `plugin_channels`
(`plugin:<name>@<marketplace>` entries) registers that seat alone for those plugins' channel
notifications, through Claude's `--channels`.
notifications, through Claude's `--channels`. `claude_mcp: none` on a Claude member starts it with no MCP
servers, plugin servers included, on fresh, resume and fork launches.
- **A new Claude member can name a prepared subscription token** with
`claude_token: {label: name, file: /abs/token}` beside its `env.CLAUDE_CONFIG_DIR`; the seat stores
the file reference and reads it at launch. A missing/unreadable token uses the existing fallback
@@ -552,11 +577,20 @@ not have to tear one down to change it, and you rarely have to start from nothin
harness its first startup text through a private file named by `OPENRIG_FIRST_INPUT`. Examples for
Cursor, Hermes and OpenClaw are in `docs/reference/community-harness-examples/`; OpenClaw's local
and Gateway-client examples each resume a named conversation. A preset can declare `runner: true`
(with `launch_result: true`) so activity follows its runner reports, recorded per launch.
(with `launch_result: true`) so activity follows its runner reports, recorded per launch. A runner seat's
permission requests reach a person: the default person, or one chosen with `rig seat set-permissions-to <seat>
--to <person> --reason "…"` (`--clear` returns to the default; `--deny` opts the seat in to refusal). When no one
can be asked, the request is permitted with the agent's own allow option, and a person's no is never a grant.
Each permit OpenRig decides by default is recorded once, as a stream item from the seat that wakes no one:
`rig stream list --tag runner-permission-permit`. A person's reply in other words chooses nothing; the read
result carries it whole as `said`, for the runner to pass to the agent as an ordinary prompt, never as the answer.
The installed package's `daemon/docs/reference/community-harness-examples/dsh/` is an example runner, and
`node "$(npm root -g)/@openrig/cli/daemon/docs/reference/runner-conformance/check-runner.mjs" --runner '<command with {agent}>' --name <harness>` checks a
runner with no daemon, model, credentials or network.
`passes_model: true` sends the declared model as `OPENRIG_MODEL`; it does not establish the
effective model. Cursor and Hermes examples leave model passing off pending native qualification.
`docs/reference/community-harness-presets.md` is the contract: what a community harness gets from
OpenRig and what it doesn't (no turn-cancel verb, no approvals reaching OpenRig), how to write
OpenRig and what it doesn't (no turn-cancel verb; permission requests reach OpenRig only from a runner preset), how to write
one, and how promotion is judged. Whether processes a harness starts outside its pane survive
isn't exercised. An OpenClaw seat whose last local turn was refused reads as needing attention
(`OPENCLAW_TURN_NOT_RECOVERED`): OpenRig doesn't recover that turn, and a send submits a new one,
@@ -568,7 +602,12 @@ not have to tear one down to change it, and you rarely have to start from nothin
human mode it first prints one line with each agent's last known activity, or “unknown”: a
best-effort snapshot, not a record of what was interrupted. With `--host`, a remote teardown
that reports errors exits 2. For a community harness seat it prints `Pane stopped: <seat>`
(`paneOnly` in `--json`): the harness's own processes may keep running after its pane.
(`paneOnly` in `--json`): the harness's own processes may keep running after its pane. Once no
live seat uses a working folder, it takes back what OpenRig added there: its guidance blocks, and
a Claude folder's status line, activity hooks and context collector. It keeps and lists an
edited skills-line block, edited team skill files, and settings or a collector that don't match
what OpenRig wrote or that another seat shares by a link. Any other guidance block goes even if
someone edited it.
- **`rig launch <rig> [seat]`** — one seat is down; start just that one, without disturbing the
rest. It takes the rig's name or ID; a name shared by more than one active rig stops and lists their IDs. Add `--plan` to preview it first, locally or with `--host`; a plan is never sent to a
daemon older than 0.5.9, which would ignore it and launch. A seat whose agent exited to a shell in
@@ -665,7 +704,8 @@ version, from checking the environment to a useful report to the OpenRig team.
`/healthz` reports the daemon's effective UID as `euid`, beside `pid`. Running several OpenRig
homes on one machine on purpose: `rig config set daemon.db_conflict stop` refuses a start whose
database another live daemon already holds (the default, `warn`, only warns), and
`rig daemon start --allow-db-conflict` allows one deliberate start.
`rig daemon start --allow-db-conflict` allows one deliberate start. A start still in progress reads
`Daemon starting: launch reserved by pid N since T; not listening yet` in `rig daemon status`, not stopped.
- **A busy daemon isn't a down one.** The CLI refuses only when the daemon is confirmed down
(stopped, stale, a refused probe), its probe is blocked, or it's a sibling home; a busy daemon
gets the real request. A write that then times out says its outcome is **unknown**: check its
@@ -676,7 +716,9 @@ version, from checking the environment to a useful report to the OpenRig team.
credential, the way setup does; it fails for a harness the person doesn't use, which they can
ignore. A pass is not "ready": local credential availability is not proof that a provider accepts
the credential or that an agent can work. `rig setup` and `rig doctor` also check that tmux
can capture a pane, on a throwaway server (`tmux_capture`); no tmux version is refused.
can capture a pane, on a throwaway server (`tmux_capture`); no tmux version is refused. When the
configured port's daemon belongs to another OS user (by `/healthz`'s `euid`), doctor warns and names the fix:
`rig config set daemon.port <free-port>`, then `rig daemon start`. Nothing is stopped or refused.
**`rig preflight`** asks whether this machine can run OpenRig at all.
- **Bare `rig`** — the same TUI for first setup, daemon-down startup and ordinary work.
After a successful daemon read it opens the ordinary work views, even when no rig is running;
@@ -735,7 +777,10 @@ version, from checking the environment to a useful report to the OpenRig team.
effectful handover path.** Both forms perform the operation by default; pass `--dry-run`
to plan without changing the seat. Check the selected source and continuity evidence first. A
seat can be handed over again into its own pane, so a seat left half-handed-over recovers by
running the handover again.
running the handover again. A Claude seat started through a `#!` wrapper that runs Claude without
`exec`, or through the installer's `~/.local/bin/claude` symlink, rebuilds with `--source
rebuild`; a failed rebuild leaves the resume target on the earlier conversation, so rebuild again
rather than relaunching it fresh.
- **`rig seat clear-attention` / `set-resume-token`** — clear a stale attention flag, or repair a
lost resume handle so restore works next time.
- **`rig seat continue <seat>`** — a launch stopped on Claude's bypass-permissions consent dialog.
@@ -805,7 +850,8 @@ version, from checking the environment to a useful report to the OpenRig team.
paste changes Claude's footer hint, and later sends wait behind it; check the seat's composer.
`rig seat status` shows the requested/effective mode; wait for pending activation to finish.
**`rig seat held-messages`** lists what was retained and **`rig seat retire-held-message`**
retires one; changing mode does not replay old held messages. Draft-aware returns the exact
retires one; changing mode does not replay old held messages. A seat that starts, or a successor
that takes it over, with held messages is told how many and how to read them; nothing is replayed. Draft-aware returns the exact
refusal and original ID immediately; callers decide further action, with no daemon retry timer.
- **`rig seat set-terminal-policy <seat> --preset no-peer-interventions|help-within-instructions|full-control
--reason <text>`** — choose how far OpenRig and peers may act on a seat: `--helper <addresses…>`
@@ -828,14 +874,21 @@ version, from checking the environment to a useful report to the OpenRig team.
that stays quiet after `/compact`; they don't wait for its next turn. The managed `/compact` is
kept short enough to run as a command and is sent only to an idle seat; its own result reads
`submitted, unconfirmed` (it reached the seat, not proof compaction ran), `uncertain` (not
replayed) or `not sent`, so confirm by effect. Claude seats that select `openrig-core` load its
replayed) or `not sent`, so confirm by effect. Before each paste (preparation prompt, `/compact`,
restore prompt) it checks the input box and holds that step while the box holds typed,
unreadable or changed input, so a draft needs no typing guard to protect it; the check is a read just before the paste,
not a lock. An idle Claude seat under the installed-update notice still reads idle, so it needs no
hand compaction for that. Claude seats that select `openrig-core` load its
hooks; a seat can give a slow refocus more time with `OPENRIG_REFOCUS_BUDGET_MS` (4500 to 52000).
The refocus work packet stays off by default for Claude Code and Codex;
`OPENRIG_REFOCUS_WORK_PACKET=1` turns it on, with each field bounded.
`policies.claude_compaction.preparation_root` (an absolute path) puts restore maps where seats
may write. A daemon restart picks a managed compaction up where it stopped: an owed restore step
resumes, a `/compact` that may have been sent is never sent again, and `rig compact --state`
shows anything stranded or owed. `rig seat status` shows an owed restore stage and its last
shows anything stranded or owed. A map finished during automatic preparation stays armed for the next idle moment
through the next retry interval; `--state` shows `idleDeadlineAt`. In either mode, expiry reads
`map_complete_seat_never_idle` when the map is finished, or `preparation_incomplete` when it is not.
`rig seat status` shows an owed restore stage and its last
delivery result, and repeated delivery failures open one queue item for the seat's orchestrator, or a
person when it has none.
- **Codex seats get `openrig-core` too: its refocus after a compaction and its skills, with no
@@ -1058,6 +1111,8 @@ reaching them is ordinary work rather than an escalation.
- **`rig host add` / `rename` / `doctor`** — make another machine reachable as one pasted address,
see which host you are acting on, and check a registered host is *actually* usable end to end.
`rig host edit <id>` corrects an entry (its id and transport stay fixed), and `rig host remove <id>` deletes
only the registry entry: credential files, the learned identity and the selection stay as they are.
- **A cross-host queue write that times out reads INDETERMINATE, not failed.** The CLI waits 15
seconds for the daemon's answer, and `rig queue create` with no answer reads the row back once by
its ID. Reconcile by that ID before any retry; don't send the write twice.
@@ -1084,14 +1139,16 @@ reaching them is ordinary work rather than an escalation.
System World teaches these mechanics; Project World decides when and why they apply.
Existing `<entityId>@host` blockers are internal custody labels mapped through the registry
to the external participant, not a separate outbound route.
- **`rig gateway human add`** — register a person and their connector binding. Registration or
- **`rig gateway human add`** — register a person and their connector binding, or none: a person
with no connector is asked on Steering and in the Feed only. Registration or
connector changes do not themselves authorize a message. Several people can be registered:
**`rig gateway default <id>`** (or `--default` on `human add`; `--clear` removes it) chooses the
default person, who receives the last escalation step, the notice digest, Mission Control's
my-queue view, workflow fallbacks and pairing approvals. One registered person is the default;
with several and none chosen, those wait (`undeliverable:no-default-person`). A registered person
can't settle an ask that was asked of someone else (`answer_not_recorded`), and a park addressed
to a registered person can be resolved.
with several and none chosen, those wait (`undeliverable:no-default-person`). Any registered
person can settle an ask that was asked of someone else, recorded as themselves beside the person
asked. The first committed answer wins; a later one gets 409 `qitem_already_terminal` and records
nothing. A park addressed to a registered person can be resolved.
- **`rig gateway channel add <id> --display-name <name> --binding
slack:<connectorRef>:<secretsRef>:<channelId>`** — a Slack shared channel as one participant,
`<id>@external` (Slack only). Anyone writing there reaches the team, registered or not. Each row
@@ -1253,7 +1310,9 @@ your circumstances is configuration, and the ones that are not, another agent ca
restarts the seat in place and prints a restart command for every peer on that home. For a Claude
seat, `--to-account <label> --token-file <absolute path>` moves it to its own subscription token
(from `claude setup-token`, in a file only the user can read); OpenRig keeps the path and label,
never the token, and `--stored-login` moves it back. These seat account and runtime moves were
never the token, and `--stored-login` moves it back. On a stopped Claude seat it relaunches nothing: it
saves the reference only when the seat's next launch would resume the conversation, and names that
`rig launch`. These seat account and runtime moves were
tested with stubs, not real accounts, before 0.7.2: check a moved seat's account and conversation
yourself after the first move.
- **Move to an existing account home** with `rig provider switch --seat <seat> --to-account <name>
@@ -1271,6 +1330,10 @@ your circumstances is configuration, and the ones that are not, another agent ca
never overrides a user-declared table; see `docs/reference/agent-spec.md`.
- **`rig env`** — the real services behind a rig: are they up, what are they saying, how do you
stop them without killing the rig.
- **`rig environment`** — not `rig env`: places a worker seat in an NVIDIA OpenShell sandbox and returns
verified result files (`setup`, `create` or `attach`, `export`; reference in the packaged
`daemon/assets/openshell/README.md`). In 0.7.8's release testing the worker never became usable, so
read the installed version's release notes before relying on it.
- **`rig setup`** — what OpenRig would change about this machine, shown before it touches anything.
It installs herdr by default on macOS and Linux (`--no-herdr` declines; if the install fails,
views fall back to plain tmux) and on a Mac offers Ghostty once (`--ghostty` / `--no-ghostty`).
@@ -1373,6 +1436,10 @@ your circumstances is configuration, and the ones that are not, another agent ca
own risk.** Local assistants can connect locally. Internet access needs your own TLS tunnel or
proxy and a narrowly scoped, revocable token; see `reference/hosted-members.md`. Task terminal
following does not require internet exposure.
- **`reference/daemon-http-clients.md`** — for a program that calls the daemon's HTTP API instead of
the CLI. It covers the registered people, a person's open updates, a rig's outbox (every seat's
messages, newest first) in one request, Slack channels and people without holding the bot token, and opt-in decision revisions,
where a stale `expectedRevision` gets 409 and writes nothing.
- **`rig skill loadout --runtime <claude-code|codex>`** — inspect the exact catalog revision,
selectors, target, and current/missing/shadowed/conflicting state for one working directory.
`--apply` writes only the managed ownership set, is idempotent, and refuses local edits or
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@openrig/daemon",
"version": "0.7.1",
"version": "0.7.9",
"private": true,
"license": "Apache-2.0",
"type": "module",
@@ -5,6 +5,7 @@ import type { PersistedEvent } from "./types.js";
import type { StreamItem, StreamStore } from "./stream-store.js";
import type { QueueRepository } from "./queue-repository.js";
import { SettingsStore } from "./user-settings/settings-store.js";
import { RUNNER_PERMIT_TAG } from "./runner-permissions.js";
export const STREAM_INTAKE_TAG = "stream-subscription";
@@ -312,6 +313,12 @@ export class StreamIntakeCoordinator {
);
return;
}
// OpenRig's record of a permit it decided by default (#2361) is pull-only: "A record wakes no one". Read past it with
// no row, note or wake.
if (item.hintTags?.includes(RUNNER_PERMIT_TAG)) {
this.writeCursor(item.streamSortKey);
return;
}
const isUrgent = item.hintUrgency === "critical" || Boolean(item.interrupt);
@@ -5,6 +5,7 @@ import { StreamStore } from "../src/domain/stream-store.js";
import { QueueRepository } from "../src/domain/queue-repository.js";
import { streamIntakeQitemId, StreamIntakeCoordinator, STREAM_INTAKE_TAG } from "../src/domain/stream-intake.js";
import { OutboxHandler, WAKE_INTENT_PREFIX } from "../src/domain/outbox-handler.js";
import { RUNNER_PERMIT_TAG } from "../src/domain/runner-permissions.js";
import { migrate } from "../src/db/migrate.js";
import { ALL_MIGRATIONS } from "../src/db/all-migrations.js";
@@ -1063,3 +1064,97 @@ describe("StreamIntakeCoordinator", () => {
db.prepare = origPrepare;
});
});
// #2361's record of a permit OpenRig decided by default is pull-only: it is written as runner-permissions.ts writes it (a low
// observation tagged RUNNER_PERMIT_TAG), and intake must never turn it into a row or a wake.
describe("StreamIntakeCoordinator with default-permit records", () => {
const permit = (streamStore: StreamStore, streamItemId?: string) => streamStore.emit({ ...(streamItemId ? { streamItemId } : {}),
sourceSession: "dev-runner@perm-rig", body: "OpenRig permitted a tool call by default", hintType: "observation", hintUrgency: "low",
hintTags: [RUNNER_PERMIT_TAG, "node:n1"] });
const cursor = (db: Database.Database) =>
(db.prepare("SELECT last_sort_key FROM stream_intake_cursor WHERE id = 1").get() as { last_sort_key: string } | undefined)?.last_sort_key;
const coordinator = (s: ReturnType<typeof setup>) =>
new StreamIntakeCoordinator({ db: s.db, eventBus: s.bus, streamStore: s.streamStore, queueRepo: s.queueRepo, destination: "queue.worker" });
const settle = () => new Promise((r) => setTimeout(r, 50));
it("live: a permit record makes no intake row and wakes no one, the cursor moves past it, and a later observation is routed", async () => {
const s = setup();
const wake = vi.spyOn(s.queueRepo, "deliverWakeForSuccessor");
const c = coordinator(s);
c.start();
try {
await settle();
const record = permit(s.streamStore);
await settle();
expect(s.queueRepo.list({ destinationSession: "queue.worker" })).toHaveLength(0);
expect(wake).not.toHaveBeenCalled();
expect(cursor(s.db)).toBe(record.streamSortKey);
const ordinary = s.streamStore.emit({ sourceSession: "dev-build@starter", body: "An ordinary observation" });
await settle();
expect(s.queueRepo.list({ destinationSession: "queue.worker" }).map((row) => row.qitemId)).toEqual([streamIntakeQitemId(ordinary.streamSortKey)]);
expect(wake).toHaveBeenCalledTimes(1);
} finally {
c.stop();
}
});
it("restart and catch-up: permit records stored while intake was stopped make no row or note, and the cursor moves past them", async () => {
const s = setup();
s.db.prepare("INSERT INTO stream_intake_cursor (id, last_sort_key, updated_at) VALUES (1, '', ?)").run(new Date().toISOString());
permit(s.streamStore);
const ordinary = s.streamStore.emit({ sourceSession: "dev-build@starter", body: "An ordinary observation" });
const last = permit(s.streamStore);
const c = coordinator(s);
c.start();
try {
await settle();
const rows = s.queueRepo.list({ destinationSession: "queue.worker" });
expect(rows.map((row) => row.qitemId)).toEqual([streamIntakeQitemId(ordinary.streamSortKey)]);
expect(s.queueRepo.listTransitions(rows[0]!.qitemId)).toHaveLength(1);
expect(cursor(s.db)).toBe(last.streamSortKey);
} finally {
c.stop();
}
});
it("a repeated permit record stays quiet, live and after a restart", async () => {
const s = setup();
const c = coordinator(s);
c.start();
await settle();
permit(s.streamStore, "permit-1");
permit(s.streamStore, "permit-1");
await settle();
c.stop();
const again = coordinator(s);
again.start();
try {
await settle();
expect(s.queueRepo.list({ destinationSession: "queue.worker" })).toHaveLength(0);
} finally {
again.stop();
}
});
it("ordinary observations still coalesce into the pending intake row around a permit record", async () => {
const s = setup();
const c = coordinator(s);
c.start();
try {
await settle();
s.streamStore.emit({ sourceSession: "dev-build@starter", body: "First observation" });
await settle();
const [row] = s.queueRepo.list({ destinationSession: "queue.worker" });
const notesBefore = s.queueRepo.listTransitions(row!.qitemId).length;
permit(s.streamStore);
await settle();
expect(s.queueRepo.listTransitions(row!.qitemId)).toHaveLength(notesBefore);
s.streamStore.emit({ sourceSession: "dev-test@starter", body: "Second observation" });
await settle();
expect(s.queueRepo.list({ destinationSession: "queue.worker" })).toHaveLength(1);
expect(s.queueRepo.listTransitions(row!.qitemId)).toHaveLength(notesBefore + 1);
} finally {
c.stop();
}
});
});
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@openrig/ui",
"version": "0.7.1",
"version": "0.7.9",
"private": true,
"license": "Apache-2.0",
"type": "module",