---
name: eforge-config
description: Initialize or edit eforge/config.yaml team-wide settings, with validation via daemon tools
disable-model-invocation: true
---

> **Note:** In Pi, the native `/eforge:config` command provides a richer interactive experience with a structured TUI panel viewer. This skill serves as a fallback for non-interactive contexts and as model-readable documentation.

# /eforge:config

Create or modify an `eforge/config.yaml` configuration file interactively. Supports two modes - init for new projects and edit for existing configs. Validation uses the eforge daemon.

`eforge/config.yaml` holds **team-wide settings only** (postMergeCommands, agent tuning, hooks, queue config, etc.). Personal dev overrides can be placed in `.eforge/config.yaml` (gitignored, highest priority) - it deep-merges over the project and user tiers. Agent runtime profiles live in named profile files (`.eforge/profiles/<name>.yaml` for project-local scope, `eforge/profiles/<name>.yaml` for project scope, or `~/.config/eforge/profiles/<name>.yaml` for user scope) and are managed by `/eforge:init` and `/eforge:profile-new`.

## Mode Detection

Determine the mode from arguments and file state:

1. If `$ARGUMENTS` contains `--init`, use **init mode**
2. If `$ARGUMENTS` contains `--edit`, use **edit mode**
3. If `eforge/config.yaml` exists in the project root, use **edit mode**
4. Otherwise, use **init mode**

## Init Mode

### Step 1: Check Existence

If `eforge/config.yaml` already exists, ask the user whether they want to switch to edit mode or overwrite. Respect their choice.

### Step 2: Require an active agent runtime profile

This skill does not create agent runtime profiles. Before gathering team-wide settings, confirm a profile is already active by calling `eforge_profile` with `{ action: "show" }`. Inspect the response:

- If `active` is `null` (no profile is active for this project) or `resolved.harness` is missing, **stop**. Tell the user:
  > `/eforge:config` only manages team-wide settings. This project has no active agent runtime profile. Run `/eforge:init` to set up eforge in a fresh project (creates a profile and `config.yaml`), or `/eforge:profile-new` to add a profile to an existing setup. Come back to `/eforge:config` once a profile is active.
  Do not proceed with the interview and do not write `eforge/config.yaml`.
- Otherwise note the resolved harness kind (`resolved.harness` is `claude-sdk` or `pi`) and, for Pi, the profile's provider. Use these when suggesting models later.

### Step 3: Gather Context

Read project context to understand the codebase:

- **AGENTS.md / CLAUDE.md** - Project overview, tech stack, build commands
- **package.json** or equivalent - Dependencies, scripts
- **Project structure** - Scan top-level directories

Share a brief summary of what you found.

### Step 4: Interview

Walk the user through configuration sections, asking about each one. Only include sections where the user wants non-default behavior. **Do not** collect harness, provider, or Pi-specific tuning here - those belong in agent runtime profile files.

Agent settings resolve through three layers of granularity: **global** (applies to every agent), **tier** (applies to a group of related agents — planning, implementation, review, evaluation), and **per-role** (applies to one named agent). The interview walks them in that order; skip layers you don't want to customize.

**Sections to cover:**

