Path: .pi/task-orchestrator.yaml
This file configures parallel orchestration behavior for /orch* commands.
Template source:
templates/config/task-orchestrator.yaml
JSON alternative: These settings can also be configured via
.pi/taskplane-config.json, which merges task-runner and orchestrator settings into a single file. See Unified JSON Config below for details and precedence rules.
orchestrator:
dependencies:
assignment:
pre_warm:
merge:
failure:
monitoring:
verification:| Field | Type | Template default | Description |
|---|---|---|---|
orchestrator.max_lanes |
number | 3 |
Maximum parallel execution lanes/worktrees. |
orchestrator.worktree_location |
"sibling" | "subdirectory" |
"subdirectory" |
Where lane worktree directories are created. |
orchestrator.worktree_prefix |
string | "taskplane-wt" |
Prefix used for worktree directory names and lane branch naming. |
orchestrator.batch_id_format |
"timestamp" | "sequential" |
"timestamp" |
Batch ID format used in logs/branch naming. |
orchestrator.spawn_mode |
"subprocess" |
"subprocess" |
How lane sessions are spawned (Runtime V2 subprocess-only). |
orchestrator.session_prefix |
string | "orch" |
Prefix for orchestrator session IDs. |
orchestrator.operator_id |
string | "" (auto-detected) |
Operator identifier for team-scale collision resistance. See naming. |
worktree_location values:
subdirectory:.worktrees/<prefix>-Nsibling:../<prefix>-N
| Field | Type | Template default | Description |
|---|---|---|---|
dependencies.source |
"prompt" | "agent" |
"prompt" |
Dependency extraction source. |
dependencies.cache |
boolean | true |
Cache dependency analysis results between runs. |
| Field | Type | Template default | Description |
|---|---|---|---|
assignment.strategy |
"affinity-first" | "round-robin" | "load-balanced" |
"affinity-first" |
Lane assignment strategy. |
assignment.size_weights |
map<string,number> | S:1, M:2, L:4 |
Relative weights used by size-aware assignment logic. |
| Field | Type | Template default | Description |
|---|---|---|---|
pre_warm.auto_detect |
boolean | false |
Enable automatic pre-warm command detection. |
pre_warm.commands |
map<string,string> | {} |
Named pre-warm commands. |
pre_warm.always |
string[] | [] |
Commands always run before wave execution. |
| Field | Type | Template default | Description |
|---|---|---|---|
merge.model |
string | "" |
Merge-agent model (empty = inherit active session model). |
merge.tools |
string | "read,write,edit,bash,grep,find,ls" |
Merge-agent tool allowlist. |
merge.verify |
string[] | [] |
Verification commands run after merge operations. |
merge.order |
"fewest-files-first" | "sequential" |
"fewest-files-first" |
Lane merge ordering policy. |
| Field | Type | Template default | Description |
|---|---|---|---|
failure.on_task_failure |
"skip-dependents" | "stop-wave" | "stop-all" |
"skip-dependents" |
Batch behavior when a task fails. |
failure.on_merge_failure |
"pause" | "abort" |
"pause" |
Behavior when a merge step fails. |
failure.stall_timeout |
number | 30 |
Stall detection threshold (minutes-equivalent logic in monitor cycle). |
failure.max_worker_minutes |
number | 30 |
Max worker runtime budget per task in orchestrated mode. |
failure.abort_grace_period |
number | 60 |
Graceful abort wait time (seconds) before forced termination. |
Note: Model fallback behavior (
modelFallback) is configured undertaskRunner, notorchestrator.failure. See task-runner.yaml reference.
When a merge fails, the orchestrator classifies the failure and consults a built-in retry policy matrix before applying the on_merge_failure policy. Retriable failures are automatically retried up to the class-specific limit; non-retriable failures immediately trigger pause or abort per policy.
| Classification | Retriable | Max attempts | Cooldown | Exhaustion action |
|---|---|---|---|---|
verification_new_failure |
✅ | 1 | 0 ms | pause — diagnostic emitted |
merge_conflict_unresolved |
❌ | 0 | — | on_merge_failure policy — operator escalation |
cleanup_post_merge_failed |
✅ | 1 | 2 000 ms | pause + wave gate (blocks next wave) |
git_worktree_dirty |
✅ | 1 | 2 000 ms | pause |
git_lock_file |
✅ | 2 | 3 000 ms | pause |
Classification descriptions:
verification_new_failure— Post-merge verification detected genuinely new test failures (not present in the pre-merge baseline). The merge commit is rolled back tobaseHEADand one retry is allowed immediately.merge_conflict_unresolved— Git merge produced conflicts that the merge agent could not resolve. Not retriable — requires operator intervention.cleanup_post_merge_failed— Post-merge cleanup operations (worktree removal, branch deletion) failed. Retriable, but acts as a wave gate: the next wave cannot start until the issue is resolved.git_worktree_dirty— The merge worktree had unexpected uncommitted changes. Retriable after a 2-second cooldown.git_lock_file— A Git lock file (.git/index.lockor similar) blocked the operation. Retriable up to 2 times with 3-second cooldowns, as lock files are often transient.
Retry behavior:
- On merge failure, the orchestrator classifies the error and looks up the policy matrix.
- If the class is retriable and the retry count (persisted in batch state) is below
maxAttempts, the orchestrator waits for the cooldown period and re-invokes the merge. - Retry counters are scoped by
{repoId}:w{N}:l{K}(e.g.,api:w0:l1). In single-repo mode,repoIddefaults to"default". Counters persist in.pi/batch-state.jsonunderresilience.retryCountByScopeand survive/orch-resume. - On retry exhaustion (all attempts consumed for a retriable class), the orchestrator forces
pausedphase regardless of theon_merge_failuresetting. This ensures operators always have a chance to inspect the failure — even whenon_merge_failureis set to"abort". - Non-retriable classes (
merge_conflict_unresolved) skip retries entirely and apply theon_merge_failurepolicy immediately (pauseorabortper configuration).
Forced pause vs. policy: Forced pause (overriding
on_merge_failure) applies only in two situations: (1) retry exhaustion for a retriable class, and (2) rollback safe-stop (when a rollback itself fails). Initial non-retriable failures always respect the configuredon_merge_failurepolicy.
Note: The retry matrix is not configurable. The values above are built-in defaults derived from the resilience roadmap. Future releases may expose per-class overrides.
Transaction records:
Each lane merge attempt (success, failure, or rollback) produces a transaction record persisted at .pi/verification/{opId}/txn-b{batchId}-repo-{repoId}-wave-{n}-lane-{k}.json. These records capture baseHEAD, laneHEAD, mergedHEAD, rollback outcome, and recovery commands. If a rollback itself fails (safe-stop), the record includes exact git commands for manual recovery, and the merge worktree and temp branch are preserved.
| Field | Type | Template default | Description |
|---|---|---|---|
monitoring.poll_interval |
number | 5 |
Poll interval (seconds) for lane/task monitoring loop. |
| Field | Type | Template default | Description |
|---|---|---|---|
verification.enabled |
boolean | false |
Enable verification baseline fingerprinting. When false, no baseline capture or comparison occurs regardless of testing commands. |
verification.mode |
"strict" | "permissive" |
"permissive" |
Behavior when baseline is unavailable (capture failure or no commands configured). |
verification.flaky_reruns |
number | 1 |
Number of re-runs for failed commands when new failures are detected. Set to 0 to disable flaky detection. |
verification.mode values:
strict: Baseline unavailable (capture failure or notaskRunner.testing.commandsconfigured) triggers a merge failure. Thefailure.on_merge_failurepolicy then determines whether the batch pauses or aborts.permissive: Baseline unavailable logs a warning and continues without orchestrator-side verification. Merge-agent verification (merge.verify) still applies independently.
How it works:
Verification baseline fingerprinting runs orchestrator-side (in the merge flow), separate from merge-agent verification (merge.verify). When enabled:
- Baseline capture: Before merging lanes, test commands from
taskRunner.testing.commandsare run on the pre-merge state to capture a baseline of existing failures. - Post-merge capture: After each successful lane merge, the same commands run again to capture post-merge state.
- Diff: Only genuinely new failures (present post-merge but absent from the baseline) block the merge. Pre-existing failures pass through.
- Flaky handling: When new failures are detected, only the failed commands are re-run up to
flaky_rerunstimes. If failures clear on re-run, the lane is classified asflaky_suspected(warning only, does not block). If failures persist, the lane is blocked asverification_new_failure.
Requirements:
verification.enabledmust betrue(feature flag, opt-in)taskRunner.testing.commandsmust have at least one command configured (these are the commands that get fingerprinted)- Both conditions must be met for baseline capture to occur
Note:
merge.verify(agent-side verification) andverification(orchestrator-side baseline fingerprinting) are independent features.merge.verifycommands are run by the merge agent and trigger agent-side revert logic.verificationcommands are run by the orchestrator and gate merge advancement based on fingerprint comparison. You can use either, both, or neither.
- The orchestrator loads this file and merges missing fields with internal defaults.
- Missing or malformed file falls back to safe defaults.
- Orchestrator also reads
.pi/task-runner.yamlfortask_areasand shared metadata.
The operator_id field controls how lane sessions, worktree directories, git branches, and merge artifacts are named. This enables collision-resistant naming when multiple operators run orchestrator batches concurrently on the same machine or repo.
The operator identifier (opId) is resolved from the first non-empty source:
TASKPLANE_OPERATOR_IDenvironment variableorchestrator.operator_idconfig field- Current OS username (auto-detected via
os.userInfo().username) - Fallback:
"op"
The resolved value is sanitized (lowercase, alphanumeric + hyphens only) and truncated to 12 characters.
| Artifact | Pattern | Example |
|---|---|---|
| Lane session ID (single-repo mode) | {session_prefix}-{opId}-lane-{N} |
orch-alice-lane-1 |
| Lane session ID (workspace) | {session_prefix}-{opId}-{repoId}-lane-{N} |
orch-alice-api-lane-1 |
| Merge session ID | {session_prefix}-{opId}-merge-{N} |
orch-alice-merge-1 |
| Worktree directory | {worktree_prefix}-{opId}-{N} |
taskplane-wt-alice-1 |
| Git branch | task/{opId}-lane-{N}-{batchId} |
task/alice-lane-1-20260315T190000 |
| Merge temp branch | _merge-temp-{opId}-{batchId} |
_merge-temp-alice-20260315T190000 |
| Merge sidecar | merge-result-w{W}-lane{L}-{opId}-{batchId}.json |
merge-result-w1-lane1-alice-20260315T190000.json |
- CI environments: Set
TASKPLANE_OPERATOR_IDexplicitly (e.g.,ci-runner-1) to avoid OS username variability. - Team usage: Ensure operator identifiers are unique within the first 12 characters after sanitization. Names like
ci-runner-team-alphaandci-runner-team-betaboth truncate toci-runner-te— use shorter, distinct prefixes instead. - Sanitization note: Dots and underscores are collapsed to hyphens, so
john.doeandjohn-doeresolve to the sameopId.
Orchestrator settings can be provided via the unified .pi/taskplane-config.json file instead of (or alongside) the YAML file. The JSON format merges settings from both task-orchestrator.yaml and task-runner.yaml into one file.
The config loader uses the following precedence:
.pi/taskplane-config.jsonexists and is valid → use it (YAML files are ignored).pi/taskplane-config.jsonexists but is malformed → error (hard failure, not a silent fallback).pi/taskplane-config.jsonabsent → fall back to.pi/task-orchestrator.yaml+.pi/task-runner.yaml- No config files present → internal defaults
Important: When
taskplane-config.jsonis present, YAML files are completely ignored — they are not merged together. This is an either/or precedence, not a layered merge.
Config files are resolved relative to the project root. In workspace/worktree mode, if the current working directory has no config files, the loader checks TASKPLANE_WORKSPACE_ROOT for config files before falling back to defaults.
| Condition | Behavior |
|---|---|
Valid JSON with configVersion: 1 |
Config loaded, missing fields filled from defaults |
Valid JSON without configVersion |
Error: CONFIG_VERSION_MISSING — loader throws |
| Malformed JSON (syntax error) | Error: CONFIG_JSON_MALFORMED — loader throws |
JSON with unsupported configVersion |
Error: CONFIG_VERSION_UNSUPPORTED — "please upgrade Taskplane" |
| YAML present, no JSON | YAML loaded and mapped to unified config shape |
| Malformed YAML | Silent fallback to internal defaults (legacy behavior preserved) |
The JSON format uses camelCase keys. YAML snake_case keys are mapped automatically by the loader.
| YAML key | JSON key |
|---|---|
max_lanes |
maxLanes |
worktree_location |
worktreeLocation |
worktree_prefix |
worktreePrefix |
batch_id_format |
batchIdFormat |
spawn_mode |
spawnMode |
session_prefix |
sessionPrefix |
operator_id |
operatorId |
size_weights |
sizeWeights |
auto_detect |
autoDetect |
pre_warm |
preWarm |
on_task_failure |
onTaskFailure |
on_merge_failure |
onMergeFailure |
stall_timeout |
stallTimeout |
max_worker_minutes |
maxWorkerMinutes |
abort_grace_period |
abortGracePeriod |
poll_interval |
pollInterval |
flaky_reruns |
flakyReruns |
Note: User-defined dictionary keys (size weight labels like
S/M/L, pre-warm command names, etc.) are preserved verbatim in both formats.
In the JSON file, orchestrator settings live under the orchestrator key:
| YAML section | JSON path |
|---|---|
orchestrator |
orchestrator.orchestrator |
dependencies |
orchestrator.dependencies |
assignment |
orchestrator.assignment |
pre_warm |
orchestrator.preWarm |
merge |
orchestrator.merge |
failure |
orchestrator.failure |
monitoring |
orchestrator.monitoring |
verification |
orchestrator.verification |
{
"configVersion": 1,
"orchestrator": {
"orchestrator": {
"maxLanes": 3,
"worktreeLocation": "subdirectory",
"worktreePrefix": "taskplane-wt",
"batchIdFormat": "timestamp",
"spawnMode": "subprocess",
"sessionPrefix": "orch",
"operatorId": ""
},
"dependencies": {
"source": "prompt",
"cache": true
},
"assignment": {
"strategy": "affinity-first",
"sizeWeights": { "S": 1, "M": 2, "L": 4 }
},
"preWarm": {
"autoDetect": false,
"commands": {},
"always": []
},
"merge": {
"model": "",
"tools": "read,write,edit,bash,grep,find,ls",
"verify": [],
"order": "fewest-files-first"
},
"failure": {
"onTaskFailure": "skip-dependents",
"onMergeFailure": "pause",
"stallTimeout": 30,
"maxWorkerMinutes": 30,
"abortGracePeriod": 60
},
"monitoring": {
"pollInterval": 5
},
"verification": {
"enabled": false,
"mode": "permissive",
"flakyReruns": 1
}
}
}The
taskRunnerkey is also available at the top level for task-runner settings — see Task Runner Config Reference.