# RigSpec Reference Version: 0.2 (pod-aware) Last validated against code: 2026-10-07, at the 0.6.6 cut `2620dea8` (whole document at `fcaf1f8e`; changes to its sources since then checked) Source of truth: `packages/daemon/src/domain/rigspec-schema.ts`, `packages/daemon/src/domain/types.ts`, `packages/daemon/src/domain/launch-wrapper.ts`, `packages/daemon/src/domain/startup-validation.ts`, `packages/daemon/src/domain/permission-policy/policy-ref.ts`, `packages/daemon/src/domain/profile-resolver.ts`, `packages/daemon/src/domain/rigspec-preflight.ts`, `packages/daemon/src/adapters/kernel-authority.ts`, `packages/daemon/src/domain/native-permission-store.ts`, `packages/daemon/src/domain/codex-team-workspace.ts` This is the canonical reference for the pod-aware RigSpec YAML format. Every field, validation rule, and default documented here was traced from the actual parser and validator code, not from prior documentation. --- For choosing IDs, see [naming rigs, pods and seats](topology-naming.md). Keep the same logical names in edges, startup instructions, workflow targets and runtime presets. ## Minimal Valid Example ```yaml version: "0.2" name: my-rig pods: - id: dev label: Development members: - id: build agent_ref: "local:agents/impl" profile: default runtime: claude-code cwd: "." edges: [] edges: [] ``` ## Complete Example (all features) ```yaml version: "0.2" name: my-product-team summary: A full product squad with orchestration, development, and review pods. culture_file: culture/CULTURE.md docs: - path: SETUP.md - path: README.md startup: files: - path: guidance/team-norms.md delivery_hint: guidance_merge required: true actions: [] services: kind: compose compose_file: docker-compose.yaml project_name: my-product profiles: [core] down_policy: down wait_for: - url: http://127.0.0.1:5432/health - service: redis condition: healthy surfaces: urls: - name: App url: http://127.0.0.1:3000 commands: - name: psql command: "psql postgresql://app:dev@127.0.0.1:5432/app" checkpoints: - id: postgres export: "docker compose exec -T postgres pg_dump -U app > {{artifacts_dir}}/postgres.sql" import: "cat {{artifacts_dir}}/postgres.sql | docker compose exec -T postgres psql -U app" pods: - id: orch label: Orchestration members: - id: lead agent_ref: "local:agents/orchestrator" profile: default runtime: claude-code cwd: "." - id: peer agent_ref: "local:agents/orchestrator" profile: default runtime: codex cwd: "." edges: [] - id: dev label: Development summary: Implementation and quality assurance pair. continuity_policy: enabled: true sync_triggers: [pre_compaction, pre_shutdown] artifacts: session_log: true restore_brief: true restore_protocol: peer_driven: true verify_via_quiz: false startup: files: - path: guidance/dev-sop.md delivery_hint: guidance_merge required: true actions: [] members: - id: build agent_ref: "local:agents/impl" profile: default runtime: claude-code cwd: "." label: "Implementation Lead" model: claude-opus-4-6 restore_policy: resume_if_possible startup: files: - path: guidance/impl-specific.md delivery_hint: send_text required: false applies_on: [fresh_start] actions: - type: send_text value: "Read your guidance and begin." phase: after_ready idempotent: true - id: qa agent_ref: "local:agents/qa" profile: default runtime: codex cwd: "." edges: - kind: delegates_to from: build to: qa - id: rev label: Review members: - id: r1 agent_ref: "local:agents/reviewer" profile: default runtime: claude-code cwd: "." - id: r2 agent_ref: "local:agents/reviewer" profile: default runtime: codex cwd: "." edges: [] edges: - kind: delegates_to from: orch.lead to: dev.build - kind: delegates_to from: orch.peer to: dev.qa - kind: can_observe from: rev.r1 to: dev.build - kind: can_observe from: rev.r2 to: dev.qa ``` --- ## Top-Level Fields | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `version` | string | yes | — | Use `"0.2"`. Any non-empty string validates; a spec is treated as pod-aware because `pods` is an array. | | `name` | string | yes | — | Rig name. Used in session naming (`{pod}-{member}@{name}`), snapshot identification, and spec library lookup. `rig up --name ` launches the spec under another name instead (see below). | | `summary` | string | no | — | Human-readable description. Shown in the spec library, review surfaces and `rig specs preview` (in `rig specs show` only with `--json`). | | `culture_file` | string | no | — | Relative path to a rig-wide culture/constitution file. Must be a safe relative path (no `..`, no absolute). | | `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`, 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. | | `services` | ServicesBlock | no | — | Optional managed services (Docker Compose). When present, `rig up` boots them before any agent launches; `rig import` doesn't boot them. | | `pods` | Pod[] | yes | — | At least one pod required. Each pod is a bounded context containing members and pod-local edges. | | `edges` | CrossPodEdge[] | no | `[]` | Cross-pod edges connecting members in different pods. Must use fully-qualified `pod.member` IDs. | **Launching one spec under another name.** `rig up --name ` creates the rig as `` instead of the spec's `name:`, so one spec can run as several rigs at once, for example `--name alpha` and `--name beta`, with seats `{pod}-{member}@alpha` and `{pod}-{member}@beta`. The spec file is not changed. The rig keeps the chosen name from then on: `rig ps`, `rig down alpha`, `rig up alpha --existing`, snapshots and restore all use it. The same checks as a launch without `--name` apply to the chosen name: a running rig of that name refuses the launch, and a stopped one is archived and replaced, as an explicit spec import does today. `--name` does not rename an existing rig, so the CLI refuses it with the name of an existing rig to restore, with a topology and with a GitHub bundle link. A library spec name works: `rig up starter --name beta` launches the library spec as `beta` even while a rig named `starter` exists. Over `--host`, pass the spec path or bundle file with `--name`. A spec that sets `services.project_name` refuses `--name`: every rig launched from it would share that Compose project, and `rig down` or a failed service boot of one would stop the others' services. Without `project_name`, each rig gets its own project. Two launches of the same spec started at the same moment can answer 409 (already in progress for this source) even under different names; start them one after the other. ### Starting one materialized seat `rig import ./team.yaml --materialize-only` records the team without starting its harnesses. For a seat that has never started, use its positional logical ID: ```bash rig launch dev.lead --json ``` This starts only that seat, preserving the rig's node IDs and the other seats. The daemon retains the materialized declaration and resolves its agent and startup files from the original source root at launch time; keep those files available. Current saved model, effort and permission choices take precedence over the retained declaration; launch reports the differences. A prior snapshot isn't required. Seats with session history continue through restore. Explicit `--snapshot-id` and `--seats` use restore too. `--plan` previews restore selection; it requires a snapshot and does not preflight this first start. Rigs materialized by older daemons lack the retained declaration; this path doesn't retrofit them. ### Attaching a permission policy Attach a policy to a rig with `permission_policy`, either at the rig level or on a member: ```yaml # a built-in, by name: permission_policy: builtin:standard # or a custom policy file, by relative path (resolved from this spec's directory): permission_policy: policies/my-cautious-dev.policy.md ``` Built-in policies (`locked` / `standard` / `open` / `yolo` / `auto`) are read-only and are referenced as `builtin:`. A custom policy lives in your own project and is referenced by a safe relative path (no `..`, no absolute). A shipped example of the custom shape is `packages/daemon/policies/examples/my-cautious-dev.policy.md` — copy it into your project and edit it to taste. This records a selection, not a live permission change. Flag-surface policies select launch flags; config-surface policies still need native configuration application and inspection. In particular, `builtin:yolo` selects Codex's `danger-full-access` sandbox and `never` approval policy, and replaces any `codex_config_profile` argument. See [practical permission choices](getting-started.md#opt-in-permissive-operation). An explicit `rig seat set-permissions` choice overrides member/rig policy for future managed launches of that stable seat; it does not rewrite this spec or its inherited policy provenance. `inherit` removes that override. See [per-seat permission mode](getting-started.md#per-seat-permission-mode). ### Per-member Claude tool rules Claude members can record tool rules independently, even when they share a project folder: ```yaml claude_permissions: allow: ["Bash(node lane-reader.mjs *)"] deny: ["Read(./private/**)"] ``` This field accepts only `allow` and `deny` lists of native Claude rule strings. It is also supported on legacy flat Claude nodes. Other runtimes, `ask`, mode settings, arbitrary settings and parenthesized MCP rules are rejected. Shape validation does not establish support by every Claude version. The initial spec records the rules on the stable seat. For an existing seat, put the complete replacement in a JSON file and use the existing audited permission command: ```sh rig seat set-permissions dev-reader@team --rules member-rules.json --reason "Select the reader lane" rig seat status dev-reader@team --json ``` Choose either `--rules` or `--mode`, never both. Empty allow and deny lists clear the seat's rules. Updating rules leaves the permission mode, running process and history unchanged; they apply at the next separately authorized managed launch or resume. Restore and same-seat handover read that same record. A fork copies the source's currently recorded rules independently, including retained-seat updates. Export contains the current record. An invalid or unreadable record refuses that seat's launch, fork or export with a repair message rather than silently omitting its denies. OpenRig composes these rules into its one operational `--settings` value, preserving team/kernel defaults, hooks and managed-compaction settings. It never writes them into the shared project's Claude settings. Without the field or a recorded update, launch arguments are unchanged. Status reports the next-launch plan and lists the added rules; it does not verify native enforcement. Rules add to inherited native permissions; an allow list cannot withdraw broader user or project grants. In bypass mode, allows do not constrain access. Managed policy and installed mods can change rule behavior; see [Claude's permission precedence](https://code.claude.com/docs/en/permissions#precedence-among-rules-hooks-and-mods). These are native tool rules, not OS-user or credential isolation. Native acceptance and effects must be checked on the Claude version and configuration being used. **Downgrade:** a file-backed SQLite and launch-composition check against the pre-feature reader at `75c1f045` found that it can open the upgraded database but omits the recorded rules from launch arguments. This pair is not restriction-preserving. The stored bytes survive and the supporting reader recovers them; a process already launched by older code is unchanged. Keep a copy of the intended rules and establish equivalent native restrictions before using an older managed-launch path. Other version pairs and native enforcement need their own checks. The launch still uses inline JSON, subject to the operating system's argument-size limits. ### Per-member Codex permissions Codex members can record their own sandbox and approval choice, even when they share a project folder: ```yaml codex_permissions: sandbox: workspace-write # workspace-write or read-only approval: never # never, on-request or untrusted network_access: false # workspace-write only writable_roots: ["/abs/lease"] # workspace-write only; absolute paths exclude_tmpdir_env_var: false # workspace-write only exclude_slash_tmp: false # workspace-write only ``` OpenRig emits this envelope as explicit `-s`, `-a` and `sandbox_workspace_write.*` flags on every managed launch and resume of the seat (fresh, both resume paths, restore and same-seat handover), so a named `codex_config_profile` or a policy posture doesn't override those fields. Omitted workspace-write fields are recorded as `false` or `[]` and emitted as such. Full access is not an envelope: it stays the `full_bypass` mode. The envelope is the seat's explicit choice, so team and kernel launch defaults don't apply to it, and status names the policy posture or ambient `OPENRIG_YOLO` it replaces. With workspace-write, OpenRig still adds its queue-state root and, on a fresh launch, git metadata folders; with read-only it adds no writable roots. It adds no network setting of its own. Codex's implicit roots (the working directory and temporary folders) still apply: this is a native sandbox and approval selection, not a complete filesystem or credential-isolation guarantee. OpenRig's tests prove these launch flags, not that Codex enforces them, so check the seat's actual behaviour before relying on a narrower envelope. An OpenRig older than 0.7.3 doesn't read a recorded envelope, so after a downgrade the seat launches without it. The field is also supported on legacy flat Codex nodes; other runtimes reject it. For an existing seat, put the envelope in a JSON file, or change its profile, with the existing audited permission command: ```sh rig seat set-permissions dev-impl@team --codex-permissions envelope.json --reason "Narrow the implementer" rig seat set-permissions dev-impl@team --codex-profile lease-impl --reason "Use the lease profile" rig seat status dev-impl@team --json ``` Choose one of `--mode`, `--rules`, `--codex-permissions` or `--codex-profile` per call. A seat keeps one explicit permission choice: setting an envelope replaces a recorded mode, and setting a mode (including `inherit`) clears the envelope, in the same audited write that names both. A runtime switch keeps the envelope while the seat runs Claude Code and applies it again when `rig seat set-runtime` brings the seat back to Codex. A switch back to Codex with an explicit `--permission-mode` replaces the envelope when the switch commits, and the switch's plan and event name it. A switch that doesn't commit leaves it in place. A profile name is separate from the envelope and from approval: a resumed conversation keeps its saved approval unless an envelope sets one. `--codex-profile inherit` clears the profile. The profile command checks the profile in the config home this seat's launches use: the seat's own `CODEX_HOME` when its `env:` declares one, else the daemon's explicit `CODEX_HOME`. There it reports an absent file (Codex loads it as an empty layer, so base config applies), records the file's hash so a later check can report a change, and refuses a profile that fails to load, keeping the previous one. When neither sets a `CODEX_HOME`, a seat's pane keeps its own, and the command reports the profile as unverified rather than checking a different home. Every launch still runs the native profile check. Updates leave the running process and history unchanged; they apply at the next separately authorized managed launch or resume. Restore reads the seat's current record, so an older snapshot doesn't bring back a replaced profile. A fork copies the source's current profile and envelope independently. Export contains the current record. A new generation from `rig up ` starts from the spec. An invalid or unreadable envelope refuses that seat's launch, fork or export with a repair message rather than launching broader. Without the field or a recorded update, launch arguments are unchanged. **Downgrade:** an older OpenRig keeps the stored envelope but doesn't read it, so its launches of the seat use the earlier posture decision, which can be broader. This is not restriction-preserving. Keep a copy of the intended envelope and establish equivalent native restrictions before using an older managed-launch path. Native enforcement must be checked on the Codex version and configuration being used. ### Team launch defaults A non-kernel Claude or Codex seat with no authored permission policy, explicit seat permission selection, or named Codex profile receives the team launch default. An explicit policy, including `none` or `builtin:locked`, keeps its existing meaning. The default is derived again for fresh launch, resume, fork and handover; it is not saved as a user choice. Kernel, Pi and terminal behavior is unchanged. Claude keeps `acceptEdits` and receives inline session settings allowing ordinary `rig` commands, project reads and common project test commands (for example, `npm test`, `pnpm test`, `pytest`, `go test` and `cargo test`). Lifecycle commands such as `rig up`, `rig down`, `rig restore`, `rig bundle install` and seat stop or handover ask through a session `PreToolUse` hook. Their literal help forms, such as `rig down --help`, `rig bundle install -h` and `rig help down`, run without that lifecycle prompt. Native personal, project and managed deny/ask rules still apply; OpenRig does not remove or override them. For allowances, the hook recognizes literal command words (including quoted or absolute paths), leading environment assignments that do not change executable lookup or startup, and `env`, `command` and `exec` wrappers without options other than `--`. Relative executable paths and assignments such as `PATH` or `NODE_OPTIONS` stay with native checks. The same existing team allowances apply after spelling normalization; project `node_modules/.bin/vitest` and `jest` paths match their existing `npx` allowances. It does not automatically allow pipelines, command substitutions, redirects, heredocs, shell functions or other unrecognized syntax. Lifecycle prefixes are checked at every word position, including after wrappers, control-flow words and CLI argument separators. A `--` separator before or after the subcommand ends option parsing, so a later `--help` is an operand and still asks. Ask detection also scans with comment text retained, so a `#` inside a substitution does not hide a later lifecycle command. Unquoted prose or comments containing a lifecycle command may also ask; ordinary trailing comments do not change allowance matching. Quoted messages and quoted heredoc bodies remain data. Arithmetic shifts are not treated as heredoc operators. These are command allowances, not containment: a project's test command can execute code. The hook and allowances are passed with `--settings` at launch, including resume and fork; nothing is written to personal or project permission files. If the helper asset is missing at launch, OpenRig falls back to the native lifecycle ask rules; help can prompt in that fallback. A helper that disappears or times out after launch cannot supply a decision, leaving Claude's remaining native rules in effect. The hook is a convenience policy, not a containment boundary. Claude sessions launched with a named `team` or `kernel` hook policy read the installed built-in lists on each Bash hook call. Replacing those files at the same path changes the hook's lists on its next call; native inline allowances remain as launched. Sessions launched with an encoded policy keep their launch lists. A session retaining a versioned runtime path continues to use the files at that path, so installing another version elsewhere does not change its hook. This does not reload all permission settings in a running session. An in-place rollback to a hook from before named policies has a different effect: that helper cannot read `team` or `kernel` and returns no hook decision. Lifecycle asks can then disappear under the native `Bash(rig:*)` allowance. Relaunch affected Claude sessions before relying on those asks after such a rollback. Native personal, project and managed restrictions are not removed. This applies when the rollback replaces the hook at the path a running named-policy session uses; it does not describe every rollback or sessions using a retained versioned path. Codex keeps `workspace-write` with its existing approval policy and receives the configured OpenRig workspace root plus its pod's shared state directory as additional writable directories. The team workspace preparer creates missing directory ancestors and excludes the home directory and its ancestors from its prepared scopes. Unavailable optional directories are warned about and omitted from that list without blocking launch. When no prepared scopes remain, the runtime adapter retains its existing pod-state `--add-dir` fallback, which this step does not prepare. This does not modify Codex permission files or enable non-interruptive mode. **Codex lifecycle commands do not gain a new approval rule.** With Codex's existing `on-request` policy, non-escalated sandboxed commands, including `rig up` and `rig down`, can run without asking. This default addresses writable workspace access; it does not promise a lifecycle confirmation prompt. ### Built-in permission policies The five built-ins live in `packages/daemon/policies/builtin/`. Action names are the policies' own semantic classes. | Policy | Surface | Runs without asking | Asks a person | Denied | |--------|---------|---------------------|---------------|--------| | `builtin:locked` | config | `run_toolchain` (npm, node, tsc, tests, lint), `rig_up`, `rig_down` | nothing | everything else (`default_posture: deny`) | | `builtin:standard` | config | everything not listed, including `push_to_remote` (`default_posture: allow`) | `create_pr`, `publish_package`, `merge_or_release`, `force_push`, and the destructive class | nothing | | `builtin:open` | config | everything, including PRs, publishing, merges and force pushes (`default_posture: allow`) | the destructive class only | nothing | | `builtin:yolo` | flag (`launch_posture: full_bypass`) | everything; the runtime's permission prompts are bypassed at launch | nothing | nothing | | `builtin:auto` | flag (`launch_posture: auto`) | Claude runs with `--permission-mode auto`; Codex, Pi and OMP launch at the floor | Claude: decided by auto mode; others: as at the floor | Claude: decided by auto mode; others: as at the floor | The destructive class is `delete_everything`, `drop_persistent_store` and `reset_or_discard_vcs`; `builtin:locked`'s list also names `delete_files` and `force_push`. **At launch.** `builtin:yolo` selects Claude `--dangerously-skip-permissions`, Codex `-s danger-full-access -a never`, and Pi `--approve`. `builtin:auto` selects Claude `--permission-mode auto`, while Codex and Pi do not have an auto mode and launch at the floor. Every other seat launches at the floor (a non-kernel seat with no policy also gets the [team launch default](#team-launch-defaults) above): - Claude `--permission-mode acceptEdits`; - Codex `-s workspace-write`, or `-p ` when the member sets `codex_config_profile`, in which case the profile governs its own sandbox; - Pi `--no-approve` by default. Seats of the rig named `kernel` are the exception. With no member or rig policy, no per-seat choice and (for Codex) no named profile, Claude launches in `acceptEdits` with a per-launch `--settings` allow list for its file tools and operational commands, and Codex launches with `-s danger-full-access -a never`, plus per-launch `-c` overrides that hide its full-access and GPT-5.1 migration notices, whatever the rig's non-interruptive choice. **Config-surface policies are recorded, not applied at launch.** The seat still starts at the floor. The `allow`, `ask` and `deny` rules take effect once they are translated into the runtime's native settings. The `applying-a-permission-policy` skill in `openrig-core` does that translation, with per-runtime limits. For example, Claude's prefix rules can't reliably tell `git push --force` from `git push`. An `ask` waits for a person, so it pauses an autonomous seat. Standard suits interactive work, and the policy files point autonomous teams to Open or YOLO. **Pi has no permission policy.** `--approve` and `--no-approve` set Pi's resource trust. The OMP variant maps that trust to its approval mode: `yolo` under `--approve`, otherwise `always-ask`. **A custom policy file** is Markdown with frontmatter: `policy_schema_version: 1`, `name`, `source: custom`, `description`, and `surface`. - `surface: flag` adds `launch_posture` (`floor`, `full_bypass` or `auto`). - `surface: config` adds `default_posture` (`allow`, `ask` or `deny`) and the `allow`, `ask`, `deny` and `destructive_class` lists (`[]` for none). **Claude Code's own checks.** The first time Claude Code starts an interactive session with permissions bypassed, it shows a warning dialog asking you to accept responsibility; declining exits. OpenRig reports such a seat as `attention_required` and holds its startup context for `rig seat continue`; `rig up --non-interruptive` or `rig bundle install --non-interruptive` accepts the warning with a launch flag instead (see [non-interruptive mode](non-interruptive-mode.md)). That choice is saved on the rig, not in `rig.yaml`. On Linux and macOS Claude Code refuses that mode when run as root or under `sudo`, except inside a recognized sandbox. See Claude Code's [Choose a permission mode](https://code.claude.com/docs/en/permission-modes#skip-all-checks-with-bypasspermissions-mode). ### Launch environment `env:` sets environment variables for the seats of a rig, so a team can share a setting such as a feature flag, a region or a log level without every member hand-editing a shell profile. Declare it on the rig, on a member, or both: ```yaml env: LOG_LEVEL: info REGION: eu pods: - id: dev members: - id: impl runtime: claude-code env: LOG_LEVEL: debug # overrides the rig's value for this seat only ``` - A seat gets the rig's `env` with its own `env` laid over it, name by name. - The same environment is applied on first launch, on `rig up ` restore, and to the successor of a seat handover. - `rig up --env KEY=VALUE` (repeatable) adds or overrides a value for one launch, except `CLAUDE_CONFIG_DIR` and `CODEX_HOME`, which the CLI leaves out with a note (see "A seat's own account" below). A one-off value belongs to the terminal that launch creates: it stays in that terminal's environment for as long as the terminal lives, and OpenRig removes it from the tmux session's environment as soon as the terminal has started, so a terminal started later in that session does not get it. If OpenRig can't take it out, the seat keeps running and the launch warns, naming the variable and the tmux command to put it back or remove it. Until then, a terminal started later in that session would get it. A restore, a handover successor or any later launch starts from the spec's `env:` alone, so the value is not saved and does not carry forward. To keep a value, put it in `env:`. One exception to "does not carry forward", for a managed Claude seat (see below): a one-off name that the seat's spec does not declare stays on the session's list of names, `OPENRIG_SEAT_ENV_KEYS` (names only, no values), because the first launch and an in-place restart of the same terminal read that list. So if a later pane in that session gets a value for that name from the tmux server's own environment, its managed Claude launch can forward that value, where a launch without `--env` would leave it out. The first launch's one-off value itself is not passed on to that pane. This comes from reading the code and has not been measured. `--env` does not apply to `rig up ` (a restore) or to a topology (several rigs from their own specs); the CLI refuses both. For a topology, put `env:` in each rig's spec. - **The runtime directory, on Linux.** When the daemon runs with an absolute `XDG_RUNTIME_DIR` (usually `/run/user/`), a seat gets the same `XDG_RUNTIME_DIR`, so a tool that keeps its socket there, such as an MCP server's daemon or `gpg-agent`, works inside the seat without per-tool setup. It comes with `DBUS_SESSION_BUS_ADDRESS=disabled:`, an address that D-Bus clients refuse. sd-bus, GDBus and libdbus look for your session bus at `$XDG_RUNTIME_DIR/bus`, or start one, only when that variable is unset, so a seat doesn't find your session bus (the keyring, notifications) on its own. This is a default, not isolation: `systemctl --user` reaches your service manager through its own socket in the runtime directory, as any tool there can. - A seat's own value wins. Set `DBUS_SESSION_BUS_ADDRESS` in `env:` to give a seat a bus. Set `XDG_RUNTIME_DIR` there to choose the directory yourself; the seat then gets no bus address, as before, so its D-Bus clients find the bus in that directory. A name listed in tmux's `update-environment` counts as the seat's own too. - A shell startup file that runs in the seat's terminal can still change either name. One that sets only `XDG_RUNTIME_DIR` now leaves the seat with `disabled:`, where before its clients found your bus; set the bus address in `env:` or in that file to keep it. - With `terminal.inherit_tmux_environment` on, a seat gets tmux's environment as before, and OpenRig adds neither name. On macOS, and for a daemon started without `XDG_RUNTIME_DIR`, nothing changes. - Codex passes only a fixed list of variables to the MCP servers it starts, so a Codex seat's MCP server gets the runtime directory only when its entry in `config.toml` names it: `env_vars = ["XDG_RUNTIME_DIR"]`. Codex's hooks and shell commands get it without that, and so do Claude Code's MCP servers and hooks. Pi and OMP agent processes don't get it (see below). - It applies at a seat's next launch, restore or handover; a seat that is up keeps the environment it started with. - Names starting `OPENRIG_` are reserved for OpenRig and refused. Empty values are refused, because the launch omits empty variables. - `rig export` writes the rig and member `env:` back out. **Where the declared names reach, by runtime.** Declared values are handed to the seat's terminal as tmux environment, so most seats take them as given: - Plain Claude Code (no permission mode selected), Codex and terminal seats take a spec `HOME`, `PATH` or `CLAUDE_CONFIG_DIR` as given. - The cleared environment (`env -i`) belongs only to a managed Claude launch, which is the one used when a permission mode is selected for the seat (for example `permission_policy: builtin:auto`). It forwards the declared names through the cleared environment, except `PATH`, `HOME` and `CLAUDE_CONFIG_DIR`, which OpenRig sets for that launch itself, and `CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN`, for which it sets its own renderer setting. A spec value for any of those four is not forwarded as such; an absolute `CLAUDE_CONFIG_DIR` becomes the config dir OpenRig sets (see "A seat's own account" below). A seat that selected the shipped `openrig-core` plugin also has `CLAUDE_CODE_PLUGIN_DIRS` (the vendored plugin, added after a list the seat declares itself) among its declared names, so it is forwarded like the others and the plugin's hooks load in a seat with a permission mode as in one without; a seat that didn't select the plugin has no such name added. Beyond the declared names, the launch also forwards, by name and only when the seat's terminal has them set, the terminal and locale variables (`TERM`, `COLORTERM`, `TERM_PROGRAM`, `TERM_PROGRAM_VERSION`, `LANG` and the `LC_*` names) and the person's editor and display session: `EDITOR`, `VISUAL`, `DISPLAY`, `XAUTHORITY` and `WAYLAND_DISPLAY`. They let Claude's external-editor action open the person's editor and let image paste reach the clipboard, without a value in a spec. `WAYLAND_DISPLAY` is passed as an absolute path: an absolute value is kept, and a relative one is joined to the terminal's `XDG_RUNTIME_DIR`; if the terminal has no `XDG_RUNTIME_DIR` for it, the name is left out. The runtime directory and its `disabled:` bus address (see "The runtime directory, on Linux" above) are forwarded together, and the runtime directory is also forwarded beside a declared `DBUS_SESSION_BUS_ADDRESS`. A terminal's own `XDG_RUNTIME_DIR` or `DBUS_SESSION_BUS_ADDRESS`, from a shell startup file or tmux's `update-environment`, is not, because D-Bus clients look for the session bus at `$XDG_RUNTIME_DIR/bus` when no address is set; declare it in `env:` to forward it. `XDG_SESSION_TYPE` and `XDG_CURRENT_DESKTOP` are not forwarded. The SSH agent socket (`SSH_AUTH_SOCK`) is forwarded when OpenRig itself was started with one, so git over SSH works in the seat; a socket only the seat's terminal has stays out. To check, run `ssh-add -l` inside the seat. If it can't reach an SSH agent, start the OpenRig daemon from a terminal where `ssh-add -l` works. A running seat picks the names up at its next managed Claude launch, not before: `rig seat launch --fresh --stop --reason ""` for that seat alone (a new conversation), or a restore of the whole rig (`rig down `, then `rig up --existing`, which resumes each conversation but restarts every seat in the rig), or a handover. A seat that is up keeps the environment it started with. - Codex seats run OpenRig's own Codex executable whatever a spec `PATH` says. They use the seat's own `CODEX_HOME` when its `env:` declares an absolute one, else the daemon's when the daemon has one. - On Pi and OMP seats the values reach the terminal. The agent process gets only what the runner passes on, so a spec or one-off value reaches it only for the names the runner always passes: `PATH`, `HOME`, `USER`, `LOGNAME`, `SSH_AUTH_SOCK`, `TERM`, `LANG`, `LC_ALL`, `SHELL` and `TMPDIR`. Pi's agent process receives the runner's `HOME`, so a spec or one-off `HOME` value that reaches the runner is passed to Pi; OMP's agent process gets its seat state directory (`$OPENRIG_HOME/state/omp/`, see Oh My Pi below) as `HOME`, so a `HOME` value does not reach it. The selected provider's variables are passed separately, as described under each runtime. **A seat's own account.** Claude Code keeps its login in its config dir and Codex in its home, so a seat whose `env:` declares an absolute `CLAUDE_CONFIG_DIR` (Claude Code) or `CODEX_HOME` (Codex) runs on whatever login that directory holds. The member's value wins over the rig's. OpenRig follows it everywhere it launches or reads that runtime: the launch, Claude's first-run trust settings, Codex's managed `config.toml`, hooks and plugin, conversation capture for resume, context usage, `rig seat status` and `rig provider status`. A value that isn't an absolute path isn't used: the seat resolves as if it declared none, and `rig seat status` shows the ignored value. Name the home in `env:`: a one-off `rig up --env` value for either variable isn't applied, because a restore, a handover and `rig seat status` read only the stored `env:`. Seats may share a home. Live usage refresh (`rig provider status --refresh`) still reads only the daemon's own logins; reading each seat's home would need a native reader per home. OpenRig doesn't copy logins or conversation history between homes, so a conversation saved under a seat's earlier home doesn't resume after the seat moves to another one. The `openrig-user` skill has the setup steps. **A seat's own subscription token.** A Claude member can start on a prepared subscription token from its first launch, with no conversation to switch first. Name the token file beside its config dir: ```yaml - id: dev runtime: claude-code env: CLAUDE_CONFIG_DIR: /abs/claude-dev claude_token: { label: dev-sub, file: /abs/tokens/dev.token } ``` The label and the absolute path are stored on the seat when the rig is created, as `rig provider switch --seat --to-account