1. **Build settings** - `postMergeCommands` (validation commands to run after merging worktrees, e.g. `pnpm install`, `pnpm type-check`, `pnpm test`), `maxValidationRetries`, landing action: use `landing.action` (`pr` opens a PR from the artifact branch to the resolved base branch; direct non-stacked PRs fetch and rebase onto `origin/<baseBranch>` before validation and check freshness again immediately before PR creation; `merge` auto-merges the artifact/worktree branch into the base branch, `leave` exits without merging or opening a PR; default is `merge`; `pr` requires `gh` CLI). `landing.pr.autoMerge` (`ask` (default) | `always` | `never`) — GitHub PR auto-merge policy when `landing.action: pr`: `ask` enables auto-merge only when a per-run `landingAutoMerge: true` override is sent; `always` enables auto-merge on every PR unless `landingAutoMerge: false` is sent per-run; `never` skips auto-merge. This is a post-PR-creation option and is distinct from `landing.action: merge`, which merges the artifact branch directly without opening a PR. `trunkBranch` (trunk branch name; detected from `origin/HEAD` at init time; fallback `main`), `allowLocalMergeToTrunk` (default `false`; set to `true` only for solo/unprotected projects to allow direct merge to trunk without a PR), `trunkSync` (pre-compile trunk freshness gate for queued root builds; `enabled` default `true`, `remote` default `origin`, `strategy: fetchedRemoteRef`, `onDiverged`: `warn` (default, emit diagnostic + fall back to local trunk) | `fail` (fail before compile) | `use-remote` (use fetched SHA with diagnostic); set `enabled: false` for offline/local-only workflows)
2. **Compile planning limits and direct PR base-sync budget** (opt-in - "Would you like to tune decomposition budgets for unusually large compile inputs or the direct PR base-sync conflict-attempt budget? Most users keep defaults.") - The `compile.planningUnit*` values only affect context-managed planning for overflow-risk compile inputs; ordinary direct planning does not use planning units. All `compile.planningUnit*` values are positive integers. `planningUnitParallelism` defaults to `2`; `planningUnitMaxObservedTurns` is optional and unset by default. The direct non-stacked PR base-sync budget is `landing.directPrBaseSync.conflictAttempts`; it defaults to `12`, is clamped to `1`-`100`, and is used as a fixed budget rather than scaling with branch size.
3. **Global agent defaults** (opt-in - "Would you like to customize thinking or effort settings? Most users keep defaults.") - Global `agents.thinking` config (`adaptive`, `enabled` with optional `budgetTokens`, or `disabled`), `agents.effort` level (`low`/`medium`/`high`/`xhigh`/`max`). Resolution order (highest → lowest): plan override → per-role config → per-tier config (including built-in tier defaults) → global fallback. Harness and model selection live in the active profile's tier entries — do not configure them here. Whenever you need to suggest a specific model ID (for per-role `model` overrides), **call `eforge_models` first** with `{ action: "list", harness: "<resolved-harness>" }` (and `provider: "<profile-provider>"` for Pi) and pick from the returned list (newest-first). Never propose a model ID from memory.
4. **Tier tuning** (opt-in - "Would you like to tune agents by group? eforge organises agents into four groups by what they do: **planning**, **implementation**, **review**, and **evaluation**. You can give each group its own effort level without touching individual roles.") - Group membership: **planning** — `planner`, `formatter`, `merge-conflict-resolver`, `gap-closer`; **implementation** — `builder`, `review-fixer`, `validation-fixer`, `doc-author`, `doc-syncer`, `test-writer`, `tester`, `recovery-analyst`, `dependency-detector`, `prd-validator`, `staleness-assessor`; **review** — `reviewer`, `plan-reviewer`; **evaluation** — `evaluator`, `plan-evaluator`. Built-in defaults: `implementation` defaults to `effort=medium`; `planning`, `review`, and `evaluation` default to `effort=high`. Harness and model selection come from the active profile's tier entries. Available per-tier knobs in config.yaml: `effort`, `model`, `thinking`, `maxTurns`, `maxBudgetUsd`, `fallbackModel`, `allowedTools`, `disallowedTools`, and runtime-choice `choices`/`routing`. Set them under `agents.tiers.<tier>`; the existing tier recipe is the implicit `default` choice, named choices inherit from it, and routing rules run after role-to-tier resolution.
5. **Agent behavior** - Global `maxTurns`, `maxContinuations` (default 3 - max continuation attempts after maxTurns hit), `permissionMode` (`bypass` or `default`), `settingSources`, `bare` (default false)
6. **Per-role overrides** (opt-in - "Would you like to tune specific agent roles differently? Most users skip this.") - Override settings per agent role. Available roles: planning (`planner`, `formatter`, `merge-conflict-resolver`, `gap-closer`), implementation (`builder`, `review-fixer`, `validation-fixer`, `doc-author`, `doc-syncer`, `test-writer`, `tester`, `recovery-analyst`, `dependency-detector`, `prd-validator`, `staleness-assessor`), review (`reviewer`, `plan-reviewer`), evaluation (`evaluator`, `plan-evaluator`). Per-role options: `model` (explicit model ref that overrides the profile tier's model for this role), `thinking`, `effort`, `maxBudgetUsd`, `fallbackModel`, `allowedTools`, `disallowedTools`, `maxTurns`, `promptAppend` (text appended to the agent's prompt - useful for project-specific rules like "flag raw SQL queries" for the reviewer). You can also set `roles.<role>.tier` to reassign a role to a different tier (rare, but supported when one role inside a group needs to behave like another group). Same model-list rule as above: call `eforge_models` with `harness:` before proposing any model ID.
7. **Prompt customization** (opt-in - "Would you like to customize agent prompts?") - `agents.promptDir` points to a directory of `.md` files that shadow bundled prompts by name (e.g. `eforge/prompts/reviewer.md` replaces the built-in reviewer prompt). Per-role `promptAppend` is safer - it appends instructions without replacing the full prompt.
8. **Hooks** - Event-driven commands that run on specific eforge events (e.g. `session:start`, `phase:end`). Each hook has `event` (pattern), `command`, and optional `timeout`.
9. **Langfuse tracing** - Whether to enable Langfuse integration (keys are typically set via env vars)
10. **Plugin settings** - Enable/disable plugin loading, include/exclude lists
11. **PRD queue** - Queue directory (`dir`), `autoBuild` (default true - desired daemon auto-build state; scheduler pause is a separate runtime launch gate), `watchPollIntervalMs` (default 5000ms), and top-level `maxConcurrentBuilds` (default 2 - max concurrent PRD builds from the queue)
12. **Recovery auto-resume** (opt-in - "Would you like the daemon to auto-queue high-confidence compiled-artifact recovery attempts?") - `recovery.autoResume.enabled` defaults to `false`, so failed builds keep manual recovery behavior and policy consumers stop before mutation. When enabled, only high-confidence `continue-repair` recommendations that pass compiled-resume artifact eligibility may auto-queue, bounded by `recovery.autoResume.maxAttempts` (default `1`; `0` is audit-only/non-mutating).
13. **Daemon** (opt-in - "Would you like to customize daemon behavior?") - `idleShutdownMs` (default 7200000 = 2 hours, set to 0 to run forever)
14. **Stacking** (opt-in - "Does this project use stacked PRs with git-spice?") - `stacking.enabled` (default `false`; set to `true` to enable git-spice-backed stacking where artifact branch PRs normally target the parent artifact branch rather than the feature/trunk branch; during landing, eforge can repair a missing integrated parent by choosing trunk as the effective base for an initially untracked child or by retargeting a child that is already tracked, then gates PR submission on provider sync/restack and a remote-base freshness proof), `stacking.gitSpice.command` (optional path to the `git-spice` binary when it is not on `$PATH`, e.g. `/usr/local/bin/git-spice`; set to `gs` only if you use the optional short alias). The `stacking.provider` is always `git-spice` and need not be set.

