Skip to content
2 changes: 1 addition & 1 deletion docs/code/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ AGENT_HARNESS=claude-code

You usually only need `AGENT_HARNESS`. By default devintern uses the harness's standard command (for example `claude` for `claude-code`) and finds it on your `PATH` automatically, so `AGENT_CLI_PATH` can be left unset.

Common `AGENT_HARNESS` values include `claude-code`, `opencode`, `codex`, `cursor`, `grok`, `deepseek`, `antigravity`, `cline`, `goose`, `kilo-code`, `kimi`, `muse`, and `qwen`. If you do need to set a path explicitly, run `which` for the harness binary (`claude`, `opencode`, `codex`, `cursor-agent`, `grok`, `reasonix`, `agy`, `cline`, `goose`, `kilo`, `kimi`, `muse`, or `qwen`).
Common `AGENT_HARNESS` values include `claude-code` (alias `claude`), `opencode`, `codex`, `cursor`, `grok`, `deepseek`, `antigravity`, `cline`, `goose`, `kilo-code`, `kimi`, `muse`, and `qwen`. If you do need to set a path explicitly, run `which` for the harness binary (`claude`, `opencode`, `codex`, `cursor-agent`, `grok`, `reasonix`, `agy`, `cline`, `goose`, `kilo`, `kimi`, `muse`, or `qwen`).

**Cursor note:** The Cursor harness uses Cursor's headless `cursor-agent` CLI (not a command named `cursor`). Cursor also installs an `agent` alias, but devintern looks for `cursor-agent` because other tools use the `agent` name too. Install Cursor and enable the CLI from Cursor's settings, then set `AGENT_HARNESS=cursor`. The `--max-turns` option has no effect with this harness; Cursor runs until the task is complete.

Expand Down
42 changes: 41 additions & 1 deletion docs/code/worker.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ devintern webhook serve

## Agent failover

