# Configuration Reference Complete reference for Oh My OpenCode plugin configuration. Every omo harness reads one unified config file, `omo.jsonc`; the legacy `oh-my-openagent.json[c]` / `oh-my-opencode.json[c]` files are imported once by the migration engine and are no longer read at runtime. --- ## Table of Contents - [Getting Started](#getting-started) - [File Locations](#file-locations) - [Quick Start Example](#quick-start-example) - [Core Concepts](#core-concepts) - [Agents](#agents) - [Categories](#categories) - [Model Resolution](#model-resolution) - [Model Profiles](#model-profiles) - [Task System](#task-system) - [Background Tasks](#background-tasks) - [Features](#features) - [Skills](#skills) - [Memory](#memory) - [Hooks](#hooks) - [Commands](#commands) - [Browser Automation](#browser-automation) - [Tmux Integration](#tmux-integration) - [Git Master](#git-master) - [Comment Checker](#comment-checker) - [Notification](#notification) - [MCPs](#mcps) - [LSP](#lsp) - [Advanced](#advanced) - [Runtime Fallback](#runtime-fallback) - [Anthropic Prompt Cache Lifetime](#anthropic-prompt-cache-lifetime) - [Model Capabilities](#model-capabilities) - [Hashline Edit](#hashline-edit) - [Experimental](#experimental) - [Reference](#reference) - [Environment Variables](#environment-variables) - [Provider-Specific](#provider-specific) --- ## Getting Started ### File Locations One unified file configures every omo harness: the OpenCode plugin, Senpi (task, config-watch), and Codex. The legacy `oh-my-openagent.json[c]` / `oh-my-opencode.json[c]` files and `~/.omo/config.jsonc` are read by nothing but the migration engine (see [Migration](#migration)). 1. User layer (lowest precedence): `~/.omo/omo.jsonc` on every platform (`omo.json` is accepted as a fallback basename). 2. Project layers: `.omo/omo.jsonc` (then `.omo/omo.json`) in every directory from the working directory up to `$HOME`. Farther ancestors merge first, so the nearest project file wins and beats the user layer. `$HOME` itself is skipped by the walk because `~/.omo` is already the user layer. If the working directory is outside `$HOME`, the walk continues to the filesystem root. #### Resolution Order Within the merged document each harness resolves its own view VSCode-style, later layers winning: 1. Shared base keys 2. The `[harness]` block: `[opencode]`, `[native]`, or `[codex]` (`[senpi]` is the accepted legacy spelling of `[native]`) 3. `profiles.` 4. `profiles..[harness]` Defaults apply once at the end. The option keys documented in this reference are the contents of the `[opencode]` block. `agents` and `categories` can also live at the shared base level so every harness sees them, using the shared field set documented in the [omo.json reference](./omo-json.md); OpenCode-specific agent options belong in `[opencode]`. #### Profiles No default profiles ship: a profile exists only when you write one under `profiles.` or the migration derives one from a legacy profile directory. Activation, highest priority first: 1. `OMO_PROFILE` 2. `OCX_PROFILE` (set by `ocx oc -p `) 3. An `OPENCODE_CONFIG_DIR` whose path ends in `profiles/` 4. None Activating a profile that does not exist produces a diagnostic and falls back to the base configuration. #### Model Catalog A top-level `models` record maps a short name to the canonical shape `{ model, reasoning? }`. Deprecated `variant` and `reasoningEffort` inputs are accepted for compatibility and normalized to `reasoning`. When an agent or category `model` string matches a catalog key, it resolves to the entry's model id and fills any unset `reasoning` from the entry; tuning written at the use site always wins. `[harness]` blocks can override individual catalog entries for one harness. #### Model Profiles Two more shared base keys, read by the Senpi harness, pick the main session model by lane instead of by model id. They live at the top level, inside `[native]`, or inside a `profiles.` layer, like any other base key. | Key | Type | Description | | --- | --- | --- | | `model_profiles` | record | Named ordered model chains. A name matching a builtin (`daily-normal`, `daily-heavy`, `geeky-normal`, `geeky-heavy`) replaces it wholesale; any other name adds one. Entries use the same string or object shape as a category chain and may reference `models.` entries. | | `model_profile` | string | Which chain starts the session: a lane id such as `daily-normal`, or a literal `provider/model` that pins one exact model. Unset applies Daily · Normal (or the first lane that resolves) on a fresh session; the retired `recommended` id does the same. | Don't confuse these with `profiles.` above: that key swaps configuration layers via `OMO_PROFILE`, while `model_profile` chooses a model within the loaded configuration. Builtin chains, session-start behavior, and override rules are in the [omo.json reference](./omo-json.md#model-profiles-native-harness). #### Security Invariants `mcp_env_allowlist` and `browser_automation_engine.playwright_mcp_args` are honored only from the user layer, including the user layer's own active profile block. Project layers cannot extend them. JSONC supports `// line comments`, `/* block comments */`, and trailing commas. Enable schema autocomplete: ```json { "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/omo.schema.json" } ``` Run `bunx oh-my-openagent install` for guided setup. Run `opencode models` to list available models. #### Migration The first time a current harness starts (and again on install or via the CLI), a lock-and-journal migration engine imports the legacy files into the unified file: - Sources: `oh-my-openagent.json[c]` / `oh-my-opencode.json[c]` in the OpenCode user config directory, in each of its `profiles//` directories, and in walked project `.opencode/` directories, plus `~/.omo/config.jsonc`. - Targets: the legacy user file imports into `~/.omo/omo.jsonc` under `[opencode]`; each legacy profile becomes `profiles.."[opencode]"` holding only the keys that differ from the user file; a project file imports into that project's `.omo/omo.jsonc`. `~/.omo/config.jsonc` imports its `[opencode]` / `[codex]` blocks, and a legacy `[omo]` block maps to `[native]`. - OpenCode legacy files import only model/provider controls (`disabled_providers`, `model_fallback`, `models`, and `omo_agent` renamed to `sisyphus_agent`); agent and category registries, agent disable lists, hooks, and unrelated plugin settings are not imported. - OmO Native's first start then reports, once, every OpenCode edition agent or category model choice (from the legacy files, their migration backups, or the `[opencode]` block) that Native does not use, each with the `"[native]": { ... }` member that would set it (`metis` as `plan-consultant`, `momus` as `plan-reviewer`, OpenCode provider ids as omo's) and a pointer to `omo setup`, which carries them over with your consent. It writes only the `2026-09-opencode-routing-notice` marker; `omo doctor` repeats the line while the gap exists. - Conflict policy: no-clobber. A value already present in the target wins, and every skipped legacy value is reported as a diagnostic instead of overwriting. Prior legacy migration history is preserved under the target's `legacy_migrations` key. - Markers: each applied migration records its id in the target's `_migrations` array, so re-runs are no-ops. `2026-07-opencode-config-unification` covers the `oh-my-*` files; `2026-07-codex-config-jsonc` covers `~/.omo/config.jsonc`; `2026-08-reasoning-unification` rewrites persisted model and reasoning fields. Codex startup runs only the second group; OpenCode plugin startup, Senpi startup, install, and the CLI run both groups, so whichever side runs first applies each group exactly once. - Backups: sources move to `~/.omo/migration-backup--opencode-config/` (project sources to `/.omo/migration-backup-/`). An interrupted run resumes from its journal on the next start. - Manual run: `oh-my-openagent config migrate`. `--dry-run` prints the transform, backup move plan, and conflicts without writing; `--json` prints machine-readable output. - Diagnostics surface once per startup: an OpenCode toast, a Senpi `session_start` notification, or Codex loader warnings. ### Quick Start Example Here's a practical starting `~/.omo/omo.jsonc`. OpenCode plugin settings live inside the `[opencode]` block: ```jsonc { "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/omo.schema.json", "[opencode]": { "agents": { // Research agents: cheap fast models are fine "librarian": { "model": "google/gemini-3.6-flash" }, "explore": { "model": "github-copilot/grok-code-fast-1" }, // Plan-gated reviewers: keep the builtin prompt, pin the model "plan-consultant": { "model": "anthropic/claude-opus-5-5", "reasoning": "max" }, "plan-reviewer": { "model": "openai/gpt-6-astra", "reasoning": "high" }, }, "categories": { // quick - Kimi high-speed by default "quick": { "model": "openai/gpt-6-luna-fast", "reasoning": "low" }, // unspecified-low - moderate tasks "unspecified-low": { "model": "anthropic/claude-sonnet-5-5", "reasoning": "medium" }, // unspecified-high - complex work "unspecified-high": { "model": "anthropic/claude-opus-5-5", "reasoning": "medium" }, // writing - docs/prose "writing": { "model": "anthropic/claude-opus-5-5", "reasoning": "low" }, // visual-engineering - Fable 5.1 max, then Opus 5 max and Kimi K3 max "visual-engineering": { "model": "anthropic/claude-opus-5-5", "reasoning": "max", }, // Custom category for git operations "git": { "model": "opencode/gpt-5-nano", "description": "All git operations", "prompt_append": "Focus on atomic commits, clear messages, and safe operations.", }, }, // Limit expensive providers; let cheap ones run freely "background_task": { "providerConcurrency": { "anthropic": 3, "openai": 3, "opencode": 10, "zai-coding-plan": 10, }, "modelConcurrency": { "anthropic/claude-opus-5-5": 2, "opencode/gpt-5-nano": 20, }, }, "experimental": { "aggressive_truncation": true, "task_system": true }, "tmux": { "enabled": false }, }, } ``` --- ## Core Concepts ### Agents Override built-in agent settings. The main agent runs in your session on your session model and has no `agents` entry. Available builtin agents: `explore`, `librarian`, `plan-consultant`, `plan-reviewer`. Any other key under `agents` defines a user-defined agent. ```json { "agents": { "explore": { "model": "anthropic/claude-haiku-5-5", "temperature": 0.5 }, "plan-reviewer": { "disable": true } } } ``` > **Removed**: the retired keys `agents.metis` and `agents.momus` no longer resolve to `plan-consultant` and `plan-reviewer`. Their one-release alias window closed after 5.0.0-beta.51, so such a key now defines an ordinary custom agent under that name. Rename it to the canonical id. The OpenCode edition adds `disabled_agents`, `agent_order`, and its own core-agent overrides. See [OpenCode edition configuration (legacy)](./opencode-config.md). #### Agent Options This table is the `[opencode].agents` surface only. Shared-base agents use the core field set (`description`, `prompt`, `model`, `models`, `reasoning`, `tools`, `execution_mode`, `background`, `max_depth`, `allowed_subagents`, `disallowed_tools`, `max_turns`, `temperature`, `disable`, and deprecated `variant` / `reasoningEffort`) documented in the [omo.json reference](./omo-json.md); OpenCode-only fields are rejected there. | Option | Type | Description | | ----------------- | ----------------------- | --------------------------------------------------------------- | | `model` | string | Model override (`provider/model`) | | `models` | array | Ordered model chain; entries are strings or per-model objects | | `fallback_models` | string\|array | Deprecated compatibility fallback chain | | `reasoning` | string | Canonical reasoning level or harness-native preset token | | `temperature` | number | Sampling temperature | | `top_p` | number | Top-p sampling | | `prompt` | string | Replace system prompt. Supports `file://` URIs | | `prompt_append` | string | Append to system prompt. Supports `file://` URIs | | `tools` | record | Per-tool enable/disable map | | `disable` | boolean | Disable this agent | | `description` | string | Agent description | | `mode` | `subagent \| primary \| all` | Agent mode | | `color` | string | Six-digit hex UI color (`#RRGGBB`) | | `displayName` | string | Localized display name shown in the agent selector | | `permission` | object | Per-tool permissions (see below) | | `category` | string | Inherit model from category | | `skills` | string[] | Skill names to inject into the agent prompt | | `variant` | string | Deprecated compatibility input; use `reasoning` | | `maxTokens` | number | Max response tokens | | `thinking` | object | Migrated legacy Anthropic form; use `reasoning` plus provider options | | `reasoningEffort` | string | Deprecated compatibility input; use `reasoning` | | `textVerbosity` | string | Text verbosity: `low`, `medium`, `high` | | `providerOptions` | object | Provider-specific options | | `ultrawork` | object | Per-message ultrawork model and reasoning override | | `compaction` | object | Compaction model and reasoning override | #### Anthropic Extended Thinking ```json { "agents": { "plan-consultant": { "reasoning": "high", "providerOptions": { "thinking": { "type": "enabled", "budgetTokens": 200000 } } } } } ``` #### Agent Permissions Control what tools an agent can use: ```json { "agents": { "explore": { "permission": { "edit": "deny", "bash": "ask", "webfetch": "allow" } } } } ``` | Permission | Values | | -------------------- | --------------------------------------------------------------------------- | | `edit` | `ask` / `allow` / `deny` | | `bash` | `ask` / `allow` / `deny` or per-command: `{ "git": "allow", "rm": "deny" }` | | `webfetch` | `ask` / `allow` / `deny` | | `task` | `ask` / `allow` / `deny` | | `doom_loop` | `ask` / `allow` / `deny` | | `external_directory` | `ask` / `allow` / `deny` | #### Fallback Models with Per-Model Settings `fallback_models` accepts either a single model string or an array. Array entries can be plain strings or objects with individual model settings: ```jsonc { "agents": { "plan-consultant": { "model": "anthropic/claude-opus-5-5", "fallback_models": [ // Simple string fallback "openai/gpt-5.6-sol", // Object with per-model settings { "model": "google/gemini-3.1-pro", "reasoning": "high", "temperature": 0.2 }, { "model": "anthropic/claude-sonnet-5", "reasoning": "high" } ] } } } ``` Object entries support canonical `model`, `reasoning`, `temperature`, `top_p`, and `maxTokens`. Deprecated `variant`, `reasoningEffort`, and `thinking` remain accepted as compatibility inputs and are normalized to `reasoning` and provider options. #### File URIs for Prompts Both `prompt` and `prompt_append` support loading content from files via `file://` URIs. Category-level `prompt_append` supports the same URI forms. ```jsonc { "agents": { "librarian": { "prompt_append": "file:///absolute/path/to/prompt.txt" }, "reviewer": { "prompt": "file://./relative/to/project/prompt.md" }, "explore": { "prompt_append": "file://~/home/dir/prompt.txt" } }, "categories": { "custom": { "model": "anthropic/claude-sonnet-5", "prompt_append": "file://./category-context.md" } } } ``` Paths can be absolute (`file:///abs/path`), relative to project root (`file://./rel/path`), or home-relative (`file://~/home/path`). Home-relative files are limited to `~/.config/opencode`, `~/.config/oh-my-openagent`, `~/.omo`, and `~/.opencode`. If a file URI cannot be decoded, resolved, accepted, or read, OmO inserts a warning placeholder into the prompt instead of failing hard. ### Categories Domain-specific model delegation used by the `task()` tool. When the main agent delegates work, it picks a category, not a model name. #### Built-in Categories | Category | Default Model | Description | | -------------------- | ------------------------------- | ---------------------------------------------- | | `visual-engineering` | `anthropic/claude-fable-5-1` (max) | Visual design, UI/UX, frontend, styling, animation, design systems | | `ultrabrain` | `openai/gpt-6-astra` (max) | Deep logical reasoning, complex architecture. Falls back to `gpt-5.6-sol` (max). | | `deep-low` | `openai/gpt-6.1-sol` (medium) | Default deep lane: 3D graphics, computer use, browser use, backend, logic, algorithms, CAPTCHA solving, multimodal, and complex research whose decisions the child can settle from evidence. Falls back to the Fast tier `gpt-6.1-sol-fast`, then `gpt-5.6-sol` (the only rung GitHub Copilot and OpenCode Zen serve), then `gpt-5.6-sol-fast`, all at medium; unavailable without a GPT-6.1 Sol or GPT-5.6 Sol tier. | | `deep-high` | `openai/gpt-6-astra` (high) | Escalation deep lane for a goal whose central decision cannot be settled from evidence. Single rung, no model fallback. | | `artistry` | `anthropic/claude-fable-5-1` (max) | Creative/unconventional approaches | | `quick` | `openai/gpt-6-luna-fast` (low) | Trivial tasks, typo fixes, single-file changes | | `unspecified-low` | `anthropic/claude-sonnet-5-5` (medium) | General tasks, low effort | | `unspecified-high` | `anthropic/claude-opus-5-5` (medium) | General tasks, high effort | | `writing` | `anthropic/claude-opus-5-5` (low) | Documentation, prose, technical writing. Unavailable when none of its Claude models (`claude-opus-5-5`, `claude-opus-4-6`) is connected; it never falls back to another family, and the installer leaves it out. | > **Note**: Built-in category defaults are available automatically. User-defined category config merges over the built-in defaults or adds custom categories. #### Category Options | Option | Type | Default | Description | | ------------------- | ------------- | ------- | ------------------------------------------------------------------- | | `model` | string | - | Model override | | `models` | array | - | Ordered model chain; entries are strings or per-model objects | | `fallback_models` | string\|array | - | Deprecated compatibility fallback chain | | `reasoning` | string | - | Canonical reasoning level or harness-native preset token | | `temperature` | number | - | Sampling temperature | | `top_p` | number | - | Top-p sampling | | `max_tokens` | number | - | Canonical max response tokens | | `provider_options` | object | - | Provider-specific request options | | `maxTokens` | number | - | Deprecated compatibility input; use `max_tokens` | | `thinking` | object | - | Migrated legacy form; use `reasoning` plus `provider_options` | | `reasoningEffort` | string | - | Deprecated compatibility input; use `reasoning` | | `textVerbosity` | string | - | Text verbosity | | `tools` | object | - | Tool usage control (disable with `{ "tool_name": false }`) | | `prompt_append` | string | - | Append to system prompt | | `max_prompt_tokens` | number | - | Maximum prompt tokens for delegated tasks | | `variant` | string | - | Deprecated compatibility input; use `reasoning` | | `description` | string | - | Shown in `task()` tool prompt | | `is_unstable_agent` | boolean | `false` | Force background mode + monitoring. Auto-enabled when the resolved model id contains "gemini" or "minimax". | | `disable` | boolean | `false` | Exclude this category from task delegation | | `warn_unavailable` | boolean | - | Present on the OpenCode schema. The dead-chain notice it suppresses is a Senpi/core `task` behavior. | Disable categories: `{ "categories": { "ultrabrain": { "disable": true } } }` ### Model Resolution Runtime priority: 1. **UI-selected model** - model chosen in the OpenCode UI, for primary agents 2. **User override** - model set in config → used exactly as-is. Even on cold cache, explicit user configuration takes precedence over hardcoded fallback chains 3. **Category default** - model inherited from the assigned category config 4. **User `fallback_models`** - user-configured fallback list is tried before built-in fallback chains 5. **Provider fallback chain** - built-in provider/model chain from OmO source 6. **System default** - OpenCode's configured default model The same resolved chain drives spawn-time selection and runtime retry fallback, so a recovered task stays on the same category chain. In the Senpi harness, `model_profile` applies to OmO Desktop and headless sessions; the interactive TUI keeps the model it started with. An explicit `--model`, scoped model or resumed session is preserved. A fresh session otherwise uses `model_profile` as a literal pin or a named chain; unset config starts on Daily · Normal, or on the first of Daily · Heavy, Obsessive · Normal and Obsessive · Heavy that resolves; the retired `recommended` id starts the same default with one log line. If no candidate is available, a notice explains the unavailable profile and the current model stays. `categories.*` and `agents.*` overrides do not select the main session model, and `model_profile` does not select delegated children. See [Model Profiles](#model-profiles). In the OpenCode plugin, every merged category appears in `availableCategories`; hiding categories with a dead fallback chain is not implemented here. That dead-chain filtering, the `model_unavailable` spawn failure, and the `task.warnings.unavailable_categories` flag belong to the Senpi/core `task` system, documented in the [omo.json reference](./omo-json.md). #### Model Settings Compatibility Model settings are compatibility-normalized against model capabilities instead of failing hard. Normalized fields: - `reasoning` - downgraded to the closest supported value, or removed if unsupported - `temperature` - removed if unsupported by the model metadata - `top_p` - removed if unsupported by the model metadata - `maxTokens` - capped to the model's reported max output limit - Provider-specific thinking options - removed if the target model does not support thinking Deprecated `reasoningEffort` and `variant` inputs are first migrated to `reasoning`. Examples: - GPT-4.1 does not support reasoning, so `reasoning` is removed - o-series models support `off` through `high`, so `xhigh` is downgraded to `high` - GPT-5 supports `off`, `minimal`, `low`, `medium`, `high`, and `xhigh` Capability data comes from provider runtime metadata first. OmO also ships bundled models.dev-backed capability data, supports a refreshable local models.dev cache, and falls back to heuristic family detection plus alias rules when exact metadata is unavailable. `bunx oh-my-openagent doctor` surfaces capability diagnostics and warns when a configured model relies on compatibility fallback. #### Agent Provider Chains The main agent has no chain of its own: it runs on your session model (Claude Opus 5.5 or GPT 5.6 Sol recommended). The four curated agents resolve through these chains (`packages/senpi-task/src/agents/builtin/fallback-chains.ts`): | Agent | Default Model | Provider Priority | | --- | --- | --- | | **explore** | `kimi-for-coding-highspeed` | `kimi-coding\|kimi-for-coding/kimi-for-coding-highspeed (off)` → `openai\|chatgpt-subscription/gpt-6-luna-fast (low)` → `anthropic\|github-copilot/claude-haiku-5-5 (medium)` → `deepseek/deepseek-flash (max)` → `opencode-go\|bailian-coding-plan/qwen3.7-plus` → `opencode-go/minimax-m2.7` | **librarian** | `kimi-for-coding-highspeed` | `kimi-coding\|kimi-for-coding/kimi-for-coding-highspeed (off)` → `openai\|chatgpt-subscription/gpt-6-luna-fast (low)` → `anthropic\|github-copilot/claude-haiku-5-5 (medium)` → `deepseek/deepseek-flash (max)` → `opencode-go\|bailian-coding-plan/qwen3.7-plus` → `opencode-go/minimax-m2.7` | **plan-consultant** | `claude-fable-5-1` | `anthropic\|github-copilot\|opencode/claude-fable-5-1 (max)` → `anthropic\|github-copilot\|opencode/claude-opus-5-5 (max)` → `opencode-go\|kimi-for-coding\|moonshotai\|opencode/kimi-k3 (max)` | **plan-reviewer** | `gpt-6-astra` | `openai\|chatgpt-subscription/gpt-6-astra (xhigh)` → `github-copilot/gpt-6-astra (high)` → `openai\|chatgpt-subscription\|opencode/gpt-6-astra (high)` → `anthropic\|github-copilot\|opencode/claude-opus-5-5 (max)` → `google\|google-vertex\|opencode/gemini-4-argon (high)` → `google\|github-copilot\|opencode/gemini-3.1-pro (high)` → `opencode-go/glm-5.2` #### Category Provider Chains This table mirrors the authoritative hardcoded category fallback chains: the chain's primary rung and its remaining provider priority. The built-in chains list no `vercel` or `quotio-openai` rungs; Vercel AI Gateway stays a manual provider choice. | Category | Provider Chain Primary | Provider Priority | | --- | --- | --- | | **Visual Engineering** | `claude-fable-5-1` | `anthropic\|anthropic-api\|github-copilot\|opencode/claude-fable-5-1 (max)` → `anthropic\|anthropic-api\|github-copilot\|opencode/claude-opus-5-5 (max)` → `kimi-for-coding\|moonshotai\|opencode-go\|opencode/kimi-k3 (max)` | | **Ultrabrain** | `gpt-6-astra` | `openai\|chatgpt-subscription/gpt-6-astra (max)` → `github-copilot/gpt-6-astra (max)` → `openai\|chatgpt-subscription\|opencode/gpt-6-astra (max)` → `openai\|chatgpt-subscription/gpt-5.6-sol (max)` → `github-copilot/gpt-5.6-sol (max)` → `openai\|chatgpt-subscription\|opencode/gpt-5.6-sol (max)` | | **Deep Low** | `gpt-6.1-sol` | `openai\|chatgpt-subscription/gpt-6.1-sol (medium)` → `openai\|chatgpt-subscription/gpt-6.1-sol-fast (medium)` → `openai\|chatgpt-subscription\|github-copilot\|opencode/gpt-5.6-sol (medium)` → `openai\|chatgpt-subscription/gpt-5.6-sol-fast (medium)` | | **Deep High** | `gpt-6-astra` | `openai\|chatgpt-subscription\|github-copilot\|opencode/gpt-6-astra (high)` | | **Artistry** | `claude-fable-5-1` | `anthropic\|anthropic-api\|github-copilot\|opencode/claude-fable-5-1 (max)` → `kimi-for-coding\|moonshotai\|opencode-go\|opencode/kimi-k3 (max)` → `anthropic\|anthropic-api\|github-copilot\|opencode/claude-opus-5-5 (max)` | | **Quick** | `gpt-6-luna-fast` | `chatgpt-subscription/gpt-6-luna-fast (low)` → `anthropic\|anthropic-api\|github-copilot/claude-haiku-5-5 (medium)` → `deepseek/deepseek-flash (off)` → `qwen-token-plan\|alibaba-token-plan\|bailian-coding-plan/qwen3.6-flash (low)` → `opencode-go/minimax-m3 (max)` → `opencode-go/minimax-m2.7 (max)` → `xai/grok-4.20-0309-non-reasoning` → `zai-coding-plan/glm-5.3-flash (low)` → `xiaomi/mimo-v2.6-flash (low)` | | **Unspecified Low** | `claude-sonnet-5-5` | `anthropic\|anthropic-api\|github-copilot\|opencode/claude-sonnet-5-5 (medium)` → `xiaomi\|opencode-go/mimo-v2.6-pro (max)` → `xai\|github-copilot\|opencode-go/grok-4.7 (xhigh)` → `zai-coding-plan\|opencode-go/glm-5.3 (high)` → `openai\|chatgpt-subscription/gpt-6.1-sol (medium)` → `openai\|chatgpt-subscription\|github-copilot\|opencode/gpt-5.6-sol (medium)` → `anthropic\|anthropic-api\|github-copilot\|opencode/claude-sonnet-5 (low)` → `qwen-token-plan\|alibaba-token-plan\|qwen-token-plan-cn\|alibaba-token-plan-cn/qwen3.8-max-preview (max)` → `deepseek\|opencode-go/deepseek-v4-pro (max)` → `xiaomi\|opencode-go/mimo-v2.5-pro (max)` | | **Unspecified High** | `claude-opus-5-5` | `anthropic\|anthropic-api\|github-copilot\|opencode/claude-opus-5-5 (medium)` → `zai-coding-plan\|opencode-go/glm-5.3 (max)` → `kimi-for-coding\|moonshotai\|opencode-go\|opencode/kimi-k3 (max)` | | **Writing** | `claude-opus-5-5` | `anthropic\|anthropic-api\|github-copilot\|opencode/claude-opus-5-5 (low)` → `anthropic\|anthropic-api\|github-copilot\|opencode/claude-opus-4-6 (max)` | Run `bunx oh-my-openagent doctor --verbose` to see effective model resolution for your config. --- ## Task System ### Background Tasks Control parallel agent execution and concurrency limits. `background_task` (camelCase) is the OpenCode plugin key; the shared/Senpi/Codex equivalent is the core `task` object (snake_case: `default_concurrency`, `provider_concurrency`, `model_concurrency`, `max_depth`, `ttl_ms`). They are separate objects, not aliases; `background_task` at the shared base fails core validation. Setting a concurrency value to `0` means unlimited (no cap) in both places: `background_task` (`defaultConcurrency`, `providerConcurrency`, `modelConcurrency`) and the core `task` object. ```json { "background_task": { "defaultConcurrency": 5, "staleTimeoutMs": 2700000, "providerConcurrency": { "anthropic": 3, "openai": 5, "google": 10 }, "modelConcurrency": { "anthropic/claude-opus-5-5": 2 } } } ``` | Option | Default | Description | | --------------------- | -------- | --------------------------------------------------------------------- | | `defaultConcurrency` | `5` | Max concurrent tasks (all providers) | | `providerConcurrency` | - | Per-provider limits (key = provider name) | | `modelConcurrency` | - | Per-model limits (key = `provider/model`). Overrides provider limits. | | `maxDepth` | - | Maximum nested subagent depth (min: 1) | | `staleTimeoutMs` | `2700000` | Interrupt tasks with no activity (min: 60000) | | `messageStalenessTimeoutMs` | `3600000` | Timeout when no progress update was ever received (min: 60000) | | `taskTtlMs` | `1800000` | Absolute non-terminal task TTL (min: 300000) | | `sessionGoneTimeoutMs` | `60000` | Timeout when a task session disappears (min: 10000) | | `taskCleanupDelayMs` | `600000` | Delay before terminal tasks are removed (min: 60000) | | `syncPollTimeoutMs` | - | Synchronous polling timeout in milliseconds (min: 60000) | | `maxToolCalls` | `4000` | Maximum tool calls per subagent task (min: 10) | | `circuitBreaker` | - | Circuit-breaker object: `enabled` (default `true`), `maxToolCalls`, `consecutiveThreshold` | Priority: `modelConcurrency` > `providerConcurrency` > `defaultConcurrency` The OpenCode edition's orchestration key (`sisyphus_agent`) and its file-based task storage options are documented on the legacy page linked from [Agents](#agents). --- ## Features ### Skills Skills bring domain-specific expertise and embedded MCPs. Selected built-in skills: `playwright`, `playwright-cli`, `dev-browser`, `git-master`, `frontend`, `review-work`, `remove-ai-slops`, `init-deep`, `debugging`, `security-research`, `security-review`, `visual-qa`, `team-mode`. The `team-mode` skill is only rendered when `team_mode.enabled` is true. Disable built-in skills: `{ "disabled_skills": ["playwright"] }`. `disabled_skills` is also a shared base key of `~/.omo/omo.jsonc`, honored by every harness including OmO Native (for example `{ "disabled_skills": ["frontend", "visual-qa"] }`); user and project layers are unioned. `skills.enable` below only filters config-sourced skills, not builtin, native, or bundled ones - use `disabled_skills` to hide those. #### Skills Configuration ```json { "skills": { "sources": [ { "path": "./my-skills", "recursive": true }, "https://example.com/skill.yaml" ], "enable": ["my-skill"], "disable": ["other-skill"], "my-skill": { "description": "What it does", "template": "Custom prompt template", "from": "source-file.ts", "model": "custom/model", "agent": "custom-agent", "subtask": true, "argument-hint": "usage hint", "license": "MIT", "compatibility": ">= 3.0.0", "metadata": { "author": "Your Name" }, "allowed-tools": ["read", "bash"] } } } ``` | `sources` option | Default | Description | | ---------------- | ------- | ------------------------------- | | `path` | - | Local path or remote URL | | `recursive` | `false` | Recurse into subdirectories | | `glob` | - | Glob pattern for file selection | ### Memory Persistent, per-agent memory stored as a git repository. Memory is on by default and learns actively: it reflects on its own, nudges when durable facts go unsaved, recalls a stored memory mid-session when it would change the next step, extracts facts in the background, consolidates during a dream pass, and keeps records about people. Configured under `memory` in `omo.json`, with per-agent overrides under `memory.agents.`. ```json { "memory": { "enabled": true, "agent": "auto", "reflection": { "trigger": { "step_count": 25 } }, "nudge": { "every_user_turns": 10 }, "dream": { "idle_minutes": 30 }, "agents": { "reviewer": { "dream": { "enabled": false } } } } } ``` | Option | Default | Description | | -------------------- | ---------- | ------------------------------------------------------------------------------- | | `enabled` | `true` | Master switch for the whole memory component | | `agent` | `"auto"` | Which agent identity owns the memory repository | | `auto_identity` | `"path"` | How `agent: "auto"` picks the identity: `"path"` or `"git"` (see below); root only, not per agent | | `compile_warn_tokens`| `30000` | Warn when the compiled memory block exceeds this many tokens | | `agents` | `{}` | Per-agent overrides; any block below may be overridden field by field | #### Sharing memory across git worktrees With `agent: "auto"`, the memory identity comes from the session's directory, so by default every git worktree of a repository gets its own, empty memory. Set `auto_identity: "git"` to resolve a linked worktree onto its main checkout instead: every worktree of one clone then reads and writes the main checkout's memory, and a subdirectory maps onto the same subdirectory of the main checkout. The main checkout keeps the identity it has today, so turning this on loses nothing. Worktrees of a bare repository share one memory named after the repository. Directories outside git, submodules (also inside a worktree), and repositories whose main working tree git does not record (a clone made with `--separate-git-dir` and no `core.worktree`) keep the path-derived identity, and an explicit `agent` name always wins. `auto_identity` is set on the `memory` block itself; the per-agent `agents` overrides do not accept it. Put the setting in the user config (`~/.omo/omo.jsonc`). Tools such as Orca create worktrees outside the main checkout, where its project `.omo/omo.jsonc` is not found. Sessions that are already bound keep their current memory; new sessions pick up the shared one. Changing `auto_identity` (on or off) rebinds the identity for new worktree sessions, and with it which memory sync stream they join: a worktree session with `"git"` reads and writes the main checkout's memory, and also syncs it when `memory.sync.enabled` is on and the device is signed in. A worktree opened through a symlinked path still maps to its main checkout. Git records the real path of the main checkout, though, so a main checkout opened through a symlinked path resolves differently from its worktrees; use an explicit `agent` name in that case. #### Memory from projects the session touches After a successful `read`, `edit` or `write` under a child project with existing memory, the tool result lists up to 30 of that project's note paths and descriptions, not their bodies. The nudge appears once per project and is re-armed after accepted compaction. A non-git workspace can link projects in through directory symlinks; links committed inside a git repository do not extend its scope. Links to an ancestor, home or a directory containing the memory root never widen scope. Linked worktrees use the session's bound `auto_identity` mode and absolute memory root. Kibitzer considers up to three recently touched projects. It ranks each project's committed notes apart from the session's own corpus and reserves up to half the available seats, rounded up, for projects in touch order. Unused seats go to the other side. Each repository uses the same persisted search-term cache as the session's own memory; an unreadable or unwritable cache is logged and recall continues. Project note keys are `@project//` so ledgers cannot confuse two projects' notes with the same name; delivered hints name the readable file path. | Option | Default | Description | | ---------------------- | ------- | ----------- | | `project_scope.nudge` | `true` | List a touched project's note paths and descriptions | | `project_scope.recall` | `true` | Include touched projects in Kibitzer recall and its memory tool | These switches belong to the root `memory` block and its harness/profile layers, not `agents` overrides. Turning both off revokes grants on the next file touch. Project access is read-only: enumeration, writes, the repository root itself, git metadata and the reserved `system/` tree stay denied. The exclusions are case-insensitive on every platform; symlinks cannot escape a granted repository, and grants are revoked when the session shuts down. #### Memory file list in the prompt Every turn the compiled memory block ends with ``, one line per directory naming the memory files outside `system/`. Two limits keep it small on a large corpus. Each directory lists its most recently committed files first (ties by name). Names that do not fit are counted as `(+N more; read $MEMORY_DIR// to list)`, and every directory keeps its line. | Option | Default | Description | | -------------------------------------- | ------- | ----------------------------------------------------------------------------- | | `projection.max_entries_per_directory` | `40` | Names listed per directory, newest first | | `projection.max_bytes` | `24576` | Byte budget for the whole list; the largest directory gives up names first | `0` disables a limit; both at `0` list every name in name order, as before. A budget smaller than one line per directory cannot be met: the list then shows only the directory lines with their counts, and `/doctor` reports the overflow. Both keys can be set per harness, per profile and per agent under `agents..projection`. `/doctor` prints a `projection` line with the names shown and omitted and the byte size. The line is a warning when names are omitted or the budget is overflowed. `/doctor --json` carries the same line in `checks`. #### Secret screening Memory never stores or shows secret-like text. One scanner covers seven pattern classes: AWS access keys, credential assignments (`token=`, `api_key:`, `password=` and similar), HTTP `Authorization: Bearer` headers, OpenAI-style `sk-` keys, vendor tokens (GitHub, GitLab and Slack prefixes), PEM private-key blocks, and credential keys split by whitespace. Before matching it strips zero-width and other invisible format characters and folds non-breaking spaces, so an obfuscated credential is still caught. - **Refused:** every commit path screens the staged file names and the full staged content. That covers the `memory` tools, background facts extraction, and reflection and dream runs, whose whole branch history is checked before it merges, including merge resolutions, renames and symlink targets. A refused write leaves the repository as it was, and the error names the file and the pattern class. A facts run refused this way parks its conversation after one failure; a refused reflection or dream run counts as a non-retryable failure, so it reaches the park threshold without the transient-failure backoff. The memory repository's pre-commit hook applies the same classes to hand commits; it cannot catch the invisible-character evasions, which the commit paths above do. - **Masked:** anything memory injects into a session, such as the compiled memory block, file paths and names, recall hints and memory command output, has every match replaced with `***`. That includes content committed before screening existed. The matched text itself never appears in an error, a log line or a notice; only the pattern class does. #### Reflection Reflection reviews the conversation and writes durable notes back into memory. An automatic run that fails (step-count or compaction trigger) is not retried on the very next trigger: the conversation backs off for 5 seconds, doubling per consecutive failure up to 5 minutes, and a successful reflection clears the backoff. `/reflect` ignores it, so you can always force a run. | Option | Default | Description | | -------------------------- | -------- | --------------------------------------------------------- | | `reflection.enabled` | `true` | Turn reflection off without disabling the rest of memory | | `reflection.trigger.step_count` | `25` | Reflect every N steps; `0` disables the step trigger | | `reflection.trigger.on_compaction`| `true` | Also reflect when the context is compacted | | `reflection.merge` | `"auto"` | `auto` or `integration` merge strategy | | `reflection.category` | `"quick"`| Task executor category for the reflection child | | `reflection.timeout_minutes`| `15` | Hard timeout for a reflection run | | `reflection.sandbox` | `"auto"` | `auto`, `required`, or `off` | #### Nudge Reminds the agent to save when durable facts have gone unwritten. | Option | Default | Description | | ------------------------- | ------- | ---------------------------------------------------- | | `nudge.enabled` | `true` | Emit the nudge line in the memory metadata block | | `nudge.every_user_turns` | `10` | Nudge after this many user turns without a save | #### Recall (recollections) The nudge above asks the agent to write. Recall is the other direction: a read-only judge called Kibitzer runs as one resident sidecar session per main agent session. It is fed the session's prompts, tool calls and tool results as bounded, redacted events, and hands back a hint only when a stored memory would change the next step (a constraint being ignored, a past failure of the same approach, an answer about to be re-derived). Silence is the default. Most turns produce nothing, and a sidecar that finds nothing leaves no trace. The sidecar only spends a model turn (a "wake") when a prompt or tool call surfaces a memory candidate it has not judged yet in its lifetime; unchanged candidates never wake it. Wakes are admitted through a lease held as lock files under the memory identity's runtime directory, shared by every omo process on the machine that uses that memory, so at most `max_concurrent_wakes` run at once - a session that finds every slot busy keeps buffering events and tries again at its next hook. Inside a wake the sidecar has exactly five read-only tools: `read` and `grep` over the workspace, `session_entries` over the parent transcript, `memory` with `search` and `read` only, and `nudge`. It has no tool that writes memory or files, no shell, and each wake is limited to `tool_budget` tool calls and 90 seconds. When its own context passes 60% of `sidecar_max_tokens` it is replaced by a fresh sidecar seeded with what it already delivered or rejected, so a long session never runs the judge out of context. A sidecar whose model fails is disposed and recreated after an exponential backoff (1 s doubling to 5 min); nothing it had buffered is lost. When no provider serving the `recall.category` chain is connected at all, that is a configuration state, not a failure: the session gets one warning notice naming the category and its unconnected providers - run `/login ` to connect one, or pin `categories..model` (or `recall.category`) in `omo.json` to a connected model - and judging resumes by itself once a chain provider connects. When it does fire, you see a recollection in the transcript identified as Kibitzer advice: a single fixed `Kibitzer` title, then `recalled memory: `, with the memory path beneath it. It's a transcript entry, not a toast. The hint is one sentence of at most 200 characters, and the same memory surfaces at most once per session. Treat it as a hint, not current state: the agent is told to verify before relying on it, and expanding the entry shows that caveat. | Option | Default | Description | | ------------------- | ------- | --------------------------------------------------------------- | | `recall.enabled` | `true` | Run the Kibitzer sidecar and surface recollections; `false` is the only off switch | | `recall.max_items` | `2` | Most memories one wake may surface (1-5) | | `recall.category` | `quick` | Model category the sidecar runs on (it never leaves that category's chain) | | `recall.event_caps` | `tool_args: 400`, `result_head: 600`, `assistant: 1500`, `prompt: 4000` | Per-event character caps, applied after secret redaction | | `recall.sidecar_max_tokens` | `48000` | Sidecar context budget; the sidecar reseeds itself at 60% of it | | `recall.max_concurrent_wakes` | `2` | Machine-wide cap on wakes running at once | | `recall.tool_budget` | `8` | Read-only tool calls one wake may make before it is cut off | | `recall.query_expansion` | `false` | Let the sidecar add synonyms, keywords in your other languages and related terms to its own memory searches; each counts for less than a query word | Like the other memory blocks, every recall option can be overridden per agent under `memory.agents..recall`; `event_caps` merges field by field. How candidate memories are picked before the sidecar judges them is not a setting. Recall picks it from the terms it plans from the conversation and from your memory repository: English terms over an English memory of fewer than 200 notes match every query word verbatim, as before. When a planned term contains Korean, Chinese or Japanese text, or at least a tenth of the letters in your notes are, candidates are scored by word rarity with the text split into two-character pieces, so `퍼블리시할` still finds a note about `퍼블리시`. The planned terms include terms taken from tool arguments and are not the raw message, so the switch goes both ways: a Korean word the planner does not keep leaves a mostly English message on verbatim matching, and a Korean file path in a tool argument such as `docs/배포-절차.md` switches the selection to word rarity. From 200 notes on, both are combined so a memory either one finds can still reach the sidecar, which decides what is worth a nudge, and the note that matches an English phrase from the conversation word for word keeps first place. Word rarity treats common English endings as one word, so `rollback` still finds a note that says `rollbacks`. Chinese characters and Japanese kanji also count one by one, so a question can find a note it shares only single characters with. With `recall.query_expansion` on, the sidecar's own `memory` search takes four optional fields next to the query: `synonyms`, `keywords` (the topic in your other working languages), `related`, and one `note_line` written like a line of the note it is looking for. The sidecar model writes them in the same tool call; nothing is stored and no other model is called. A match on a synonym or keyword scores 0.75 of the same match on a query word and a match on a related term or the note line 0.4 (a rare added term can still outscore a common query word), and a note that holds every word of the query stays ahead of the notes only an added term found, in the order it has without them. Each list is capped at 16 terms of 80 characters, and `note_line` at 300 characters; invalid added terms are reported in the result while the query still runs as plain recall. It is off by default because the sidecar spends extra output tokens on every search it widens. Off, the tool and its results are exactly what they are without the option. The candidates picked from the conversation before the sidecar wakes are not widened either way: no model runs at that step. #### Facts Background extraction of durable facts from settled turns. | Option | Default | Description | | ------------------------- | ------- | -------------------------------------------------------- | | `facts.enabled` | `true` | Run background fact extraction | | `facts.debounce_settles` | `4` | Settled turns to accumulate before extracting | #### Dream A consolidation pass that reorganizes memory, audits skill usage, and updates people records. It runs opportunistically when the session goes idle, and optionally at shutdown. | Option | Default | Description | | ----------------------------- | -------- | ------------------------------------------------------------ | | `dream.enabled` | `true` | Enable the dream pass | | `dream.idle_minutes` | `30` | Idle minutes before a dream may start; `0` disables the trigger| | `dream.min_hours_between` | `24` | Minimum hours between two dream runs | | `dream.shutdown_launch` | `true` | Allow a dream to be launched at shutdown | | `dream.auto_select_max` | `5` | Conversations `--auto` may select (1-10) | | `dream.auto_select_max_chars` | `150000` | Byte budget for auto-selected conversations | #### Memory doctor In Senpi, `/doctor` checks the working memory corpus and reports `ok`, `warn`, or `fail`. Existing checks are `repository`, `frontmatter`, `persona`, `soul-seed`, `locks`, `worktrees`, `abandoned-runs`, `quarantined-runs`, `receipts`, `reservation`, `reflection-health`, `tokens`, `projection` (see "Memory file list in the prompt"), and the conditional `facts` advisory. The `skills` lines report the existing missing-name frontmatter repair. `receipts` shows the newest outcome of each kind of memory maintenance, for example `dream merged 2d ago (run 1a2b3c4d); reflection failed 1h ago (validation_failed); facts never`. It reads `receipts.jsonl` in the identity's runtime directory, an append-only record outside the memory repository. Each line is one lifecycle event: `launched`, `recovered`, `merged`, `no_changes`, `failed`, `abandoned` and `quarantined` for reflection and dream runs, and `committed`, `no_facts`, `failed` and `parked` for facts batches. A receipt is written after the run's own terminal file, and a lost reflection or dream receipt is rebuilt from that file at the next startup, so each run outcome is recorded exactly once. A lost facts receipt is rebuilt from the batch's `final.json` or `abandoned.json` with its event and sha only; a lost `parked` receipt is not rebuilt. The check warns when the file ends in a partial line. `quarantined-runs` lists unfinished runs that startup reconciliation could neither finish nor release: an unreadable ledger, a run directory that never got a prelaunch record, or a terminal claim that cannot be read. The first two are quarantined only after the launcher is proven dead on this machine and, for an unreadable ledger, its recorded supervisor and child too. An unreadable terminal claim is quarantined where reconciliation would otherwise have abandoned or failed the run. A finished run is never quarantined: when its timestamps cannot be attributed, its terminal file stays its one recorded outcome and the reservation is released. Quarantine writes `quarantined.json` with the reason and keeps every file in the run directory. It saves the held reservation as `reservation.quarantined.json` and releases it, so later runs proceed. Inspect and remove a quarantined directory by hand; `/doctor` never deletes it. When a run's supervisor dies after the model child already exited cleanly before its deadline (recorded in the run's `child-exit.json`), with a complete committed result, startup recovers that result: it is validated and merged like a normal run, and the receipts show `recovered` before `merged`. `recovered` marks any run that startup reconciliation settled after the process that ran it died, so it precedes whatever outcome that run reaches, including `failed`. A launch interrupted before its run started is recorded as `abandoned` with reason `launch_interrupted`; its worktree is already removed, so `abandoned-runs` does not list it. `OMO_MEMORY_KILL_POINT` is a test-only seam: when it names a recovery point (`after-reserve`, `after-prelaunch`, `after-worktree`, `after-child-exit`, `after-validate`, `after-merge`, `before-receipt`), the process that reaches it kills itself. Never set it outside a crash test. The structural audit uses these stable codes: | Code | Meaning | | --- | --- | | `link_dangling` | A root-relative wiki link or file-relative Markdown link has no confined target. | | `frontmatter_invalid` | A memory file violates the description frontmatter contract. | | `content_duplicate` | Multiple Markdown files have identical bodies. | | `path_orphan` | A Markdown file is outside the five memory homes and allowed root files. | | `file_unreadable` | A Markdown file cannot be read or decoded as UTF-8. | | `system_pressure` | The system estimate has reached 80% of `compile_warn_tokens`. | Frontmatter failures retain the existing `frontmatter` check; other findings appear as `audit:`. A clean corpus produces one `audit` success line. Unreadable files and invalid frontmatter fail; other structural findings warn. The audit is read-only. `/doctor` has no `--fix` flag and rejects unknown flags; the existing skill-name repair is its only automatic repair. `/doctor --json` returns `{ identity, level, checks: [{ name, level, detail }], audit: { version: 1, generatedAt, issues: [{ code, path, detail, related? }], counts: { : } }, skills: { scanned, repaired }, receipts: { dream, reflection, facts }, quarantinedRuns: [{ runId, reason, at, dir, evidence }] }`. Each `receipts` entry is `null` or `{ event, at, trigger, runId | batchId, reason?, sha?, detail? }`. `audit` and `receipts` are `null` when the repository is missing. Every string value is secret-screened before JSON serialization; numeric counts stay intact. Dream runs receive the same redacted audit computed over their own worktree at `AUDIT_PATH`. They repair moved links, consolidate duplicate bodies with a pointer, and move orphaned files into the appropriate home. They leave evidence and `system/boundaries.md` untouched and report fixed and unresolved codes. Reflection runs do not receive this input. #### People Records about individuals, stored as cards with an observation ledger. | Option | Default | Description | | ------------------------- | ------- | ------------------------------------------------- | | `people.enabled` | `true` | Maintain people records | | `people.max_entries` | `40` | Maximum observation entries per person (1-100) | | `people.max_entry_chars` | `200` | Maximum characters per entry (50-500) | #### Agreements Standing agreements with your person, kept in `system/agreements.md` with how many times each was broken. The facts extractor counts a repeated correction against the agreement it repeats; past the threshold the agreement is flagged and you see a notice. Manage the list with `/agreements` (`list`, `add [--project] `, `ack `, `drop `). Agreements you add can only be changed by you. | Option | Default | Description | | ---------------------------------- | ------- | -------------------------------------------------------------- | | `agreements.enabled` | `true` | Count repeated corrections and announce flagged agreements | | `agreements.needs_human_threshold` | `3` | Breaks at which an agreement is flagged as needing you (min 1) | | `agreements.max_items` | `64` | Ledger size; at the cap the oldest never-reaffirmed agent item is evicted (8-256) | #### Soul | Option | Default | Description | | ------------------ | ------- | ------------------------------------------------------ | | `soul.edit_notice` | `true` | Surface a notice when the persona, identity, or boundaries block changes | #### Write Notice | Option | Default | Description | | ----------------------- | ------- | ------------------------------------------------------------------ | | `write_notice.enabled` | `true` | Render memory writes as a notice row instead of the plain commit line | #### Sync and Search | Option | Default | Description | | ---------------- | ------- | ---------------------------------------------- | | `sync.enabled` | `true` | End-to-end encrypted memory sync on this device (needs sign-in and sync on for the account) | | `sync.remote` | - | Deprecated, ignored; the git mirror is set with `/memfs` | | `search.enabled` | `true` | Enable memory search | In Senpi, run `/sleeptime` to see every resolved memory value, including which ones a per-agent override changed. OpenCode has no `/sleeptime` command; inspect the `memory` block in `omo.jsonc` instead. ### Hooks Disable built-in hooks via `disabled_hooks`: ```json { "disabled_hooks": ["comment-checker"] } ``` Available hooks: `todo-continuation-enforcer`, `session-notification`, `comment-checker`, `tool-output-truncator`, `question-label-truncator`, `directory-agents-injector`, `directory-readme-injector`, `empty-task-response-detector`, `think-mode`, `model-fallback`, `anthropic-context-window-limit-recovery`, `preemptive-compaction`, `rules-injector`, `background-notification`, `auto-update-checker`, `ast-grep-sg-provision`, `startup-toast`, `keyword-detector`, `agent-usage-reminder`, `non-interactive-env`, `interactive-bash-session`, `tool-pair-validator`, `monitor-status-injector`, `goal`, `category-skill-reminder`, `compaction-context-injector`, `compaction-todo-preserver`, `claude-code-hooks`, `auto-slash-command`, `edit-error-recovery`, `json-error-recovery`, `delegate-task-retry`, `team-tool-gating`, `ulw-execute`, `unstable-agent-babysitter`, `task-resume-info`, `stop-continuation-guard`, `tasks-todowrite-disabler`, `runtime-fallback`, `write-existing-file-guard`, `notepad-write-guard`, `bash-file-read-guard`, `hashline-read-enhancer`, `read-image-resizer`, `todo-description-override`, `webfetch-redirect-guard`, `fsync-skip-warning`, `plan-format-validator`, `legacy-plugin-toast` Hooks that exist only in the OpenCode edition are listed on the legacy page linked from [Agents](#agents). Guard hooks such as `team-tool-gating`, `write-existing-file-guard`, `bash-file-read-guard`, `webfetch-redirect-guard`, `rules-injector`, and `tool-pair-validator` protect safety, permissions, or provider protocol correctness. Disable them only for audited local debugging in a trusted environment. **Notes:** - `directory-agents-injector` - auto-disabled on OpenCode 1.1.37+ (native AGENTS.md support) - `startup-toast` is a sub-feature of `auto-update-checker`. Disable just the toast by adding `startup-toast` to `disabled_hooks`. ### Commands Disable built-in commands via `disabled_commands`: ```json { "disabled_commands": ["refactor", "ulw-execute"] } ``` Available commands: `goal`, `refactor`, `ulw-execute`, `stop-continuation`, `remove-ai-slops`, `handoff`, `hyperplan`. The `disabled_commands` option currently accepts only the schema enum, which does not include `handoff`. ### Browser Automation `browser_automation_engine.provider` accepts only the three values below. Unsupported values fail schema validation and `oh-my-opencode doctor` reports both the rejected value and the replacement: use the built-in browser path: Bun.WebView / playwright-core scripts. Remove the obsolete provider override from the active `[opencode]` block in `omo.jsonc` (including project/profile layers); it is not silently mapped to another provider. | Provider | Interface | Installation | | ---------------------- | --------- | --------------------------------------------------- | | `playwright` (default) | MCP tools | Auto-installed via npx | | `dev-browser` | Skill | Uses persistent dev-browser state | | `playwright-cli` | Bash CLI | Uses the token-efficient `@playwright/cli` | Browser skills use two tiers from js eval: `new Bun.WebView()` on Bun >= 1.4 (macOS default; Linux/Windows require installed Chrome/Chromium/Edge), otherwise write and run a `playwright-core` script against local Chrome (`channel: "chrome"`). Use the script tier for Chrome semantics, stealth, traces, and authenticated profiles; `launchPersistentContext` receives a CLONED profile, never the live one. Codex uses `browser:control-in-app-browser` for ordinary page control. These are skill execution paths, not new values of `browser_automation_engine.provider`. ### Tmux Integration Run background subagents in separate tmux panes. Requires running inside tmux with `opencode --port `. ```json { "tmux": { "enabled": true, "layout": "main-vertical", "main_pane_size": 60, "main_pane_min_width": 120, "agent_pane_min_width": 40, "isolation": "inline" } } ``` | Option | Default | Description | | ---------------------- | --------------- | ----------------------------------------------------------------------------------- | | `enabled` | `false` | Enable tmux pane spawning | | `layout` | `main-vertical` | `main-vertical` / `main-horizontal` / `tiled` / `even-horizontal` / `even-vertical` | | `main_pane_size` | `60` | Main pane % (20–80) | | `main_pane_min_width` | `120` | Min main pane columns | | `agent_pane_min_width` | `40` | Min agent pane columns | | `isolation` | `inline` | `inline` / `window` / `session` | ### Git Master Configure git commit behavior: ```json { "git_master": { "include_co_authored_by": true, "commit_footer": false, "git_env_prefix": "GIT_MASTER=1" } } ``` `include_co_authored_by` (default `true`) adds `Co-authored-by: sisyphus-dev-ai ` to every commit git-master writes, exactly once: it is passed with `git -c trailer.ifexists=addIfDifferent commit --trailer`, so amending or rewording a commit that already has it does not add a second copy, and after a squash git-master rewrites the joined message once. It needs git 2.32 or newer. Set it to `false` to keep the trailer out of your history. `commit_footer` (default `false`) opts in to an "Ultraworked with Sisyphus" footer in the commit body; a string replaces the builtin text. Either way, commits keep your own git author and committer. This key configures the OpenCode plugin inside `[opencode]`. The Senpi harness reads the typed shared `git_master` section instead, documented in the [omo.json reference](./omo-json.md#git_master-native-harness). `git_env_prefix` (default `"GIT_MASTER=1"`) is prepended to git commands; set it to `""` to disable. ### Comment Checker Customize the comment quality checker: ```json { "comment_checker": { "custom_prompt": "Your message. Use {{comments}} placeholder." } } ``` ### Notification Force-enable session notifications: ```json { "notification": { "force_enable": true } } ``` `force_enable` (`false`) - force session-notification even if external notification plugins are detected. OpenCode also has native TUI Attention notifications in `tui.json`. Use either native Attention or OmO `session-notification` for the same events, not both, or you may receive duplicate desktop notifications. OmO auto-disables `session-notification` when it detects known external notification plugins such as `opencode-notifier`, but native Attention is OpenCode TUI config rather than a plugin entry, so OmO cannot detect it through the plugin list. Keep `session-notification` enabled when you want OmO's richer idle notification body with session title and recent message context; otherwise prefer native Attention for basic TUI notification and sound events. ### MCPs Built-in MCPs (enabled by default): `websearch` (Exa AI), `context7` (library docs), `grep_app` (GitHub code search), and `lsp` (local language-server tools). Structural search and rewrite is provided by the `ast-grep` skill instead of a built-in MCP. ```json { "disabled_mcps": ["websearch", "context7", "grep_app", "lsp"] } ``` ### LSP LSP tools are served by the built-in `lsp` MCP server (see [MCPs](#mcps)). The previous top-level `"lsp"` block in the plugin config is no longer read; the unified config migration strips it when importing a legacy file. To configure custom language servers, create `.opencode/lsp.json`, `.omo/lsp.json`, or `.omo/lsp-client.json` at the project root. The MCP server launches with `LSP_TOOLS_MCP_PROJECT_CONFIG` set to a platform-delimiter-separated search list of those three paths and reads the first applicable server maps. The schema lives in the `packages/lsp-tools-mcp` vendored package (upstream: [code-yeongyu/lsp-tools-mcp](https://github.com/code-yeongyu/lsp-tools-mcp)). To disable the LSP MCP entirely: ```json { "disabled_mcps": ["lsp"] } ``` Process hygiene is unconditional and has no config keys: a parent-liveness watchdog exits MCP server processes when their parent dies, a newly started lsp daemon reaps older-version daemons at startup, and a best-effort family sweep removes orphaned lsp processes at startup on every adapter (OpenCode plugin startup, the Codex `SessionStart` hook, and Senpi session start). --- ## Advanced ### Runtime Fallback Auto-switches to backup models on API errors. **Simple configuration** (enable/disable with defaults): ```json { "runtime_fallback": true } ``` ```json { "runtime_fallback": false } ``` **Advanced configuration** (full control): ```json { "runtime_fallback": { "enabled": true, "retry_on_errors": [429, 500, 502, 503, 504], "max_fallback_attempts": 3, "cooldown_seconds": 60, "timeout_seconds": 30, "notify_on_fallback": true } } ``` | Option | Default | Description | | ----------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `enabled` | `false` | Enable runtime fallback | | `retry_on_errors` | `[429,500,502,503,504]` | HTTP codes that trigger fallback. Also handles classified provider key errors. | | `max_fallback_attempts` | `3` | Max fallback attempts per session (1–20) | | `cooldown_seconds` | `60` | Seconds before retrying a failed model | | `timeout_seconds` | `30` | Seconds before forcing next fallback. **Set to `0` to disable timeout-based escalation and `message.updated` provider retry signal detection.** Structured `session.status` retry events can still trigger fallback. | | `notify_on_fallback` | `true` | Toast notification on model switch | | `restore_primary_after_cooldown` | `false` | Return to the primary model after its cooldown expires | #### Speeding Up Fallback (Proxy APIs) If you are using a proxy API provider, they may return different error codes (e.g., `401`, `403`, `404`) for quota exhaustion or model unavailability. To make fallback trigger instantly without waiting for long timeouts: ```jsonc { "runtime_fallback": { "enabled": true, // Add your proxy's specific error codes to retry_on_errors "retry_on_errors": [400, 401, 403, 404, 429, 500, 502, 503, 504], "max_fallback_attempts": 3, "cooldown_seconds": 15, // Shorter cooldown "timeout_seconds": 10 // Detect hung proxy requests faster } } ``` Define `fallback_models` per agent or category: ```json { "agents": { "plan-consultant": { "model": "anthropic/claude-opus-5-5", "fallback_models": [ "openai/gpt-5.6-sol", { "model": "google/gemini-3.1-pro", "reasoning": "high" } ] } } } ``` `fallback_models` also supports object-style entries so you can attach settings to a specific fallback model: ```json { "agents": { "plan-consultant": { "model": "anthropic/claude-opus-5-5", "fallback_models": [ "openai/gpt-5.6-sol", { "model": "anthropic/claude-sonnet-5", "reasoning": "high" }, { "model": "openai/gpt-5.6-sol", "reasoning": "high", "temperature": 0.2, "top_p": 0.95, "maxTokens": 8192 } ] } } } ``` Mixed arrays are allowed, so string entries and object entries can appear together in the same fallback chain. #### Object-style `fallback_models` Object entries use the following shape: | Field | Type | Description | | ----- | ---- | ----------- | | `model` | string | Fallback model ID. Provider prefix is optional when OmO can inherit the current/default provider. | | `reasoning` | string | Canonical reasoning override for this fallback entry. | | `temperature` | number | Temperature applied if this fallback model becomes active. | | `top_p` | number | Top-p applied if this fallback model becomes active. | | `maxTokens` | number | Max response tokens applied if this fallback model becomes active. | | `variant` | string | Deprecated compatibility input normalized to `reasoning`. | | `reasoningEffort` | string | Deprecated compatibility input normalized to `reasoning`. | | `thinking` | object | Legacy form normalized to `reasoning` plus `provider_options.thinking` in the unified shape. | Per-model settings are **fallback-only**. They are promoted only when that specific fallback model is actually selected, so they do not override your primary model settings when the primary model resolves successfully. `thinking` uses the same shape as the normal agent/category option: | Field | Type | Description | | ----- | ---- | ----------- | | `type` | string | `enabled` or `disabled` | | `budgetTokens` | number | Optional Anthropic thinking budget | Object entries can also omit the provider prefix when OmO can infer it from the current/default provider. Canonical `reasoning` takes precedence over deprecated `reasoningEffort`, which takes precedence over deprecated `variant`; an inline model suffix is normalized separately. #### Full examples **1. Simple string chain** Use strings when you only need an ordered fallback chain: ```json { "agents": { "reviewer": { "model": "anthropic/claude-sonnet-5", "fallback_models": [ "anthropic/claude-haiku-5-5", "openai/gpt-5.6-sol", "google/gemini-3.1-pro" ] } } } ``` **2. Same-provider shorthand** If the primary model already establishes the provider, fallback entries can omit the prefix: ```json { "agents": { "reviewer": { "model": "openai/gpt-5.6-sol", "fallback_models": [ "gpt-6-luna-fast", { "model": "gpt-5.6-sol", "reasoning": "medium", "maxTokens": 4096 } ] } } } ``` In this example OmO treats `gpt-6-luna-fast` and `gpt-5.6-sol` as OpenAI fallback entries because the current/default provider is already `openai`. **3. Mixed cross-provider chain** Mix string entries and object entries when only some fallback models need special settings: ```json { "agents": { "plan-consultant": { "model": "anthropic/claude-opus-5-5", "fallback_models": [ "openai/gpt-5.6-sol", { "model": "anthropic/claude-sonnet-5", "reasoning": "high" }, { "model": "google/gemini-3.1-pro", "reasoning": "high" } ] } } } ``` **4. Category-level fallback chain** `fallback_models` works the same way under `categories`: ```json { "categories": { "deep-low": { "model": "openai/gpt-5.6-sol", "fallback_models": [ { "model": "openai/gpt-5.6-sol", "reasoning": "xhigh", "maxTokens": 12000 }, { "model": "anthropic/claude-opus-5-5", "reasoning": "max", "temperature": 0.2 }, "google/gemini-3.1-pro(high)" ] } } } ``` **5. Full object entry with every supported field** This shows every supported object-style parameter in one place: ```json { "agents": { "plan-reviewer": { "model": "openai/gpt-5.6-sol", "fallback_models": [ { "model": "openai/gpt-5.6-sol(low)", "reasoning": "high", "temperature": 0.3, "top_p": 0.9, "maxTokens": 8192 } ] } } } ``` In this example the explicit `"reasoning": "high"` is canonical; deprecated fields are resolved with precedence `reasoning` > `reasoningEffort` > `variant`, while the inline `(low)` suffix is normalized separately. This final example is a **complete canonical shape reference** for `[opencode]` fallback objects. Prefer unified `reasoning` for model tuning, and use provider-specific `[opencode]` fields only when the target model requires them. ### Anthropic Prompt Cache Lifetime OmO Native caches the prompt prefix on Anthropic models. The cached prefix lives for 5 minutes by default. If your turns are usually more than 5 minutes apart (long reviews, waiting on builds, stepping away), every turn after a gap rewrites the whole prefix into the cache, and cache-write tokens dominate the bill. Anthropic also offers a 1-hour cache lifetime, and OmO Native can use it. **Turn it on for every Anthropic model** by setting the environment variable before you start `omo`: ```sh export PI_CACHE_RETENTION=long ``` **Or turn it on per model** in `~/.omo/agent/models.json`, one entry per model id. This applies to every session that reads that agent directory, regardless of the shell it was started from: ```jsonc { "providers": { "anthropic": { "modelOverrides": { "claude-opus-5-5": { "cacheRetention": "long" }, "claude-sonnet-5-5": { "cacheRetention": "long" } } } } } ``` `cacheRetention` accepts `"short"` (5 minutes, the default), `"long"` (1 hour) and `"none"` (no caching). A per-model value wins over the environment variable. Put it under `modelOverrides`: a `cacheRetention` set directly on a built-in provider such as `anthropic` is rejected, because provider-level values only apply to the custom models you define under that provider's `models`. The 1-hour lifetime is sent only to the Anthropic API itself (`https://api.anthropic.com`). A model served through a proxy or another base URL keeps the 5-minute lifetime even when `cacheRetention` is `"long"`, because those endpoints don't all accept it. **Cost trade-off.** On Anthropic, a 5-minute cache write costs 1.25x the base input price, a 1-hour cache write costs 2x, and a cache read costs 0.1x either way. The longer lifetime makes each write more expensive, so it pays off when your turns are often more than 5 minutes apart. If you usually answer within a few minutes, keep the default. **Keeping the cache warm instead.** `promptCache.keepAlive` in `~/.omo/agent/settings.json` sends a small warm-up request shortly before the cache would expire while the session is idle, so the next turn reads the cache instead of rewriting it. It is off by default and capped per session: ```jsonc { "promptCache": { "keepAlive": { "enabled": true, "maxRequestsPerSession": 3, // warm-up requests per session (default 3) "maxCostUsdPerSession": 0.05, // estimated spend cap per session (default 0.05) "marginSeconds": 60 // send this many seconds before expiry (default 60) } } } ``` The keep-alive also runs only for Anthropic Messages models on the Anthropic API base URL. Use it for short pauses. For gaps that are regularly longer than a few warm-ups would cover, the 1-hour lifetime is the simpler option. ### Model Capabilities OmO can refresh a local models.dev capability snapshot on startup. This cache is controlled by `model_capabilities`. ```jsonc { "model_capabilities": { "enabled": true, "auto_refresh_on_start": true, "refresh_timeout_ms": 5000, "source_url": "https://models.dev/api.json" } } ``` | Option | Default behavior | Description | | ------ | ---------------- | ----------- | | `enabled` | enabled unless explicitly set to `false` | Master switch for model capability refresh behavior | | `auto_refresh_on_start` | refresh on startup unless explicitly set to `false` | Refresh the local models.dev cache during startup checks | | `refresh_timeout_ms` | `5000` | Timeout for the startup refresh attempt | | `source_url` | `https://models.dev/api.json` | Override the models.dev source URL | Notes: - Startup refresh runs through the auto-update checker hook. - Manual refresh is available via `bunx oh-my-openagent refresh-model-capabilities`. - Provider runtime metadata still takes priority when OmO resolves capabilities for compatibility checks. ### Hashline Edit Replaces the built-in `Edit` tool with a hash-anchored version using `LINE#ID` references to prevent stale-line edits. Disabled by default. ```json { "hashline_edit": true } ``` When enabled, OmO registers the hash-anchored `edit` tool and activates the `hashline-read-enhancer` companion hook, which annotates Read output with `LINE#ID` markers. Opt in by setting `hashline_edit: true`. Disable the companion hook via `disabled_hooks` if needed. ### Experimental ```json { "experimental": { "truncate_all_tool_outputs": false, "aggressive_truncation": false, "disable_omo_env": false, "task_system": true, "dynamic_context_pruning": { "enabled": false, "notification": "detailed", "turn_protection": { "enabled": true, "turns": 3 }, "protected_tools": [ "task", "todowrite", "todoread", "lsp_rename", "session_read", "session_write", "session_search" ], "strategies": { "deduplication": { "enabled": true }, "supersede_writes": { "enabled": true, "aggressive": false }, "purge_errors": { "enabled": true, "turns": 5 } } } } } ``` | Option | Default | Description | | ---------------------------------------- | ---------- | ------------------------------------------------------------------------------------ | | `truncate_all_tool_outputs` | `false` | Truncate all tool outputs (not just whitelisted) | | `aggressive_truncation` | `false` | Aggressively truncate when token limit exceeded | | `disable_omo_env` | `false` | Disable auto-injected `` block (date/time/locale). Improves cache hit rate. | | `task_system` | `false` | Enable the file-based task system | | `dynamic_context_pruning.enabled` | `false` | Auto-prune old tool outputs to manage context window | | `dynamic_context_pruning.notification` | `detailed` | Pruning notifications: `off` / `minimal` / `detailed` | | `turn_protection.turns` | `3` | Recent turns protected from pruning (1–10) | | `strategies.deduplication` | `true` | Remove duplicate tool calls | | `strategies.supersede_writes` | `true` | Prune write inputs when file later read | | `strategies.supersede_writes.aggressive` | `false` | Prune any write if ANY subsequent read exists | | `strategies.purge_errors.turns` | `5` | Turns before pruning errored tool inputs | | `preemptive_compaction` | - | Enable preemptive context compaction | | `plugin_load_timeout_ms` | `10000` | Plugin component load timeout in milliseconds (min: 1000) | | `safe_hook_creation` | `true` | Isolate hook creation failures at the runtime call site | | `model_fallback_title` | `false` | Append fallback model information to the session title | | `max_tools` | - | Maximum number of tools to register (min: 1) | | `disable_live_parent_wake_routing` | `false` | Restore pre-migration in-process parent wake dispatch | ### Telemetry Two distinct keys exist. The `[opencode]` block takes a boolean: ```jsonc { "[opencode]": { "telemetry": false } } ``` The shared base and Senpi use an object: ```jsonc { "telemetry": { "enabled": false } } ``` | Option | Default | Description | | ----------- | ------- | ---------------------------------------------------------------------- | | `[opencode].telemetry` | `true` (enabled when omitted) | Enable anonymous daily-active telemetry for the OpenCode plugin. Set to `false` to disable it. | | `telemetry.enabled` (shared base) | `true` | Object form used by the shared base and Senpi. A bare boolean at the shared base fails validation. | --- ## Reference ### Environment Variables | Variable | Description | | --------------------- | ----------------------------------------------------------------- | | `OPENCODE_CONFIG_DIR` | Override OpenCode config directory (useful for profile isolation) | | `OPENGATEWAY_API_KEY` | API key for the OpenGateway provider; without this or an `opengateway` auth entry, the plugin does not inject the provider | | `PI_CACHE_RETENTION` | Set to `long` to use Anthropic's 1-hour prompt cache lifetime instead of 5 minutes. A per-model `cacheRetention` in `models.json` takes precedence. See [Anthropic Prompt Cache Lifetime](#anthropic-prompt-cache-lifetime). | | `OMO_DEBUG` | Set to `1` (any non-empty value) to print omo-senpi component `info` diagnostics on stderr. Unset, those lines are silent. `warn` and `error` still print. Component logs never go to stdout. | | `OMO_SEND_ANONYMOUS_TELEMETRY` | Set to `0`, `false`, or `no` to disable anonymous telemetry | | `OMO_DISABLE_POSTHOG` | Legacy telemetry opt-out flag. Set to `1`, `true`, or `yes` to disable PostHog | | `OMO_DISABLE_TELEMETRY` | Set to `1`, `true`, or `yes` to disable anonymous telemetry in every omo package | | `OMO_CODEX_DISABLE_POSTHOG` | Set to `1`, `true`, or `yes` to disable PostHog telemetry for the `omo-codex` adapter. Global `OMO_DISABLE_POSTHOG` also disables Codex telemetry. | | `OMO_CODEX_SEND_ANONYMOUS_TELEMETRY` | Set to `0`, `false`, `no`, or `yes` to disable anonymous telemetry for `omo-codex` | | `OMO_CODEX_GIT_BASH_PATH` | Native Windows Codex installs only. Absolute path to Git Bash, for example `C:\Program Files\Git\bin\bash.exe`, when `where bash` cannot find it | | `LAZYCODEX_CONFIG_MIGRATION_DISABLED` | Set to `1` to skip the Codex config migration that runs on every session start (including the `multi_agent_v2` force-disable and managed reasoning-profile sync), leaving `config.toml` untouched | | `OMO_CODEX_CONFIG_MIGRATION_DISABLED` | Alias of `LAZYCODEX_CONFIG_MIGRATION_DISABLED` | | `LSP_TOOLS_MCP_INSTALL_DECISIONS` | Override the LSP install-decisions path. Codex defaults to `$CODEX_HOME/lsp-install-decisions.json`; OpenCode injects its OpenCode config-directory path. | | `POSTHOG_API_KEY` | Optional override for the built-in PostHog project API key | | `POSTHOG_HOST` | Override the PostHog ingestion host. Defaults to `https://us.i.posthog.com` | ### LSP Install Decisions When an LSP tool hits a language server that is not installed, it asks once per server and persists the answer to a harness-specific file: Codex uses `$CODEX_HOME/lsp-install-decisions.json`, while OpenCode injects `lsp-install-decisions.json` under its OpenCode config directory. Override either path with `LSP_TOOLS_MCP_INSTALL_DECISIONS`. A `declined` entry collapses all future diagnostics for that server to a one-line note. To get prompted again - or to re-enable a server that an agent declined on your behalf - delete the file or the server's entry in it. ### Codex Light Git Bash MCP Native Windows Codex installs bundle a `git_bash` MCP server and write `[plugins."omo@sisyphuslabs".mcp_servers.git_bash] enabled = true`. Non-Windows installs keep the bundled manifest entry but write `enabled = false`, so the plugin detail can still show the server while policy prevents exposure. The installer discovers Git Bash with `OMO_CODEX_GIT_BASH_PATH`, standard Git for Windows locations, and PATH. If discovery fails, it prints manual install guidance and stops without running `winget` or changing system dependencies. The Light plugin also emits a fixed reminder before the first Codex shell-like `Bash` hook call in a Windows session, and resets that reminder after `PostCompact` so the first post-compaction shell call recommends `git_bash` again. ### Codex Companion Plugin Compatibility LazyCodex can coexist with other Codex plugins, but if LazyCodex is your primary Codex workflow the `codex@openai-codex` companion plugin adds its own `SessionStart` and `Stop` lifecycle hooks. Those extra hooks can produce confusing Codex hook-failure banners even when the LazyCodex hooks are healthy. `lazycodex doctor` warns when `omo@sisyphuslabs` is enabled and the companion plugin is enabled, or when stale `[hooks.state."codex@openai-codex:..."]` SessionStart/Stop trust entries remain in `~/.codex/config.toml`. The doctor only reports this condition; it does not disable or delete another plugin for you. If LazyCodex is the primary workflow, disable the companion plugin explicitly: ```toml [plugins."codex@openai-codex"] enabled = false ``` If doctor still warns afterward, remove the stale `[hooks.state."codex@openai-codex:..."]` SessionStart/Stop entries from the Codex config after making your own backup. ### Provider-Specific #### Google Auth Install [`opencode-antigravity-auth`](https://github.com/NoeFabris/opencode-antigravity-auth) for Google Gemini. Provides multi-account load balancing, dual quota, and variant-based thinking. ##### Split Claude Routing Provider path can change Claude context limits. Confirm the active model's context window with `bunx oh-my-openagent doctor --verbose` rather than assuming 200k vs 1M. Use Antigravity for cheaper or quota-balanced work. Use direct Anthropic for long-context planning, review, and research sessions when the account, model, and required beta/header setup support a larger window. ```jsonc { "agents": { // Google Antigravity Claude. "explore": { "model": "google/antigravity-claude-sonnet-4-6" }, "librarian": { "model": "google/antigravity-claude-sonnet-4-6" }, // Direct Anthropic, only for eligible long-context accounts/models. "plan-consultant": { "model": "anthropic/claude-opus-5-5", "reasoning": "max" }, "plan-reviewer": { "model": "anthropic/claude-opus-5-5" } } } ``` If you see an error like `prompt is too long ... > 200000`, check whether the agent is routed through `google/antigravity-*`. Move that agent to a direct `anthropic/*` model only when the account, model, and required beta/header setup support a larger context window. Keep the Antigravity path explicit when you want that provider's quota and routing behavior. #### Ollama **Must** disable streaming to avoid JSON parse errors: ```json { "agents": { "explore": { "model": "ollama/qwen3-coder" } } } ``` **Note:** The `stream` option should be configured in your OpenCode settings or via environment variables, not in the agent config. See [Ollama Troubleshooting](../troubleshooting/ollama.md) for details on disabling streaming. Common models: `ollama/qwen3-coder`, `ollama/ministral-3:14b`, `ollama/lfm2.5-thinking` See [Ollama Troubleshooting](../troubleshooting/ollama.md) for `JSON Parse error: Unexpected EOF` issues. #### OpenGateway The `omo-opencode` plugin exposes the OpenAI-compatible `opengateway` provider at `https://apis.opengateway.ai/v1` when `OPENGATEWAY_API_KEY` is set or an `opengateway` auth entry exists. No provider is injected if neither credential is present.