Files
openrig/docs/reference/agent-startup-guide.md
T
Mike Schwarz ac7a6b0713 fix(handover): put a Codex seat's guidance back before its successor launches (#2444)
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.
2026-11-03 03:15:29 -08:00

58 KiB
Raw Blame History

Agent Startup Guide

Last validated: 2026-10-05, against main 9b88b118 Applies to: OpenRig 0.6.8

This guide teaches you how to think about what goes into an agent's startup experience — what files to write, where to put them, and how the layering model delivers them. It is an authoring guide, not a schema reference. For field-level details, see rig-spec.md and agent-spec.md.


If a fresh Claude seat is waiting at its bypass-permissions warning, review and answer that warning in the seat's native pane. Unless the rig was launched with --non-interruptive (see non-interruptive mode), OpenRig does not accept it for you, and it never sends configured startup context into the active dialog. The seat reports attention_required and retains that context for the same occupant.

After accepting, run rig seat continue <seat> (or press c on the seat in the TUI, shown while its context is pending). This delivers the pending context without relaunching Claude, and its first prompt reminds the seat that this is a fresh conversation and how to find its work. It does not replay a delivery that already started. A timeout, or a refusal because nothing is pending, reports an unknown outcome; inspect rig seat status <seat> before taking another action. It is also refused while the prompt is still showing (attention_required), or when the seat's binding or runtime has changed since launch. Runtime readiness, startup delivery, and orientation proof remain separate; a missing proof does not block this continuation.

Other fresh-launch prerequisites, such as login or workspace trust, use the same continuation: resolve the prerequisite in the native pane, then run the displayed command. rig ps --nodes shows it under "Startup details ()", and rig up and rig bundle install print it as "Startup attention ()": "After resolving it in , run: rig seat continue ".

The attention codes that stop startup this way are trust_gate, hook_trust_gate, update_gate, login_required, mcp_gate, bypass_consent_gate, codex_auth_refusal and codex_client_incompatible. They are recognized from each harness's English prompt text.

Continue a Codex fork whose child ID arrived late

For a member with session_source.mode: fork, OpenRig waits for Codex's new child thread ID before sending its role and other startup prompts. It saves the ID and sends the role only for a verified child: the bound pane's foreground process forks the requested parent, and its own native record, matched to its start time, supplies the ID. This capture uses runtime.readiness_timeout_seconds (30 seconds by default, configurable from 1 to 600 seconds), as does the later readiness check. The two waits have separate budgets. A seat handover with --source fork:<seat> is different: its fork carries the conversation itself, and a missed ID capture leaves the ID pending rather than stopping the handover.

If the ID is still unavailable, the seat reports attention_required and keeps its startup context pending. Wait for the child to finish starting, resolve any native prompt, then run rig seat continue <seat>. The continuation verifies that the bound pane is running a fork of the requested parent and reads the child's own native identity before delivering the saved context. It does not launch another fork or resume the parent. The parent ID is never saved as the child's resume token.

An unavailable or conflicting identity leaves the context pending. Once prompt delivery has been attempted, it cannot be replayed by continue, including after a daemon restart: inspect rig seat status <seat> and the native pane before deciding how to recover an uncertain delivery.

Two Categories of Startup

Everything an agent receives at boot time falls into one of two categories:

Category 1: Context Loading

The agent reads markdown files into its context window. This is the primary mechanism in OpenRig today and the one you should invest most of your authoring effort in.

Context loading shapes what the agent knows, believes, and is capable of:

  • Who it is (role, identity, pod membership)
  • How the team works (culture, communication norms, coordination protocols)
  • What the project is (codebase context, architecture, domain knowledge)
  • What it can do (skills, SOPs, operational procedures)
  • What its environment looks like (services, access credentials, tools)

Category 2: Deterministic Configuration

The rig spec declaratively installs things into the agent's runtime environment:

  • Hooks (shipped inside plugins)
  • Permissions (.claude/settings.json allowlists, approval modes)
  • MCPs (Model Context Protocol servers)
  • System dependencies (tools, packages)

See the Current Support Matrix section at the end of this guide for what is reliable today vs experimental.

The recommendation: Put as much of your setup logic as possible into Category 1 (context loading via markdown files). Describe the desired end state in startup files and let the agent handle the configuration. The deterministic path exists in the spec and will become more reliable over time, but for now the context-loading path is the one that works consistently across runtimes.


Context Loading: What Goes Where

Skills vs Startup Files — The Key Distinction

Skills are reusable SOPs. They teach an agent HOW to do something — how to use OpenRig, how to do TDD, how to run a code review, how to operate Vault. Skills transfer across rigs. A skill you write once can be used by any agent in any rig.

Startup/guidance files are rig-specific and role-specific. They tell an agent WHO it is, WHAT it's working on, and HOW this particular team operates. They are authored per-rig and often per-pod or per-member.

Put in a skill when... Put in a startup/guidance file when...
The knowledge is reusable across rigs The knowledge is specific to this rig or project
It teaches a procedure or methodology It teaches identity, role, or team context
It could be useful to any agent of this type It's only useful to agents in this specific topology
Examples: openrig-user, test-driven-development, vault-user Examples: role.md, CULTURE.md, startup/context.md

The Belt-and-Suspenders Pattern for Skills

Skills are delivered to agents through two parallel paths:

  1. Spec declaration: The agent spec's resources.skills + profile uses.skills ensures the skill files are projected to the agent's workspace (installed before harness boot)
  2. Startup instruction: The guidance/startup files tell the agent to actually read and load those skills

Both paths are important. The spec projection ensures the files are physically present. The startup instruction ensures the agent knows to read them. This redundancy is intentional — it handles cases where one path fails.

Example: An implementer agent's startup guidance says "Load the following skills: openrig-user, test-driven-development, systematic-debugging" — AND the agent spec's profile uses those same skill IDs. The agent gets the files from projection and the instruction to read them from guidance.