Set `AGENT_HARNESS=codex,grok` (comma-separated, priority first) in the workspace `.env` so the worker keeps going when one agent hits a usage limit. Failover applies to every worker job: tracker tasks, PR review addressing, `@mention` runs, conflict resolution, scheduled automations, estimations, dashboard retries, and relay-driven work. Details: [Failover across multiple harnesses](./configuration.md#failover-across-multiple-harnesses).
Set `AGENT_HARNESS=codex,grok` (comma-separated, priority first) in the workspace `.env` so the worker keeps going when one agent hits a usage limit. Failover applies to every worker job: tracker tasks, PR review addressing, `@mention` runs, conflict resolution, scheduled automations, estimations, dashboard retries, and relay-driven work — except automations, estimations, or conflict resolution that pin their own `harness` (see [Choosing the agent per schedule](#choosing-the-agent-per-schedule)). Details: [Failover across multiple harnesses](./configuration.md#failover-across-multiple-harnesses).

## Error-monitor auto-fixes

Expand Down Expand Up @@ -153,6 +153,44 @@ The prompt replaces the ticket description the agent would normally read, so tre

When `open_pr = true`, occurrences receive the same flags as polled tasks: `[defaults].worker_task_args` in `workspace.toml` (default `--create-pr`). For example, set `worker_task_args = "--create-pr --auto-review"` to have every automated PR go through the review loop too, and add `--auto-review-iterations 3` to the same value (or set the `AUTO_REVIEW_ITERATIONS` env var) to raise the shared review–fix cycle cap (default: 2). This setting applies to polled tracker tasks as well. Automations with `open_pr` off (the default) are never affected by these flags — PR and review flags are stripped from them and `--no-git` is applied instead.

### Choosing the agent per schedule

By default every run uses the workspace `AGENT_HARNESS` / `AGENT_MODEL` / `AGENT_EFFORT` from the [environment layers](./configuration.md). An individual `[[automations]]` or `[[estimations]]` entry can override the agent for its own runs, so a cheap model can handle a daily triage job while a frontier model handles release work:

```toml
# Smaller model on the workspace primary harness (here claude-code); failover still applies.
[[automations]]
id = "dependency-bump"
enabled = true
interval = "1d"
model = "sonnet"
prompt = "Upgrade one outdated patch-level dependency and run the tests."

[[automations]]
id = "log-triage"
enabled = true
interval = "1d"
harness = "codex"
model = "gpt-5.2"
effort = "low"
prompt = "Summarize new errors and file at most one issue."

[[automations]]
id = "release-manager"
enabled = true
cron = "0 9 * * 1"
harness = "claude-code"
effort = "high"
open_pr = true
prompt = "Cut the next release and update the changelog."
```

- `harness` names **one** registered agent (aliases such as `claude` or `agy` resolve to their canonical name). An unknown name fails validation, naming the entry id and the `harness` field. Because a pin opts out of failover, the worker warns at startup (and on config reload) when an entry's `harness` is registered but its CLI is not installed or its `<HARNESS>_CLI_PATH` points at a missing executable, so the problem is visible before the first occurrence.
- `model` is a harness-specific string (see that agent's CLI docs) and is never validated cross-harness. Without `harness`, it applies to the workspace's primary harness only (it is passed as that harness's `<HARNESS>_MODEL`, e.g. `CLAUDE_CODE_MODEL`), so the entry keeps normal failover and a fallback harness runs on its own default model. With `harness`, it applies to the pinned agent; when `harness` names a different agent than the workspace primary, an inherited `AGENT_MODEL` chosen for that primary is dropped unless the entry sets `model`.
- `effort` accepts only `low`, `medium`, or `high`; anything else fails validation. It applies to whichever harness runs the entry (effort levels are harness-neutral); when a pin changes the harness, an inherited `AGENT_EFFORT` is dropped unless the entry sets `effort`. It is applied only for harnesses that support reasoning effort — on any other harness it is ignored with a warning, and the run proceeds.
- Precedence for the entry's runs is **entry field > team/repo env > workspace env > harness default**. Fields you omit fall through to the existing workspace/repo/team values, and entries that set none of the three fields behave exactly as before.
- An entry that pins `harness` uses exactly that agent and opts out of the workspace `AGENT_HARNESS` failover chain for its runs; entries without `harness` keep normal failover, and their `model` never reaches a fallback harness. A pinned harness also drops the inherited global `AGENT_CLI_PATH` (which names the workspace primary's binary) and, for a non-`claude-code` pin, the resolver's `CLAUDE_CLI_PATH` fallback, so it resolves its own `<HARNESS>_CLI_PATH` or default. The selection applies to subsequent occurrences after a live reload; a run already in flight keeps the agent it started with.

### Schedule semantics

Schedule cursors and claims live in `queue.db`. Missed occurrences coalesce to at most one immediate run after startup. The occurrence cursor advances atomically when claimed, so a crash does not replay a possibly completed run. Active claims receive heartbeats; after two minutes without a heartbeat a later due occurrence may recover the stale claim. If the same automation is still active at its next occurrence, that occurrence is logged and skipped without creating a run record. If the repository lock is held by another task, the occurrence is also skipped. This is an at-most-once policy: skipped occurrences are not replayed. A dashboard-triggered manual run holds the same lease while it is active, so a scheduled occurrence coming due mid-validation is skipped rather than run concurrently.
Expand Down Expand Up @@ -191,6 +229,8 @@ query = "sprint in openSprints() AND \"Story Points\" is EMPTY"

Each entry needs a unique `id`, boolean `enabled`, non-empty `query`, and exactly one of `cron` or `interval`. Omitting the table (or leaving every entry disabled) changes nothing: estimation is simply off, and `[defaults].task_query` is never estimated as a side effect. Entries without `team` use the workspace tracker, which must support estimation (Jira, Linear, Azure DevOps, Asana via a custom field; GitHub and GitLab post comment-only estimates); Trello/markdown workspaces fail at startup with a clear error. `worker init` does not ask about estimations — add tables by hand.

Estimation entries accept the same optional `harness`, `model`, and `effort` overrides as `[[automations]]`, so a scheduled sweep can run on a cheap model — see [Choosing the agent per schedule](#choosing-the-agent-per-schedule). Because estimation never touches a repository, these fields do not require (and are unaffected by) a `repo`.

Estimation entries live-reload with `workspace.toml`: added and re-enabled entries schedule their next future occurrence, changed schedules reset their cursor, query edits apply to the next sweep, and removed entries stop scheduling without interrupting a sweep already in progress.

When an entry comes due the worker runs one-shot `devintern --estimate --query "<query>"` from the workspace home. That path keeps all of the interactive behavior: tickets younger than 24 hours are skipped, already-estimated tickets are skipped unless the ticket changed since the estimate, changed tickets are re-estimated with the estimate comment updated in place, points are written to the tracker field, and usage-limit aborts exit cleanly so the next occurrence retries. Each sweep is recorded with its own `estimate` origin (plus the schedule id) in the [dashboard](./dashboard.md) — it never shows up as a scheduled implement run.
Expand Down
13 changes: 13 additions & 0 deletions docs/code/workspaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ dashboard = true
# conflict_resolution_cron = "0 3 * * *" # worker host timezone
# conflict_resolution_interval = "1d" # exactly one of cron / interval
# Or turn it off entirely: conflict_resolution = "disabled"
# conflict_resolution_model = "sonnet" # cheaper model for conflict resolution (primary harness)

# Optional bounded host concurrency (see warning below):
# [workspace.execution]
Expand Down Expand Up @@ -108,6 +109,7 @@ prompt = "Review the frontend and clean up one source of recurring noise."
- `[worker].auto_update` keeps a globally installed CLI current while the worker is idle (checked at most daily, installed without interrupting work, restart on success). See [Running the Worker Unattended: Keeping the worker up to date](./worker.md#keeping-the-worker-up-to-date).
- `[[automations]]` uses the same schema as single-repo `.devintern-code/automations.toml`. An entry must name `repo` when the workspace has more than one repository. `open_pr` (boolean, default `false`) decides whether occurrences open a pull request: opt-in per automation, and workspace-level `worker_task_args` never overrides it. `allow_no_changes` (boolean, default `false`) records an occurrence that changes nothing as a successful no-op instead of a failed run — use it for jobs whose prompt says to make no changes when nothing needs doing. See [Worker Daemon → Recurring automations](./worker.md#recurring-automations) for prompt-writing guidance and schedule semantics.
- `[[estimations]]` schedules unattended story-point sweeps (tracker query + cron/interval, no `prompt`, no `repo`). An entry runs against `[defaults].tracker`, or against one team's tracker and credentials when it sets `team = "<team name>"`. That tracker must support estimation. See [Worker Daemon → Scheduled story-point estimation](./worker.md#scheduled-story-point-estimation).
- `[[automations]]` and `[[estimations]]` entries may each set `harness` (one agent name), `model`, and `effort` (`"low"`, `"medium"`, or `"high"`) to run that schedule on a different agent than the workspace default. Precedence for an entry's runs is **entry field > team/repo env > workspace env > harness default**; omitted fields keep the existing workspace/repo/team values. A `model` without `harness` applies to the workspace's primary harness only (as its `<HARNESS>_MODEL`), so `model = "sonnet"` alone keeps failover and a fallback harness runs on its own default model. A pinned `harness` opts the entry out of failover; when it names a different agent than the workspace primary, an inherited `model`/`effort` and the primary's `AGENT_CLI_PATH` (plus the `CLAUDE_CLI_PATH` fallback for a non-`claude-code` pin) are dropped so the pinned agent uses its own binary and defaults. An unknown `harness` or an `effort` outside the three levels fails validation naming the entry id and field. A pinned `harness` whose CLI is not installed is warned about at worker startup and on reload, naming the entry. `effort` on a harness that does not support reasoning effort is ignored with a warning instead of failing the run. These overrides apply on live reload to subsequent occurrences; in-flight runs finish under the selection they started with. See [Worker Daemon → Choosing the agent per schedule](./worker.md#choosing-the-agent-per-schedule).
- `[[error_monitors]]` maps each Sentry project to one repo and an optional team, with per-source credential layers for multi-project setups. See [Sentry Auto-fixes](./sentry-integration.md).

### Bounded host concurrency
Expand Down Expand Up @@ -280,6 +282,17 @@ Two things are never delayed by scheduled mode: review feedback on the agent's P

The setting is workspace-wide (per-repo overrides are not supported in v1) and live-reloads with the rest of the runtime configuration. The tradeoff to keep in mind: between windows a conflicted PR cannot be merged, so on fast-moving branches where an instant rebase unblocks a waiting reviewer, `auto` stays the better choice. See [Worker Daemon → Merge conflicts on the agent's PRs](./worker.md#merge-conflicts-on-the-agents-prs) for how resolution itself works.

**Choosing the agent.** Conflict resolution is usually mechanical, so it can run on a cheaper model than feature work. Set any of `conflict_resolution_harness`, `conflict_resolution_model`, and `conflict_resolution_effort` under `[workspace]`; they behave exactly like an automation's `harness` / `model` / `effort` (see [Worker Daemon → Choosing the agent per schedule](./worker.md#choosing-the-agent-per-schedule)) and apply to automatic resolution on GitHub (`auto` and `scheduled`) and GitLab (`auto`):

```toml
[workspace]
conflict_resolution_model = "sonnet" # a model id for your primary harness (here claude-code); failover still applies
# conflict_resolution_harness = "codex" # pin one agent (opts out of failover)
# conflict_resolution_effort = "low"
```

A model-only setting applies to the workspace's primary harness and keeps failover. A pinned harness that hits a usage limit does not fail over: the resolution is deferred and retried on later polls, and after three consecutive deferrals the PR waits until its head or base moves again (or until you run `devintern resolve-conflicts <pr-url>`). Changes live-reload; a manual `devintern resolve-conflicts <pr-url>` uses the normal environment.

Set `conflict_resolution = "disabled"` to turn automatic conflict resolution off entirely: the worker stops watching for conflicts on the agent's PRs altogether — no detection, no queuing, no agent runs. A PR that conflicts with its base simply stays conflicted until someone resolves it (by hand, or on demand via `devintern resolve-conflicts <pr-url>`). Review feedback and @mention handling are unaffected. This is a valid choice when the team prefers to rebase manually, or when the agent is not trusted to resolve conflicts in a sensitive repository.

### How workspace automations differ from single-repo ones
Expand Down
2 changes: 1 addition & 1 deletion docs/pm/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ You can also override the harness for a single run with `--harness <name>` (CLI
2. Harness-specific env var (e.g. `OPENCODE_CLI_PATH` when `AGENT_HARNESS=opencode`)
3. Harness default command, located on your `PATH` (e.g. `claude`)

Common `AGENT_HARNESS` values include `claude-code`, `opencode`, `codex`, `cursor`, `grok`, `deepseek`, `antigravity`, `cline`, `goose`, `kilo-code`, `kimi`, `muse`, and `qwen`. If you do need to set a path explicitly, run `which` for the harness binary (`claude`, `opencode`, `codex`, `cursor-agent`, `grok`, `reasonix`, `agy`, `cline`, `goose`, `kilo`, `kimi`, `muse`, or `qwen`).
Common `AGENT_HARNESS` values include `claude-code` (alias `claude`), `opencode`, `codex`, `cursor`, `grok`, `deepseek`, `antigravity`, `cline`, `goose`, `kilo-code`, `kimi`, `muse`, and `qwen`. If you do need to set a path explicitly, run `which` for the harness binary (`claude`, `opencode`, `codex`, `cursor-agent`, `grok`, `reasonix`, `agy`, `cline`, `goose`, `kilo`, `kimi`, `muse`, or `qwen`).

**Cursor note:** The Cursor harness uses Cursor's headless `cursor-agent` CLI (not a command named `cursor`). Cursor also installs an `agent` alias, but devpm looks for `cursor-agent` because other tools use the `agent` name too. Install Cursor and enable the CLI from Cursor's settings, then set `AGENT_HARNESS=cursor`.

Expand Down
4 changes: 3 additions & 1 deletion packages/agent-harness/src/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export const HARNESS_ALIASES: Readonly<
> = {
/** Binary name users often type as the harness id. */
agy: { target: "antigravity" },
/** Binary name (and the short form users type, e.g. `claude,codex,grok`). */
claude: { target: "claude-code" },
/** Product name users may type instead of the binary name. */
"muse-code": { target: "muse" },
/**
Expand Down Expand Up @@ -67,7 +69,7 @@ export function registerHarness(harness: AgentHarness): void {
/**
* Look up a registered harness by its machine-readable name.
*
* Resolves {@link HARNESS_ALIASES} (e.g. `agy` / `gemini` → `antigravity`)
* Resolves {@link HARNESS_ALIASES} (e.g. `claude` → `claude-code`, `agy` / `gemini` → `antigravity`)
* without emitting deprecation warnings; callers that need warnings should use
* {@link resolveHarness}.
*
Expand Down
10 changes: 10 additions & 0 deletions packages/agent-harness/tests/registry.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, expect, test } from "bun:test";
import { getHarness, listHarnesses, registerHarness } from "../src/registry.js";
import { parseHarnessList } from "../src/harness-chain.js";
import { ClaudeCodeHarness } from "../src/harnesses/claude-code.js";

describe("registry", () => {
Expand Down Expand Up @@ -46,6 +47,15 @@ describe("registry", () => {
expect(getHarness("muse-code")?.defaultPath).toBe("muse");
});

test("getHarness resolves the claude binary-name alias", () => {
expect(getHarness("claude")).toBe(getHarness("claude-code"));
});

test("parseHarnessList canonicalizes claude in a failover chain", () => {
expect(parseHarnessList("claude,codex,grok")).toEqual(["claude-code", "codex", "grok"]);
expect(parseHarnessList("claude,claude-code")).toEqual(["claude-code"]);
});

test("getHarness returns undefined for unknown name", () => {
expect(getHarness("nonexistent")).toBeUndefined();
});
Expand Down
Loading
Loading