II.
Page JSON
Structured · livepage:docs-agent-mux-reference-02-run-options-and-profiles
RunOptions, Profiles, and Run Configuration json
Inspect the normalized record payload exactly as the atlas UI reads it.
{
"id": "page:docs-agent-mux-reference-02-run-options-and-profiles",
"_kind": "Page",
"_file": "wiki/docs/agent-mux/reference/02-run-options-and-profiles.md",
"_cluster": "wiki",
"attributes": {
"nodeKind": "Page",
"sourcePath": "docs/agent-mux/reference/02-run-options-and-profiles.md",
"sourceKind": "repo-docs",
"title": "RunOptions, Profiles, and Run Configuration",
"displayName": "RunOptions, Profiles, and Run Configuration",
"slug": "docs/agent-mux/reference/02-run-options-and-profiles",
"articlePath": "wiki/docs/agent-mux/reference/02-run-options-and-profiles.md",
"article": "\n# RunOptions, Profiles, and Run Configuration\n\n**Specification v1.0** | `@a5c-ai/agent-mux`\n\n> **Note:** hermes-agent is included as a 10th supported agent per project requirements, extending the original scope's 9 agents.\n\n---\n\n## 1. Overview\n\nThis specification defines the complete parameter surface for invoking agents through agent-mux: the `RunOptions` interface, supporting types (`Attachment`, `RetryPolicy`, `McpServerConfig`), the profile system for storing and resolving named option presets, and the validation, capability-gating, and resolution rules that govern how options flow from source to subprocess.\n\nAll ten built-in agents are covered: claude, codex, gemini, copilot, cursor, opencode, pi, omp, openclaw, and hermes.\n\n### 1.1 Cross-References\n\n| Type / Concept | Spec | Section |\n|---|---|---|\n| `AgentName`, `BuiltInAgentName` | `01-core-types-and-client.md` | 1.4 |\n| `AgentMuxClient`, `createClient()` | `01-core-types-and-client.md` | 5 |\n| `RunHandle`, `RunResult` | `03-run-handle-and-interaction.md` | 1 |\n| `AgentEvent` union type | `04-agent-events.md` | 4 |\n| `AgentCapabilities`, `ModelCapabilities` | `04-capabilities-and-models.md` | 1 |\n| `ConfigManager` | `08-config-and-auth.md` | 17 |\n| `CapabilityError`, `ValidationError` | `01-core-types-and-client.md` | 3.2, 3.3 |\n| `ErrorCode` | `01-core-types-and-client.md` | 3.1 |\n| `GlobalConfig` | `01-core-types-and-client.md` | 4.1.2 |\n| `RetryPolicy` | `01-core-types-and-client.md` | 5.1.1 |\n| Storage layout | `01-core-types-and-client.md` | 4 |\n| Adapter contract | `05-adapter-system.md` | 1 |\n| CLI commands | `10-cli-reference.md` | 1 |\n\n---\n\n## 2. RunOptions Interface\n\n`RunOptions` is the single configuration object passed to `mux.run()`. It controls every aspect of an agent invocation: which agent and model to use, how to prompt it, how to handle streaming, thinking, sessions, approval, execution context, retry behavior, and metadata.\n\n```typescript\n/**\n * Complete parameter set for a single agent invocation via `mux.run()`.\n *\n * Fields fall into three categories:\n * - **Universal**: supported by all agents (may be ignored by agents that lack the feature).\n * - **Capability-gated**: throw `CapabilityError` if the target agent/model does not support them.\n * - **Metadata-only**: stored in `run-index.jsonl` but never sent to the agent.\n */\ninterface RunOptions {\n // ── Required ─────────────────────────────────────────────────────────\n\n /**\n * The agent to invoke.\n *\n * Must be a registered agent name: one of the ten built-in agents or\n * a plugin adapter registered via `mux.adapters.register()`.\n *\n * @throws {AgentMuxError} code `AGENT_NOT_FOUND` if no adapter is registered for this name.\n * @throws {AgentMuxError} code `AGENT_NOT_INSTALLED` if the adapter exists but the CLI binary\n * is not found on `$PATH`.\n */\n agent: AgentName;\n\n // ── Prompt / Input ───────────────────────────────────────────────────\n\n /**\n * The prompt to send to the agent.\n *\n * When a `string`, it is sent as a single user message.\n * When a `string[]`, elements are concatenated with `\\n\\n` as the separator\n * before being sent to the agent. This is a convenience for multi-paragraph\n * prompts; it does not create multiple turns.\n *\n * An empty string or empty array throws `ValidationError`.\n */\n prompt: string | string[];\n\n /**\n * System prompt injected into the agent's context.\n *\n * How the system prompt is injected depends on `systemPromptMode` and the\n * agent's native support. Agents that support a native `--system-prompt` flag\n * (claude, codex, gemini, opencode, hermes) use it directly. Others receive\n * the system prompt prepended to the user prompt.\n */\n systemPrompt?: string;\n\n /**\n * How `systemPrompt` is combined with the agent's existing system prompt.\n *\n * - `'prepend'` -- insert before the agent's default system prompt.\n * - `'append'` -- insert after the agent's default system prompt.\n * - `'replace'` -- fully replace the agent's default system prompt.\n *\n * Not all agents support all modes natively. When the agent does not support\n * the requested mode, the adapter approximates it:\n * - `'prepend'` and `'append'` are simulated by concatenation in the prompt.\n * - `'replace'` may not be possible for agents with hardcoded system prompts;\n * in that case, `'prepend'` behavior is used as a fallback.\n *\n * @default 'prepend'\n */\n systemPromptMode?: 'prepend' | 'append' | 'replace';\n\n /**\n * File attachments included with the prompt.\n *\n * @capability Requires `supportsFileAttachments: true` or `supportsImageInput: true`\n * on the agent's capabilities. Throws `CapabilityError` if the agent\n * supports neither.\n *\n * See Section 3 for the `Attachment` type definition.\n */\n attachments?: Attachment[];\n\n // ── Model ────────────────────────────────────────────────────────────\n\n /**\n * Model ID to use for this run.\n *\n * Must be a model ID recognized by the target agent. Use `mux.models.validate()`\n * to check ahead of time. If not specified, the agent's default model is used.\n *\n * Resolution order:\n * 1. This field (per-call).\n * 2. Profile's `model` field (if `profile` is set).\n * 3. Project config's `defaultModel` (if it matches the target agent).\n * 4. Global config's `defaultModel` (if it matches the target agent).\n * 5. Adapter's `defaultModelId`.\n */\n model?: string;\n\n // ── Thinking / Reasoning ─────────────────────────────────────────────\n\n /**\n * Normalized thinking effort level.\n *\n * Translated by each adapter to the agent's native thinking parameters.\n * See Section 8 for per-adapter translation tables.\n *\n * @capability Requires the selected model to have `supportsThinking: true`.\n * Throws `CapabilityError` if the model does not support thinking.\n */\n thinkingEffort?: 'low' | 'medium' | 'high' | 'max';\n\n /**\n * Thinking budget in tokens.\n *\n * For Claude Code, this maps directly to `budget_tokens`.\n * For other agents, it is translated to the closest native equivalent.\n * When both `thinkingEffort` and `thinkingBudgetTokens` are set,\n * `thinkingBudgetTokens` takes precedence.\n *\n * @capability Requires the selected model to have `supportsThinking: true`\n * AND the agent to have `supportsThinkingBudgetTokens: true`.\n * Throws `CapabilityError` if either is false.\n */\n thinkingBudgetTokens?: number; // Minimum 1024 derived from Claude's documented budget_tokens minimum.\n\n /**\n * Full escape hatch for thinking configuration.\n *\n * An arbitrary key-value map merged over the normalized thinking parameters\n * after adapter translation. This allows consumers to pass native,\n * agent-specific thinking parameters that the normalized interface does\n * not cover.\n *\n * Merged last, overriding both `thinkingEffort` and `thinkingBudgetTokens`.\n *\n * @capability Same gating as `thinkingEffort`.\n */\n thinkingOverride?: Record<string, unknown>;\n\n // ── Sampling ─────────────────────────────────────────────────────────\n\n /**\n * Sampling temperature.\n *\n * Controls randomness: 0.0 is deterministic, higher values increase\n * randomness. Not all agents expose temperature control via CLI.\n * When the agent does not support it, this field is silently ignored\n * (no error thrown).\n *\n * @valid [0.0, 2.0]\n * @throws {ValidationError} if outside the valid range.\n */\n temperature?: number;\n\n /**\n * Top-P (nucleus) sampling parameter.\n *\n * Limits token selection to the smallest set whose cumulative probability\n * exceeds this threshold. Silently ignored by agents that do not support it.\n *\n * @valid [0.0, 1.0]\n * @throws {ValidationError} if outside the valid range.\n */\n topP?: number;\n\n /**\n * Top-K sampling parameter.\n *\n * Limits token selection to the top K most probable tokens.\n * Silently ignored by agents that do not support it.\n *\n * @valid >= 1 (integer)\n * @throws {ValidationError} if less than 1 or not an integer.\n */\n topK?: number;\n\n /**\n * Maximum total tokens for the response.\n *\n * This is the maximum number of tokens the model may generate in its\n * response. Mapped to the agent's native `--max-tokens` or equivalent.\n * Silently ignored if the agent does not support it.\n *\n * @valid >= 1 (integer)\n * @throws {ValidationError} if less than 1.\n */\n maxTokens?: number;\n\n /**\n * Maximum output tokens.\n *\n * Alias for `maxTokens` with identical semantics. When both `maxTokens`\n * and `maxOutputTokens` are set, `maxOutputTokens` takes precedence.\n *\n * @valid >= 1 (integer)\n * @throws {ValidationError} if less than 1.\n */\n maxOutputTokens?: number;\n\n // ── Session ──────────────────────────────────────────────────────────\n\n /**\n * Resume an existing session by its native session ID.\n *\n * The agent must support session resumption (`canResume: true`).\n * If the session does not exist, the agent may create a new one or throw\n * an error, depending on its native behavior.\n *\n * @mutuallyExclusive Cannot be set together with `noSession`.\n * Cannot be set together with `forkSessionId`.\n * @capability Silently ignored (not an error) if the agent does not support sessions.\n */\n sessionId?: string;\n\n /**\n * Fork an existing session, creating a new branch from the specified\n * session's state.\n *\n * @mutuallyExclusive Cannot be set together with `sessionId`.\n * Cannot be set together with `noSession`.\n * @capability Requires `canFork: true`. Throws `CapabilityError` if false.\n */\n forkSessionId?: string;\n\n /**\n * Run without session persistence. The conversation is ephemeral and\n * not saved to disk.\n *\n * @mutuallyExclusive Cannot be set together with `sessionId`.\n * Cannot be set together with `forkSessionId`.\n * @default false\n */\n noSession?: boolean;\n\n // ── Streaming ────────────────────────────────────────────────────────\n\n /**\n * Streaming mode for this run.\n *\n * - `'auto'` -- stream text output if the adapter supports it; fall back\n * to buffered for capabilities the adapter lacks. Emits `stream_fallback`\n * events when fallback activates.\n * - `true` -- require text streaming. Throws `CapabilityError` if the\n * adapter has `supportsTextStreaming: false`. Tool call and thinking\n * streaming still fall back silently.\n * - `false` -- all output is buffered. A single `text_delta` with full\n * accumulated text fires on completion, followed by `message_stop`.\n *\n * Consumers always receive the same event types regardless of mode.\n *\n * @default 'auto'\n */\n stream?: boolean | 'auto';\n\n // ── Output Format ────────────────────────────────────────────────────\n\n /**\n * Output format for the agent's response.\n *\n * - `'text'` -- plain text output (default).\n * - `'json'` -- request JSON-formatted output from the agent.\n * - `'jsonl'` -- request JSON Lines formatted output.\n *\n * @capability `'json'` requires `supportsJsonMode: true` on the agent.\n * Throws `CapabilityError` if false.\n * @capability `'jsonl'` requires `supportsJsonMode: true` on the agent.\n * Throws `CapabilityError` if false.\n */\n outputFormat?: 'text' | 'json' | 'jsonl';\n\n // ── Execution Context ────────────────────────────────────────────────\n\n /**\n * Working directory for the agent subprocess.\n *\n * Must be an absolute path. The directory must exist.\n *\n * @default process.cwd()\n * @throws {ValidationError} if the path is not absolute or the directory\n * does not exist.\n */\n cwd?: string;\n\n /**\n * Additional environment variables passed to the agent subprocess.\n *\n * Merged with the current process environment. Keys in this map override\n * `process.env` values. Values must be strings; non-string values throw\n * `ValidationError`.\n */\n env?: Record<string, string>;\n\n /**\n * Maximum wall-clock time for the entire run, in milliseconds.\n *\n * When exceeded, the agent subprocess is terminated and a `timeout` event\n * with `kind: 'run'` is emitted. 0 means no timeout.\n *\n * @default 0\n * @valid >= 0 (integer)\n * @throws {ValidationError} if negative.\n */\n timeout?: number;\n\n /**\n * Maximum time between agent output events, in milliseconds.\n *\n * If the agent produces no output for this duration, the subprocess is\n * terminated and a `timeout` event with `kind: 'inactivity'` is emitted.\n * 0 means no inactivity timeout.\n *\n * @default 0\n * @valid >= 0 (integer)\n * @throws {ValidationError} if negative.\n */\n inactivityTimeout?: number;\n\n /**\n * Maximum number of agentic turns (tool-use loops) allowed.\n *\n * When the agent reaches this limit, it is instructed to stop (if\n * the agent supports turn limiting) or forcefully interrupted. A\n * `turn_limit` event is emitted.\n *\n * @valid >= 1 (integer)\n * @throws {ValidationError} if less than 1.\n */\n maxTurns?: number;\n\n // ── Approval / Interaction ───────────────────────────────────────────\n\n /**\n * How tool calls and file operations are approved.\n *\n * - `'yolo'` -- auto-approve all actions. Maps to the agent's equivalent\n * of unrestricted mode (e.g., `--dangerously-skip-permissions` for Claude,\n * `--full-auto` for Codex, `--yolo` for others).\n * - `'prompt'` -- emit `approval_request` events and wait for consumer\n * response via `onApprovalRequest` or `RunHandle.approve()`/`deny()`.\n * - `'deny'` -- auto-deny all actions requiring approval. The agent can\n * only perform read-only operations.\n *\n * @default 'prompt'\n */\n approvalMode?: 'yolo' | 'prompt' | 'deny';\n\n /**\n * Callback invoked when the agent requires free-form text input\n * (not a tool approval).\n *\n * If not provided and the agent requests input, a `input_required` event\n * is emitted on the `RunHandle`. If no handler is registered on the\n * `RunHandle` either, the run blocks until `RunHandle.send()` is called.\n *\n * This field is per-run and cannot be stored in a profile.\n */\n onInputRequired?: (event: InputRequiredEvent) => Promise<string>;\n\n /**\n * Callback invoked when the agent requests approval for an action.\n *\n * Interaction with `approvalMode` is asymmetric by design:\n *\n * - **`'deny'` mode**: `onApprovalRequest` is **never invoked**. All approval\n * requests are auto-denied before the callback is consulted. `deny` mode\n * overrides the callback entirely.\n * - **`'yolo'` mode**: `onApprovalRequest` **is invoked** for each request.\n * This allows the callback to selectively approve or deny individual actions,\n * effectively narrowing yolo to per-request approval. If no callback is set,\n * all actions are auto-approved.\n * - **`'prompt'` mode**: `onApprovalRequest` **is invoked** for each request,\n * replacing the default UI prompt. If no callback is set, an\n * `approval_request` event is emitted on the `RunHandle` instead.\n *\n * This field is per-run and cannot be stored in a profile.\n */\n onApprovalRequest?: (event: ApprovalRequestEvent) => Promise<'approve' | 'deny'>;\n\n // ── Skills / Agent Docs ──────────────────────────────────────────────\n\n /**\n * Skills to load for this run.\n *\n * The meaning of \"skill\" is agent-specific: for Claude Code, these are\n * skill directories loaded via `--add-dir`; for hermes, these are skills\n * from `~/.hermes/skills/` or agentskills.io; for opencode, pi, and omp,\n * these are npm package names or file paths.\n *\n * @capability Requires `supportsSkills: true`. Throws `CapabilityError` if false.\n */\n skills?: string[];\n\n /**\n * Path to an agents.md or equivalent agent documentation file.\n *\n * Loaded by agents that support custom agent documentation\n * (Claude Code's `CLAUDE.md` pattern, hermes skill definitions).\n *\n * @capability Requires `supportsAgentsMd: true`. Throws `CapabilityError` if false.\n */\n agentsDoc?: string;\n\n // ── MCP ──────────────────────────────────────────────────────────────\n\n /**\n * MCP server configurations to connect for this run.\n *\n * Each entry defines a server the agent should connect to for additional\n * tool capabilities. See Section 4 for the `McpServerConfig` type.\n *\n * @capability Requires `supportsMCP: true`. Throws `CapabilityError` if false.\n */\n mcpServers?: McpServerConfig[];\n\n // ── Retry ────────────────────────────────────────────────────────────\n\n /**\n * Retry policy for transient failures during this run.\n *\n * Overrides the client-level and config-level retry policies.\n * See Section 5 for the `RetryPolicy` type definition.\n */\n retryPolicy?: RetryPolicy;\n\n // ── Metadata ─────────────────────────────────────────────────────────\n\n /**\n * Custom run ID.\n *\n * If not provided, a ULID is generated automatically. Must be a valid\n * ULID string if provided.\n *\n * Stored in `run-index.jsonl`. Not sent to the agent.\n */\n runId?: string;\n\n /**\n * Tags for categorizing and filtering runs.\n *\n * Stored in `run-index.jsonl`. Not sent to the agent.\n * Used by `amux sessions search --tag` and `amux cost report --tag`.\n */\n tags?: string[];\n\n /**\n * Project identifier for cost roll-ups and session grouping.\n *\n * Stored in `run-index.jsonl`. Not sent to the agent.\n */\n projectId?: string;\n\n // ── Profile ──────────────────────────────────────────────────────────\n\n /**\n * Name of a saved profile to apply as the base for this run.\n *\n * Profile values are merged beneath per-call values: any field explicitly\n * set in the `RunOptions` object takes precedence over the profile.\n *\n * @throws {AgentMuxError} code `PROFILE_NOT_FOUND` if the named profile\n * does not exist in either global or project profile directories.\n */\n profile?: string;\n}\n```\n\n### 2.1 RunOptions Field Reference\n\nThe following table lists every field with its type, requirement status, default value, valid range, and which agents support it. Fields marked \"All\" are universal. Fields marked with specific agents are capability-gated.\n\n| Field | Type | Required | Default | Valid Range | Agents | Category |\n|---|---|---|---|---|---|---|\n| `agent` | `AgentName` | Yes | -- | Registered agent name | All | Required |\n| `prompt` | `string \\| string[]` | Yes | -- | Non-empty | All | Prompt |\n| `systemPrompt` | `string` | No | `undefined` | Any string | All | Prompt |\n| `systemPromptMode` | `'prepend' \\| 'append' \\| 'replace'` | No | `'prepend'` | One of three literals | All | Prompt |\n| `attachments` | `Attachment[]` | No | `undefined` | See Section 3 | claude, codex, gemini, cursor, opencode, openclaw | Prompt |\n| `model` | `string` | No | Adapter default | Agent-specific model ID | All | Model |\n| `thinkingEffort` | `'low' \\| 'medium' \\| 'high' \\| 'max'` | No | `undefined` | One of four literals | claude, codex, gemini, omp + model-dep: cursor, opencode, pi, openclaw | Thinking |\n| `thinkingBudgetTokens` | `number` | No | `undefined` | `>= 1024`, `<= model.maxThinkingTokens` | claude, codex, gemini | Thinking |\n| `thinkingOverride` | `Record<string, unknown>` | No | `undefined` | Any key-value map | claude, codex, gemini, pi, omp | Thinking |\n| `temperature` | `number` | No | `undefined` | `[0.0, 2.0]` | All (silently ignored if unsupported) | Sampling |\n| `topP` | `number` | No | `undefined` | `[0.0, 1.0]` | All (silently ignored if unsupported) | Sampling |\n| `topK` | `number` | No | `undefined` | `>= 1` (integer) | All (silently ignored if unsupported) | Sampling |\n| `maxTokens` | `number` | No | `undefined` | `>= 1` (integer) | All (silently ignored if unsupported) | Sampling |\n| `maxOutputTokens` | `number` | No | `undefined` | `>= 1` (integer) | All (silently ignored if unsupported) | Sampling |\n| `sessionId` | `string` | No | `undefined` | Non-empty string | claude, codex, gemini, copilot, cursor, opencode, pi, omp, openclaw, hermes | Session |\n| `forkSessionId` | `string` | No | `undefined` | Non-empty string | claude, opencode, pi, omp | Session |\n| `noSession` | `boolean` | No | `false` | `true \\| false` | All | Session |\n| `stream` | `boolean \\| 'auto'` | No | `'auto'` | `true`, `false`, `'auto'` | All | Streaming |\n| `outputFormat` | `'text' \\| 'json' \\| 'jsonl'` | No | `'text'` | One of three literals | claude, codex, gemini, opencode | Output |\n| `cwd` | `string` | No | `process.cwd()` | Absolute path to existing dir | All | Execution |\n| `env` | `Record<string, string>` | No | `undefined` | String keys and values | All | Execution |\n| `timeout` | `number` | No | `0` | `>= 0` (integer, ms) | All | Execution |\n| `inactivityTimeout` | `number` | No | `0` | `>= 0` (integer, ms) | All | Execution |\n| `maxTurns` | `number` | No | `undefined` | `>= 1` (integer) | claude, codex, gemini, opencode, pi, omp, hermes | Execution |\n| `approvalMode` | `'yolo' \\| 'prompt' \\| 'deny'` | No | `'prompt'` | One of three literals | All | Approval |\n| `onInputRequired` | `(event) => Promise<string>` | No | `undefined` | Function | All | Approval |\n| `onApprovalRequest` | `(event) => Promise<'approve' \\| 'deny'>` | No | `undefined` | Function | All | Approval |\n| `skills` | `string[]` | No | `undefined` | Array of strings | claude, opencode, pi, omp, openclaw, hermes | Skills |\n| `agentsDoc` | `string` | No | `undefined` | File path | claude, hermes | Skills |\n| `mcpServers` | `McpServerConfig[]` | No | `undefined` | See Section 4 | claude, codex, gemini, cursor, opencode, openclaw, hermes | MCP |\n| `retryPolicy` | `RetryPolicy` | No | `undefined` | See Section 5 | All | Retry |\n| `runId` | `string` | No | Auto-generated ULID | Valid ULID string | All (metadata only) | Metadata |\n| `tags` | `string[]` | No | `undefined` | Array of strings | All (metadata only) | Metadata |\n| `projectId` | `string` | No | `undefined` | Any string | All (metadata only) | Metadata |\n| `profile` | `string` | No | `undefined` | Profile name matching `^[a-zA-Z0-9_-]{1,64}$` | All | Profile |\n| `collectEvents` | `boolean` | No | `false` | `true \\| false` | All | Streaming |\n| `gracePeriodMs` | `number` | No | `5000` | `>= 0` (integer, ms) | All | Execution |\n\n> **Spec extensions:** `collectEvents` and `gracePeriodMs` are defined by `03-run-handle-and-interaction.md` (§1.2 and §6.2 respectively). `collectEvents` enables populating `RunResult.events` with all emitted events. `gracePeriodMs` configures the delay between `SIGTERM` and `SIGKILL` during two-phase shutdown.\n\n---\n\n## 3. Attachment Type\n\nThe `Attachment` type represents a file or image attached to a prompt. Attachments are sent to agents that support file or image input.\n\n```typescript\n/**\n * A file or image attachment included with a prompt.\n *\n * Exactly one of `filePath`, `url`, or `base64` must be provided.\n * Providing zero or more than one throws `ValidationError`.\n */\ninterface Attachment {\n /**\n * Absolute path to a local file.\n *\n * The file must exist and be readable. The adapter translates this to\n * the agent's native attachment mechanism (e.g., Claude Code's file\n * references, Gemini's `--file` flag).\n */\n filePath?: string;\n\n /**\n * URL to a remote resource.\n *\n * Support is agent-dependent. Agents that do not support URL attachments\n * will download the resource to a temp file and attach that instead.\n */\n url?: string;\n\n /**\n * Base64-encoded file content.\n *\n * Used for in-memory attachments that do not exist on disk.\n * Must be accompanied by `mimeType`.\n */\n base64?: string;\n\n /**\n * MIME type of the attachment.\n *\n * Required when `base64` is provided. Optional for `filePath` (inferred\n * from file extension) and `url` (inferred from Content-Type header).\n *\n * Common values: `'image/png'`, `'image/jpeg'`, `'image/gif'`,\n * `'image/webp'`, `'application/pdf'`, `'text/plain'`,\n * `'application/json'`.\n */\n mimeType?: string;\n\n /**\n * Display name for the attachment.\n *\n * Used in logging and events. If not provided, derived from `filePath`\n * basename, `url` last path segment, or `'attachment'` for `base64`.\n */\n name?: string;\n}\n```\n\n### 3.1 Attachment Field Reference\n\n| Field | Type | Required | Default | Valid Range | Description |\n|---|---|---|---|---|---|\n| `filePath` | `string` | Conditional | `undefined` | Absolute path to existing file | Local file attachment. |\n| `url` | `string` | Conditional | `undefined` | Valid HTTP(S) URL | Remote resource attachment. |\n| `base64` | `string` | Conditional | `undefined` | Valid base64 string | Inline content attachment. |\n| `mimeType` | `string` | Conditional | Inferred | Valid MIME type string | Content type. Required with `base64`. |\n| `name` | `string` | No | Derived | Any string | Display name. |\n\n### 3.2 Attachment Support by Agent\n\n| Agent | File Input | Image Input | URL Input | Base64 Input | Supported MIME Types |\n|---|---|---|---|---|---|\n| claude | Yes | Yes | No | Yes | Images: png, jpeg, gif, webp. Files: text/*, application/json, application/pdf |\n| codex | No | No | No | No | -- |\n| gemini | Yes | Yes | Yes | Yes | Images: png, jpeg, gif, webp. Files: text/*, application/json, application/pdf, audio/*, video/* |\n| copilot | No | No | No | No | -- |\n| cursor | No | Yes | No | No | Images: png, jpeg |\n| opencode | Yes | Yes | No | Yes | Images: png, jpeg, gif, webp. Files: text/* |\n| pi | No | No | No | No | -- |\n| omp | No | No | No | No | -- |\n| openclaw | No | No | No | No | -- |\n| hermes | No | No | No | No | -- |\n\n### 3.3 Attachment Validation Rules\n\n1. Exactly one of `filePath`, `url`, or `base64` must be non-`undefined`. Zero or multiple sources throws `ValidationError` with field `'attachments[N]'` and message `\"Exactly one of filePath, url, or base64 must be provided\"`.\n2. When `base64` is provided, `mimeType` is required. Omitting it throws `ValidationError`.\n3. When `filePath` is provided, the path must be absolute. Relative paths throw `ValidationError`.\n4. When `filePath` is provided, the file must exist and be readable. Missing files throw `ValidationError`.\n5. Passing `attachments` to an agent where both `supportsFileAttachments` and `supportsImageInput` are `false` throws `CapabilityError` with capability `\"attachments\"`.\n6. Passing an image attachment (MIME type `image/*`) to an agent where `supportsImageInput` is `false` throws `CapabilityError` with capability `\"imageInput\"`.\n7. Passing a non-image attachment to an agent where `supportsFileAttachments` is `false` throws `CapabilityError` with capability `\"fileAttachments\"`.\n\n---\n\n## 4. McpServerConfig Type\n\nMCP (Model Context Protocol) server configurations allow agents to connect to external tool servers. This type is used in `RunOptions.mcpServers` and in profile data.\n\n```typescript\n/**\n * Configuration for a single MCP server connection.\n *\n * The agent connects to this server during the run, making the server's\n * tools available to the agent. The agent must have `supportsMCP: true`.\n */\ninterface McpServerConfig {\n /**\n * Unique name for this MCP server within the run.\n *\n * Used as a namespace for tools (e.g., `mcp__<name>__<toolName>`)\n * and in MCP-related events.\n */\n name: string;\n\n /**\n * Transport type for connecting to the server.\n *\n * - `'stdio'` -- spawn the server as a subprocess and communicate\n * via stdin/stdout.\n * - `'sse'` -- connect to a running server via Server-Sent Events\n * over HTTP.\n * - `'streamable-http'` -- connect via the Streamable HTTP transport.\n */\n transport: 'stdio' | 'sse' | 'streamable-http';\n\n /**\n * Command to spawn the MCP server (stdio transport only).\n *\n * Required when `transport` is `'stdio'`. Ignored otherwise.\n */\n command?: string;\n\n /**\n * Arguments for the spawn command (stdio transport only).\n */\n args?: string[];\n\n /**\n * Environment variables for the spawned server process (stdio transport only).\n */\n env?: Record<string, string>;\n\n /**\n * URL of the MCP server (sse and streamable-http transports only).\n *\n * Required when `transport` is `'sse'` or `'streamable-http'`.\n */\n url?: string;\n\n /**\n * HTTP headers to include when connecting (sse and streamable-http transports only).\n */\n headers?: Record<string, string>;\n}\n```\n\n### 4.1 McpServerConfig Field Reference\n\n| Field | Type | Required | Default | Valid Range | Description |\n|---|---|---|---|---|---|\n| `name` | `string` | Yes | -- | Non-empty, matches `^[a-zA-Z0-9_-]+$` | Server identifier. |\n| `transport` | `'stdio' \\| 'sse' \\| 'streamable-http'` | Yes | -- | One of three literals | Connection transport. |\n| `command` | `string` | Conditional | `undefined` | Non-empty string | Spawn command. Required for `stdio`. |\n| `args` | `string[]` | No | `[]` | Array of strings | Command arguments. `stdio` only. |\n| `env` | `Record<string, string>` | No | `undefined` | String keys and values | Server environment. `stdio` only. |\n| `url` | `string` | Conditional | `undefined` | Valid URL | Server URL. Required for `sse`/`streamable-http`. |\n| `headers` | `Record<string, string>` | No | `undefined` | String keys and values | HTTP headers. `sse`/`streamable-http` only. |\n\n### 4.2 MCP Support by Agent\n\n| Agent | supportsMCP | Native Flag / Config | Notes |\n|---|---|---|---|\n| claude | Yes | `--mcp-config`, settings.json `mcpServers` | Full MCP client support |\n| codex | Yes | Config-based MCP | MCP client support |\n| gemini | Yes | Config-based MCP | MCP client support |\n| copilot | No | -- | No MCP support |\n| cursor | Yes | settings.json `mcpServers` | MCP client support |\n| opencode | Yes | `opencode.json` MCP config | MCP client support |\n| pi | No | -- | No MCP support |\n| omp | No | -- | No MCP support |\n| openclaw | Yes | Config-based MCP | MCP client support |\n| hermes | Yes | Config-based MCP, `mcp_serve.py` | MCP client and server (via `hermes-acp`) |\n\n---\n\n## 5. RetryPolicy Type\n\nThe `RetryPolicy` type controls automatic retry behavior for transient failures. It can be set at three levels: client defaults (via `createClient()`), global/project config, and per-run (via `RunOptions.retryPolicy`). Per-run takes precedence.\n\n```typescript\n/**\n * Configuration for automatic retry on transient failures.\n *\n * Uses exponential backoff with optional jitter:\n * delay = min(baseDelayMs * 2^(attempt-1), maxDelayMs) * (1 + random(0, jitterFactor))\n */\ninterface RetryPolicy {\n /**\n * Maximum number of retry attempts.\n *\n * The total number of invocations is `maxAttempts + 1` (initial + retries).\n * Setting to 0 disables retries.\n *\n * @default 3\n */\n maxAttempts: number;\n\n /**\n * Base delay between retries in milliseconds.\n *\n * The actual delay uses exponential backoff: `baseDelayMs * 2^(attempt-1)`,\n * capped by `maxDelayMs`.\n *\n * @default 1000\n */\n baseDelayMs: number;\n\n /**\n * Maximum delay between retries in milliseconds.\n *\n * Caps the exponential backoff to prevent excessively long waits.\n *\n * @default 30000\n */\n maxDelayMs: number;\n\n /**\n * Jitter factor applied to the computed delay.\n *\n * 0.0 means no jitter (deterministic delays).\n * 1.0 means up to 100% random jitter is added to the delay.\n * Jitter helps prevent thundering-herd effects when multiple clients\n * retry simultaneously.\n *\n * @default 0.1\n */\n jitterFactor: number;\n\n /**\n * Which error codes are eligible for automatic retry.\n *\n * Only runs that fail with one of these error codes are retried.\n * All other errors propagate immediately.\n *\n * @default ['RATE_LIMITED', 'AGENT_CRASH', 'TIMEOUT']\n */\n retryOn: ErrorCode[];\n}\n```\n\n### 5.1 RetryPolicy Field Reference\n\n| Field | Type | Required | Default | Valid Range | Description |\n|---|---|---|---|---|---|\n| `maxAttempts` | `number` | No | `3` | `>= 1` (integer) | Maximum retry attempts. |\n| `baseDelayMs` | `number` | No | `1000` | `>= 0` (integer, ms) | Base delay for exponential backoff. |\n| `maxDelayMs` | `number` | No | `30000` | `>= baseDelayMs` (integer, ms) | Maximum delay cap. |\n| `jitterFactor` | `number` | No | `0.1` | `[0.0, 1.0]` | Random jitter factor. |\n| `retryOn` | `ErrorCode[]` | No | `['RATE_LIMITED', 'AGENT_CRASH', 'TIMEOUT']` | Array of valid `ErrorCode` values | Retryable error codes. |\n\n### 5.2 RetryPolicy Validation Rules\n\n1. `maxDelayMs` must be `>= baseDelayMs`. Setting `maxDelayMs < baseDelayMs` throws `ValidationError`.\n2. `jitterFactor` outside `[0.0, 1.0]` throws `ValidationError`.\n3. `retryOn` values must be valid `ErrorCode` strings. Unknown error codes throw `ValidationError`.\n4. When a `retry` event is emitted, it includes `attempt`, `maxAttempts`, `reason`, and `delayMs` fields for consumer observability.\n\n### 5.3 Retry Behavior\n\nWhen a run fails with an error code listed in `retryOn`:\n\n1. agent-mux emits a `retry` event on the `RunHandle`.\n2. The computed delay is applied: `min(baseDelayMs * 2^(attempt-1), maxDelayMs) * (1 + random(0, jitterFactor))`.\n3. A new subprocess is spawned with identical `RunOptions`.\n4. The new subprocess's events are emitted on the same `RunHandle` (the consumer sees a continuous event stream).\n5. If all retry attempts are exhausted, the final error propagates to the `RunResult`.\n\n---\n\n## 6. Mutual Exclusivity Constraints\n\nCertain `RunOptions` fields cannot be set simultaneously. Setting mutually exclusive fields throws `ValidationError` before the agent is spawned.\n\n### 6.1 Session Constraints\n\n| Fields | Rule | Error Message |\n|---|---|---|\n| `sessionId` + `noSession` | Cannot set both | `\"sessionId and noSession are mutually exclusive\"` |\n| `sessionId` + `forkSessionId` | Cannot set both | `\"sessionId and forkSessionId are mutually exclusive\"` |\n| `forkSessionId` + `noSession` | Cannot set both | `\"forkSessionId and noSession are mutually exclusive\"` |\n\nThese three constraints mean that at most one of `sessionId`, `forkSessionId`, or `noSession: true` may be specified per run.\n\n### 6.2 Token Constraints\n\n| Fields | Rule | Error Message |\n|---|---|---|\n| `maxTokens` + `maxOutputTokens` | Both can be set, but `maxOutputTokens` wins | No error; `maxOutputTokens` takes precedence. |\n| `thinkingEffort` + `thinkingBudgetTokens` | Both can be set, but `thinkingBudgetTokens` wins | No error; `thinkingBudgetTokens` takes precedence. |\n\n### 6.3 Approval Constraints\n\n| Fields | Rule | Error Message |\n|---|---|---|\n| `approvalMode: 'yolo'` + `onApprovalRequest` | Allowed | No error. The callback **is invoked** for each request, enabling selective per-request approval even in yolo mode. |\n| `approvalMode: 'prompt'` + `onApprovalRequest` | Allowed | No error. The callback **is invoked** and replaces the default UI prompt. |\n| `approvalMode: 'deny'` + `onApprovalRequest` | Allowed | No error. The callback is **never invoked**; deny mode overrides the callback and auto-denies all requests. |\n\n### 6.4 Validation Order\n\nMutual exclusivity checks are performed in this order, and validation stops at the first failure:\n\n1. Session mutual exclusivity (`sessionId`, `forkSessionId`, `noSession`).\n2. Required field validation (`agent`, `prompt`).\n3. Range validation (temperature, topP, topK, maxTokens, etc.).\n4. Capability gating (thinkingEffort, stream, mcpServers, skills, etc.).\n5. Type coercion and normalization.\n\n---\n\n## 7. Capability Gating\n\nCapability gating prevents consumers from requesting features that the target agent or model does not support. Gated fields are checked after option resolution (profile merge, config defaults) but before subprocess spawning.\n\n### 7.1 Capability Gating Matrix\n\nThe following table shows which `RunOptions` fields are capability-gated, the capability they require, and the `CapabilityError` thrown when the requirement is not met.\n\n| RunOptions Field | Required Capability | Capability Level | Error Capability String |\n|---|---|---|---|\n| `thinkingEffort` | `model.supportsThinking: true` | Model | `\"thinking\"` |\n| `thinkingBudgetTokens` | `model.supportsThinking: true` AND `agent.supportsThinkingBudgetTokens: true` | Both | `\"thinkingBudgetTokens\"` |\n| `thinkingOverride` | `model.supportsThinking: true` | Model | `\"thinking\"` |\n| `stream: true` | `agent.supportsTextStreaming: true` | Agent | `\"textStreaming\"` |\n| `outputFormat: 'json'` | `agent.supportsJsonMode: true` | Agent | `\"jsonMode\"` |\n| `outputFormat: 'jsonl'` | `agent.supportsJsonMode: true` | Agent | `\"jsonMode\"` |\n| `mcpServers` (non-empty) | `agent.supportsMCP: true` | Agent | `\"mcp\"` |\n| `skills` (non-empty) | `agent.supportsSkills: true` | Agent | `\"skills\"` |\n| `agentsDoc` | `agent.supportsAgentsMd: true` | Agent | `\"agentsMd\"` |\n| `attachments` (non-empty) | `agent.supportsFileAttachments: true` OR `agent.supportsImageInput: true` | Agent | `\"attachments\"` |\n| `forkSessionId` | `agent.canFork: true` | Agent | `\"sessionFork\"` |\n| `sessionId` | `agent.canResume: true` | Agent | `\"sessionResume\"` |\n\n### 7.2 Capability Support by Agent\n\n| Capability | claude | codex | gemini | copilot | cursor | opencode | pi | omp | openclaw | hermes |\n|---|---|---|---|---|---|---|---|---|---|---|\n| `supportsThinking` | Yes | Yes | Yes | No | Model-dep | Model-dep | Model-dep | Yes | Model-dep | No |\n| `supportsThinkingBudgetTokens` | Yes | No | Yes | No | No | No | No | No | No | No |\n| `supportsTextStreaming` | Yes | Yes | Yes | Yes | Partial | Yes | Yes | Yes | Partial | Yes |\n| `supportsJsonMode` | Yes | Yes | No | No | No | Yes | No | No | Yes | No |\n| `supportsMCP` | Yes | Yes | Yes | No | Yes | Yes | No | No | Yes | Yes |\n| `supportsSkills` | Yes | No | No | No | No | Yes | Yes | Yes | Yes | Yes |\n| `supportsAgentsMd` | Yes | No | No | No | No | No | No | No | Yes | No |\n| `supportsFileAttachments` | Yes | No | Yes | No | No | Yes | No | No | Yes | No |\n| `supportsImageInput` | Yes | Yes | Yes | No | Yes | Yes | No | No | Yes | No |\n| `canFork` | Yes | No | No | No | No | Yes | Yes | Yes | No | No |\n| `canResume` | Yes | No | No | No | No | Yes | Yes | Yes | No | Yes |\n\n> **Cross-reference:** All values in this table are derived from `06-capabilities-and-models.md` §12 (authoritative capability profiles). Spec 06 is the source of truth for any discrepancies.\n\n### 7.3 Non-Gated Fields (Silently Ignored)\n\nThe following fields are not capability-gated. If the agent does not support them, they are silently ignored without throwing an error:\n\n- `temperature`, `topP`, `topK` -- sampling parameters not supported by all agent CLIs.\n- `maxTokens`, `maxOutputTokens` -- not all agents expose token limits via CLI.\n- `systemPrompt`, `systemPromptMode` -- approximated by prompt concatenation when native support is missing.\n- `maxTurns` -- agent may not support turn limiting natively; the adapter enforces it by counting `turn_end` events and interrupting.\n\n---\n\n## 8. Thinking Effort Translation Tables\n\nEach adapter translates the normalized `thinkingEffort` levels to the agent's native thinking parameters. The `thinkingBudgetTokens` field provides a direct numeric override that bypasses these translations.\n\n### 8.1 Claude Code\n\n| Effort Level | Native Parameter | Value |\n|---|---|---|\n| `'low'` | `--thinking-budget` | `1024` |\n| `'medium'` | `--thinking-budget` | `8192` |\n| `'high'` | `--thinking-budget` | `32768` |\n| `'max'` | `--thinking-budget` | Model's `maxThinkingTokens` (e.g., `131072` for Claude Opus) |\n\nWhen `thinkingBudgetTokens` is set, it is passed directly as `--thinking-budget <value>`.\n\n### 8.2 Codex CLI\n\n| Effort Level | Native Parameter | Value |\n|---|---|---|\n| `'low'` | `--thinking-effort` | `'low'` |\n| `'medium'` | `--thinking-effort` | `'medium'` |\n| `'high'` | `--thinking-effort` | `'high'` |\n| `'max'` | `--thinking-effort` | `'high'` (capped; Codex does not have a `'max'` level) |\n\n### 8.3 Gemini CLI\n\n| Effort Level | Native Parameter | Value |\n|---|---|---|\n| `'low'` | `thinkingConfig.thinkingBudget` | `1024` |\n| `'medium'` | `thinkingConfig.thinkingBudget` | `8192` |\n| `'high'` | `thinkingConfig.thinkingBudget` | `32768` |\n| `'max'` | `thinkingConfig.thinkingBudget` | Model's maximum thinking budget |\n\n### 8.4 Copilot CLI\n\nCopilot CLI does not support thinking/reasoning. Setting `thinkingEffort` throws `CapabilityError`.\n\n### 8.5 Cursor\n\nThinking support is model-dependent. When the selected model supports thinking:\n\n| Effort Level | Native Parameter | Value |\n|---|---|---|\n| `'low'` | `--thinking` | `'concise'` |\n| `'medium'` | `--thinking` | `'normal'` |\n| `'high'` | `--thinking` | `'verbose'` |\n| `'max'` | `--thinking` | `'verbose'` (capped) |\n\n### 8.6 OpenCode\n\nThinking support is model-dependent. When the selected model supports thinking:\n\n| Effort Level | Native Parameter | Value |\n|---|---|---|\n| `'low'` | Provider-level param | Low effort equivalent |\n| `'medium'` | Provider-level param | Medium effort equivalent |\n| `'high'` | Provider-level param | High effort equivalent |\n| `'max'` | Provider-level param | Maximum effort equivalent |\n\nTranslation depends on the configured provider (Anthropic, OpenAI, etc.) and is handled by the adapter.\n\n### 8.7 Pi\n\nThinking support is model-dependent. Translation is passed as a provider-level parameter determined by the model's provider (Anthropic, OpenAI, Google, etc.).\n\n| Effort Level | Native Mapping |\n|---|---|\n| `'low'` | Provider-specific low |\n| `'medium'` | Provider-specific medium |\n| `'high'` | Provider-specific high |\n| `'max'` | Provider-specific max |\n\n### 8.8 omp (oh-my-pi)\n\n| Effort Level | Native Parameter | Value |\n|---|---|---|\n| `'low'` | `budget_tokens` | `1024` |\n| `'medium'` | `budget_tokens` | `8192` |\n| `'high'` | `budget_tokens` | `32768` |\n| `'max'` | `budget_tokens` | `max_budget_tokens` |\n\nomp unconditionally supports all thinking effort levels regardless of model. The `budget_tokens` parameter is passed directly to the provider. When `thinkingBudgetTokens` is set, it is passed directly as `budget_tokens`.\n\n### 8.9 OpenClaw\n\nThinking support is model-dependent. When the selected model supports thinking, the effort level is passed through the model's provider configuration.\n\n| Effort Level | Native Mapping |\n|---|---|\n| `'low'` | Provider-specific low |\n| `'medium'` | Provider-specific medium |\n| `'high'` | Provider-specific high |\n| `'max'` | Provider-specific max |\n\n### 8.10 Hermes\n\nHermes does not expose an explicit extended-thinking/reasoning mode. Setting `thinkingEffort` throws `CapabilityError`. Hermes focuses on skill creation and procedural learning rather than chain-of-thought reasoning controls.\n\n### 8.11 thinkingOverride Merge Behavior\n\nWhen `thinkingOverride` is set alongside `thinkingEffort` or `thinkingBudgetTokens`:\n\n1. The adapter first translates `thinkingEffort` (or `thinkingBudgetTokens`) to native parameters.\n2. The `thinkingOverride` map is shallow-merged over the translated parameters.\n3. Keys in `thinkingOverride` replace keys from the translation; keys not in `thinkingOverride` are preserved.\n\nThis gives consumers full escape-hatch control over the native thinking configuration.\n\n---\n\n## 9. Approval Mode Translation\n\nEach adapter maps the normalized `approvalMode` values to the agent's native approval flags.\n\n| Agent | `'yolo'` | `'prompt'` | `'deny'` |\n|---|---|---|---|\n| claude | `--dangerously-skip-permissions` | (default behavior) | `--permission-mode deny` |\n| codex | `--approval-mode full-auto` | `--approval-mode suggest` | `--approval-mode deny` |\n| gemini | `--sandbox none` | (default behavior) | `--sandbox strict` |\n| copilot | (not supported; silently uses default) | (default behavior) | (not supported; silently uses default) |\n| cursor | `--yolo` | (default behavior) | `--read-only` |\n| opencode | `--auto-approve` | (default behavior) | `--deny-all` |\n| pi | `--yolo` | (default behavior) | `--deny` |\n| omp | `--yolo` | (default behavior) | `--deny` |\n| openclaw | `--auto-approve` | (default behavior) | `--read-only` |\n| hermes | Command allowlist config | Command approval prompt (default) | Deny-all via config |\n\n### 9.1 Approval Mode Capability\n\n| Agent | Supported Modes |\n|---|---|\n| claude | `'yolo'`, `'prompt'`, `'deny'` |\n| codex | `'yolo'`, `'prompt'`, `'deny'` |\n| gemini | `'yolo'`, `'prompt'`, `'deny'` |\n| copilot | `'prompt'` only |\n| cursor | `'yolo'`, `'prompt'`, `'deny'` |\n| opencode | `'yolo'`, `'prompt'`, `'deny'` |\n| pi | `'yolo'`, `'prompt'`, `'deny'` |\n| omp | `'yolo'`, `'prompt'`, `'deny'` |\n| openclaw | `'yolo'`, `'prompt'`, `'deny'` |\n| hermes | `'yolo'`, `'prompt'`, `'deny'` |\n\nWhen an unsupported approval mode is requested for copilot, the adapter silently falls back to `'prompt'` and emits a `debug` event noting the fallback.\n\n---\n\n## 10. ProfileManager API\n\nThe `ProfileManager` provides CRUD operations for named `RunOptions` presets. Accessed via `mux.profiles` or `mux.config.profiles()`.\n\n```typescript\n/**\n * Manages named RunOptions presets (profiles).\n *\n * Profiles are stored as JSON files in `~/.agent-mux/profiles/` (global)\n * and `.agent-mux/profiles/` (project-local). Project profiles override\n * global profiles of the same name via deep merge.\n */\ninterface ProfileManager {\n /**\n * List all available profiles.\n *\n * Returns profiles from both global and project directories, merged\n * by name. When a profile exists in both directories, the returned\n * entry reflects the merged result with `scope: 'project'`.\n *\n * @param options - Optional filter criteria.\n * @returns Array of profile metadata entries, sorted by name.\n */\n list(options?: ProfileListOptions): Promise<ProfileEntry[]>;\n\n /**\n * Show the resolved contents of a named profile.\n *\n * If the profile exists in both global and project directories, the\n * returned data is the deep-merged result (project overrides global).\n *\n * @param name - Profile name. Must match `^[a-zA-Z0-9_-]{1,64}$`.\n * @returns The resolved profile data.\n * @throws {AgentMuxError} code `PROFILE_NOT_FOUND` if no profile with\n * this name exists in either directory.\n */\n show(name: string): Promise<ResolvedProfile>;\n\n /**\n * Create or update a named profile.\n *\n * Writes the profile to the specified scope directory. If a profile\n * with this name already exists in the target scope, it is overwritten\n * entirely (not merged).\n *\n * @param name - Profile name. Must match `^[a-zA-Z0-9_-]{1,64}$`.\n * @param data - Profile data to write. Validated against `ProfileData` schema.\n * @param options - Write options including target scope.\n * @throws {ValidationError} if `name` does not match the naming pattern.\n * @throws {ValidationError} if `data` contains invalid field values.\n * @throws {ValidationError} if `data` contains prohibited per-run fields\n * (see Section 10.2).\n */\n set(name: string, data: ProfileData, options?: ProfileSetOptions): Promise<void>;\n\n /**\n * Delete a named profile.\n *\n * Deletes the profile file from the specified scope. If `scope` is not\n * specified, the method uses scope-preference logic: it checks project scope\n * first and, if the profile is found there, deletes it from project scope and\n * returns. If not found in project scope, it falls back to global scope.\n * Only one scope is modified per call — the method never deletes from both\n * scopes in a single invocation.\n *\n * @param name - Profile name.\n * @param options - Delete options including target scope.\n * @throws {AgentMuxError} code `PROFILE_NOT_FOUND` if the profile does\n * not exist in the target scope (or in either scope when scope is\n * unspecified).\n */\n delete(name: string, options?: ProfileDeleteOptions): Promise<void>;\n\n /**\n * Apply a profile to a partial `RunOptions` object, returning the\n * merged result.\n *\n * This is the programmatic equivalent of setting `profile` in `RunOptions`.\n * Fields present in `overrides` take precedence over the profile.\n *\n * @param name - Profile name to apply.\n * @param overrides - Per-call `RunOptions` fields that override the profile.\n * @returns Merged `RunOptions` with profile as base and overrides on top.\n * @throws {AgentMuxError} code `PROFILE_NOT_FOUND` if the profile does\n * not exist.\n */\n apply(name: string, overrides?: Partial<RunOptions>): Promise<Partial<RunOptions>>;\n}\n```\n\n### 10.1 Supporting Types\n\n```typescript\n/**\n * Options for filtering profile listings.\n */\ninterface ProfileListOptions {\n /**\n * Filter by scope.\n * - `'global'` -- only profiles from `~/.agent-mux/profiles/`.\n * - `'project'` -- only profiles from `.agent-mux/profiles/`.\n * - `undefined` -- profiles from both directories (default).\n */\n scope?: 'global' | 'project';\n}\n\n/**\n * Metadata for a single profile entry in a listing.\n */\ninterface ProfileEntry {\n /** Profile name (filename without `.json` extension). */\n name: string;\n\n /** Where this profile is stored. `'project'` if present in both. */\n scope: 'global' | 'project';\n\n /** Whether a global profile is also present (only relevant for project scope). */\n hasGlobalOverride: boolean;\n\n /** The agent specified in this profile, if any. */\n agent?: AgentName;\n\n /** The model specified in this profile, if any. */\n model?: string;\n\n /**\n * `true` if the profile file exists on disk but could not be parsed.\n *\n * When `true`, `agent` and `model` will be `undefined` (the file content\n * could not be read). `ProfileManager.list()` includes corrupt entries so\n * consumers can detect and report them without throwing.\n */\n corrupt?: boolean;\n}\n\n/**\n * A resolved profile: the merged result of global + project data.\n */\ninterface ResolvedProfile {\n /** Profile name. */\n name: string;\n\n /** The resolved profile data after merging global and project layers. */\n data: ProfileData;\n\n /** Source scope of the resolved profile. */\n scope: 'global' | 'project';\n\n /** Absolute path to the global profile file, if it exists. */\n globalPath?: string;\n\n /** Absolute path to the project profile file, if it exists. */\n projectPath?: string;\n}\n\n/**\n * Options for writing a profile.\n */\ninterface ProfileSetOptions {\n /**\n * Target scope for the profile file.\n * @default 'project' (if a project directory exists, else 'global')\n */\n scope?: 'global' | 'project';\n}\n\n/**\n * Options for deleting a profile.\n */\ninterface ProfileDeleteOptions {\n /**\n * Target scope to delete from.\n *\n * - `'project'` -- delete only from `.agent-mux/profiles/`.\n * - `'global'` -- delete only from `~/.agent-mux/profiles/`.\n * - `undefined` (default) -- prefer project scope: delete from project scope\n * if the profile exists there; otherwise delete from global scope. Only one\n * scope is affected per call.\n */\n scope?: 'global' | 'project';\n}\n```\n\n### 10.2 ProfileData Type\n\n`ProfileData` is a strict subset of `RunOptions`. Certain per-run ephemeral fields are prohibited in profiles because they are inherently tied to a single invocation.\n\n```typescript\n/**\n * A named set of RunOptions fields that can be stored on disk.\n *\n * Excludes per-run ephemeral fields that cannot be meaningfully persisted.\n */\ninterface ProfileData {\n /** The agent to use. */\n agent?: AgentName;\n\n /** The model to use. */\n model?: string;\n\n /** Approval mode. */\n approvalMode?: 'yolo' | 'prompt' | 'deny';\n\n /** Run timeout in milliseconds. */\n timeout?: number;\n\n /** Inactivity timeout in milliseconds. */\n inactivityTimeout?: number;\n\n /** Maximum turns. */\n maxTurns?: number;\n\n /** Thinking effort level. */\n thinkingEffort?: 'low' | 'medium' | 'high' | 'max';\n\n /** Thinking budget in tokens. */\n thinkingBudgetTokens?: number;\n\n /**\n * Override thinking behavior entirely: arbitrary native thinking\n * parameters passed directly to the underlying provider.\n */\n thinkingOverride?: Record<string, unknown>;\n\n /** Temperature for sampling. */\n temperature?: number;\n\n /** Top-P for sampling. */\n topP?: number;\n\n /** Top-K for sampling. */\n topK?: number;\n\n /** Maximum output tokens. */\n maxTokens?: number;\n\n /** Maximum output tokens (alias). */\n maxOutputTokens?: number;\n\n /** Streaming mode. */\n stream?: boolean | 'auto';\n\n /** Output format. */\n outputFormat?: 'text' | 'json' | 'jsonl';\n\n /** Retry policy. */\n retryPolicy?: RetryPolicy;\n\n /** System prompt to inject. */\n systemPrompt?: string;\n\n /** System prompt injection mode. */\n systemPromptMode?: 'prepend' | 'append' | 'replace';\n\n /** Skills to load. */\n skills?: string[];\n\n /** MCP server configurations. */\n mcpServers?: McpServerConfig[];\n\n /** Tags for run indexing. */\n tags?: string[];\n}\n```\n\n### 10.3 Prohibited Profile Fields\n\nThe following `RunOptions` fields must not appear in a `ProfileData` object. If present, `ProfileManager.set()` throws `ValidationError`:\n\n| Prohibited Field | Reason |\n|---|---|\n| `prompt` | Per-invocation input; not reusable across runs. |\n| `onInputRequired` | Function; cannot be serialized to JSON. |\n| `onApprovalRequest` | Function; cannot be serialized to JSON. |\n| `env` | Environment-specific; security risk if persisted. |\n| `cwd` | Working directory is per-invocation context. |\n| `sessionId` | Per-run session identity; not meaningful as a default. |\n| `forkSessionId` | Session fork is a one-time operation. |\n| `noSession` | Session ephemerality is a per-run decision. |\n| `attachments` | File references are per-invocation. |\n| `runId` | Auto-generated per run. |\n| `projectId` | Per-project metadata, not a run preset. |\n| `profile` | Self-referential; profiles cannot reference other profiles. |\n| `agentsDoc` | Path reference is per-invocation context. |\n\n---\n\n## 11. Profile Resolution Order\n\nWhen `mux.run()` is called, `RunOptions` are resolved through a five-level cascade. Each level overrides the one below it, at the individual field level (not wholesale replacement).\n\n### 11.1 Resolution Cascade\n\n```\nPriority (highest to lowest):\n 1. Per-call RunOptions fields (explicitly set by the consumer)\n 2. Named profile (if RunOptions.profile is set)\n 3. Project config (.agent-mux/config.json)\n 4. Global config (~/.agent-mux/config.json)\n 5. Adapter defaults (hardcoded in the adapter)\n```\n\n### 11.2 Resolution Algorithm\n\n```typescript\n/**\n * Pseudocode for RunOptions resolution.\n * Executed inside mux.run() before validation and capability gating.\n */\nfunction resolveRunOptions(\n perCall: RunOptions,\n client: AgentMuxClient\n): ResolvedRunOptions {\n // Step 1: Start with adapter defaults\n const adapter = client.adapters.get(perCall.agent);\n let resolved = { ...adapter.defaults };\n\n // Step 2: Merge global config defaults\n const globalConfig = client.config.getGlobalConfig();\n resolved = deepMerge(resolved, extractRunOptionFields(globalConfig));\n\n // Step 3: Merge project config defaults\n const projectConfig = client.config.getProjectConfig();\n if (projectConfig) {\n resolved = deepMerge(resolved, extractRunOptionFields(projectConfig));\n }\n\n // Step 4: Merge named profile (if specified)\n if (perCall.profile) {\n const profile = client.profiles.show(perCall.profile);\n resolved = deepMerge(resolved, profile.data);\n }\n\n // Step 5: Merge per-call fields (highest priority)\n // Only fields explicitly set (not undefined) override.\n resolved = deepMerge(resolved, stripUndefined(perCall));\n\n return resolved as ResolvedRunOptions;\n}\n```\n\n### 11.3 Deep Merge Rules\n\n- **Scalar fields** (string, number, boolean): higher-priority value replaces lower-priority value entirely.\n- **Array fields** (`tags`, `skills`, `mcpServers`): higher-priority array replaces lower-priority array entirely (no concatenation).\n- **Object fields** (`retryPolicy`, `thinkingOverride`, `env`): shallow-merged at the top level of the object. Keys in the higher-priority object replace keys in the lower-priority object; keys not present in the higher-priority object are preserved.\n- **`undefined` fields**: an `undefined` value in a higher-priority layer does not override a lower-priority value. Only explicitly set (non-`undefined`) values participate in the merge.\n\n### 11.4 Resolution Examples\n\n**Example 1: Profile overrides global config, per-call overrides profile.**\n\n```json\n// ~/.agent-mux/config.json (global config)\n{\n \"defaultAgent\": \"claude\",\n \"approvalMode\": \"prompt\",\n \"timeout\": 60000\n}\n```\n\n```json\n// ~/.agent-mux/profiles/fast.json (global profile)\n{\n \"agent\": \"codex\",\n \"approvalMode\": \"yolo\",\n \"thinkingEffort\": \"low\",\n \"maxTurns\": 5\n}\n```\n\n```typescript\n// Per-call RunOptions\nmux.run({\n agent: 'claude', // overrides profile's \"codex\"\n prompt: 'Fix the bug',\n profile: 'fast', // applies the \"fast\" profile\n maxTurns: 10, // overrides profile's 5\n});\n\n// Resolved result:\n// agent: 'claude' (per-call wins over profile)\n// approvalMode: 'yolo' (profile wins over global config)\n// thinkingEffort: 'low' (from profile)\n// maxTurns: 10 (per-call wins over profile)\n// timeout: 60000 (from global config, nothing above overrides)\n```\n\n**Example 2: Project profile overrides global profile.**\n\n```json\n// ~/.agent-mux/profiles/careful.json (global)\n{\n \"thinkingEffort\": \"high\",\n \"approvalMode\": \"prompt\",\n \"maxTurns\": 20,\n \"timeout\": 300000\n}\n```\n\n```json\n// .agent-mux/profiles/careful.json (project)\n{\n \"thinkingEffort\": \"max\",\n \"maxTurns\": 50\n}\n```\n\n```\n// Resolved \"careful\" profile (deep merge, project over global):\n// thinkingEffort: 'max' (project overrides global)\n// approvalMode: 'prompt' (from global, project doesn't set it)\n// maxTurns: 50 (project overrides global)\n// timeout: 300000 (from global, project doesn't set it)\n```\n\n---\n\n## 12. Profile Storage Format and Locations\n\n### 12.1 File Locations\n\n| Scope | Directory | Example Path |\n|---|---|---|\n| Global | `~/.agent-mux/profiles/` | `~/.agent-mux/profiles/fast.json` |\n| Project | `.agent-mux/profiles/` | `.agent-mux/profiles/careful.json` |\n\nPlatform-specific home directory resolution follows the rules defined in `01-core-types-and-client.md` Section 4.1.1.\n\n### 12.2 File Format\n\n- **Encoding**: UTF-8, no BOM.\n- **Format**: Strict JSON (no comments, no trailing commas).\n- **Trailing newline**: Permitted.\n- **Schema**: `ProfileData` interface (Section 10.2).\n\n### 12.3 Naming Rules\n\nProfile names must match the regular expression `^[a-zA-Z0-9_-]{1,64}$`:\n\n- Allowed characters: alphanumeric, underscore, hyphen.\n- Length: 1 to 64 characters.\n- Case-sensitive: `Fast` and `fast` are different profiles.\n- File extension `.json` is appended automatically and must not be included in the name.\n\nInvalid profile names throw `ValidationError` with field `'name'` and expected value `\"string matching ^[a-zA-Z0-9_-]{1,64}$\"`.\n\n### 12.4 File Discovery\n\nFiles in the profiles directory that do not match the naming pattern (e.g., `.backup.json`, `README.md`, `profile with spaces.json`) are silently ignored by `ProfileManager.list()`.\n\n### 12.5 Corrupt File Handling\n\nIf a profile JSON file exists but cannot be parsed:\n\n- `ProfileManager.show()` throws `AgentMuxError` with code `CONFIG_ERROR` and a message identifying the file path and parse error location.\n- `ProfileManager.list()` includes the entry with a flag indicating the parse error, but does not throw. This allows the consumer to see which profiles exist even if some are corrupt.\n- `ProfileManager.apply()` throws the same `CONFIG_ERROR` as `show()`.\n\n### 12.6 Example Profile Files\n\n**`~/.agent-mux/profiles/fast.json`**:\n```json\n{\n \"agent\": \"codex\",\n \"approvalMode\": \"yolo\",\n \"thinkingEffort\": \"low\",\n \"maxTurns\": 5,\n \"timeout\": 30000,\n \"stream\": true\n}\n```\n\n**`~/.agent-mux/profiles/careful.json`**:\n```json\n{\n \"agent\": \"claude\",\n \"model\": \"claude-opus-4-0520\",\n \"approvalMode\": \"prompt\",\n \"thinkingEffort\": \"max\",\n \"maxTurns\": 50,\n \"timeout\": 600000,\n \"retryPolicy\": {\n \"maxAttempts\": 5,\n \"baseDelayMs\": 2000,\n \"maxDelayMs\": 60000,\n \"jitterFactor\": 0.2,\n \"retryOn\": [\"RATE_LIMITED\", \"AGENT_CRASH\", \"TIMEOUT\"]\n }\n}\n```\n\n**`.agent-mux/profiles/ci.json`** (project-specific):\n```json\n{\n \"approvalMode\": \"yolo\",\n \"thinkingEffort\": \"medium\",\n \"maxTurns\": 30,\n \"timeout\": 120000,\n \"tags\": [\"ci\", \"automated\"],\n \"stream\": false\n}\n```\n\n---\n\n## 13. CLI Integration\n\nThe `amux profiles` subcommand maps directly to the `ProfileManager` API.\n\n### 13.1 CLI Commands\n\n```\namux profiles list [--scope global|project]\namux profiles show <name>\namux profiles set <name> [run flags]\namux profiles delete <name> [--scope global|project]\namux profiles apply <name>\n```\n\n### 13.2 CLI Flag to Profile Field Mapping\n\nWhen using `amux profiles set`, run flags are translated to `ProfileData` fields:\n\n| CLI Flag | ProfileData Field |\n|---|---|\n| `--agent`, `-a` | `agent` |\n| `--model`, `-m` | `model` |\n| `--yolo` | `approvalMode: 'yolo'` |\n| `--deny` | `approvalMode: 'deny'` |\n| `--thinking-effort <level>` | `thinkingEffort` |\n| `--thinking-budget <tokens>` | `thinkingBudgetTokens` |\n| `--max-tokens <n>` | `maxTokens` |\n| `--max-turns <n>` | `maxTurns` |\n| `--timeout <ms>` | `timeout` |\n| `--inactivity-timeout <ms>` | `inactivityTimeout` |\n| `--stream` / `--no-stream` | `stream: true` / `stream: false` |\n| `--output-format <fmt>` | `outputFormat` |\n| `--system <prompt>` | `systemPrompt` |\n| `--system-mode <mode>` | `systemPromptMode` |\n| `--tag <tag>` | `tags` (repeatable, builds array) |\n\n### 13.3 Using Profiles with `amux run`\n\n```bash\n# Apply a profile to a run\namux run claude \"Fix the bug\" --profile careful\n\n# Profile values can be overridden by explicit flags\namux run --profile fast --model gpt-4o \"Explain this code\"\n# Uses profile \"fast\" as base, but overrides model to gpt-4o\n```\n\n---\n\n## 14. Edge Cases and Error Conditions\n\n### 14.1 Invalid Combinations\n\n| Scenario | Behavior |\n|---|---|\n| `agent` is not registered | Throws `AgentMuxError` code `AGENT_NOT_FOUND`. |\n| `agent` is registered but CLI binary not found on `$PATH` | Throws `AgentMuxError` code `AGENT_NOT_INSTALLED`. |\n| `prompt` is empty string or empty array | Throws `ValidationError`. |\n| `prompt` is `string[]` with all empty strings | Throws `ValidationError` (concatenated result is empty). |\n| `temperature: -0.5` | Throws `ValidationError` (below valid range `[0.0, 2.0]`). |\n| `temperature: 3.0` | Throws `ValidationError` (above valid range `[0.0, 2.0]`). |\n| `topP: 1.5` | Throws `ValidationError` (above valid range `[0.0, 1.0]`). |\n| `topK: 0` | Throws `ValidationError` (below valid minimum `1`). |\n| `topK: 3.5` | Throws `ValidationError` (not an integer). |\n| `maxTokens: 0` | Throws `ValidationError` (below valid minimum `1`). |\n| `maxTokens: -100` | Throws `ValidationError` (below valid minimum `1`). |\n| `thinkingBudgetTokens: 512` | Throws `ValidationError` (below minimum `1024`). |\n| `thinkingBudgetTokens` exceeds model's `maxThinkingTokens` | Throws `ValidationError`. |\n| `timeout: -1` | Throws `ValidationError` (negative not allowed). |\n| `inactivityTimeout: -1` | Throws `ValidationError` (negative not allowed). |\n| `maxTurns: 0` | Throws `ValidationError` (below valid minimum `1`). |\n| `cwd` is a relative path | Throws `ValidationError`. |\n| `cwd` directory does not exist | Throws `ValidationError`. |\n| `sessionId` + `noSession: true` | Throws `ValidationError` (mutually exclusive). |\n| `sessionId` + `forkSessionId` | Throws `ValidationError` (mutually exclusive). |\n| `forkSessionId` + `noSession: true` | Throws `ValidationError` (mutually exclusive). |\n| `profile` references non-existent name | Throws `AgentMuxError` code `PROFILE_NOT_FOUND`. |\n| `runId` is not a valid ULID | Throws `ValidationError`. |\n| `outputFormat: 'json'` on agent without `supportsJsonMode` | Throws `CapabilityError`. |\n| `stream: true` on agent without `supportsTextStreaming` | Throws `CapabilityError`. |\n| `thinkingEffort` on model without `supportsThinking` | Throws `CapabilityError`. |\n| `forkSessionId` on agent without `canFork` | Throws `CapabilityError`. |\n| `mcpServers` on agent without `supportsMCP` | Throws `CapabilityError`. |\n| `skills` on agent without `supportsSkills` | Throws `CapabilityError`. |\n| `agentsDoc` on agent without `supportsAgentsMd` | Throws `CapabilityError`. |\n| `attachments` on agent without file/image support | Throws `CapabilityError`. |\n\n### 14.2 Type Coercion Rules\n\nagent-mux does not perform implicit type coercion on `RunOptions` fields. All values must be the correct type as specified in the interface. Passing incorrect types (e.g., `temperature: \"0.5\"` as a string instead of number) throws `ValidationError`.\n\nThe only exception is `prompt`: when passed as a `string[]`, elements are concatenated with `\\n\\n` into a single string before being sent to the agent. This is normalization, not coercion.\n\n### 14.3 Missing Required Fields\n\n| Missing Field | Behavior |\n|---|---|\n| `agent` not set and no `defaultAgent` in config or profile | Throws `ValidationError` with field `'agent'` and message `\"agent is required: set it in RunOptions, a profile, or defaultAgent in config\"`. |\n| `prompt` not set | Throws `ValidationError` with field `'prompt'` and message `\"prompt is required\"`. |\n\nThe `agent` field has a special resolution path: it can come from the per-call `RunOptions`, the applied profile, or the `defaultAgent` config field. If none provides it, validation fails.\n\n### 14.4 Undefined vs. Null\n\n- `undefined`: the field is not set; it does not participate in resolution/merge.\n- `null`: treated as an explicit value. For most fields, `null` throws `ValidationError` because the field type does not include `null`. There is no \"unset a profile field\" semantic via `null`; to remove a field from a profile, call `ProfileManager.set()` with the field omitted.\n\n### 14.5 Empty Arrays vs. Undefined\n\n- `skills: []` (empty array) is treated as \"explicitly no skills\". It overrides a profile that sets skills. No `CapabilityError` is thrown for empty arrays (capability gating only triggers on non-empty arrays).\n- `skills: undefined` (not set) defers to the profile or lower-priority layer.\n- The same logic applies to `tags`, `mcpServers`, and `attachments`.\n\n### 14.6 Model Validation Timing\n\nModel validation (checking that the specified `model` is known to the target agent) occurs after option resolution but before capability gating. If the model is unknown:\n\n- The adapter's `mux.models.validate()` returns `{ valid: false }`.\n- agent-mux emits a `debug` event with a warning but does not throw. The model string is passed through to the agent CLI, which may accept it (e.g., for newly released models not yet in the bundled model list).\n\n---\n\n## 15. Complete Type Summary\n\nAll types defined or referenced in this specification:\n\n| Type | Defined In | Section |\n|---|---|---|\n| `RunOptions` | This spec | 2 |\n| `Attachment` | This spec | 3 |\n| `McpServerConfig` | This spec | 4 |\n| `RetryPolicy` | `01-core-types-and-client.md` (reproduced in this spec) | 5 |\n| `ProfileData` | This spec | 10.2 |\n| `ProfileManager` | This spec | 10 |\n| `ProfileEntry` | This spec | 10.1 |\n| `ResolvedProfile` | This spec | 10.1 |\n| `ProfileListOptions` | This spec | 10.1 |\n| `ProfileSetOptions` | This spec | 10.1 |\n| `ProfileDeleteOptions` | This spec | 10.1 |\n| `AgentName` | `01-core-types-and-client.md` | 1.4 |\n| `ErrorCode` | `01-core-types-and-client.md` | 3.1 |\n| `CapabilityError` | `01-core-types-and-client.md` | 3.2 |\n| `ValidationError` | `01-core-types-and-client.md` | 3.3 |\n| `InputRequiredEvent` | `03-run-handle-and-interaction.md` | 2 |\n| `ApprovalRequestEvent` | `03-run-handle-and-interaction.md` | 2 |\n\n### 15.1 Method Summary\n\n| Interface | Method | Returns | Throws |\n|---|---|---|---|\n| `ProfileManager` | `list(options?)` | `Promise<ProfileEntry[]>` | -- |\n| `ProfileManager` | `show(name)` | `Promise<ResolvedProfile>` | `PROFILE_NOT_FOUND`, `CONFIG_ERROR` |\n| `ProfileManager` | `set(name, data, options?)` | `Promise<void>` | `ValidationError` |\n| `ProfileManager` | `delete(name, options?)` | `Promise<void>` | `PROFILE_NOT_FOUND` |\n| `ProfileManager` | `apply(name, overrides?)` | `Promise<Partial<RunOptions>>` | `PROFILE_NOT_FOUND`, `CONFIG_ERROR` |\n\n---\n\n## Implementation Status (2026-04-12)\n\n### RunOptions.invocation\n\n`RunOptions` now carries an optional `invocation?: InvocationMode` — a discriminated union of `LocalInvocation | DockerInvocation | SshInvocation | K8sInvocation`. When omitted or set to `{ mode: 'local' }` the harness runs in-process on the host; other modes rewrap the spawn args via `buildInvocationCommand()`. See `docs/13-invocation-modes.md` for field definitions and semantics.\n\nProfiles may include an `invocation` field; it is merged onto `RunOptions` like any other field with caller overrides winning.\n",
"documents": []
},
"outgoingEdges": [],
"incomingEdges": [
{
"from": "page:docs-agent-mux-reference",
"to": "page:docs-agent-mux-reference-02-run-options-and-profiles",
"kind": "contains_page"
}
]
}