The File Types

Role guidance (guidance/role.md)

Tells the agent who it is and what its responsibilities are:

  • Title and primary function
  • Specific responsibilities (bulleted list)
  • Working rhythm (how the agent operates day-to-day)
  • Principles (behavioral guidelines)
  • Relationship to other team members

This is the agent's identity document. Every agent should have one.

Rig culture (CULTURE.md)

The rig-wide constitution. Applied to ALL agents in the rig:

  • Communication norms (use rig send/chatroom, not raw tmux)
  • Coordination protocol (how work flows between pods)
  • Quality standards (what "done" means)
  • Commit/merge policy
  • Escalation rules

Think of this as the team operating manual that every team member reads on day one.

Startup context (startup/context.md)

Boot-time grounding information specific to this agent's operating environment:

  • Identity recovery instructions (rig whoami --json)
  • Environment details (service URLs, access credentials, ports)
  • System check instructions (what should be running, how to verify)
  • Role-specific delegation information (who to ask, who delegates to you)

This is especially important for managed-app specialists (e.g., a Vault specialist needs to know the Vault address and dev token).

Project documentation

Rig-level startup files that teach the agent about the project:

  • Architecture overview
  • Key conventions
  • Domain vocabulary
  • Recent context (what's been happening, what's in progress)

These go in rig-level or pod-level startup blocks and are delivered to relevant agents.


The Layering Model

Startup content is merged additively through layers. Each layer adds to what the previous layers provided. Later layers do NOT replace earlier layers — they append.

The Layers (in delivery order)

1. Agent layer     — from the AgentSpec's top-level startup block
2. Profile layer   — from the active profile's startup block
3. Culture layer   — OpenRig's default culture, then the RigSpec's culture_file
4. Rig layer       — from the RigSpec's top-level startup block
5. Pod layer       — from the pod's startup block
6. Member layer    — from the member's startup block in the RigSpec
7. Operator layer  — added by OpenRig: openrig-start.md and, on a fresh start, the onboarding pack

A member with a starter_ref gets that agent starter's files in front of the agent layer. Terminal seats (builtin:terminal) skip the agent and profile layers.

What Each Layer Is For

Layer Authored by Purpose Example content
Agent Agent spec author Core identity and capabilities that travel with this agent type Role guidance, default skills
Profile Agent spec author Profile-specific variations Different skill sets for "default" vs "minimal" profiles
Culture OpenRig, then the rig spec author Team norms OpenRig's CULTURE-default.md for every seat, then the rig's own CULTURE.md
Rig Rig spec author Rig-wide context for all agents Project documentation
Pod Rig spec author Pod-specific coordination context Pod SOP, intra-pod workflow
Member Rig spec author Individual member overrides Member-specific instructions, cwd-specific context
Operator OpenRig system System-injected runtime content openrig-start.md (optional delivery); on a fresh start, openrig-onboarding-01.md and -02.md unless onboarding.default_pack.enabled is false

After the files, OpenRig sends a session-identity message (rig, pod, member, logical ID) once the seat is ready.

Practical Guidance

Most rigs only need three layers: agent (role.md), culture, and the built-in operator layer. Start simple. Add pod and member layers only when agents in the same pod need different startup content.

The rig's own culture file is high-value and often skipped. Every seat gets OpenRig's default culture, but that says nothing about how this team communicates and coordinates. Write a CULTURE.md. Even a short one dramatically improves team coherence.

Member-level startup is for exceptions, not the rule. If every member has its own startup block, the layering model is being used as a configuration dump. Refactor shared content up to the pod or rig level.


Delivery Mechanisms

How Files Reach the Agent

Delivery Hint When How Use For
auto Depends on the file Resolved per file: a SKILL.md (or content starting # SKILL) is skill_install, any other .md is guidance_merge, and any other file is send_text Default — let OpenRig decide
guidance_merge Before harness boot Merged as a managed block into the repository AGENTS.md (Codex, OMP), a fresh Pi seat's own agent-directory AGENTS.md (see Runtime Differences), or CLAUDE.md (Claude; CLAUDE.local.md with the rig's managed_blocks: { claude-code: CLAUDE.local.md }). A fresh launch of Codex 0.164.0 or later can carry it as launch developer instructions instead (see Codex guidance at launch) Role guidance, culture, project context
skill_install Before harness boot Copied to runtime's skill directory Skills
send_text After harness is ready Sent as text to agent's terminal via tmux Boot-time grounding, identity hints, instructions to read skills

Delivery Timing Matters

Files delivered via guidance_merge and skill_install happen BEFORE the agent's harness boots. The agent sees them immediately when it starts — they're part of the initial context.

Files delivered via send_text happen AFTER the harness is ready. The agent receives them as messages in its terminal. Use this for:

  • Identity grounding (agent reads and processes the instructions)
  • Instructions to load skills (the files are already projected, the message tells the agent to read them)
  • Context that should feel like an operator briefing, not pre-loaded content

On a fresh launch the first message is one turn: the session identity, the first send_text file and, when a startup proof is selected, its challenge (below).

A file's required field defaults to true: a required file that can't be delivered fails startup, and an optional one is dropped without a message.

Readiness, startup proof and failure

Readiness. After launch OpenRig polls the pane until the harness is interactive (backing off 1, 2, 4, 8 and 16 seconds), for up to 30 seconds by default. Change the window with rig config set runtime.readiness_timeout_seconds <1-600> (or OPENRIG_RUNTIME_READINESS_TIMEOUT_SECONDS); it applies to the next launch. A recognized prompt (the attention codes above) ends the wait at once as attention_required. A timeout ends startup as failed ("Readiness timeout after Ns — harness did not become interactive"), and the post-launch files aren't sent. A Codex fork that the rig launches (a member with session_source.mode: fork, or an agent image's fork) is the exception: its timeout ends as attention_required, with its startup context kept for rig seat continue (see below). A seat handover's fork has its own readiness check.

