Configuration

eforge is configured via eforge/config.yaml (searched upward from cwd). All fields are optional - defaults work for most projects. This page covers the most commonly tuned options. For the full schema see the Configuration Reference.

The Three Config Tiers

Config merges from three levels (lowest to highest priority):

Tier Path Committed? Purpose
User ~/.config/eforge/config.yaml No Cross-project, personal
Project eforge/config.yaml Yes Team-canonical
Project-local .eforge/config.yaml No (gitignored) Personal override

The project-local tier deep-merges over the others. Use it for personal tuning - different model choices, extra verbosity, or test commands you do not want to commit.

Initialization

The fastest way to set up config is /eforge:init in Claude Code or Pi. It scaffolds eforge/config.yaml with sensible defaults and walks you through harness and model selection.

To edit config interactively after initialization: /eforge:config --edit.

Workflow Presets

Use /eforge:workflow after initialization when you want a guided choice for landing action, pull-request auto-merge, and stacking. Pi also exposes /eforge:workflow:init and /eforge:workflow:reconfigure; Claude Code uses /eforge:workflow and /eforge:workflow --reconfigure.

Preset Config keys written
solo-merge landing.action: merge, build.allowLocalMergeToTrunk: true, stacking.enabled: false
solo-pr landing.action: pr, landing.pr.autoMerge: always, stacking.enabled: false
team-pr landing.action: pr, landing.pr.autoMerge: ask, stacking.enabled: false
stacked-pr landing.action: pr, stacking.enabled: true
stacked-pr-autosync landing.action: pr, stacking.enabled: true, stacking.sync.afterBuild: true

For stacking presets, the wizard also writes stacking.gitSpice.command when you provide a custom git-spice path. Use /eforge:config --edit for fine-grained changes after applying a preset.

Agent Tiers

Tiers are the primary configuration axis. Each tier is a self-contained recipe: harness + model + effort.

agents:
  tiers:
    planning:
      harness: pi
      model: anthropic/claude-opus-4-6
      effort: high
      pi:
        provider: openrouter
    implementation:
      harness: pi
      model: anthropic/claude-sonnet-4-6
      effort: medium
      pi:
        provider: openrouter
    review:
      harness: pi
      model: anthropic/claude-opus-4-6
      effort: high
      pi:
        provider: openrouter
    evaluation:
      harness: pi
      model: anthropic/claude-opus-4-6
      effort: high
      pi:
        provider: openrouter

Pi is the recommended harness for new profiles. The engine still has current compatibility fallback defaults for omitted tiers; those defaults are claude-sdk, so Pi profiles should list all four tiers explicitly.

Effort levels: low, medium, high, xhigh, max. Higher effort means more agent turns and more thorough output, at higher cost.

Thinking: Add thinking: true to a tier to enable extended thinking. It is coerced to adaptive mode for models that only support adaptive thinking.

Using the Pi Harness

Pi is the recommended provider-flexible execution harness for new eforge setup. Set harness: pi and add a pi.provider block:

agents:
  tiers:
    planning:
      harness: pi
      model: anthropic/claude-opus-4-6
      effort: high
      pi:
        provider: openrouter
    implementation:
      harness: pi
      model: anthropic/claude-sonnet-4-6
      effort: medium
      pi:
        provider: openrouter

Pi supports OpenAI, Google, Mistral, Groq, xAI, Bedrock, Azure, OpenRouter, and local models. Authentication resolves from provider-specific environment variables or ~/.pi/agent/auth.json. For OAuth providers (OpenAI Codex, GitHub Copilot), run pi auth login <provider> first.

By default, eforge runs Pi harness sessions with pi.resources: isolated so ambient Pi resources (project/user/global extensions, skills, prompts, and themes) are not loaded into headless build agents. Set pi.resources: ambient on a tier only when you intentionally want those ambient Pi resources available during eforge agent runs. Pi-specific tier options also include pi.thinkingLevel, pi.extensions, pi.compaction, and pi.retry; keep provider selection in pi.provider.

Optional Claude SDK Harness

claude-sdk remains supported for Anthropic Claude Agent SDK users:

agents:
  tiers:
    implementation:
      harness: claude-sdk
      model: claude-sonnet-4-6
      effort: medium
      claudeSdk:
        disableSubagents: true

Choose this path only when you intentionally want the Anthropic-specific Claude Agent SDK and have appropriate credentials for that provider. Claude SDK tiers disable Claude Code subagents by default by denying the Task tool; set claudeSdk.disableSubagents: false only when you intentionally want subagents available.

