A Codex seat that hands over to a new occupant keeps OpenRig's guidance. Since #2441, a fresh Codex launch carries the guidance as launch developer instructions instead of writing AGENTS.md, but a fresh or rebuild handover successor launches without them, so it started with no guidance (a fork very likely too; the put-back covers it). Before the successor launches, the handover now puts the seat's missing guidance blocks back in the repository AGENTS.md from its saved startup context, using the same steps restore uses: only missing blocks are written, the person's text around them is unchanged, and a seat that already has them, or has no saved context, is left alone. A discovered handover launches nothing and is skipped. The startup guide lists a handover successor among the cases that still use the repository AGENTS.md. Part of #875, which stays open.
docs/reference/agent-startup-guide.md gains a "Codex guidance at launch" section. On a fresh launch of Codex 0.164.0 or later, with no developer_instructions in a config layer OpenRig reads, a seat's managed guidance is passed as launch developer instructions, and neither the tracked AGENTS.md nor the Codex home is written. The section lists the layers read and the home used, and when the repository AGENTS.md is still used: a configured or unreadable layer, a resume or fork, a restore (even one that starts fresh), and an older or unknown Codex. If your shell alone sets CODEX_HOME, declare that same absolute path in the member's or rig's env:, so OpenRig checks it and a configured value keeps the repository file. Packaged, MDM and enterprise-managed settings aren't read and may be replaced, and legacy MDM config can override the guidance. The delivery table and the Pi and OMP lines now match where their guidance goes. Part of #875, which stays open.
`rig seat set-typing-guard` now works from an ordinary terminal. Without a seat identity, a valid request applies, and the preference and its audit entry record the actor as `unattributed (no seat identity)`. With one, attribution is unchanged, and neither the request body's actor nor the target seat's name stands in for it. Target, mode and reason validation, activation, pending changes and held messages are unchanged, and there's no migration. The command's help and docs/reference/seat-delivery.md describe both cases. Reported in #2350 by @Aschenbath.
A fresh Codex seat's managed guidance now arrives as a launch developer instruction instead of being written into the person's tracked AGENTS.md. On a fresh launch with Codex 0.164.0 or newer and no developer_instructions configured in any layer, OpenRig passes the guidance with -c developer_instructions, and the guidance tells Codex to follow AGENTS.md where they conflict. With no Codex home declared, it reads ~/.codex. Resume, fork, older or unknown Codex versions, a configured value, and a CODEX_HOME set only in a shell profile keep today's repository file. Part of #875, which stays open.
A long one-line delivery to a Claude seat is now submitted when Claude wraps it short of the input box's width. The check that the input holds only the delivery's own text accepts Claude's wrap points, up to 6 columns short, and each row must still match exactly. Codex is unchanged. Reported in #2429 by @billbenetti.
A seat that starts with held messages is now told about them once: it gets one notice saying how many were kept and not delivered, and to read them with `rig seat held-messages <seat>`. The same line appears in the launch's warnings. Nothing is replayed. Handover and runtime-switch successors don't reach this point yet, and a follow-up gives them the same notice. Reported in #2431 by @billbenetti.
A watchdog job now holds at most one reminder per seat: holding a newer reminder retires that job's earlier held one, inside the retention transaction and before the capacity check, so it frees the slot even at a full quota. A queue handoff (`rig queue handoff`, and handoff-and-complete) is no longer blocked by the recipient's full held quota: it stages its wake in a savepoint, so a full quota records that the wake wasn't retained on the successor and the handoff commits, while any other staging failure still rolls it back. That deliberately changes "one act or none" for this one failure. Mission control's route and handoff verbs and workflow step projections still roll back on a full quota; a follow-up gives them the same savepoint. Reported in #2430 by @billbenetti.
`rig scope` now gives each slice one standing and counts it once. A slice in `closed/` is closed; one whose status is shipped* or done is shipped; one whose status is closed* or superseded, or whose stage is superseded or retired, is closed; everything else is active, blocked included. `slice ls --state` filters on that standing (the default stays `active`, and the help says what each value means), and `mission ls` and `mission show` count by it, with a new `shipped=` count, so a mission whose slices all shipped no longer reads as all active. A malformed slice file reads as empty frontmatter instead of throwing. The advisory dependency graph reads the active set, so it no longer offers a superseded slice as ready to build. The daemon's copy of the scope types matches the CLI's.
Reported in #2427; thanks to @gifflen.
A Claude seat no longer loads openrig-core's skills twice. Each seat's own `openrig` skill plugin received a copy of every selected plugin's skills, and the seat also loads the vendored `openrig-core` plugin directly for its hooks, which includes its skills. The copy is now skipped exactly when that direct load applies (a Claude seat, the vendored manifest present, and a selection that is `<plugins>/openrig-core`). Both routes reach Claude through `CLAUDE_CODE_PLUGIN_DIRS`, so no skill is lost. Existing seats change at their next fresh launch. Codex is unchanged.
Thanks to @billbenetti for the report.
The draft-aware typing guard's input reader treated a Claude prompt suggestion (ghost text) as a person's draft unless every cell, spaces included, was dim, so a multi-word suggestion with plain spaces held every send and wake. It now uses the rule `rig capture`'s classifier already applies: a cursor-to-end suffix is a suggestion when every non-space character is dim, and any undimmed word keeps it a draft. Text before the cursor stays authored input. The test uses a retained Claude 2.1.296 frame with synthetic dim styling, not a capture of the reporter's 2.1.316 session; grey text without SGR dim is outside this change.
Reported in #2428 by @billbenetti.
A handover whose source OpenRig launches (fresh, fork or rebuild) records its successor session as launched, in the same transaction that registers it, as a runtime switch already does. `rig seat stop`, `restart` and `clean` then treat the seat like any seat OpenRig launched, instead of refusing it as "adopted, not launched by OpenRig". A discovered-source handover, and genuine `claim`, `adopt` and `attach` sessions, stay adopted and are still refused. Tenure, the reserved occupant generation, predecessor supersession and events are unchanged. Seats handed over before this keep their adopted origin until their next launched handover or managed launch.
A Pi seat's managed guidance now goes to its own agent folder (`PI_CODING_AGENT_DIR`'s `AGENTS.md`), so a person's tracked repository `AGENTS.md` stays byte-identical across launch, `rig down`, restore and exact resume. A fresh launch asks the adapter once and records the answer in a new nullable `node_startup_context.guidance_file` (migration 146); restore, the exact-resume put-back, teardown, inventory and snapshots all read that one target through `seatGuidanceFile`. Rows and snapshots recorded before this keep the repository file. An exact restore of an older snapshot records that snapshot's choice, and a symlink or hard link introduced after launch is reported as a gap rather than written through. Codex, OMP and Claude are unchanged; this does not stop Codex's tracked-file writes, and #875 stays open.
A bounded `rig queue show` body now ends with `…[cut at N of M chars; rig queue show '<id>' --full]`, so a reader that keeps only `.body` can see that more text exists and how to get it. Counts are code points, like the preview; the row ID is shell-quoted. `bodyTruncated`, `bodyBytes`, `readView`, the stderr notice and `--full` are unchanged, and short or empty bodies are untouched.
Thanks to @genius8267.
A click on a re-parked Slack ask records the person's new choice for that park and wakes the agent, instead of silently resuming it with the person's earlier answer. The click branch looks up the current park's answer, as typed replies already do; a direct answer to a blocked ask, typed or clicked, carries the park's episode key, so a re-park's click gets its own answer row, which is the owner's wake; and a complete stored set is final only for a pending ask, or for a park this person has already answered, with other questions' answers carried forward as on a typed re-park. The unused typedReply input flag is removed. The committed-answer guard for another person's answer to the current park and the winner check are unchanged, and a pending ask's clicks behave as before.
Source +11/-7 across three files; the pinned re-park click test now expects the new answer, plus one cell for a re-park over a complete set.
For an ask to, or a park on, a registered person with no connector binding, the Feed's detail now reads "Slack: not sent: this person has no connector binding, so they're asked on Steering and in the Feed only", and its route line says "answer on Steering; it wasn't sent to Slack", the wording quiet asks already use. A real Slack receipt still takes precedence, and a Slack-bound person's wording is unchanged. Wording and row selection only, local to the attention route.
routes/attention.ts +16/-5; one test cell.
A rebuild handover's receipt now says what happened. sideEffects.provenanceRecordWritten is measured: commit() reads the node's occupant tenure before and after recording the handover in its own transaction, and reports true when a new handover-kind tenure exists for the reserved generation; a swallowed mint failure reads false. A new sideEffects.departingProcess reports ended-graceful, ended-forced or not-touched, from the in-place termination the successor launch already performs. departingSessionKilled stays false, which is accurate: the tmux session is kept and the pane respawns. The CLI's human lines and the seat-continuity-and-handover skill now say whether the lineage record was written, how the old process ended, and that a bound seat whose record is missing shouldn't be handed over again.
7 files, +125/-20, most of it tests.
An environment seat's startup identity, selected role and context references, and text actions travel to the guest through a hash-checked first-input file, with a bounded initial prompt pointing to it; nothing is typed through the host viewer, so startup text can't reach the host shell if the viewer exits. Readiness is the guest execution's identity, generation and running state; only environment connect execs; the viewer does one live identity and generation lookup (driver 30 s, client 35 s) instead of serial file validation, while launch and the guest latch still validate. A failed owned delete reports its observed stage and error and whether dispatch is unknown, keeping the original intent, claim and same-operation reconciliation. An older guest helper's rejection of first input names the likely outdated image and the rebuild step.
Limits: a same-generation reconciled launch opens no viewer, and a non-text interactive startup action on an environment seat fails without host fallback.
313 lines added and 18 removed across 15 files, about two thirds of them tests.
The dsh example runner reads DSH_SEAT_BIN and DSH_SEAT_HOME, which a member's env: can set (OPENRIG_* stays reserved for OpenRig's own per-launch values and is still read first when exported). It doesn't read dsh's own DSH_HOME: a seat's shell runs the person's startup files after the seat's env:, so an exported DSH_HOME would rewrite their personal dsh profile and skip the uploads-off patch; any inherited DSH_HOME is still replaced with the seat's home. The launch record's producer takes dsh's version from dsh --version, killed with SIGKILL at a 5-second timeout so a wrapper that ignores SIGTERM can't hold the seat's start, falling back to the handshake's version, labelled as such. Not verified: a real dsh's --version output and whether it makes a network call.
Runner +23/-6; README, one preset limit line and the dsh example test.
Any registered person in a shared workspace settles another person's ask as themselves, from the Feed, Steering, the TUI or Slack, and the record keeps who answered whose ask. The first committed answer is the answer on every surface and in either order: a later answer from someone else gets 409 qitem_already_terminal with answeredBy and records nothing, a click honours an answer already committed (on a pending ask or a park's current question), an interrupted close finishes as the first answerer, and a typed reply routed to a person who isn't the asker stays correspondence. A person may have no connector binding (rig gateway human add without --binding): they load through every registry reader, are reached in the Feed and Steering only, get no Slack post, and their asks never read never-posted or undelivered. The terminal's primary-user act is recorded as human@host, unattributed, whoever is registered.
Known limit: a click's option written before its answer row (and, on an ask with several questions, partial clicks completed or closed by another person) can stand beside or inside the closer's answer without the first clicker's authorship.
Source +210/-90, tests +455/-119.
The kernel hook-review reader now recognises a managed Codex seat whose pane reports the launcher shell: OpenRig runs managed Codex through a staged `/bin/sh` script, so the pane command reads `sh` and the reader's `codex` check never passed. For a non-`codex` pane it now requires the existing `verifyCodexPaneProcess` proof before and after the screen capture, with the same pane PID and lineage fingerprint, and keeps every binding, session, pane and dead check; an unverified chain stays unknown and neither raises nor clears an escalation. The direct `codex` path is unchanged. Nothing approves, edits or trusts hooks. The scheduler's tmux dependency gains `getPanePid`. Source proof, not a native pass.
Daemon +14/-5, tests +65.
For You shows Approve only on an ask with no structured questions, or a runner permission request, where a close means permitted. For an ask with questions it says to answer in the TUI (`rig tui`) or tell the agent in Chat, so the request stays open; an ask from a remote host names that host and doesn't suggest Chat. "Has questions" is a non-empty `humanQuestions`, the rule the daemon's attention view already uses, and the card's payload now carries the ask's questions and tags from the start. This is a UI guard only: the action API still accepts approve.
Reported in #2406. Contributed by @w4rc0n. UI +29/-7, tests +86/-1.
The web UI's maintenance notice now renders in its own row above the header rather than over it, so the header's name, host label and Theme control are visible on first visit; dismissing it gives the space back. The notice and its existing dismissal storage move out of `App.tsx` into `UiMaintenanceNotice`, and the shell, explorer, shared detail drawer and proof image viewer take the notice's height into account on phones, and the phone rail's links stay above the bottom navigation and scroll.
Contributed by @w4rc0n. UI +68/-68, tests +38/-6.
The Dashboard's kernel card uses the UI's surface token instead of a fixed white background, so it follows the dark theme and its heading and status text stay readable.
Contributed by @w4rc0n. UI +3/-3, tests +9.
The Topology table no longer auto-resets its page index when its data changes (`autoResetPageIndex: false`). The table has no pagination, and each reset queued a state change that re-rendered, rebuilt the data and reset again, so one keypress in "Filter agents" kept the tab busy. Filtering, clearing and sorting while filtered now settle. A regression test types into the filter and checks that it settles.
Contributed by @w4rc0n. UI +3, tests +44.
A manual `rig compact` whose idle wait expires now reports `map_complete_seat_never_idle` when a completed restore map exists and `preparation_incomplete` when no completed map remains, matching the automatic preparation-expiry path. Other transport failures, the idle requirement, the timeout budget and command delivery are unchanged. HTTP-route tests cover a complete map, an absent map (the published and `.tmp` paths both removed) and a non-timeout refusal.
Credit to Seán McNamara (@seanmcn) for the timeout-reason mapping in his fork PR #3. Enforcer +5/-1, tests +28.
The OpenShell README's actor-attribution example now uses `operator-agent@kernel`, the kernel operator's actual address (as the Slack CLI's default already names it), in place of `operator@kernel`, which isn't a kernel seat. The surrounding instruction to use the actual acting member's address is unchanged.
Docs +1/-1.
Fact-only corrections so the in-repo docs and `--help` match what 0.7.8 ships: the CLI reference gains the `rig environment` section and the `host edit`, `host remove` and `terminal attach` rows; both contracts link the in-tree presets; the guest listener's permission routes, port and host defaults are stated as they are; status labels say what 0.7.8 ships for grants, the answered permission path and `rig environment`, keeping "no harness end to end" and "native verdict pending"; `rig provider switch --help` says a stopped seat isn't relaunched (it prints the `rig launch` that resumes it) and that the switch asks to keep the running effort, using the saved effort when it can't, unless the seat chose its own; `rig host` help says add, edit and remove rewrite hosts.yaml from the known fields, dropping comments and unknown keys; the capability map's verb count reads 91; and `/abort` and the shipped dsh preset's resume behaviour are described as they are. Section 7's text is unchanged.
Docs and help text +70/-28; no behaviour change.
A newly created default kernel starts in `<workspace.root>/kernel` instead of `workspace.root`, through both creation paths (daemon start and the TUI's first setup), so a Claude kernel seat's managed `CLAUDE.md` no longer lands above the default projects folder and isn't loaded by every project seat. If the folder can't be prepared, the kernel falls back to `workspace.root` with a warning; nothing refuses a boot. Existing kernels keep their stored working folder and restored conversations; nothing is moved or cleaned up. The operator role's working-directory line follows the new folder, and `docs/reference/agent-startup-guide.md` explains the optional move for existing users: a fresh launch starts a new native conversation, custom text in a shared `CLAUDE.md` is kept, and step 3 waits until no running rig's seat still works in the old folder, as `rig down` checks.
Reported by @ch-davidriott in #2390.
Daemon +24/-11, guidance and docs +28/-1, tests +59/-6.
An assistant connected through `rig mcp serve --http --grant-service` can now claim, update and park the rows it owns and see its team, with the scope that show, list and create already apply to external callers: claim and update responses go through `projectForExternal`, so a blocker, closure target or waiting detail the caller can't read stays out; an external owner needs both the exact destination and the existing item scope, so a known id outside the grant gets `out_of_scope` and stays unchanged; an external park must name a `blockedOn` the caller can read, so a bare re-park can't restore or reveal the blocker saved at claim; and `rig_team`'s `lastWake` for a hosted member counts only rows in the caller's item scope. Local and token-grant behaviour and the error codes are unchanged; refusals use the existing `out_of_scope`. `docs/reference/hosted-members.md` describes the loop.
MCP server +51/-16, docs +32, tests +329.
Pi's stream heartbeat (#2336, by @porterchild: one 5 s tick, a beat once 5 s pass since the runner's last post, stopping when streaming clears or the runner exits) now posts the runner contract's working beat (section 4): `active` / `liveness` with the open run's `turnId` and `livenessSeconds: 30`, in place of `active` / `streaming`. The Pi runner now posts `UserPromptSubmit` / `accepted` with the next `turnId` for each prompt it gives Pi: a prompt or `/followup` sent into an open run is posted as it's sent, and a prompt sent while idle is posted when its run opens, so a command Pi handles without a run (such as `/model`) opens no turn. `/abort` opens no turn, and OMP is unchanged.
Named limit: after a daemon restart in the middle of a run, the run's beats are ignored until the next prompt.
Pi runner +30/-4, tests +145/-19.
docs/reference/slack-app-setup.md's channel-map section said missed-message recovery covers the default channel only. Since #1536 it recovers the default channel and every mapped channel, except a channel relayed to another machine, as the guide's reconnect-recovery sections already say. The historical 0.6.12 release note is unchanged.
Docs +1/-1. Reported by @ziadeh.
The dsh example runner now posts the runner contract's working beat (section 4): while a turn runs and nothing waits on a person, it posts `active` / `liveness` every 10 s with the turn's `turnId`, `livenessSeconds: 30` and its rising `sequence` and `occurredAt`, and stops at the turn's `Stop` and at dsh's exit. It pauses from a permission ask until the agent has its answer, and posts `active` / `permission_resolved` with the turn and `askId` once no ask waits and none is being submitted, never after a refusal, a cancel, a permit with no ask open, or dsh's exit. The agent's own reports go out only when a beat could.
The conformance check's cases 4b and 7 now count only the non-exit `Stop`s posted after the turn's accepted prompt, ordered as OpenRig orders them (`occurredAt` when it reads as a time, else arrival; then a positive `sequence`), so a runner's idle report from before the turn is no longer a false failure. The check still doesn't validate those two fields' types, and it is stricter than OpenRig for a `Stop` that precedes a later post that arrived first.
Example and check +69/-16, tests +213/-16.
A restore whose snapshot has no saved startup context reaches #2338's existing hook recovery only for a legacy (non-pod) seat whose live runtime still matches the snapshot; a changed runtime or a pod-aware seat gets no new hooks write, and the restore itself is unchanged. Every restore step that uses the seat's directory now takes the launch directory (the snapshot's cwd, else the live node's, as the launcher does): the projected hooks, the startup replay's plan and binding, the legacy resume's managed launch and read policy, and both saved-guidance checks. A snapshot with no cwd therefore no longer writes the context collector or settings into the daemon's own folder, or prepares a resume at the filesystem root. Seats whose snapshot has a cwd are unchanged.
Restore orchestrator +48/-12, tests +232/-6.
The queue list route accepts a sort by last update, and the TUI uses it for JUST FINISHED on Pulse and RECENTLY FINISHED on an agent's page, so those lanes hold the items closed most recently instead of those created most recently. It is a read order only: no queue state, write or delivery path changes, and the default list order is unchanged.
Source +13/-10, tests +33/-8.
The transcript store's forward reader keeps an unfinished line as fragments until a newline arrives, instead of prepending it to each new 64 KiB chunk and splitting the whole result again. A very long captured line is therefore scanned once rather than once per chunk, so `rig transcript --grep` holds the daemon for less time. The lines it yields, and so the grep output, are unchanged.
Transcript store +11/-5, tests +14/-0.
`rig tui`'s Pulse shows each JUST FINISHED row's clock in the TUI's configured time zone (`ui.timezone`), through the existing display-time helper, rather than as UTC with no zone mark. Other Pulse lanes and the stored timestamps are unchanged.
TUI +12/-16, tests +23/-2.
The workspace frontmatter validator reports an opened but unclosed frontmatter block, and a YAML sequence instead of a metadata mapping, as a parse-error gap, including when no workspace kind is selected. An unclosed block is recognized as present rather than silently treated as body text. The report path, gap kinds and HTTP response are unchanged, so `rig workspace validate` now exits 1 for such a file through its existing gap exit status; nothing else gates on it.
Validator +4/-3, tests +50/-0.
`rig scope ... progress` edits only the tasks a reader sees. `--set` no longer changes or counts example checkboxes inside fenced code blocks, and a text found only inside an example takes the existing no-match error, leaving the example untouched. `--add` ignores example headings and rows when it picks a section, and inserts before an unfinished trailing fence rather than inside it. The fence rules match the progress indexer's.
Command +36/-7, tests +42/-0.
`rig down` removes OpenRig's managed blocks from the guidance file of every seat runtime that writes one, now including Pi and omp (AGENTS.md), with the same target used for teardown, live-file protection, exact resume and node inventory. Once a working folder has no live Claude seat of any rig, down also removes OpenRig's own Claude additions there: the collector status line and the relay hook groups in .claude/settings.local.json (matched exactly as launch claims them, keeping other hooks and fields), and .openrig/context-collector.cjs when its bytes still match the shipped asset. Empty .openrig and .claude folders are removed without recursion, and settings.local.json is kept as {}. Edited, unreadable and still-shared files, including targets another live seat reaches through a symlink or hardlink, are kept and reported. An exact resume puts its activity hooks back.
Source +211/-64, tests +248/-8, docs +4/-2.
The /broadcast route adds its unsigned-sender notice only when the broadcast reached at least one recipient, counting sends a recipient holds for later delivery (sent + retained > 0). When every recipient holds the text, the notice begins "Held for delivery" rather than "Delivered"; when it reached nobody, there is no notice. The CLI prints the route's warning as before.
Source +9/-4, tests +31/-0.
A harness runner may post `active` / `liveness` beats while a prompt is outstanding and the agent it started is alive, so a long tool or model call keeps the seat reading working. A beat counts only when it carries the seat's live generation, names the latest accepted turn that hasn't stopped, carries a sequence above every earlier counted post, and the seat has no attention outstanding; otherwise it is ignored, and it never starts or extends a turn or clears attention. After delivering an answer or permit, a runner posts `active` / `permission_resolved`, which clears attention. Section 4 of the runner contract documents both from 0.7.9; section 7 is unchanged. The conformance checker gains the beat cells. A runner that sends no beats behaves as before.
Source +181/-8, contract and conformance kit +100/-2, tests +313/-7.
`rig seat set-runtime`'s refusal for a seat with no bound tmux pane now says what to do: start a stopped seat first, then switch it; for a seat that never started, declare the other runtime for the member in its rig spec and bring the team up again from it, which makes a new generation without the seat's edges, settings or queued work. `rig seat set-runtime --help` says the same. The existing refusal passes one guidance line; nothing changes in how a seat switches or starts.
Source +11/-2 (text and help), tests +6.
`rig queue create`, `handoff` and `handoff-and-complete` lowercase a documented --priority (routine, urgent, critical) given in another case, so `--priority Critical` files as critical. Any other value is kept exactly as given, with one note on stderr that it is outside the documented choices. create still defaults to routine, and a handoff with no --priority still sends none, so it inherits. --tier is unchanged.
Command +14/-3, tests +56/-3.
`rig queue list --state` warns on stderr about any name that isn't a queue state, naming the supported states, then sends the query exactly as before. A mixed filter still returns its known-state matches; an all-unknown filter still returns nothing, and its warning says an empty result doesn't mean an empty queue. Stdout, --json, the empty and default filters and the HTTP API are unchanged.
Command +13/-0, tests +55/-0.
`rig export <rig>` with no -o no longer replaces an existing rig.yaml. It claims the next unused sibling (rig-export.yaml, then rig-export-2.yaml and so on) by creating it exclusively, writes the export there, and prints the path only after the write succeeds. An explicit -o keeps its replacement behaviour, including -o rig.yaml, and filesystem or export failures still exit nonzero.
Command +39/-5, tests +79/-1.
0.7.7 is published, so main's CHANGELOG records it: the [0.7.7] section reads published 2026-11-02 as @openrig/[email protected], built from the commit tagged v0.7.7, and the 0.7.7 status paragraph inside [0.7.8] no longer says it isn't published. [0.7.8] itself stays not yet published. docs/reference/whats-new.md, versions and pins stay as main has them.
CHANGELOG.md +2/-1.
When a person answers a runner's permission ask in words that are neither a choice nor a plain yes or no, the read result (`GET /api/activity/permission-requests/<askId>`) now carries those words, whole, as `said`, beside `permitted` (closed without a choice) or `refused` / `no_allow_offered`. The words come from the row that holds the answer, so a colleague's words count when the ask was routed to them. A plain yes or no keeps today's mapping with no `said`, and words are never read as a no.
The harness runner contract's section 7 gains two passages describing `said` and how a runner delivers it: queued for the agent as an ordinary prompt, after the current turn and behind any prompt already waiting, never as the answer. A runner that drops them still keeps the contract. The dsh example runner and the minimal ACP conformance fixture deliver the outcome as before and queue the words; the conformance check covers it.
10 files: runtime +20/-4; the dsh example runner and the conformance fixture +18/-2; the contract, the dsh README, the conformance README and check +34/-6; tests +61/-7.
The OpenShell guest reader opened `/` before walking to the selected workspace, which a sandbox that grants only the workspace refuses, so a worker never started. It now anchors descriptor traversal at the selected root, with no ancestor opens, and keeps the selected-path symlink checks and `O_NOFOLLOW` reads beneath it.
When a resource is created but projection fails before a generation exists, `rig environment` can now delete that owned resource by its selected, immutable resource id, with no generation or node binding required. Attached resources are refused before any mutation, the one-dispatch journal and dependent cleanup are kept, and the selection is checked again after the driver's final identity call. Uncertain or lost replies still require observed absence.
The example `rig.yaml` gains its required pod label and a pod-local delegation edge, and validates through the normal parser and plan path. The CLI help and the OpenShell README describe cleanup by resource id, `OPENRIG_SESSION_NAME` as attribution rather than authentication, and guest-reachable listener addressing.
Runtime +30/-14, tests +165/-2 across five files. No new error code, dependency, migration or policy.
Carries the 0.7.8 section of `CHANGELOG.md` from `release/0.7.8-prep` (16121548) onto main byte for byte, adding only a "Not yet published" line, plus the 0.7.7 status paragraph from its release source. Neither version is dated or marked shipped; each label changes when that version is published. `docs/reference/whats-new.md`, versions and pins stay as main has them.
CHANGELOG.md +167/-0.
`rig specs ls`, `show` and `preview` accept any --kind. For a value other than rig, agent or workflow, the command prints one stderr warning naming the supported kinds, still queries the daemon with the value URL-encoded, and keeps only the entries whose kind matches exactly. A known kind trusts the daemon's filtered list as before, so `rig up`'s library lookup is unchanged. When a --kind filter matches nothing, `specs ls` prints "No <kind> specs found" instead of the empty-library message. The warning goes only to stderr, so stdout and --json stay parseable.
Command +35/-10, tests +105/-1.