Fresh Claude handovers. rig handover --source fresh keeps the previous agent's scrollback. Before sending the handover context, OpenRig waits for the latest prompt inside Claude's complete input frame, and confirms that the process in the pane belongs to the new launch. A shell's ❯ prompt or echoed launch command is not enough, even with an old Claude header or input frame above it and Claude already running. A header alone or a partially drawn input frame also keeps the check pending. If the current screen or process cannot be read, or the process identity is unconfirmed, the handover keeps waiting within the readiness window and fails if that window expires. Old screen text or the claude process label alone cannot complete this check. A healthy agent can therefore remain unconfirmed while those observations are unavailable; inspect its pane before deciding how to recover.

Claude submission check. For each startup message to a Claude seat, OpenRig makes a bounded check that the prompt was submitted. When the capture can't tell, it records the submission as unverified and carries on. When it sees the same prompt still sitting in the input, it presses Enter once more; if it's still there, it warns "Startup prompt still staged in ; press Enter in that pane." The seat can still end ready with its submission unverified or staged, so ready doesn't prove the prompt was submitted. After all files and actions are delivered, readiness is checked once more, so a prompt that appears by then gives attention_required.

Startup proof. A startup_proof action selects authenticated or none (the default is none; the last applicable one wins, and it needs idempotent: true). With authenticated, a fresh launch of an agent seat includes a challenge, and the seat answers it with rig startup-proof submit --challenge-id <id> --answer <answer>. rig ps --nodes --full shows the result in the ORIENTED column: verified, missing, rejected or n-a. A missing proof doesn't block startup.

After a failure. Delivery, launch and action failures, and readiness timeouts other than a Codex fork's, give failed. Every 30 seconds the context monitor marks a failed or attention_required seat ready once its pane reads ready, unless its fresh or Codex fork context is still pending for rig seat continue. A seat that timed out can therefore read ready without having received its post-launch files. If it needs them, first look at what that occupant has done; then rig seat launch <seat> --fresh --reason <text> starts a blank conversation for the seat. If the occupant is still live, the command refuses with session_live unless you also pass --stop, which replaces that occupant deliberately.

Preview before replacing a seat. rig seat launch <seat> --fresh --plan inspects the selected seat without starting tmux, stopping or launching a process, projecting startup files, or changing lifecycle/queue records. Add --json for structured output. --fresh is still required; --reason is optional for the preview and required when executing. Pass --stop to preview the intended replacement of a live managed occupant. Adopted and unmanaged occupants remain blocked.

The plan identifies the target, current occupant, stop requirements, blockers, startup counts, and each sibling seat's unchanged effect. Siblings stay attached and untouched. Launch composes a new command through the runtime adapter using current settings; it does not replay a stored wrapper command. The seat's launch environment and managed executable selection still apply. The plan does not construct or display the launch command.

A preview is a snapshot, not a reservation or a native-readiness check. Unreadable tmux produces complete: false and launchable: null (unknown), unless a known blocker already proves launchable: false. A successful preview exits 0, including blocked or incomplete plans; scripts must inspect launchable === true before deciding to execute. Request failures still exit nonzero. Execution always resolves and checks the current state again. An older daemon without the preview endpoint refuses the request; the CLI never falls back to executing a launch.

Change an existing seat's working directory

Editing cwd in an already applied RigSpec doesn't update an existing seat's saved launch directory. Use set-cwd to save a new one for the seat's next launch:

rig seat set-cwd planning-lead@project \
  --cwd /work/project/seats/planning-lead --reason "isolate planning"
rig seat status planning-lead@project

The path: it must be absolute on the daemon's host. OpenRig creates a missing directory, resolves symlinks to its real path, and refuses a filesystem root.

What it saves: the directory and a node.cwd_changed audit event (from, to, reason, operator), together, in one transaction. The seat's node, canonical address, queue, session history and skill selection stay as they are. rig seat status shows Working directory for future launches (launch_cwd in JSON). That's saved configuration, not where the running process is.

What it writes: it creates the folder if it's missing, and writes nothing else there. The seat's next fresh launch or rebuild handover writes its startup files there, as launch does today.

set-cwd's preflight policy: before saving, it reads, without writing, the enumerated resource copies (including nested plugin files) and startup skills that launch would place in the new directory, at their actual destinations, as they are at that moment. It's a point-in-time check, not a global overwrite guarantee or protection against later changes; at adoption, launch applies its own policy. (The commit's re-check covers the saved startup configuration, not the destination's file contents.)

  • Refused, with the protected paths named (cwd_destination_conflict): a file there that an operator edited, an unrecorded file with different content, or two sources for one destination. That's stricter than launch, which overwrites an unrecorded differing file with a warning; the refusal is deliberate, while the occupant still runs.
  • What to do: use a clean destination, or move or merge those files, then retry.
  • Not checked: managed guidance blocks and native configuration merges keep the adapter's policy at launch, and launch-owned files such as .openrig/context-collector.cjs are written by launch as always.
  • A community harness takes no startup files, so none are checked for it.

The running occupant keeps its current directory. Adopt the new one with:

  • rig seat launch <seat> --fresh --stop --reason <text>, which starts a blank conversation; or
  • rig handover <seat> --source rebuild --reason <text>, which carries durable context.

For Codex rebuilds, OpenRig also carries a complete supported native permission policy read from the live predecessor's own conversation and process, without adding fresh-launch grants. A later recorded seat permission or policy choice, or a differing declared successor profile, takes precedence. Missing, stale or unsupported observations keep the existing launch behaviour and are reported as unverified in the handover result; requested modes and profiles alone don't prove preservation. The result reports what was emitted, not native successor equivalence.