Agent Runtime Profiles

A profile bundles tier recipes into a reusable named file. This lets you switch between configurations - such as "use Claude for review, local model for implementation" - without editing eforge/config.yaml.

Profiles live at three scopes (highest-priority-first):

Scope Directory Committed?
Project-local .eforge/profiles/ No (gitignored)
Project eforge/profiles/ Yes
User ~/.config/eforge/profiles/ No

Set the active profile from Claude Code or Pi with:

/eforge:profile <name>

The standalone CLI can override the active profile for one build with eforge build --profile <name> ...; profile creation and switching are handled by the host skills. For a complete walkthrough covering profile creation, scope resolution, toolbelts inside profiles, and profile-router precedence, see Profiles.

Native Extensions

Configuration is split between core build/daemon/profile settings and optional producer surfaces. The build-engine kernel consumes normalized build source; native extensions, the first-party eforge-playbooks extension, and session-plan compatibility tools can prepare, route, or govern that source before enqueue without becoming kernel capabilities.

Native eforge extensions are TypeScript/JavaScript modules discovered from three scopes:

Scope Directory Trust default
User ~/.config/eforge/extensions/ trusted
Project/team eforge/extensions/ skipped unless a matching local trust record exists
Project-local .eforge/extensions/ trusted

Precedence is project-local > project-team > user. Use project-local extensions for experiments, then promote to eforge/extensions/ when the team should share them. Project/team extensions require a per-extension local trust record in .eforge/extension-trust.json created by eforge extension trust <name> after inspecting the code. Any code change invalidates the stored hash and blocks the extension until re-trusted. The content hash covers supported source files plus every regular file under workstation-assets/, so declared workstation bundle assets are covered; broad top-level dist/ output, package sidecars, and files outside the extension unit remain outside the trust hash.

extensions:
  enabled: true                  # default
  eventHookTimeoutMs: 5000       # native onEvent and extension action timeout in ms
  agentContextHookTimeoutMs: 5000 # optional onAgentRun timeout; defaults to eventHookTimeoutMs
  profileRouterTimeoutMs: 5000   # optional registerProfileRouter timeout; defaults to eventHookTimeoutMs
  policyGateTimeoutMs: 5000      # optional policy gate timeout; defaults to eventHookTimeoutMs
  validationProviderTimeoutMs: 5000 # optional validation-provider timeout; defaults to eventHookTimeoutMs
  policyGateFailurePolicy: fail-closed # fail-closed blocks on failures; fail-open allows after diagnostics
  include:
    - build-notifier             # optional allowlist by name
  exclude:
    - experimental-policy        # optional denylist by name
  paths:
    - ./tools/eforge-audit.ts    # explicit file/directory paths

Extension action handlers use extensions.eventHookTimeoutMs; agentContextHookTimeoutMs, profileRouterTimeoutMs, policyGateTimeoutMs, and validationProviderTimeoutMs remain scoped to their existing registration families.

Supported extension entrypoints are .ts, .mts, .js, and .mjs files or directories with index.* / supported package.json entrypoints. TypeScript loads through jiti; JavaScript uses dynamic import. The loader executes the default-export factory in the eforge daemon/worker Node process without a sandbox, records registrations, and surfaces status, diagnostics, shadows, trust, source, strategy, registration counts, and event replay results through eforge extension list/show/validate/test and extension API routes.

