//! §4 "Presets" (`docs/composable-harness/COMPOSABLE-HARNESS-DESIGN.md`) —
//! P2 of the composable-harness migration (design §5.2, phase **P2**).
//!
//! The six reserved built-in presets, compiled in as TOML consts, transcribed
//! faithfully from the design doc's §4.1-§4.5 TOML blocks (and §4's intro
//! paragraph for the sixth, `supercode-default`, which the doc defines by
//! prose rather than a TOML block — S10 fix: "`pi-core` MINUS `{trust,
//! session_tree, session_share, server, plugins}`", not "`pi-core` plus
//! extras").
//!
//! **Syntax fix (P2 judgment call).** The design doc's `[capabilities.X]
//! { enabled = true, ... }` lines combine a TOML table-HEADER and an
//! inline-table VALUE on one line, which is not valid TOML (verified
//! empirically against the `toml` crate: `invalid table header, expected
//! newline`). Naively rewriting them as dotted-key assignments
//! (`capabilities.X = { ... }`) is also unsafe wherever such a line appears
//! *after* an already-open `[capabilities.permissions]` table (cc-parity,
//! cx-parity, oc-parity all have this): a dotted key inside an open table is
//! relative to the CURRENT table, so `capabilities.permissions.sandbox = {..}`
//! written while inside `[capabilities.permissions]` nests as
//! `capabilities.permissions.capabilities.permissions.sandbox`, silently
//! corrupting the structure. The transcription below instead expands every
//! `[capabilities.X] { k = v, ... }` shorthand into the equivalent explicit
//! form — a real `[capabilities.X]` table header (always root-absolute,
//! never context-relative) followed by `k = v` lines — which is safe
//! regardless of surrounding context and preserves the exact same resolved
//! structure. Every block below is verified to parse into `HarnessConfig`
//! in this module's tests, and each preset's resolved shape is golden-tested
//! against §4.6's per-preset verdicts in
//! `crates/harness/tests/composable_presets.rs`.
//!
//! Comments from the design doc are preserved verbatim inside each TOML
//! block for traceability back to the source section.
/// `pi-core` — design §4.1.
pub const PI_CORE_TOML: &str = r#"# built-in preset: pi-core — the §1 core with pi's exact defaults, plus pi's four kept extras.
schema_version = 1
[core]
effort = "medium" # pi default thinking level (pi§3, src:core/defaults.ts:3)
max_tool_output_bytes = 51200 # pi's shared truncation policy: 50KB / 2000 lines (pi§1, truncate.ts)
project_context = true # AGENTS.md/CLAUDE.md global + ancestor walk (pi§2 "Context files")
env_context = true # pi appends Current date + cwd to the prompt (pi§2, system-prompt.ts:88-170)
[core.retry] # pi agent-level auto-retry (pi§3)
enabled = true
max_retries = 3
base_delay_ms = 2000
[core.tools]
enabled = ["read_file", "bash", "edit_file", "write_file"] # pi's default-ACTIVE four (pi§1, src:core/sdk.ts:245)
schema_tier = "full"
[core.tools.read_file]
multimodal = true # pi read returns images as attachments (pi§1, read.ts)
[core.skills]
enabled = true # agentskills.io discovery + progressive disclosure (pi§2; D-7 met by read_file)
[core.compaction]
enabled = true
after_messages = 0 # pi's trigger is token pressure, never message count (pi§2)
reserve_tokens = 16384 # compaction.reserveTokens default (pi§2, pi§6)
keep_recent_tokens = 20000 # compaction.keepRecentTokens default (pi§2)
summarize = true # structured Goal/Constraints/Progress/… summary (pi§2, compaction.md)
[core.steering]
steering_mode = "one-at-a-time" # pi delivery-mode defaults (pi§3 "Message queue", pi§6)
follow_up_mode = "one-at-a-time"
# ---- modules ON (each is on pi's kept-list, pi§10 closing) ----
[capabilities.trust]
enabled = true
default = "ask"
# pi's ONE built-in gate (pi§4, trust-manager.ts; defaultProjectTrust "ask")
[capabilities.session_tree]
enabled = true
branch_summaries = true
labels = true
# THE core pi feature (pi§10; D5)
[capabilities.session_share]
enabled = true
# /share gist public link (pi§8); human /export HTML is core now (§1.6, S6) and on regardless
[capabilities.server]
enabled = true
# --mode json / --mode rpc embedding ladder (pi§8, pi§10)
[capabilities.plugins]
enabled = true
# everything-is-an-extension (pi§7; D-10 dep satisfied by trust above)
[capabilities.tui]
enabled = true
# §1.9 recorded deviation, default-on in parity presets
# ---- notable OFFs (each a pi FIRST-PARTY omission, pi§10 / catalog §3) ----
[capabilities.tools_search]
enabled = false
# grep/find/ls exist but are OPT-IN even in pi (pi§1 "--tools"); one line re-enables
[capabilities.mcp]
enabled = false
# "intentionally does not include built-in MCP" (pi§7)
[capabilities.subagents]
enabled = false
# example extension only (pi§3 "NO subagents")
[capabilities.permissions]
enabled = false
approval = "never"
sandbox = "danger_full_access"
# pi has NO popups/rules/sandbox (pi§4; README "Permissions & containerization").
# C3 fires its MANDATORY warning here BY DESIGN — pi's own docs say containers, not trust in the harness.
[capabilities.plan_mode]
enabled = false
# example ext only (pi§10)
[capabilities.todos]
enabled = false
# example ext only (pi§10)
[capabilities.tools_background]
enabled = false
# "tmux instead" (pi§10)
[capabilities.tools_web]
enabled = false
# web search ships as a SKILL in pi (pi§10)
[capabilities.checkpoint]
enabled = false
# git-checkpoint example ext only (pi§5)
[capabilities.memory]
enabled = false
# no memory subsystem (pi§2)
[capabilities.hooks]
enabled = false
# pi's "hooks" are code extensions, not config-registered commands
[capabilities.deferred_tools]
enabled = false
[capabilities.cache]
enabled = false
[capabilities.reduction]
enabled = false
# supercode-only OPTIONAL policies off; A7 truncation + rehydrate stay always-on core regardless (§1.13, S1/S7 — no longer a D-8/D-8-error risk)
[capabilities.model_catalog]
enabled = false
[capabilities.model_oauth]
enabled = false
# pi HAS /login OAuth (pi§9) — deferred module 27, recorded gap
"#;
/// `cc-parity` — design §4.2.
pub const CC_PARITY_TOML: &str = r#"# built-in preset: cc-parity — Claude Code's default surface, composed.
schema_version = 1
[core]
model = "anthropic/claude-opus-4-8" # CC account-default Opus 4.8 (cc§9 "Account-type defaults")
effort = "medium"
env_context = true # CC startup context: cwd/git status (cc§2 "Startup context")
project_context = true # CLAUDE.md tiers + directory walk (cc§2); global tier included (§1.4)
nested_instructions = true # S6/S12 home: subdir CLAUDE.md loaded on demand, CC default (catalog:84) — closes a gap-ledger row
instruction_imports = true # S6/S12 home: `@path` imports, depth 4, CC default (catalog:85) — closes a gap-ledger row
[core.tools]
enabled = ["read_file", "bash", "edit_file", "write_file"]
schema_tier = "full"
[core.tools.read_file]
multimodal = true # CC Read renders images/PDFs/notebooks (cc§1 Read)
[core.tools.edit_file]
require_read_before_edit = true # S6/S12 home: CC Edit refuses unless the file was read this conversation (catalog:32) — closes the cc-parity gap-ledger row
notebook_aware = true # S6/S12 home: NotebookEdit cell-level replace/insert/delete (catalog:40) — closes the cc-parity gap-ledger row
[core.tools.bash]
timeout_secs = 120 # CC default 2 min, model-raisable (cc§1 Bash)
[core.skills]
enabled = true # SKILL.md dirs + commands, descriptions-only until invoked (cc§7 Skills)
[core.compaction]
enabled = true
summarize = true # CC auto-compaction near limit + /compact [instructions] (cc§2)
reserve_tokens = 16384 # CC's threshold is pct-based (CLAUDE_CODE_AUTOCOMPACT_PCT_OVERRIDE, cc§2); reserve is our §1.5 equivalent
# ---- modules ON ----
[capabilities.tools_search]
enabled = true
glob = true
content_search = true
list_dir = false
# Glob + Grep, promptless read-class (cc§1); list_dir OFF (S15 fix) — CC lists dirs via Bash/Glob, Read rejects directories (catalog:33 fn²), so the module's dir-listing sub-tool would be a capability CC users never see
[capabilities.tools_web]
enabled = true
fetch = true
search = true
# WebFetch + WebSearch (cc§1)
[capabilities.tools_question]
enabled = true
# AskUserQuestion (cc§1)
[capabilities.todos]
enabled = true
persist = true
# Task*/TodoWrite checklist, persists across compaction (cc§1, cc§3)
[capabilities.plan_mode]
enabled = true
# Shift+Tab / EnterPlanMode read-only mode (cc§3); dep met by permissions.rules below
[capabilities.subagents]
enabled = true
max_depth = 2
background = true
background_prompts = "parent"
# Agent tool; background-by-default v2.1.198+, nested allowed (cc§1, cc§3)
# C6 resolved via the schema key (S2 fix, not prose): background_prompts = "parent" — background children surface prompts in the parent session (cc§3, claude-code.md:98)
[capabilities.tools_background]
enabled = true
# run_in_background + Ctrl+B (cc§1, cc§8); C6: same parent-surfaced queue as subagents.background_prompts above
[capabilities.permissions]
enabled = true
approval = "untrusted" # CC tiered default: read-only never prompts, Bash/edits prompt first-use (cc§4 "Tiered defaults")
# No C3: approval != never.
auto_approved_tools = ["read_file", "glob", "search"] # CC read-only tier (cc§4); `list_dir` removed (S15 — module tool is off above)
# module 12 in table form (S5): OS sandbox OFF (CC's `/sandbox` is opt-in, cc§4), fs tier unconfined.
# One key `permissions.sandbox` — the table form, not the bare-scalar shorthand, so no collision.
[capabilities.permissions.sandbox]
enabled = false
tier = "danger_full_access"
# flip enabled=true + set network.*/escalation for `/sandbox` parity later
[capabilities.permissions.rules]
enabled = true # deny→ask→allow first-match IS the CC algebra — native, no translation (C5 decision; cc§4 "Rule sets & evaluation")
deny = []
ask = []
allow = [] # CC ships empty rule sets; "don't ask again" persists into allow at runtime (cc§4)
[capabilities.permissions.protected_paths]
enabled = true # never-auto-approved set (cc§4 "Protected paths")
# Rule-layer floor: file-tools + bash redirect targets + apply_patch + known
# argv-writers (tee/dd/cp/mv/install/sed -i/truncate/ln); an opaque or
# dynamic bash write is forced to Ask. Complete OS-level write confinement
# is `permissions.sandbox`'s job (module 10), not this table's — see
# `crate::permissions` module doc / `Config::permissions_protected_paths`.
paths = [".git/**", ".env*", ".claude/**", ".vscode/**", ".idea/**", "~/.claude/settings*"]
[capabilities.trust]
enabled = true
default = "ask"
# workspace trust gates project allow-rules (cc§4)
[capabilities.mcp]
enabled = true
# stdio/HTTP/OAuth, resources, prompts-as-commands (cc§7)
[capabilities.deferred_tools]
enabled = true
core = ["read_file", "bash", "edit_file", "write_file", "glob", "search", "update_plan"]
# CC defers MCP tool definitions BY DEFAULT behind ToolSearch (cc§7 "Tool search"); builtins stay eager
[capabilities.hooks]
enabled = true
# config-registered lifecycle hooks (cc§7: 30 events; module ships the CC-compatible subset first)
[capabilities.memory]
enabled = true
# auto memory MEMORY.md + topic files (cc§2); D-9 dep → model_catalog below
[capabilities.checkpoint]
enabled = true
# per-prompt file-history-snapshot → /rewind (cc§5)
[capabilities.session_tree]
enabled = true
branch_summaries = false
labels = false
# CC has the tree DATA MODEL (uuid/parentUuid, cc§5) + /rewind; summaries/labels are pi-isms
[capabilities.model_catalog]
enabled = true
small_model = "anthropic/claude-haiku-4-5"
fallback = []
# aliases + ANTHROPIC_SMALL_FAST_MODEL + fallback chains (cc§9)
[capabilities.tui]
enabled = true
# ---- notable OFFs ----
[capabilities.tools_apply_patch]
enabled = false
# CC is edit-only (C1; catalog §5 conflict 1)
[capabilities.tools_persistent_shell]
enabled = false
[capabilities.lsp]
enabled = false
# CC's LSP is inactive until a plugin installs it (cc§1) — off matches default
[capabilities.formatters]
enabled = false
[capabilities.session_share]
enabled = false
# no PUBLIC share links in CC (D5 OC+PI-only row); `/export`+`/copy` are core now (§1.6 `export_format`, S6) and stay on regardless
[capabilities.server]
enabled = false
# CC has no local HTTP server surface; SDK is in-process
[capabilities.reduction]
enabled = false
[capabilities.cache]
enabled = false
# CC caching is provider-automatic (cc§2); plan machinery is a token-saver concern
[capabilities.structured_output]
enabled = false
# --json-schema is headless-only surface; enable per-run
[capabilities.model_oauth]
enabled = false
# recorded gap: CC's DEFAULT auth is subscription OAuth (cc§9) — module 27 deferred
"#;
/// `cx-parity` — design §4.3.
pub const CX_PARITY_TOML: &str = r#"# built-in preset: cx-parity — Codex's default surface, composed.
schema_version = 1
[core]
effort = "medium" # model_reasoning_effort default tier (cx§6, cx§9)
env_context = true # <environment_context> block: cwd/sandbox/approval (cx§2)
project_context = true # AGENTS.md hierarchy, root-down concat, 32KiB cap (cx§2)
# P2 placement fix: §4.3's own TOML block places `shell_env_snapshot` under
# `[core.tools]`, but §3.1's schema (the "annotated, exhaustive" canonical
# definition, line ~598) defines it as a direct `[core]` scalar, not a
# `core.tools.*` key — `CoreToolsConfig` has no such field, so a literal
# under-`[core.tools]` placement would silently parse-and-drop it. Moved
# here to match §3.1 (the schema doc doesn't have this key twice with two
# different homes; §4.3 is corrected to agree with it).
shell_env_snapshot = true # S6/S12 home: shell-env snapshotting, cx stable-on feature (catalog:338) — closes a gap-ledger row
# C4 (catalog §5 conflict 4): Codex's base prompt VARIES BY APPROVAL MODE (cx§2: "proactively run
# tests only under never"). This preset pins prompt + approval together; when CONTINUING an
# imported rollout, the emulate path replays the rollout's own persisted base_instructions
# verbatim (session_meta carries them — cx§2:101; supercode SessionMeta.system_prompt), which is
# exact prompt parity by construction rather than imitation.
[core.tools]
enabled = ["bash", "view_image"] # Codex has NO read/write/edit/glob/grep function tools:
# reads via shell (cat, rg), writes via apply_patch (cx§1 "File reads/writes"; D1 footnote ¹).
# Disabling edit/write advertising is ALSO the C1 resolution.
# `view_image` (S6/S12 home, catalog:28) closes the gap-ledger row: with `read_file` off, cx-parity
# would otherwise have NO image-input pathway at all, unlike stock Codex's dedicated tool.
schema_tier = "full"
[core.skills]
enabled = true # SKILL.md discovery, $skill mentions (cx§7 Skills).
# D-7 (S3-amended, no longer a judgment call): the read pathway is bash (`cat`) in the codex
# shape — §2.1's D-7 now names read_file|bash explicitly; the resolver warns, doesn't error.
[core.compaction]
enabled = true
summarize = true # /compact + auto-compaction at model_auto_compact_token_limit (cx§2)
# ---- modules ON ----
[capabilities.tools_persistent_shell]
enabled = true
# exec_command/write_stdin PTY unified exec (cx§1); supercode has it (builtins.rs:981-984)
[capabilities.tools_apply_patch]
enabled = true
per_model = true
# freeform envelope, default write path (cx§1); per_model honors C1 via model_catalog bits (cx§9)
[capabilities.todos]
enabled = true
persist = true
# update_plan is ALWAYS registered (cx§1); goals (S6/S12 home, catalog:138, cx `/goal`) is the persistent-objective variant of this same module — closes a gap-ledger row
[capabilities.tools_web]
enabled = true
fetch = false
search = true
# Codex has hosted web_search but NO web-fetch tool (cx§1); cached mode default
[capabilities.tools_background]
enabled = true
# background terminals, /ps //stop (cx§1); C6 (S8-corrected defense): under `model_requested`, tools run sandboxed WITHOUT prompting unless the model itself escalates — from a background task's perspective that's an auto-run default, satisfying C6's auto-policy requirement without needing a separate allow-list
[capabilities.subagents]
enabled = true
max_depth = 1
background = false
# multi_agent default-on, agents.max_depth default 1 (cx§1, cx§6)
[capabilities.deferred_tools]
enabled = true
core = ["bash", "shell", "apply_patch", "update_plan"]
# ToolExposure::Deferred + native tool_search is Codex's own mechanism (cx§1)
[capabilities.structured_output]
enabled = true
# --output-schema final-response contract (cx§8); module 33, Config.response_format
[capabilities.permissions]
enabled = true
approval = "model_requested" # S8 fix: Codex `on-request` default is "the MODEL decides when to ask" (cx§4, protocol.rs:921-924) — NOT supercode's `OnRequest` (client-side allowlist check, config.rs:39-40, 299-302); using the wrong enum value would prompt on every non-allowlisted tool call where stock Codex prompts almost never. `model_requested` is the NEW distinct mode (§3.2) the module must re-implement escalation-initiated-by-the-model for.
sandbox = "workspace_write" # writes in cwd + tmp, no network (cx§4); RECOMMENDED POSTURE (S16 fix), not upstream's labeled default — codex.md names no sandbox mode "(default)" (unlike approval); this is upstream's own steered guidance ("prefer --sandbox workspace-write", the deprecated --full-auto warning)
[capabilities.permissions.rules]
enabled = true # execpolicy .rules allow/prompt/forbidden → translated into deny→ask→allow (C5)
deny = []
ask = []
allow = []
[capabilities.permissions.protected_paths]
enabled = true
# Rule-layer floor (file-tools + bash redirect targets + apply_patch + known
# argv-writers; opaque/dynamic bash writes forced to Ask) — NOT the same as
# cx's `workspace_write` OS sandbox read-only mount above; see
# `crate::permissions` module doc for exactly what is/isn't covered here.
paths = [".git/**", ".codex/**"] # read-only even inside writable roots (cx§4 workspace-write)
[capabilities.trust]
enabled = true
default = "ask"
# [projects] trust_level gate + hook hash-trust (cx§4:153, cx§7)
[capabilities.mcp]
enabled = true
serve = true
# full client stack (cx§7); serve = codex mcp-server analog (module 16)
[capabilities.hooks]
enabled = true
# CC-compatible 10-event shape, hash-trusted (cx§7 "Lifecycle hooks")
[capabilities.model_catalog]
enabled = true
# capability bits (apply_patch_tool_type, supports_search_tool) drive
# per-model tool swaps — the C1 resolution machinery (cx§9 "Model catalog")
[capabilities.tui]
enabled = true
# ---- notable OFFs ----
[capabilities.tools_search]
enabled = false
# no glob/grep tools; "prefer rg" via shell is prompt guidance (cx§2)
[capabilities.tools_question]
enabled = false
# request_user_input is experimental-gated at the pin (cx§1)
[capabilities.plan_mode]
enabled = false
# /plan is effort-tier steering, not a CC/OC restriction mode (cx§6; catalog D1 CC+OC)
[capabilities.memory]
enabled = false
# [features].memories = false default (cx§6, cx§7)
[capabilities.checkpoint]
enabled = false
# no shadow-git; ghost_snapshot is a legacy no-op (cx§6)
[capabilities.session_tree]
enabled = false
# rollout is STRICTLY LINEAR (C7); fork = truncate+copy (D5 footnote ¹³)
[capabilities.session_share]
enabled = false
[capabilities.lsp]
enabled = false
[capabilities.formatters]
enabled = false
[capabilities.server]
enabled = false
# app-server parity is out of preset scope — see gaps
[capabilities.reduction]
enabled = false
[capabilities.cache]
enabled = false
[capabilities.model_oauth]
enabled = false
# ChatGPT-subscription login (cx§9) — module 27 deferred
"#;
/// `oc-parity` — design §4.4.
pub const OC_PARITY_TOML: &str = r#"# built-in preset: oc-parity — opencode's default surface, composed.
schema_version = 1
[core]
env_context = true
project_context = true # AGENTS.md + instructions[] concat (oc§6)
nested_instructions = true # S6/S12 home: nested AGENTS.md auto-attached only for touched-file dirs, oc default (catalog:84; opencode.md:34 "nested-AGENTS.md") — closes an oc-parity gap-ledger row
instruction_imports = true # S6/S12 home: `instructions[]` config imports, oc default (catalog:85; opencode.md:367) — closes an oc-parity gap-ledger row
max_tool_output_bytes = 51200 # tool_output.max_bytes default 51200 / 2000 lines (oc§1 Truncate service)
[core.session]
auto_title = true # S6/S12 home: hidden title+summary agents (deny-all utility agents) on small_model, oc default (catalog:150; opencode.md:169-170,235) — closes an oc-parity gap-ledger row; small_model is "" below so this falls back to the main model per D-9 until a cheap model is configured
[core.tools]
enabled = ["read_file", "bash", "edit_file", "write_file"] # oc registry core (oc§1; read subsumes ls)
schema_tier = "full"
[core.tools.read_file]
multimodal = true # images/PDFs as attachments (oc§1 read)
[core.tools.bash]
timeout_secs = 120 # flags.bashDefaultTimeoutMs default 120000 (oc§1 bash)
[core.skills]
enabled = true # skill tool + .opencode/skills + remote registries (oc§7)
[core.compaction]
enabled = true
summarize = true # compaction{auto,prune,…} (oc§6)
# ---- modules ON ----
[capabilities.tools_search]
enabled = true
# glob + grep via ripgrep (oc§1)
[capabilities.todos]
enabled = true
persist = true
# todowrite → SQLite todo table (oc§1)
[capabilities.tools_web]
enabled = true
fetch = true
search = false
# webfetch is default; websearch only under the Zen provider / exa flags (oc§1 "webSearchEnabled")
[capabilities.subagents]
enabled = true
max_depth = 2
background = false
# task tool → child session via parentID, resumable task_id (oc§1); background is env-gated experimental → off
[capabilities.tools_apply_patch]
enabled = true
per_model = true
# THE C1 precedent: swapped in (edit/write out) for gpt-* models (oc§1 apply_patch; catalog §5 conflict 1)
[capabilities.plan_mode]
enabled = false
# S18 fix (flipped from `true`): opencode's plan_enter/plan_exit TOOLS — exactly what this module is defined by (§2 module 8) — are DENY-BY-DEFAULT at the pin (opencode.md:251), and this preset's own translated rule set below denies them. What oc actually runs by default is the LEGACY generation: the plan agent is a permission-ruleset agent (edit denied) — already expressible as an agent-scoped `permissions.rules` restriction, not the tool-based `plan_mode` module. Enabling `plan_mode` here would contradict oc's own deny-default; off is the honest reading.
[capabilities.permissions]
enabled = true
approval = "on_request" # ask-flow with once|always|reject replies (oc§4 "Ask/approve flow")
sandbox = "danger_full_access" # opencode has NO OS sandbox (catalog D4: sandbox is CC+CX only)
[capabilities.permissions.rules]
enabled = true
# opencode's default policy, TRANSLATED per the C5 decision (last-match-wins → deny→ask→allow
# first-match). Source policy (oc§4 "Default policy"): {"*": allow} with carve-outs
# doom_loop: ask, external_directory: ask, question: deny, plan_enter/plan_exit: deny,
# read {*.env: ask, *.env.*: ask, *.env.example: allow}.
#
# S4 fix — this is NOT "the same fixed point" as oc's last-match algebra, and is recorded honestly
# as THREE NAMED DEVIATIONS rather than claimed as exact parity:
# 1. `.env.example` → ASK here, not ALLOW. Under first-match deny→ask→allow, a read of
# `.env.example` matches the ask-rule `read_file(*.env.*)` (glob matches) BEFORE the allow
# list is ever consulted, so it asks where stock opencode allows. The engine's rule grammar
# has no specificity/negation to express "ask unless a more-specific allow" — fixing this
# would require adding that to the grammar (not done here); the deviation is in the SAFE
# direction (stricter) and is named, not hidden.
# 2. `doom_loop` is NOT a rule-language pattern at all — it's a repetition TRIGGER (same tool
# call repeated), not a tool/path match. Routed instead to its actual mechanism: the P4
# doom-loop breaker (a call-repetition counter + PreToolHook default, §5.2 P4) — no rule
# entry for it below.
# 3. `external_directory` is an oc PERMISSION CATEGORY (any tool touching paths outside the
# worktree), not a tool name — routed instead to its actual permission category: `[core]
# additional_dirs` (Config.additional_dirs, config.rs:169) governs which extra roots are
# writable at all; paths outside cwd AND outside `additional_dirs` are simply not reachable,
# which is a stricter (not equivalent) reading of oc's ask-by-default.
deny = ["plan_enter", "plan_exit"] # matches module 8's off-by-default above (S18) and oc's own "plan_enter/plan_exit: deny"
ask = ["read_file(*.env)", "read_file(*.env.*)"] # includes .env.example per deviation 1 above (glob matches before any allow)
allow = ["*"]
[capabilities.permissions.protected_paths]
enabled = false
# oc does .env protection through rules (above), not a path module
[capabilities.trust]
enabled = true
default = "ask"
# DELIBERATE SAFETY DEVIATION: opencode LACKS a project trust gate (catalog §3 closing) yet loads
# .opencode/ plugins/tools/commands from the repo. Our resolver treats plugins→trust as a HARD dep
# (D-10: "config-borne code execution without a trust gate is an injection hole") — so oc-parity
# ships the gate ON. This only NARROWS behavior (§3.3 monotonic-tightening spirit); recorded, not hidden.
[capabilities.mcp]
enabled = true
# local/remote/OAuth servers (oc§7)
[capabilities.plugins]
enabled = true
# .opencode/plugin + npm specs (oc§7); dep on trust satisfied above
[capabilities.lsp]
enabled = true
# 38 auto-spawned servers; diagnostics into edit/write results (oc§7, oc§10)
[capabilities.formatters]
enabled = true
diff_back = true
# ~27 format-on-write formatters; diff_back honors C10 (oc§7; oc§10; catalog §5 conflict 10)
[capabilities.checkpoint]
enabled = true
# shadow-git snapshots + revert/unrevert (oc§4 "Snapshots"/"Revert")
[capabilities.session_share]
enabled = true
# share manual|auto|disabled, default manual (oc§5, oc§6)
[capabilities.server]
enabled = true
# the client/server split: every frontend is an HTTP client (oc§8)
[capabilities.model_catalog]
enabled = true
small_model = ""
# models.dev catalog + small_model config key (oc§6, oc§9)
[capabilities.tui]
enabled = true
# ---- notable OFFs ----
[capabilities.tools_question]
enabled = false
# question tool is DENY-by-default outside build/plan agents (oc§1, oc§4)
[capabilities.tools_background]
enabled = false
# background subagents are env-gated experimental at the pin (oc§1)
[capabilities.session_tree]
enabled = false
# oc sessions are parent/child linear, no in-place tree (D5; C7)
[capabilities.memory]
enabled = false
[capabilities.hooks]
enabled = false
# no config-registered hooks; the plugin API is the interception layer (oc§7)
[capabilities.deferred_tools]
enabled = false
# opencode advertises eagerly (D1: deferred is CC+CX)
[capabilities.cache]
enabled = false
[capabilities.reduction]
enabled = false
# oc "prune" is the LOSSY analog (catalog §1 UNIQUE OC note); ours stays off to match, mechanism on per §1.13
[capabilities.structured_output]
enabled = false
[capabilities.model_oauth]
enabled = false
# provider /login flows (oc§9) — module 27 deferred
"#;
/// `token-saver` — design §4.5.
pub const TOKEN_SAVER_TOML: &str = r#"# built-in preset: token-saver — the reduction spine over the minimal core.
schema_version = 1
extends = "pi-core" # smallest surface = cheapest surface; every knob below overrides it
[core.tools]
schema_tier = "minimal" # TR-8/T5 schema tiering (config.rs:219-225)
# C9 (catalog §5 conflict 9): a GLOBAL minimal tier is a footgun for models trained on exact
# schemas. Per-tool override survives the global — pin any load-bearing tool back:
[core.tools.edit_file]
schema_tier = "full" # exact-string edit is the least forgiving schema; keep it verbatim
[core.compaction]
enabled = true
reserve_tokens = 24576 # trigger earlier than pi's 16384 — spend the summary, save the window
keep_recent_tokens = 10000 # aggressive: half of pi's keep budget (recall traded — see caveats)
summarize = true # SpanSummary side-call (reduce.rs:274-289) → small_model below (D-9)
[capabilities.reduction] # module 23 — ALL genuinely-optional passes on (≡ CLI reduce=true, userconfig.rs:33-38)
enabled = true
# NOTE (S7): no `truncation` key here — A7 ToolOutputTruncated (reduce.rs:95-103) is always-on core
# plumbing (§1.13), never a `[capabilities.reduction]` toggle, in token-saver same as every other preset.
stale_reads = true # A8 FileReadElided (reduce.rs:104-111)
diff_reads = true # TR-3 FileReadDiffed (reduce.rs:202-217)
duplicates = true # TR-2 DuplicateOutput (reduce.rs:228-234)
supersede = true # TR-6 Superseded (reduce/supersede.rs)
tool_input_elision = true # TR-10 ToolInputElided (reduce.rs:148-169)
normalize_output = true # T30 OutputNormalized (reduce/normalize.rs)
image_redaction = true # A9 ImageRedacted (reduce.rs:112-116) — ON here, off everywhere else
span_summaries = true # TR-7 (reduce/summarize.rs; D-9)
handoff = true # reduce/handoff.rs — smallest-faithful-context model handoff
[capabilities.deferred_tools] # module 24 — the FLAGSHIP lever (SPEC.md B6)
enabled = true
core = ["read_file", "bash", "edit_file", "write_file"] # builtins stay eager; everything else behind tool_search
[capabilities.cache] # module 25 — the C2 referee
enabled = true
plan = "imported_prefix" # CachePlan::ImportedPrefix (config.rs:88-96)
warnings = true # cache_warnings (config.rs:227-239): every prefix-churning feature must answer to this
[capabilities.model_catalog] # module 26 — D-9 consumer
enabled = true
small_model = "anthropic/claude-haiku-4-5" # compaction summaries + span summaries route here, not the main model
"#;
/// `supercode-default` — design §4 intro (S10 fix).
pub const SUPERCODE_DEFAULT_TOML: &str = r#"# built-in preset: supercode-default — pi-core MINUS {trust, session_tree,
# session_share, server, plugins}, PLUS the six extra with_builtins() builtins
# ON, notify available, reduction off (design §4 intro paragraph, S10 fix: NOT
# "pi-core plus extras" — pi-core itself turns those five modules ON to match
# pi's kept-list, so this preset is pi-core's core knobs UNCHANGED with a
# capability delta). This is what `supercode` resolves to with NO config file
# at all — "today's defaults, named and warned" rather than implicit
# (design:958-960).
schema_version = 1
extends = "pi-core"
# core knobs identical to pi-core (§4.1's [core]/[core.retry]/[core.tools]/
# [core.skills]/[core.compaction]/[core.steering] blocks) — unchanged, nothing
# to override here; inherited verbatim via `extends`.
# ---- the S10 delta over pi-core: OFF (today's CLI has none of these — "—"
# across the board in §2's Today column) ----
[capabilities.trust]
enabled = false
[capabilities.session_tree]
enabled = false
[capabilities.session_share]
enabled = false
[capabilities.server]
enabled = false
[capabilities.plugins]
enabled = false
# ---- the six extra with_builtins() builtins (tools/mod.rs:179-192), ON ----
# list_dir/glob/search -> tools_search; apply_patch -> tools_apply_patch;
# persistent_shell -> tools_persistent_shell; update_plan -> todos.
[capabilities.tools_search]
enabled = true
[capabilities.tools_apply_patch]
enabled = true
# NOT per_model: with_builtins() registers every tool struct unconditionally
# with no per-model filtering at all (§4.6 "faithful to today's actual
# unfiltered default stack") — this is what makes C1's warning fire here,
# honestly, rather than suppressing it with a `per_model` bit today's CLI
# doesn't actually have.
[capabilities.tools_persistent_shell]
enabled = true
[capabilities.todos]
enabled = true
# notify available (today's CLI already ships full notify support end to end
# — userconfig.rs:61-71 — unlike pi-core, which doesn't mention it at all).
[capabilities.notify]
enabled = true
# reduction off (policies only; A7 truncation + rehydrate stay always-on core
# regardless, §1.13) — already off by inheritance from pi-core; restated for
# clarity per the design intro's explicit "reduction off" callout.
[capabilities.reduction]
enabled = false
# permissions stays off too (approval = never, sandbox = danger_full_access)
# — identical to pi-core's own values (config.rs:36-38, tools/mod.rs:40-42);
# restated verbatim so the C3 mandatory warning fires here by the same
# mechanism as pi-core's, naming today's actual default stack rather than
# leaving it implicit (design:971-974).
[capabilities.permissions]
enabled = false
approval = "never"
sandbox = "danger_full_access"
"#;
/// The six reserved built-in preset names (design §4, opening paragraph).
pub const RESERVED_PRESET_NAMES: &[&str] = &[
"pi-core",
"cc-parity",
"cx-parity",
"oc-parity",
"token-saver",
"supercode-default",
];
/// Look up a built-in preset's compiled-in TOML text by name. Returns `None`
/// for anything not one of the six [`RESERVED_PRESET_NAMES`] — the resolver
/// (`configfile.rs` §3.5) falls back to treating the name as a file path in
/// that case (user/global layer only, §3.3).
pub fn lookup(name: &str) -> Option<&'static str> {
match name {
"pi-core" => Some(PI_CORE_TOML),
"cc-parity" => Some(CC_PARITY_TOML),
"cx-parity" => Some(CX_PARITY_TOML),
"oc-parity" => Some(OC_PARITY_TOML),
"token-saver" => Some(TOKEN_SAVER_TOML),
"supercode-default" => Some(SUPERCODE_DEFAULT_TOML),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::configfile::HarnessConfig;
/// Every reserved preset's compiled-in TOML must be valid TOML that
/// parses into a `HarnessConfig` — the design's own claim ("they were
/// made TOML-valid in the final design commit") verified mechanically
/// rather than trusted, since the doc's literal `[capabilities.X] { .. }`
/// shorthand is NOT valid TOML as written (see the module doc comment).
#[test]
fn every_reserved_preset_parses() {
for name in RESERVED_PRESET_NAMES {
let toml = lookup(name).unwrap_or_else(|| panic!("no TOML for preset `{name}`"));
HarnessConfig::from_toml_str(toml)
.unwrap_or_else(|e| panic!("preset `{name}` failed to parse: {e}"));
}
}
/// `lookup` returns `None` for anything not a reserved name (the
/// resolver's built-in-vs-path branch point, §3.5 step 1).
#[test]
fn lookup_returns_none_for_non_preset_names() {
assert!(lookup("not-a-real-preset").is_none());
assert!(lookup("./some/path.toml").is_none());
assert!(lookup("").is_none());
}
/// `pi-core` has no `extends` (it is a root); the other five all resolve
/// somewhere (four are roots too, `token-saver`/`supercode-default`
/// extend `pi-core`) — sanity-checking the chain shape golden tests will
/// exercise in full.
#[test]
fn token_saver_and_supercode_default_extend_pi_core() {
let ts = HarnessConfig::from_toml_str(TOKEN_SAVER_TOML).unwrap();
assert_eq!(ts.extends.as_deref(), Some("pi-core"));
let sd = HarnessConfig::from_toml_str(SUPERCODE_DEFAULT_TOML).unwrap();
assert_eq!(sd.extends.as_deref(), Some("pi-core"));
let pc = HarnessConfig::from_toml_str(PI_CORE_TOML).unwrap();
assert_eq!(pc.extends, None);
}
}