Adopt it promptly. The saved directory doesn't move the running process, but background work can use it before adoption. Saved configuration isn't the live process's directory, and these examples aren't the only consumers. For a running Claude seat:

  • Collector provisioning writes .openrig/context-collector.cjs and merges .claude/settings.local.json in the saved directory, under its existing preservation rules.
  • Managed compaction, manual (POST /api/compaction/trigger) or automatic, asks the seat to write its restore map under it. That may be outside the seat's current workspace, so a seat without bypass permissions may meet a write prompt.

Adopting promptly is guidance. It doesn't protect against a poll or a compaction that runs before your next command.

Before adopting, don't rig down/rig up or hand over by fork. Those resume the existing conversation in the saved directory, and Claude keeps conversations per project directory, so a resume there may not find it. Cross-directory resume hasn't been run natively; this is the order to follow. rig seat restart --keep-conversation (Codex) isn't adoption: it respawns in the directory it observes from the running process.

Failures: a refusal, missing or malformed saved startup context, or an audit-write failure leaves the prior directory saved, and changes no seat, custody or startup file (a refusal may leave the new folder created). A concurrent configuration change is reported, not overwritten. A CLI timeout means the outcome is unknown; check rig seat status before retrying. Choosing the same directory again records no second event.

The old directory: files there are left as they are, since other seats may still use them. The source RigSpec declaration isn't changed either; update it separately if a future recreation should use the new directory.

The applies_on Field

Each startup file and action specifies when it applies:

  • fresh_start — delivered when a new conversation starts: the first launch, rig seat launch --fresh, rig seat continue, and seats restored with --fresh
  • restore — delivered when a seat is restored without its earlier conversation: a restore whose resume falls back to a fresh conversation, or a retried start
  • Default: [fresh_start, restore] (both)

An exact resume delivers neither. When a restore reopens the seat's saved conversation (rig up <rig> --existing with a resume token), OpenRig types no startup actions and delivers no startup files, restore ones included: the conversation already has them, and typing them again would add turns to it. The one exception: a managed guidance block missing from the guidance file is put back before the harness starts.

Use this to avoid re-sending context that a fresh conversation doesn't need twice. For example, a one-time project briefing might only apply on fresh_start, while identity grounding should apply on both, so a seat that comes back fresh gets it again.


What a seat is set to, and what it's running

A seat's settings can disagree with its running agent: what's saved for the next launch, what the running process actually uses, and what its conversation has already read are three separate things. OpenRig reports each one separately. Without live evidence it says unknown (or not verified), never the saved value. A saved change reaches the running seat only through the action in the last column.