Current runtime support includes discovery, trust gating, loading, diagnostics, provenance output, registration capture for runtime-wired families, native onEvent dispatch and replay testing, onAgentRun prompt-context augmentation, per-run extension tool injection, per-run tool availability tuning, pre-build registerProfileRouter dispatch, runtime policy gates for beforeQueueDispatch, beforePlanMerge, and beforeFinalMerge, registerInputSource enqueue preprocessing, registerPrdEnricher content enrichment, reviewer perspective execution, validation-provider execution, engine-side extension action/contribution/workstation registry support, Console System rendering for declarative contributions, sandboxed Console workstation rendering from srcDoc or daemon-owned frameBundle frame/asset URLs, host discovery/detail/invocation for actions, integration commands, and action-backed deep links, daemon-owned ctx.agentTasks dispatch for supported single-shot read-only planner tasks, and management commands (eforge extension list/show/validate/test/new/reload/trust/untrust/install/update/remove/promote/demote). Package-managed extensions installed via eforge extension install carry nested package.* and install.* provenance fields such as install.sourceKind, install.sourceSpec, and install.installedAt; install sidecar files are excluded from the trust hash. registerTool records loader-time provenance; onAgentRun({ tools: [...] }) is the per-run injection path. The first-party eforge-playbooks extension exposes playbook actions through the native extension contribution model, backed by public @eforge-build/input helpers; the session-planning helpers remain separate from that playbook extension boundary. These are not user-authored native workflow registration points. beforeEnqueue, beforeValidation, approval workflow/state/UI, modify decisions, raw extension-owned HTTP routes, arbitrary frontend plugin bundles outside registered workstation iframes, direct React loading into the parent Console, private Console imports, extension-owned AI planning/chat APIs outside ctx.agentTasks, arbitrary raw prompt templates, multi-turn chat, and user-authored workflow registration for custom session-plan or playbook extraction are not supported by native extensions in the current release. See Extensions and Extensions API Reference.

Guided Toolbelt Presets

Toolbelts let a tier opt into a named bundle of project MCP servers from .mcp.json. When creating a profile, Pi's native /eforge:profile:new wizard (and Claude Code's /eforge:profile-new fallback) includes an optional toolbelt step after tier configuration.

What the wizard asks:

  • Skip / default — omit toolbelt from all tiers; all project MCP servers from .mcp.json pass through (original behavior).
  • No project MCP access — set all four tiers to toolbelt: none; no project MCP servers reach agents in any tier.
  • Choose a preset — configure a named toolbelt bundle with least-privilege tier assignments.

Least-privilege rule: Presets explicitly assign toolbelt: none to tiers that do not need project MCP servers. An omitted toolbelt keeps the all-project-MCP default.

Preset Typical MCP servers Tiers receiving access Missing-server behavior
browser-ui playwright implementation, review Show .mcp.json snippet; ask before adding
docs-research fetch, context7 planning, implementation Show setup guidance; do not create tier references
issue-triage github planning Show setup guidance; do not create tier references
repo-review github planning, review Show setup guidance; do not create tier references
observability datadog, sentry planning, evaluation Show setup guidance; do not create tier references
database-readonly postgres, sqlite planning Show setup guidance; do not create tier references
api-testing fetch implementation, review Show setup guidance; do not create tier references
design-ui figma planning, implementation, review Show setup guidance; do not create tier references

Toolbelts filter only project MCP servers from .mcp.json. They do not affect Pi extensions, Claude Code plugins, engine-internal tools, or harness built-ins.

browser-ui — Playwright setup

The browser-ui preset is the only one that the profile wizard can auto-configure after explicit confirmation. For UI-heavy or browser-validation work, pair your profile with browser-ui backed by the Playwright MCP server.

Step 1 - Register the toolbelt in eforge/config.yaml:

tools:
  toolbelts:
    browser-ui:
      description: Browser automation for UI implementation and review.
      mcpServers:
        - playwright

Step 2 - Create eforge/profiles/ui.yaml:

# eforge/profiles/ui.yaml
description: UI-heavy feature work with browser validation.
whenToUse:
  - Frontend features
  - Layout bugs
  - Screenshot-driven UI fixes
tags:
  - ui
  - frontend
  - browser
 
agents:
  tiers:
    planning:
      harness: pi
      model: anthropic/claude-opus-4-6
      effort: high
      pi:
        provider: openrouter
      toolbelt: none
 
    implementation:
      harness: pi
      model: anthropic/claude-sonnet-4-6
      effort: medium
      pi:
        provider: openrouter
      toolbelt: browser-ui
 
    review:
      harness: pi
      model: anthropic/claude-opus-4-6
      effort: high
      pi:
        provider: openrouter
      toolbelt: browser-ui
 
    evaluation:
      harness: pi
      model: anthropic/claude-opus-4-6
      effort: high
      pi:
        provider: openrouter
      toolbelt: none

Step 3 - Add the Playwright MCP server to .mcp.json:

{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": ["-y", "@playwright/mcp@latest"]
    }
  }
}

For other presets: Add the required MCP servers to .mcp.json manually, declare tools.toolbelts.<preset> in eforge/config.yaml, then use /eforge:profile-new to create a profile referencing the toolbelt.

