Repository navigation
[DEV-184] Per-job agent harness, model, and effort for automations, estimations, and conflict resolution - #286
Conversation
…ness, model, and effort selection
1. [MEDIUM] bug in automation/acquirer.ts: a pinned harness skipped the
whole failover overlay, which was also the only thing setting
DEVINTERN_WORKER_CHILD=1. The pinned CLI child therefore stopped being a
worker child, so a usage-limit abort wrote no hint and exited 0 (falling
through to one-shot success handling) instead of exiting 75. Keep the
worker-child marker on pinned children and drop any inherited hint path;
the existing !options.harnessPin guard already prevents re-spawning on
another harness.
2. [MEDIUM] testing: add spawnAutomationProcess coverage for pinned runs
(stub child exits 75 with a started failover) asserting the pinned
AGENT_HARNESS, the worker-child marker, no leaked hint path, and no
failover, plus the estimation variant and an unpinned failover control.
Iteration 2 fixes:
3. [MEDIUM] bug in agent/selection.ts: an entry that set model/effort without
a harness kept workspace failover, and the failover overlay only rewrote
AGENT_HARNESS, so a harness-specific model leaked onto whichever fallback
harness ran. applyAgentSelectionEnv now writes model/effort only when the
entry also pins a harness, so a model-only entry leaves the inherited value
untouched and can never reach a different harness.
4. [MEDIUM] bug in automation/acquirer.ts: a pinned harness left the
inherited AGENT_CLI_PATH in place, so the child resolved the workspace
primary's binary for the pinned harness. applyAgentSelectionEnv now drops
AGENT_CLI_PATH when the pin differs from the env's primary harness (keeping
it for a primary pin), and pinAutomationEnv applies the same guard for
direct callers. Shared via a new primaryHarnessName helper.
5. [testing] add unit and spawnAutomationProcess integration coverage for
model-only entries (no leak to primary or fallback) and for AGENT_CLI_PATH
being dropped on a non-primary pin but kept on a primary pin; update the
worker/workspaces docs to state the model/effort-require-harness contract.
Iteration 3 fixes:
6. [HIGH] bug in agent/selection.ts and automation/acquirer.ts: dropping only
AGENT_CLI_PATH on a non-primary pin left the resolver's CLAUDE_CLI_PATH
backward-compat fallback in place. resolveEnvHarnessPath (resolver.ts) uses
CLAUDE_CLI_PATH for ANY harness, so a codex pin still resolved to the claude
binary. applyAgentSelectionEnv and pinAutomationEnv now share a
dropForeignHarnessCliPaths helper that also drops CLAUDE_CLI_PATH for a
non-claude pin (kept when pinning claude-code).
7. [MEDIUM] bug in agent/selection.ts: an entry that pinned a different
harness but omitted model/effort inherited the env primary's
harness-specific AGENT_MODEL/AGENT_EFFORT. When the pin changes the harness
those inherited values are now dropped unless the entry sets its own,
mirroring the failover-case reasoning (same pin keeps them).
8. [testing] regression coverage: unit tests that a non-claude pin resolves its
own binary rather than the inherited CLAUDE_CLI_PATH and that inherited
model/effort are dropped/kept by whether the pin changes harness, plus
spawnAutomationProcess integration tests for CLAUDE_CLI_PATH dropped on a
non-claude pin, kept on a claude pin, and the inherited model dropped on a
harness-changing pin. Docs updated.
Iteration 4 fixes:
9. [MEDIUM] bug in automation/manual.ts: the standalone dashboard's 'Run now'
composed context.env from process.env without pinning
DEVINTERN_WORKER_SUBPROCESS, so the spawned CLI child ran loadEnvironment()
and dotenv re-populated the AGENT_CLI_PATH/AGENT_MODEL that the entry's
harness pin had deliberately dropped (the cross-harness leak this PR fixes
on the scheduled/estimation paths). Pin the worker-subprocess marker in the
manual context so the child skips the checkout's .env; end-to-end test
composes the real automationRunEnv child env, runs the real loadEnvironment
in a subprocess, and asserts the dropped keys stay dropped (and would be
restored without the marker).
10. [MEDIUM] bug in agent/selection.ts: parseAgentSelection validated a pinned
harness only against the registry, so an entry pinned to a
registered-but-uninstalled harness (or a wrong <HARNESS>_CLI_PATH) failed
every occurrence silently with no failover. Add warnUninstalledHarnessPins
and call it at worker startup (applyWorkspaceEnv) and on config reload
(validateReloadedWorkspaceConfig), mirroring the AGENT_HARNESS chain's
installability warnings; unit tests cover the missing-CLI warning and the
AGENT_CLI_PATH rules for primary vs non-primary pins. Docs updated.
Iteration 5 fixes:
11. [MEDIUM] bug in automation/manual.ts: iteration 4 pinned the broad
DEVINTERN_WORKER_SUBPROCESS marker on the standalone dashboard manual
spawn purely to skip dotenv, but isWorkerSubprocess() also switches
resolveRuntimeStateDir() from the project .devintern-code to the
workspace state dir (~/.devintern/state/code). An automated (systemd/CI)
dashboard-spawned child then read the wrong .auth-session.json in
loadSupabaseConfig() (index.ts) and could fail the automation-license
gate. Add a narrow DEVINTERN_SKIP_PROJECT_ENV marker and a
shouldSkipProjectEnv() helper that loadEnvironment() consults; manual.ts
sets the narrow marker instead, so the child skips the checkout .env
while its runtime state and auth session still resolve to the project
config dir. Scheduled runs keep the worker marker (and workspace state).
12. [testing] automation-manual.test.ts: assert the narrow marker is set and
add a regression test that runs the real resolveRuntimeStateDir /
loadSupabaseConfig in a subprocess and asserts the manual child stays
project-scoped, with the worker marker control showing the workspace-dir
relocation the fix avoids. Make the dotenv-leak control hermetic against
an inherited worker marker.
Artifacts & verificationRun: 763 · Commit: Change explanationProduced by Per-automation and per-estimation agent harness, model, and effort selection (DEV-184)No PR template is present in this repository ( Why this change is neededThe task states the motivation directly: today the agent is chosen once per workspace/repo. What changedGrouped by purpose: New configuration surface. Precedence and env composition. Failover opt-out. A pinned Startup/reload visibility. Because a pin disables failover, Manual dashboard path isolation. The standalone dashboard's manual spawn applies the same selection but must not let the child's dotenv reload re-populate the dropped keys. This introduced a narrow Docs. Validation actually performedGrounded in the pipeline step results provided:
Observed vs inferred:
Material risks
Where reviewers should focusThe smallest set of files and behaviors that carry the risk:
Visual explanationProduced by Show artifactSummary
EvidenceThe change is one new module ( Files added/changed: Validation, as reported by the pipeline evidence (not independently re-run here):
Before/after execution results were not captured in the supplied evidence; the only side-by-side is test source (a pinned child vs. a deliberately "leaky" control), not recorded command output. Merge DangerReversible: a plain revert of these files; no schema change, migration, or persisted data, and existing Blast radius: the changed env builders ( Shipability: no staged rollout is strictly required because entries without the new fields behave exactly as before and validation is fail-closed at parse time. The residual risk is concentrated in the failover opt-out and the manual skip-env marker; both have targeted tests, but the auto-review did not reach a passing state, so a staged rollout or a second review is the prudent call before broad release. Verification reportProduced by Show artifactFurther artifacts omitted because they exceed the comment preview budget. |
|
Visual overview of the per-schedule agent selection added by this PR: Each automation or estimation can select its own agent. Entry Examples show selection fields only, not complete schedule definitions. Image generated with OpenAI image generation and checked against the current PR diff. |
# Conflicts: # packages/code/src/lib/automation/config.ts
|
🔀 This branch had merge conflicts with |
|
@devintern-ai Please resolve this PR's conflicts against its base branch main on the omarchy worker. Merge latest main into the existing branch without rewriting history or force-pushing. Preserve behavior, run relevant tests/checks, push to the same PR branch and report results here. No merge, close, deploy, or changes to PR #224. |
# Conflicts: # docs/code/workspaces.md # packages/code/src/lib/automation/acquirer.ts # packages/code/src/lib/workspace/workspace-worker.ts # packages/code/tests/automation-acquirer.test.ts
…esolution - A `model` without `harness` on an automation/estimation is no longer silently ignored: it is written to the primary harness's own `<HARNESS>_MODEL`, so `model = "sonnet"` works while keeping failover and a fallback harness keeps its default model. A pinned `model` also outranks an inherited `<HARNESS>_MODEL`. `effort` applies without a pin. - Add `[workspace].conflict_resolution_harness/_model/_effort`, applied to GitHub and GitLab automatic conflict resolution. `runWithFailover` gains a `harnessPin` option: a pinned run spawns once on that agent and defers on a usage limit instead of failing over. Startup/reload warns when the pin's CLI is missing. - Team-scoped estimations (added on main) apply the entry selection too. - Fix PR tests for main's async `startWorkerFailover`. - Move scheduled-job context resolvers to `workspace/schedule-context.ts` to keep `workspace-worker.ts` under the max-lines limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The selection canonicalizes aliases before spawning, so the child never saw "gemini" and the resolver's deprecation warning was lost. Emit it at parse time, naming the entry and key. Adds alias tests (agy, gemini, alias vs canonical primary). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Model-only examples name the primary harness they assume (claude-code). - Use gpt-5.2 for the Codex example, matching the rest of the docs. - Conflict agent applies to GitHub auto/scheduled and GitLab auto only. - Describe the real deferral behavior for a pinned harness on a usage limit (three consecutive deferrals, then wait for head/base to move). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Users write the binary name in AGENT_HARNESS chains (claude,codex,grok) and in per-job harness settings; it now resolves like agy -> antigravity. Docs list the alias. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Why
Task DEV-184: per-job agent harness, model, and effort selection.
Every worker job used the workspace/repo
AGENT_HARNESS,AGENT_MODEL, andAGENT_EFFORT, so cheap, mechanical work (triage, estimation, merge-conflict resolution) ran on the same frontier model as feature work. This adds optional per-job overrides so e.g. scheduled automations or conflict resolution can run on Sonnet while tracker tasks stay on Opus.What changed
agent/selection.ts(new):AgentSelection,parseAgentSelection(validates one harness name against the registry, canonicalizes aliases, reusesparseAgentEffort, optional key prefix),applyAgentSelectionEnv,pinnedHarnessChildEnv, and the uninstalled-pin warning.[[automations]]/[[estimations]]: optionalharness,model,effort. Applied in the context env and again in the run env. Team-scoped estimations (buildTeamWorkspaceContextEnv) apply it too.[workspace].conflict_resolution_harness/_model/_effort, applied to automatic resolution on GitHub (fleet-events.ts) and GitLab (gitlab-fleet-events.ts). It's read at run time, so live reload applies it.modelwithoutharnessis written to the primary harness's own<HARNESS>_MODEL(e.g.CLAUDE_CODE_MODEL). Somodel = "sonnet"alone works, failover stays on, and a fallback harness keeps its own default model. A pinnedmodelalso outranks an inherited<HARNESS>_MODEL.harnesspins one agent and opts that job out of the failover chain. A pinned automation fails that run on a usage limit. A pinned conflict resolution (runWithFailover({ harnessPin })) spawns once and defers. When the pin changes the harness, the inheritedAGENT_MODEL/AGENT_EFFORT/AGENT_CLI_PATH(and theCLAUDE_CLI_PATHfallback) are dropped.effortis harness-neutral and applies with or without a pin.DEVINTERN_SKIP_PROJECT_ENV, so dotenv can't re-add the keys the selection dropped.workspace/schedule-context.ts(keepsworkspace-worker.tsundermax-lines). They're re-exported fromworkspace-worker.ts.agent-harness:claudeis now an alias forclaude-code(e.g.AGENT_HARNESS=claude,codex,grok). Pinning the deprecatedgeminialias on a job prints its deprecation warning when the config loads, since the child process only ever sees the real name.docs/code/worker.md("Choosing the agent per schedule", with a model-only example) anddocs/code/workspaces.md(entry bullet, plus a conflict resolution "Choosing the agent" paragraph).Example
Tradeoffs
harnessis a single name, not a chain. Pinning opts out of failover, so a usage limit on a pinned agent fails (automation) or defers (conflict resolution) instead of switching agents. Leaveharnessunset to keep failover.modelis never validated across harnesses.devintern resolve-conflicts <pr-url>uses the normal environment, not the conflict-resolution setting.Blast radius
All new fields are optional and additive. Configs that set none of them behave as before.
runWithFailoverand the env builders take new optional parameters; existing callers are unchanged. Editing a model or harness doesn't reset an automation's schedule cursor.Verification
bun run typecheck,bun run lint(lefthook): pass.bun run test(packages/code, ambientAGENT_*unset): 2893 pass, 0 fail.runWithFailover, fleet conflict wiring under live mutation, and the failover probe for model-only entries.🤖 Generated with Claude Code