What Saved for the next launch What the running seat has How the running seat gets it
Model rig seat set-model rig ps --nodes shows the configured model beside the last observed one, or UNKNOWN (<reason>) The next managed resume or successor launch
Reasoning effort rig seat set-effort rig seat effort <seat>: requested, saved, sent at launch, and what the session recorded, which is not verified until it takes a turn Its next managed continuation; nothing restarts
Permissions rig seat set-permissions (--mode, --rules, or for Codex --codex-permissions or --codex-profile), shown by rig seat status What it launched with; a status read doesn't prove the running process enforces the selection Its next managed launch or resume (per-seat permission mode)
Working directory rig seat set-cwd, shown by rig seat status as Working directory for future launches The directory it started in rig seat launch --fresh --stop (a blank conversation) or rig handover --source rebuild (durable context); details
Startup files and skills The spec's startup files, skills and actions What its conversation read when it started; editing a file afterwards doesn't reach it A fresh launch or a handover delivers them again; for the running seat, send or walk the change (rig walk <seat> --through <files>)
Tools (MCP servers, plugins) Selected fragments, merged into the harness's own config (.mcp.json for Claude, config.toml for Codex) Unknown to OpenRig: registered isn't active, Claude asks before it uses a new server, and no OpenRig command reports which tools a running harness loaded Check in the harness itself (Claude Code's /mcp); a running session may need a restart to pick up newly written config (runtime config disclosure)

Per harness: Claude Code's and Codex's permission choices are in the setup reference (Claude Code, Codex), Codex homes are below, community harnesses' limits are in their presets, and moving a live seat to the other runtime is in the setup reference.


Deterministic Configuration

The AgentSpec and RigSpec allow declaring deterministic environment configuration:

What the Spec Supports

Hooks ship inside plugins: declare the plugin under resources.plugins[]. AgentSpec validation refuses resources.hooks. OpenRig loads one plugin's hooks into a seat itself: the shipped openrig-core plugin, for a Claude seat that selects it (see the refocus channel). The folder a declared plugin is projected into is not one Claude Code or Codex loads, so a plugin of your own does not get its hooks run by that projection.

Runtime resources (in resources.runtime_resources):

resources:
  runtime_resources:
    - id: claude-settings
      path: runtime/claude-settings.fragment.json
      runtime: claude-code
      type: claude_settings_fragment

Runtime-specific configuration files projected into the agent's runtime environment.

Startup actions (in startup.actions):

startup:
  actions:
    - type: send_text
      value: "/install-mcp my-server"
      phase: after_ready
      idempotent: true

Each value is typed into the harness's input and submitted, so it is a prompt or a slash command, not a shell command. Types are send_text, slash_command and startup_proof (see "Startup proof" above); shell is refused. idempotent is required, and an action that isn't idempotent must not list restore in applies_on. Both phases run after the harness is ready: after_files (the default) after the post-launch files, then after_ready.

Don't rename a managed Claude seat. OpenRig starts every Claude seat with --name <session>, on a fresh start and on every resume, and its own reads of the seat use that name: context usage and managed compaction read the status-line record Claude saves under it, and the input box is recognized by the name on its border. A startup /rename to another name breaks those reads until the next resume puts the session name back.

Current Support Matrix

Capability Status Notes
Guidance file projection (guidance_merge) Supported Reliable. Primary delivery mechanism.
Skill projection (skill_install) Supported Reliable. Skills are copied to workspace.
send_text delivery after ready Supported Reliable. Requires harness to be ready.
Hooks Through plugins, limited resources.hooks is refused. OpenRig loads the shipped openrig-core plugin's hooks into a Claude seat that selects it (Claude Code v2.1.280 or later); a plugin you declare under resources.plugins[] is projected, but its hooks are not loaded.
Runtime resource projection Supported for recognized fragments claude_settings_fragment, claude_mcp_fragment, and codex_config_fragment are applied to provider config. Unknown types are copied to runtime extension directories.
Permission configuration Native settings plus managed launch flags OpenRig launches Claude with acceptEdits and Codex with workspace-write unless an explicit supported selection changes them; a seat with no policy, selection or named Codex profile also gets the per-launch team default, and the kernel rig's seats get a wider operational default (rig spec, "At launch"). Neither is written to a settings file. Use the first-user permission guide for opt-in and custom choices.
MCP installation Supported for Claude fragments A selected claude_mcp_fragment is merged into the project's .mcp.json. Claude asks to approve new servers found there; that prompt stops startup as mcp_gate until someone answers it and runs rig seat continue. Otherwise use /mcp or claude mcp add, or describe the servers in startup files for the agent to configure.
System dependency installation Not deterministic Describe in startup files; agent handles via shell commands.
Recurring tasks / wake timers Runtime-dependent Claude Code supports recurring tasks via the /loop command. Codex does not have a confirmed equivalent. Orchestrators should include /loop instructions in startup for Claude Code agents.

The Approach: Describe, Then Let the Agent Handle It

For anything beyond guidance_merge, skill_install, and send_text, the recommended approach is:

  1. Describe the desired end state in a startup file (e.g., "You need these MCP servers configured, these permissions set, these hooks installed")
  2. Include a system check in the startup instructions ("Verify your environment: check that X is installed, Y is configured, Z is accessible")
  3. Empower the agent to self-configure ("If any of these are missing, install/configure them")
  4. Optionally also declare it in the spec for when deterministic support improves — the spec serves as the blueprint, and the startup file serves as the fallback instruction

This way, when deterministic support becomes fully reliable, the agent will boot up, see that everything is already set up (by the deterministic path), run its system check, confirm everything looks good, and proceed. Until then, the agent reads the instructions and handles the setup itself.

Runtime Config Disclosure

OpenRig performs best-effort deterministic runtime configuration for managed sessions. Core bootstrap stays minimal; user/custom policy belongs in spec-selected runtime resources. These writes are intentionally invasive and should be disclosed plainly:

  • Claude global config: ~/.claude/settings.json OpenRig no longer writes a core Bash(rig:*) permission allowance here. Older installations may retain one; existing user settings are not removed.
  • Claude global state: ~/.claude.json Purpose: pre-trust managed workspaces and mark onboarding complete for fresh managed sessions. Explicit permission modes use the launch-selected HOME/.claude.json, or <CLAUDE_CONFIG_DIR>/.claude.json when that variable is set. Classic startup retains the daemon-home path.
  • Claude project-local config: .claude/settings.local.json Purpose: apply context collector/activity hooks and selected claude_settings_fragment resources inside the project without committing them to git.
  • Claude project-local MCP config: .mcp.json Purpose: apply selected claude_mcp_fragment resources for Claude in that project.
  • Codex global config: $CODEX_HOME/config.toml, or ~/.codex/config.toml when unset Purpose: pre-trust managed workspaces and apply selected codex_config_fragment resources. Codex currently has no equivalent project-local MCP config path for global profile settings.
  • Global skill folders: ~/.claude/skills and ~/.agents/skills Purpose: daemon startup seeds the openrig-skills and refocusing skills and the person-facing rigs skill. Each upgrade refreshes the rigs skill; a copy installed another way is left as it is.

Two important caveats:

  • these writes are best-effort and should still be paired with startup guidance so the local agent can verify and repair them if needed
  • already-running adopted sessions may need restart before they pick up newly written config

Permission mode and runtime resource projection are separate. A selected fragment can affect native configuration, but OpenRig's launch flags can override those values. Recording a config-surface permission_policy is not proof that its rules were translated or applied. See permission precedence and limits.

Separate Codex homes for separate installations

Set an absolute CODEX_HOME in the environment that starts the daemon when you want a separate Codex configuration and state root. OpenRig uses that root for its startup hook setup/removal and trust records, feature flag, workspace trust and selected config fragments. Startup can write those files even when the daemon has no seats.

The explicit selection also applies to future managed Codex fresh, resume and fork launches, their capability/profile/configuration probes, native thread and context reads, and plugin-cache discovery. Launch commands reassert it after pane shell startup, so an rc file cannot silently switch that launch to another Codex home. Executable and PATH selection stay the same. Existing running sessions are not moved or restarted; a thread in another home is not searched as a fallback for an explicitly selected home.

With CODEX_HOME unset, a second install still shares ~/.codex. Setting only OPENRIG_HOME separates OpenRig state, not provider configuration. The default launch behavior is unchanged, including existing pane-shell overrides.

OpenRig does not copy or migrate authentication, history, rules, trust or settings into the selected home. A separate file-backed home needs its intended authentication provisioned if absent; OS credential stores and other config layers can behave differently, so a new login is not universally required. Explicit Codex auth commands resolve their caller's CODEX_HOME, otherwise $HOME/.codex; contacting a daemon with OPENRIG_URL does not retarget auth save/switch/registry writes to that daemon's selection. Set the intended root on those commands as well.

This separates provider files, not OS users or accounts. Project-local resources still use the selected project directory; shared project and system settings remain shared.

Codex guidance at launch

On a fresh launch of Codex 0.164.0 or later, OpenRig passes a seat's managed guidance (OpenRig's start file and the rig's and project's context, culture and SOP files) with the launch, as -c developer_instructions=…, instead of merging it into the repository AGENTS.md. Neither the tracked AGENTS.md nor the Codex home is written for it, and both still load as usual. The text opens: "OpenRig seat guidance for this session follows. Where it conflicts with AGENTS.md guidance in the repository or the Codex home, follow AGENTS.md."