MVP constraints:

  1. Toolbelts filter only project MCP servers from .mcp.json - they do not affect Pi extensions, Claude Code plugins, engine-internal tools, or harness built-ins.
  2. Each tier picks at most one toolbelt via the singular toolbelt field.
  3. toolbelt: none passes no project MCP servers to agents in that tier.
  4. An omitted toolbelt keeps the default: all servers from .mcp.json are passed through.
  5. Pi extensions and Claude Code plugins are out of scope for this MVP - toolbelts are MCP-only and declarative.
  6. Toolbelts are declarative MCP bundles; extensions are imperative lifecycle behavior. Extensions may inspect toolbelt and profile metadata when making routing decisions, but extensions should not redefine toolbelts or act as a hidden config layer.

For the complete field schema and validation behavior, see the Toolbelts section in the Configuration Reference. For the extension/toolbelt boundary, see the Extensions API Reference.

Playbook Profiles

Playbooks are optional workflow artifacts that resolve outside the build-engine kernel through eforge-playbooks:run-playbook before autonomous runs normalize to build source or planning runs route to eforge-plan handoff metadata. The extension owns playbook management/run behavior and uses pure @eforge-build/input helpers for parsing, storage, validation, compilation, and seed generation. Playbooks support an optional profile frontmatter field that names an agent runtime profile to use when the playbook runs:

---
name: docs-sync
description: Sync project documentation
scope: project-team
mode: autonomous
profile: docs-heavy    # Optional — omit to allow router/active-profile/default resolution
---
 
## Goal
Keep all documentation in sync with the latest code changes.

Precedence: an optional profile field on the eforge-playbooks:run-playbook action input overrides the playbook frontmatter for that run. When no action input override is supplied, the playbook profile field overrides the project's active-profile marker and any registered profile router. For session-plan builds, an explicit --profile flag or enqueue request field overrides the session plan's inherited agent_profile.

Validation timing: the named profile is validated at execution time, not when the playbook is saved. Inherited agent_profile values on session plans are validated when the session plan is enqueued.

Planning playbooks: when a planning-mode playbook has a profile field, eforge-playbooks:run-playbook includes it in the eforge-plan planning handoff seed. The eforge-plan flow applies it as the session plan's agent_profile frontmatter when a plan is created; when that session plan is enqueued, agent_profile is used as the effective profile unless an explicit override is supplied.

Blank profile fallback: omitting profile allows a registered profile router to select a profile first; if no router selects one, eforge uses the project's active-profile marker or engine defaults.

Queue and Auto-Build

The daemon watches prdQueue.dir for normalized PRDs. Enqueue stores a validated hidden canonical acceptance-criteria inventory in each queued PRD; missing, duplicated, or malformed inventories fail queued builds before orchestration and require re-enqueue. Queue mutations under prdQueue.dir are runtime filesystem operations in .eforge/queue/ by default, are gitignored, and produce no git commits. Leave prdQueue.autoBuild enabled for normal usage so queued PRDs start automatically; disable it when you want to stage multiple queue items before running them. Scheduler pause is a separate runtime gate that leaves desired auto-build enabled but prevents new launches until resume. prdQueue.watchPollIntervalMs tunes how often the auto-build watcher polls for queue changes.

maxConcurrentBuilds: 2   # default: concurrent PRD builds across the queue
prdQueue:
  dir: .eforge/queue
  autoBuild: true
  watchPollIntervalMs: 5000

PRD provenance: when the daemon dispatches a PRD from .eforge/queue/, it writes a canonical copy to eforge/prds/{prdId}.md. Queue state is ephemeral and gitignored; eforge/prds/ files are committed provenance artifacts that record what was built. The hidden acceptance-criteria inventory is consumed for validation IDs and stripped from the committed prose artifact.