For each section, explain what it controls and suggest values based on the project context gathered in Step 3. Skip sections the user isn't interested in.

### Step 5: Present Draft

Show the user the complete `eforge/config.yaml` content before writing. Confirm it contains no harness-specific keys at the top level (harness configuration belongs only in agent runtime profile files under `eforge/profiles/`). Ask for any changes.

### Step 6: Write

Save to `eforge/config.yaml` in the project root.

### Step 7: Validate

Call the `eforge_config` tool with `{ action: "validate" }`.

If validation returns errors, show them to the user and offer to fix them.

## Edit Mode

### Step 1: Read Current Config

Read the existing `eforge/config.yaml` file and summarize its current settings for the user.

### Step 2: Identify Changes

Ask the user what they want to change. If `$ARGUMENTS` contains additional context beyond `--edit`, use that to understand the desired changes. If the requested change is really about the harness (switching harness kind, provider, or the profile's own model/tuning), stop and redirect them to `/eforge:profile` (switch active profile) or `/eforge:profile-new` (create a new profile) - that configuration does not belong in `eforge/config.yaml`.

### Step 3: Apply Changes

Modify the config based on the user's requests. Present the updated content before writing. If the change involves a model ID anywhere (per-role `model`), first resolve the active profile by calling `eforge_profile` with `{ action: "show" }`, then call `eforge_models` with `{ action: "list", harness: "<resolved-harness>" }` (and `provider` for Pi) to fetch the live list. Pick from that list; do not suggest model IDs from memory.

### Step 4: Write

Save the updated `eforge/config.yaml`.

### Step 5: Validate

Call the `eforge_config` tool with `{ action: "validate" }`.

If validation returns errors, show them to the user and offer to fix them.

## Show Resolved Config

At any point, you can show the user the fully resolved configuration (all layers merged) by calling:

`eforge_config` with `{ action: "show" }`

This returns the merged result of defaults + global config + project config + active profile. The `/eforge:config` viewer also lists registered `tools.toolbelts` entries and available presets in a dedicated Toolbelts section. Use `/eforge:profile-new` to configure toolbelt presets interactively.

## Configuration Reference

Available top-level sections in `eforge/config.yaml`. Note: harness-specific configuration belongs in agent runtime profile files under `eforge/profiles/` or `~/.config/eforge/profiles/`, not in `config.yaml`.

```yaml
# Queue concurrency
maxConcurrentBuilds: 2                 # Max concurrent PRD builds (default: 2)

# Build settings
build:
  worktreeDir: "../my-worktrees"       # Custom worktree directory
  postMergeCommands:                   # Commands to run after merge
    - pnpm install
    - pnpm type-check
    - pnpm test
  maxValidationRetries: 2              # Retry count for validation fixes
  cleanupPlanFiles: true               # Remove plan files and temporary plan-ID markers after successful build
  # trunkBranch: main                  # Trunk branch name (default: detected from origin/HEAD, fallback: main)
  # allowLocalMergeToTrunk: false      # Allow landing.action: merge to land directly on trunk without a PR
                                       #   Default false; set to true only for solo/unprotected projects
  # trunkSync:                         # Pre-compile trunk freshness gate (queued root builds only)
  #   enabled: true                    # Default true; set false for offline or local-only workflows
  #   remote: origin                   # Remote to fetch trunk from (default: origin)
  #   strategy: fetchedRemoteRef       # Only 'fetchedRemoteRef' is supported in v1
  #   onDiverged: warn                 # warn (default) | fail | use-remote


# Compile settings: planning-unit limits for overflow-risk context-managed planning.
compile:
  planningUnitParallelism: 2            # Max planning units running at once
  planningUnitMaxDepth: 3               # Max recursive planning-unit split depth
  planningUnitMaxPromptSourceBytes: 40000
  planningUnitMaxPromptBytes: 80000
  planningUnitMaxObservedInputTokens: 120000
  # planningUnitMaxObservedTurns: 20    # Optional; unset by default
  planningUnitMaxCompactHandoffBytes: 12000
  planningUnitMaxLocalExplorationToolUses: 24
  planningUnitMaxCriteriaPerUnit: 20
  planningUnitMaxSubsystemsPerUnit: 2
  planningUnitMaxSplitAttemptsPerUnit: 2

# Landing action
landing:
  action: pr                           # pr | merge | leave (default: merge)
                                       # pr: open a PR from the artifact branch targeting the resolved base branch (requires gh CLI)
                                       #     Direct non-stacked PRs rebase onto origin/<baseBranch> before validation
                                       #     and check freshness again immediately before PR creation.
                                       # merge: auto-merge the artifact branch into the base branch
                                       # leave: commit to artifact branch and exit without merging or opening a PR
  directPrBaseSync:
    conflictAttempts: 12               # Direct non-stacked PR base-sync conflict attempts (clamped 1-100)
  pr:
    # autoMerge: ask                   # ask (default) | always | never
                                       # Only applies when landing.action: pr
                                       # ask: enable auto-merge only when landingAutoMerge is explicitly true per-run
                                       # always: enable auto-merge on every PR unless landingAutoMerge is explicitly false per-run
                                       # never: never enable auto-merge (emits skipped event)

# Stacking (git-spice backed stacked PRs)
# stacking:
#   enabled: false                     # Default false. Set to true to enable git-spice stacking.
#                                      # When enabled, artifact branch PRs normally target the parent artifact branch.
#                                      # Landing can repair a missing integrated parent by choosing trunk as the effective base
#                                      # for initially untracked children or retargeting a child that is already tracked,
#                                      # then gates PR submission on provider sync/restack and a remote-base freshness proof.
#                                      # Run 'git-spice repo init' in the repo before enabling.
#   gitSpice:
#     command: git-spice               # Default. Set to 'gs' only if you use the optional short alias.

# Agent settings
agents:
  maxTurns: 50                         # Global fallback max agent turns
  maxContinuations: 3                  # Max continuation attempts after maxTurns hit
  permissionMode: bypass               # bypass or default
  settingSources:                      # Which settings files agents load
    - project
  bare: false                          # Bare mode
  # model:                             # Global model override for all roles
  #   id: <model-id>                   # overrides the profile tier's model; call eforge_models first
  # thinking:                          # Thinking config
  #   type: adaptive                   # 'adaptive', 'enabled' (+ budgetTokens), or 'disabled'
  # effort: xhigh                      # 'low', 'medium', 'high', 'xhigh', 'max'
  # thinkingLevel: xhigh              # Pi only: 'off', 'low', 'medium', 'high', 'xhigh'
  # --- Prompt customization ---
  # promptDir: eforge/prompts           # Directory of .md files that shadow bundled prompts
  # --- Per-tier overrides ---
  # tiers:
  #   planning:
  #     effort: high           # default; lower this to save tokens on planning
  #   implementation:
  #     effort: medium         # default
  #   review:
  #     effort: high
  #   evaluation:
  #     effort: high
  # --- Per-role overrides ---
  # roles:
  #   builder:
  #     model:
  #       id: <model-id>       # overrides the profile tier's model for this role
  #     maxTurns: 50
  #     maxBudgetUsd: 10.0
  #   formatter:
  #     effort: low
  #   reviewer:
  #     promptAppend: |                # Append project-specific rules to the prompt
  #       ## Project Rules
  #       - Flag raw SQL queries
  #   tester:
  #     tier: review           # Reassign role to a different tier

# Plan output
plan:
  outputDir: eforge/plans              # Where plan artifacts are written

# Langfuse tracing (keys usually via env vars)
langfuse:
  enabled: false
  host: https://cloud.langfuse.com

# Plugin loading
plugins:
  enabled: true
  include: []                          # Only load these plugins
  exclude: []                          # Skip these plugins
  paths: []                            # Additional plugin paths

# PRD queue
prdQueue:
  dir: eforge/queue
  autoBuild: true                      # Desired auto-build state; scheduler pause can gate launches
  watchPollIntervalMs: 5000            # Poll interval for watch mode (ms)

# Recovery auto-resume (disabled by default)
recovery:
  autoResume:
    enabled: false                     # Opt in to bounded daemon-owned continue-repair automation
    maxAttempts: 1                     # Automatic attempts per failed PRD; 0 = audit-only/no mutation

# Daemon
daemon:
  idleShutdownMs: 7200000              # Idle timeout (2h). 0 = run forever.

# Project MCP toolbelts (optional; define named bundles for profile toolbelt: fields)
tools:
  toolbelts:
    browser-ui:                        # Named toolbelt bundle
      description: Browser automation for UI implementation and review.
      mcpServers:
        - playwright                   # Must be present in .mcp.json
    # docs-research, observability, database-readonly, etc. follow the same pattern

# Event hooks
hooks:
  - event: "session:start"
    command: "echo 'Starting eforge session'"
    timeout: 5000
```

## Error Handling

| Condition | Action |
|-----------|--------|
| No active agent runtime profile | Stop and direct the user to `/eforge:init` or `/eforge:profile-new` |
| `eforge_config` validate returns errors | Show errors, offer to fix |
| Validation error mentions a backend key is not valid here | Remove the key; harness-specific config lives in profile files, not `config.yaml` |
| Validation error mentions an unrecognized top-level key | Remove the key (typo or stale feature reference) - the error includes the recognized-key list |
| YAML syntax error in existing file | Report the error, offer to recreate |

<!-- parity-skip-start -->
| Daemon connection failure | The daemon is not running. Tell the user to start it with `eforge_daemon { action: "start" }`, `/eforge:restart`, or `eforge daemon start`. |
<!-- parity-skip-end -->

## Related Skills

| Skill | Command | When to suggest |
|-------|---------|----------------|
| Init | `eforge_init` | No eforge config found in the project, or no active agent runtime profile |
| Profile | `eforge_profile` | User wants to list, inspect, or switch agent runtime profiles |
| Profile (new) | `/eforge:profile-new` | User wants to create a new agent runtime profile (project or user scope) |
| Build | `eforge_build` | User wants to enqueue work for the daemon to build |
| Extension contributions | `eforge_extension_contribution` | User wants to discover or invoke workflows provided by loaded extensions |
| Status | `eforge_status` | User wants to check build progress or queue state |
| Restart | `eforge_restart` | User wants to restart the eforge daemon |
| Update | `eforge_update` | User wants to check for or install eforge updates |