A launch value replaces a configured one, so this happens only when no Codex config layer OpenRig reads sets developer_instructions. OpenRig reads:

  • /etc/codex/config.toml and /etc/codex/managed_config.toml;
  • the Codex home's config.toml, profiles included, and the selected profile's <profile>.config.toml;
  • each .codex/config.toml from the seat's working directory up.

The Codex home it reads is the seat's own (a CODEX_HOME in its env:, or a recorded account switch), else the daemon's CODEX_HOME, else ~/.codex.

The guidance still goes to the repository AGENTS.md, as before, when:

  • a layer OpenRig reads sets developer_instructions, or can't be read. A launch warning names the file;
  • the launch resumes or forks a conversation. Developer instructions on a resume haven't been tested, and an exact resume puts the guidance back in the repository AGENTS.md;
  • the seat is restored, even when the restore starts a new conversation. A restore keeps the guidance file its launch recorded, which for Codex is the repository AGENTS.md;
  • a handover successor starts (fresh, fork or rebuild). Its launch carries no guidance of its own, so the handover first puts the seat's missing blocks back in the repository AGENTS.md from its saved startup context;
  • Codex is older than 0.164.0, or its version is unknown.

A seat's role still arrives as startup text, and the seat-skills line, after the person's yes, still goes to AGENTS.md.

If your shell sets CODEX_HOME. When neither the seat nor the daemon declares a CODEX_HOME, a seat's pane keeps its own, and OpenRig reads ~/.codex. A CODEX_HOME that only your shell startup sets (in ~/.zshrc, for example), with the daemon started without it, isn't seen. A developer_instructions set in that home is then replaced by the launch value for the session. To have it checked, declare that same absolute path as CODEX_HOME in the member's or rig's env: (see Launch environment, "A seat's own account"). OpenRig then reads that home at the seat's next fresh launch, and a developer_instructions set there keeps the repository AGENTS.md. The seat keeps the same home and login.

What it doesn't cover. OpenRig doesn't read Codex's packaged defaults, or MDM- or enterprise-managed settings. A developer_instructions set there may be replaced for the session. Legacy MDM-managed config ranks above launch flags, so it can override OpenRig's guidance, and the seat then gets none of it.

Not established. The order Codex assembles developer instructions and AGENTS.md in, and how it settles every conflict, aren't established; the opening sentence asks it to follow AGENTS.md. The native behaviour was observed on Codex 0.164.0 alone, on fresh launches without tools.

Runtime Differences

Claude Code:

  • Reads from CLAUDE.md and .claude/ directory
  • Recurring tasks via the /loop command (e.g., /loop 5m "check rig health") — this is NOT hooks; hooks are event-driven
  • MCP server management via /mcp interactive command or claude mcp add from CLI
  • Event-driven hooks system in .claude/settings.json — reacts to events like PreToolUse, PostToolUse, SessionStart, etc. (not time-based)
  • Permission allowlisting via .claude/settings.json (permissions.allow, permissions.deny, permissions.defaultMode)
  • Can self-configure MCP servers, permissions, and hooks from startup instructions

Codex:

  • Reads from AGENTS.md and .agents/ directory
  • Recurring task support is limited — no confirmed equivalent of Claude Code's /loop command
  • MCP configuration mechanism differs from Claude Code
  • Approval policy and sandbox access are separate controls. OpenRig's default -s workspace-write selects the sandbox; it does not force -a. A member's codex_config_profile selects native -p, distinct from the AgentSpec profile.
  • On that plain -s workspace-write launch (fresh, resume, fork or restore), a short-lived codex app-server first reads Codex's own configuration with the seat's executable, home and working directory. OpenRig adds -c sandbox_workspace_write.network_access=true only when no configuration layer sets network access and no managed requirement could restrict it. A timeout, error or unrecognized answer adds nothing. Named profiles and full bypass are not read. The read may write Codex's own state files in CODEX_HOME, read its login and fetch managed policy, as a Codex start does.
  • Managed launches pass -c check_for_update_on_startup=false, so Codex skips its startup update check; your Codex config is not changed.
  • Can self-install dependencies from instructions but timer/recurring behavior is not reliably available

Pi and OMP:

  • Read AGENTS.md in the working directory, added to Pi's own system prompt. Pi also reads the one in its agent directory: on a fresh launch, a Pi seat's guidance is merged there, under OpenRig's state, not into the repository's. When that file or its directory is a link, the repository AGENTS.md is used instead. A restore keeps the file the seat launched with. OMP keeps its guidance in the repository AGENTS.md
  • Selected skills are projected into the seat's own agent directory under OpenRig's state, not into a shared workspace folder

For Pi tool-selection diagnosis and deliberate in-place recovery, see Pi tool-selection recovery. It distinguishes configured, currently intended, registered and active tools, including the TUI/RPC boundary.

Terminal seats (builtin:terminal) get no agent or profile layers and no startup proof.

Claude compaction preparation directory

Managed Claude compaction asks the seat to write a restore map before /compact. By default, OpenRig uses <cwd>/.openrig/compaction/preparation/, falling back to its instance home when that directory cannot be prepared. A filesystem write check cannot establish whether Claude's file-edit permissions allow the seat to write there.

If the launch workspace is denied by Claude's permissions, select an absolute preparation root:

rig config set policies.claude_compaction.preparation_root /absolute/path/to/restore-maps