Artifact cleanup and preserved history: when build.cleanupPlanFiles: true (default), eforge removes committed build artifacts — the PRD copy in eforge/prds/, compiled plan files in eforge/plans/{planSet}/, and orchestration.yaml — from HEAD during the pr or merge landing flows after a successful build. Cleanup also strips temporary plan-ID eforge region marker comment lines from tracked JavaScript/TypeScript-family source files while preserving durable semantic markers and marked code. landing.action: leave does not run cleanup and leaves the artifact branch intact for inspection. These files are not permanently lost. When the artifact branch is landed with a merge commit (eforge's local merge action, or a GitHub PR merged via "Create a merge commit"), the commits that originally added the artifacts remain reachable in Git history. PR bodies include an Eforge provenance section with commit-pinned references (git show <sha>:<path>) that can be used to recover any artifact. The durable guarantee is Git history, not the final tree — squash or rebase merge strategies applied after a PR is opened can collapse intermediate commits and make artifact references unreachable. When landing.action: pr is used, provenance durability depends on the repository's chosen merge strategy.

Queue item frontmatter can also carry scheduling hints:

---
title: Add billing export
priority: 10          # lower numbers run earlier within the same dependency wave
depends_on: [api-v2]  # wait for pending/running/waiting queue item ids
---

depends_on is validated at enqueue time. Dependencies may be active queue items (pending/running/waiting) or completed items with usable artifacts. Items blocked on active upstream dependencies live under the queue's waiting/ subdirectory until all upstream items complete; items whose dependencies are already completed with usable artifacts are eligible immediately and remain in the queue root. If an upstream item fails or is cancelled, its waiting dependents move to skipped/.

Queue controls operate on this runtime state. eforge queue priority <prdId> <priority> mutates pending or waiting PRD frontmatter; lower numeric priority values run earlier within each dependency wave, failed and skipped items reject priority mutation with a conflict until recovery/requeue makes them runnable, and running items reject priority changes because active cancellation requires live queue-lock and daemon run/session ownership evidence. Queue hold state is runtime-only PRD frontmatter (held, hold_reason, held_at) on pending or waiting items; held items keep their location and ordering metadata but scheduler ticks skip them until they are unheld. eforge queue remove <prdId> deletes non-running pending, waiting, failed, or skipped queue files; failed removal deletes matching .recovery.md and .recovery.json sidecars. The daemon API can also remove one depends_on id from a pending or waiting PRD; if the override clears the final dependency on a waiting PRD, the file moves back to the queue root. Legacy removal fails closed when live pending/waiting dependents exist and lists dependent ids. Cascade remove and cancel use preview/apply controls that recheck an expected affected token and require explicit dependent confirmation before mutating dependents. The daemon notifies the scheduler after successful priority, removal, dependency override, hold, unhold, or cascade mutations; when the scheduler is not explicitly paused, it re-reads queue files before dispatch.

Explicit deterministic handoff: instead of writing depends_on in frontmatter, pass --after <queue-id> to the CLI or afterQueueId to the eforge_build MCP/Pi tool to create an explicit dependency on an active or completed queue entry. Active upstream items (pending/running/waiting) are held in waiting/ and unblocked when the upstream completes. Completed upstream items with a usable artifact are enqueued immediately as eligible dependents. Explicit afterQueueId takes precedence over automatic dependency detection, which remains best effort and is only used when no explicit dependency is supplied. Failed, skipped, and unknown IDs are rejected at enqueue time.

Post-Merge Commands

Commands to run after all plans merge - compile, test, lint, or any validation step:

build:
  postMergeCommands:
    - "pnpm type-check"
    - "pnpm test"
  postMergeCommandTimeoutMs: 300000
  maxValidationRetries: 2

build.postMergeCommands run in order after the merge. Queued PRD postMerge metadata, when present, is appended after the configured commands for that build. build.postMergeCommandTimeoutMs is the wall-clock timeout for each command in milliseconds (default 300000, five minutes). On failure, a validation-fixer agent attempts repairs up to build.maxValidationRetries times (default 2). When retries are exhausted, the build is marked failed. See Troubleshooting - Validation-fixer retries exhausted for recovery steps.

Within a single build, plans run in parallel automatically as their dependencies are satisfied - no configuration needed there.

Landing Action

landing.action controls what happens when a build completes successfully.

landing.action Behavior
merge Merges the artifact branch into the resolved base branch automatically. This is the engine default.
pr Opens a GitHub pull request from the artifact branch targeting the resolved base branch. For direct non-stacked builds, eforge fetches origin/<baseBranch>, rebases the artifact branch before validation, and checks freshness again immediately before PR creation. For stacked builds the base is normally the parent artifact branch; landing can use trunk as the effective base when stale-parent repair proves the parent artifact is already integrated. Requires the gh CLI.
leave Leaves the artifact branch in place without merging or creating a PR. Useful when you want to inspect the output or handle the branch manually.
landing:
  action: pr    # pr | merge (default) | leave

pr prerequisite: ensure gh is installed (gh --version) and authenticated (gh auth status). Builds configured with landing.action: pr will fail at the landing step if gh is unavailable.

Migrating from build.onSuccess: if you have the old build.onSuccess key in your config, replace it with landing.action. The values map as follows: issue-prpr, merge-to-base-branchmerge, leave-branchleave. New builds reject both the old build.onSuccess key and the legacy full-string values (issue-pr, merge-to-base-branch, leave-branch) with migration guidance - only pr, merge, and leave are valid for landing.action.

PR auto-merge policy

landing.pr.autoMerge controls whether GitHub PR auto-merge is enabled after a PR is opened. Only applies when landing.action: pr. Default: ask.

Value Behavior
ask (default) Enable auto-merge only when the per-run landingAutoMerge flag is explicitly true.
always Enable auto-merge on every PR unless the per-run landingAutoMerge flag is explicitly false.
never Never enable auto-merge; skips auto-merge and emits a skipped event.

Individual builds and playbook runs can override the policy with --landing-auto-merge or --no-landing-auto-merge (CLI), or by sending landingAutoMerge: true/false in the enqueue body. Omitting the flag defers to the configured policy.

Note: landing.pr.autoMerge is distinct from landing.action: merge. The action: merge setting merges the artifact branch directly into the base branch without opening a PR.

landing:
  action: pr
  pr:
    autoMerge: ask    # ask (default) | always | never

Stacked PRs

When stacking.enabled: true, each build's artifact branch normally targets the parent artifact branch instead of the trunk, creating a stack of pull requests. Requires git-spice to be installed. 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:
  enabled: true                # Default false
  gitSpice:
    command: git-spice         # Default. Set to 'gs' if you use the short alias.
  sync:
    afterBuild: false          # Default false. Set to true for daemon-owned after-build sync.
 
landing:
  action: pr                   # Required for stacking

PRD frontmatter fields control the stack topology:

  • stack_id - logical stack name shared by all PRDs in the stack (optional; inferred from root PRD id)
  • stack_parent - parent PRD id (optional for single-dependency PRDs; required for multi-dependency PRDs)

For single-dependency builds (depends_on has one entry), stack_parent is inferred automatically. For multi-dependency builds, set stack_parent explicitly to indicate the direct parent layer.

Set stacking.sync.afterBuild: true to have the daemon automatically sync the stack after each queued build reaches a terminal state. When active builds overlap the stack candidates, sync is deferred and the daemon retries automatically. Prefer this over build.postMergeCommands: ["eforge stack sync"] for automatic sync.

See Stacked PRs for the full guide including git-spice setup, stack sync, deferred retry, manual sync conflict recovery, automatic stacked PR landing conflict recovery, branch-scoped stale-parent landing repair, and landing-time sync/freshness.

Trunk Branch Policy

build.trunkBranch and build.allowLocalMergeToTrunk govern how eforge lands builds when you are on the project's trunk branch.

eforge detects the trunk automatically from origin/HEAD during /eforge:init and writes the result to eforge/config.yaml. Override build.trunkBranch if the detected value is wrong or the repository uses a non-standard default branch name.

landing:
  action: merge                   # or 'pr' to open a pull request; 'leave' to skip both
build:
  trunkBranch: main               # detected from origin/HEAD; fallback: main
  allowLocalMergeToTrunk: false   # default: false; set to true for solo/unprotected projects

What each option does:

Scenario allowLocalMergeToTrunk: false (default) allowLocalMergeToTrunk: true
On trunk, landing.action: merge Rejected; CLI prompts to redirect Merges directly to trunk
On trunk, landing.action: pr PR from artifact branch to trunk PR from artifact branch to trunk
On feature branch (either action) Normal behavior, unaffected Normal behavior, unaffected

When allowLocalMergeToTrunk is false and you run interactively on trunk with landing.action: merge, the CLI prompts before enqueue and offers four alternatives: switch to pr, cancel, create or switch to a feature branch, or enable the solo-dev opt-in in eforge/config.yaml. With --auto, the engine rejects the build at runtime with a clear error message.

Pre-Compile Trunk Sync

By default, eforge fetches the configured remote trunk before creating the merge worktree for a queued root build. This prevents stale-base builds when origin/main has advanced but the local branch has not been pulled.

build:
  trunkSync:
    enabled: true             # default; set false for offline/local-only workflows
    remote: origin            # remote to fetch trunk from
    strategy: fetchedRemoteRef # only supported strategy in v1
    onDiverged: warn          # warn | fail | use-remote

What it does: before compile, eforge runs git fetch --no-tags origin main (using your configured remote and trunk branch), resolves the fetched commit SHA, and compares it to the local trunk. When the remote is ahead or equal, the fetched SHA is used as the compile base. When local and remote have diverged, the onDiverged policy applies.

onDiverged options:

Value Behavior
warn (default) Emit a config:warning diagnostic and fall back to the local trunk as the compile base.
fail Fail the build before compile begins.
use-remote Use the fetched remote SHA with a diagnostic.

Fetch-unavailable fallback: if the configured remote does not exist, the remote trunk branch is missing, the fetch fails, or FETCH_HEAD cannot be resolved, trunk sync is skipped. The build continues with the original candidate base and emits a planning:progress diagnostic. The onDiverged policy applies only to true local/remote divergence - not to network failures or unavailable remotes.

Validation and failure before compile: the remote value is validated before the fetch runs. It must be a registered git remote name: non-empty, must not start with -, must contain no whitespace or control characters, and must not be a URL (containing ://) or path (starting with /, ./, or ../). The resolved trunk branch must also be a valid git branch refname. Invalid values fail the build before compile - they do not fall back to the fetch-unavailable behavior. Use enabled: false to skip trunk sync for offline or local-only workflows.

What it does not do: trunkSync only fetches and selects a base ref. It does not checkout, pull, reset, rebase, or move local branch refs or your working tree. Only FETCH_HEAD is updated as part of the fetch. Direct PR base sync is separate: direct non-stacked PR publication later fetches origin/<baseBranch>, rebases the artifact branch before validation, and runs a final pre-PR freshness guard.

Scope: only applies to queued root builds whose candidate base is the trunk branch. Child stacked PRDs use the parent artifact ref unchanged during trunkSync. Builds queued from a non-trunk feature branch are not retargeted by trunkSync. Direct PR base sync applies later only to direct non-stacked landing.action: pr publication, including non-trunk feature bases.

Disabling trunk sync

For offline workflows or repositories without a remote:

build:
  trunkSync:
    enabled: false

Distinction from stack sync

build.trunkSync selects a fresh compile base before a build starts. It runs once, before the merge worktree is created, and does not affect the stack topology.

Direct PR base sync is a later mutating publication gate for direct non-stacked landing.action: pr builds. After all plans merge and before validation, eforge fetches origin/<baseBranch> and rebases the artifact branch onto that fetched base. Immediately before PR creation, eforge fetches the base again; if it advanced after validation, eforge performs a bounded resync plus command validation and PRD/acceptance validation retry before attempting the PR again. If the retry budget is exhausted or sync cannot complete, landing fails closed with landing:skipped rather than opening a stale PR.

Stacked PR landing does not use the direct non-stacked PR base sync path. Instead, it stays behind the stack provider boundary: eforge runs provider repo sync, branch restack, and a remote-base freshness proof for the branch being submitted. Manual eforge stack sync remains the separate whole-stack maintenance path after trunk or parent branches move.

Per-Role Tuning

Fine-tune individual agent roles without reassigning them to a different tier:

agents:
  roles:
    builder:
      effort: high
      maxTurns: 80
    reviewer:
      promptAppend: |
        ## Project Rules
        - Flag raw SQL queries
        - Require error handling for all async operations
    formatter:
      effort: low

Available per-role fields: tier, effort, thinking, maxTurns, allowedTools, disallowedTools, promptAppend, shards (builder-only). Per-role overrides do not change the harness or model directly; move the role to a different tier with tier when one role should use a different tier recipe.

Custom Prompts

Override any bundled agent prompt by placing a .md file in eforge/prompts/ with the same name as the role:

agents:
  promptDir: eforge/prompts

If eforge/prompts/reviewer.md exists, it replaces the bundled reviewer prompt entirely. Use promptAppend on a role for additive rules instead of full replacement.

Hooks

Hooks are fire-and-forget shell commands triggered by eforge events - useful for notifications, logging, and external integrations:

hooks:
  - event: plan:build:complete
    command: "notify-send 'Build complete'"
    timeout: 5000
  - event: plan:build:failed
    command: "curl -X POST $SLACK_WEBHOOK -d '{\"text\": \"Build failed\"}'"

Hooks do not block the pipeline. See the Hooks section in the Configuration Reference for field details and the Integrations page for examples.

Full Reference

For the complete eforge/config.yaml schema with all fields, types, and defaults, see the Configuration Reference.