Grant the seat file-edit access to that root in its Claude configuration. OpenRig does not change Claude permissions; choose a directory outside any matching deny rule. The corresponding environment variable is OPENRIG_POLICIES_CLAUDE_COMPACTION_PREPARATION_ROOT.

New manual and automatic attempts use <root>/<session>/<attempt-id>/RESTORE-MAP.md; an in-flight attempt keeps its original path through the post-compaction restore request. The root gets an exclusive .gitignore containing *, and the daemon publishes the marked .tmp file with an atomic rename. Use a dedicated root: a conflicting ignore file or an unwritable configured directory stops preparation with preparation_directory_unavailable, visible in rig compact <session> --state, without choosing another location. Fix the directory and explicitly retry. --skip-map still skips the map once.

Reset with rig config reset policies.claude_compaction.preparation_root to restore the default. The setting applies to new attempts without restarting the daemon.

The managed /compact command keeps its 600-character budget. When the selected map path or the attempt's compaction instruction does not fit, the existing restore request carries it; it never substitutes a newer preparation root or instruction. An explicitly selected root's published map is also named in that request when the path fit in /compact. The default root keeps the existing command/restore handoff. Under --skip-map, restore still recognizes an optional map completed at that attempt's path, or says that no map was required.

The compaction preparation and command-submission tests cover manual and automatic attempts, long paths and instructions, settings changed during preparation, directory failures and late maps after cancellation or expiry. They use synthetic delivery and filesystem fixtures; they do not prove a live Claude session's permission choices or compaction behavior.

Codex compaction and sizing

OpenRig's managed compaction is for Claude Code seats only, by design. That covers rig compact, the policies.claude_compaction.* automatic policy, and the watchdog jobs that the managed-compaction and apprentice-handover strategies create. A Codex seat compacts with Codex's own automatic compaction, and OpenRig leaves that to Codex.

  • rig compact <session> refuses a Codex seat: "not a Claude (claude-code) seat".
  • rig compact-plan lists a Codex seat as skipped (codex_not_managed_by_claude_compact_in_place).
  • The automatic policy never acts on a Codex seat.
  • A Codex member with compaction_strategy: managed-compaction or apprentice-handover gets no watchdog jobs.

What Codex compacts at. Codex describes model_auto_compact_token_limit as the "token threshold that triggers automatic history compaction (unset uses model defaults)". Its configuration reference does not say what those defaults are, and the model catalog's auto_compact_token_limit is null for the models we checked in Codex 0.162.1. Measure a seat's own compactions before relying on an unset limit.

What window the model has. codex debug models prints the catalog. Each model carries context_window, max_context_window and effective_context_window_percent. In Codex 0.162.1, gpt-5.6-sol has a context_window of 272000 at 95%, which is 258400. That is the model_context_window its rollout token_count events report. A limit only matters when it is below that effective window. Leave model_context_window alone: don't raise it toward max_context_window to avoid compaction. Set the limit instead.

Setting the limit for seats only. Put model_auto_compact_token_limit in a Codex profile file, $CODEX_HOME/<name>.config.toml (~/.codex/<name>.config.toml when CODEX_HOME is unset), and name it on each Codex member that should use it:

# ~/.codex/seat-compaction.config.toml
model_auto_compact_token_limit = 150000   # choose your own value
codex_config_profile: seat-compaction

OpenRig passes -p seat-compaction on that seat's fresh, fork and resume launches. Codex layers the profile file on top of the base user config, so only seats that name the profile see the lower limit. A Codex session started without that profile keeps the base config.toml in the same Codex home ($CODEX_HOME/config.toml, or ~/.codex/config.toml when CODEX_HOME is unset). The 150000 is an example, not a recommendation.

With the normal launch mode, codex_config_profile replaces OpenRig's explicit workspace-write sandbox flag, so the profile must set the sandbox and approvals you want. See permission precedence and limits, which also gives Codex's order of precedence between a profile, project settings and CLI overrides.

The trade-off. Every model call carries the whole context so far, so a smaller context makes each call cheaper. The cost is that Codex compacts sooner and more often, so more of the conversation is carried as a summary instead of verbatim. A higher limit keeps more detail and makes each call larger.

Related keys. These also shape compaction. This guide doesn't recommend values for them. See Codex's configuration reference:

Key What Codex says it does
model_auto_compact_token_limit_scope total (the default) counts the full active context; body_after_prefix counts only growth after the carried compaction-window prefix.
compact_prompt Inline override for the history compaction prompt.
experimental_compact_prompt_file Loads the compaction prompt override from a file (experimental).
tool_output_token_limit Token budget for storing individual tool or function outputs in history.

When authoring startup content, note which instructions are runtime-specific. For example, an orchestrator that needs a monitoring loop should include instructions like: "If running Claude Code, use /loop 3m to periodically check rig health. If running Codex, check rig health at the start of each task cycle instead."


Patterns and Anti-Patterns

Good Patterns

Start with role + culture + one skill

agent.yaml → guidance/role.md
rig.yaml → culture_file: CULTURE.md
profile → uses.skills: [openrig-user]

This is the minimum effective startup. The agent knows who it is, how the team works, and how to use the rig.

For seats sharing a working directory, keep each role in its own AgentSpec startup entry:

startup:
  files:
    - path: guidance/role.md
      orientation: role
      delivery_hint: send_text
      required: true

rig queue whoami --json reports only the calling seat's explicitly marked role bindings, independently of its current work. The role.state is no-record, not-declared, missing, present, or unknown when identity, storage or the observation is unavailable. Files retain their original path, absolutePath and ownerRoot plus the currently resolved resolvedPath and resolvedOwnerRoot. Built-in packaged paths follow the running installation; existing development-checkout paths stay where they were recorded.

recordedAt is the startup-context record write/attempt, which happens before launch succeeds. Exact resume can retain an older record. present means the resolved file currently exists, not that its bytes still match the spec or that the agent read it. Unmarked startup files and startup actions are not exposed.

Refocus includes the role path and a re-read reminder with topology or both trees, even if the trace fails. Work-only mode omits it; disabling refocus still disables the entire hook. Custom refocus prose keeps its existing precedence. The hook reuses its work lookup, or makes one bounded lookup when the work node is explicit; a failed or skipped lookup reports unknown.

The shipped kernel seats no longer project their role into shared guidance. Existing user files and legacy role blocks are left intact; this is not a cleanup of already-installed shared blocks. General user-authored guidance projection is unchanged.

The other shipped agent specs that start a seat with guidance/role.md mark that file orientation: role too, so rig queue whoami --json reports their role and refocus names it. They never projected a role into shared guidance; their startup delivery is unchanged.

Removing a leftover shared role block by hand. A kernel installed before 0.6.5 may still hold one seat's old role in its working directory's CLAUDE.md (or the rig's configured managed_blocks file) or AGENTS.md. Upgrading doesn't remove it. rig down removes OpenRig's managed blocks from that file when no other running rig uses it, so it stays until then. To remove it sooner, by hand:

  1. Find the exact block: the lines from <!-- BEGIN OpenRig MANAGED BLOCK: role --> through <!-- END OpenRig MANAGED BLOCK: role --> (in older files, RIGGED instead of OpenRig). Only the block whose id is exactly role; leave rig-role, other ids and everything outside the markers alone.
  2. Confirm each seat that shares that directory has its per-seat replacement: rig queue whoami --json run in the seat reports role.state as present. A seat started before it was upgraded still has the old content in its conversation; restart it fresh if you want it to start from the per-seat role.
  3. Keep a copy of the file, remove only those lines, and compare the result with the copy: nothing else in the file should differ.

The kernel's own folder. A newly created default kernel works in <workspace.root>/kernel, beside your projects (<workspace.root>/projects by default) rather than above them. Claude reads CLAUDE.md from a seat's folder and from every folder above it, so a kernel working in the workspace root put its guidance into every Claude project seat below it. If that folder can't be made, the new kernel works in the workspace root, as before.

A kernel that already exists keeps its folders, usually workspace.root. Upgrading moves no seat, changes no restored conversation and removes no file. To move an existing kernel yourself, if you want to:

  1. Save the new folder for each kernel seat with rig seat set-cwd <seat> --cwd <workspace.root>/kernel --reason <text>, using the absolute path. It creates the folder (details).
  2. Choose when each seat adopts it. A seat keeps its current folder until it starts again, and the new start doesn't continue its current native conversation: rig seat launch <seat> --fresh --stop --reason <text> starts a blank one, and rig handover <seat> --source rebuild --reason <text> carries durable context into a new one. Nothing does this for you.
  3. Tidy the old <workspace.root>/CLAUDE.md (or AGENTS.md) by hand, once no running rig's seat still works in that folder: a seat there still reads those blocks, and rig down checks the same before it removes them. Remove only OpenRig's managed blocks, each from its <!-- BEGIN OpenRig MANAGED BLOCK: … --> line through its END line, as in the steps above, and keep everything else, including your own text. Don't move or delete the whole file.

Separate project context from role

Don't put project documentation inside the role guidance. The role is about the agent's function; project context is about what the agent is working on. Use rig-level or pod-level startup files for project context.

Tell the agent about its skills explicitly

In a startup file or guidance, include a line like:

You have the following skills loaded: openrig-user, test-driven-development, systematic-debugging. Use them.

This prompts the agent to actually invoke the skills, not just have them as passive context.

Include a system check in startup context

## System Check

After identity recovery, verify:
1. `rig ps --nodes` shows your rig running (scoped to your session's rig by default; outside a managed session name it explicitly: `rig ps --nodes --rig <name>`)
2. `rig env status <rig>` shows services healthy (if applicable)
3. Your working directory is correct
4. Required tools are available (node, npm, git, etc.)

If anything is missing, fix it before starting work.

Anti-Patterns

Dumping everything into one giant CLAUDE.md

Don't. Use the layering model. Role goes in the agent spec. Culture goes in the rig spec. Project context goes in rig/pod startup files. If everything is in one file, you can't reuse any of it.

Duplicating skill content in guidance files

If you find yourself copying text from a skill into a guidance file, stop. Reference the skill instead. Skills are projected; guidance should point to them, not duplicate them.

Using send_text for content that should be pre-loaded

If the agent needs to know something BEFORE it starts reasoning, use guidance_merge (delivered before boot), not send_text (delivered after boot). send_text is for instructions the agent should process as a first task, not for foundational context.

Over-specifying member-level startup

If every member has a large startup block, the rig spec becomes a configuration dump. Refactor shared content up to the pod level. Member-level startup should be small overrides, not complete agent briefings.

Relying on deterministic hooks for critical setup

If your rig REQUIRES a hook to function and the hook installation fails silently, the agent won't know something is wrong. Always pair deterministic config with a startup instruction or system check that verifies the result.


Authoring Checklist

When creating a new agent's startup experience:

  • Write a guidance/role.md — who is this agent?
  • Reference it in startup.files with orientation: role and delivery_hint: send_text. Avoid projecting distinct seat roles into one shared AGENTS.md or CLAUDE.md.
  • Write a rig CULTURE.md if the rig doesn't have one
  • Choose skills from the shared pool via profile uses
  • Write a startup/context.md if the agent needs environment grounding
  • Include a system check in the startup context
  • Verify the agent spec validates: rig agent validate agent.yaml
  • Verify the rig spec validates: rig spec validate rig.yaml
  • Run the advisory authoring check: rig spec audit rig.yaml
  • Test by launching the rig and checking that the agent received the expected content