Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/the-supervisor-changes-agents.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@openspec-ui/core": minor
"@openspec-ui/webui": minor
"@openspec-ui/server": minor
"@openspec-ui/cli": minor
"openspec-ui-vscode": minor
---

The supervisor can act on a failed stage (the-supervisor-changes-agents, ADR 0039 decision 4). Under `supervisor.mode: "act"`, set in a change's own `harness.json` together with `autonomyLevel: "autonomous"` and more than one attempt per stage, a stage whose failure is likely to pass is attempted again on the same agent, and one that repeating cannot fix is moved to the first agent of `supervisor.fallback.<stage>` the change's policy allows: `allowProviderChange` for another provider, `allowCostIncrease` for any agent but the local model. Every repeat and move is said in the chain's events, recorded in the audit log, and costs an attempt; a chain says `failed` only for a stage it does not try again. Every registered agent now names its provider. Under `advise`, the last-run suggestion names the fallback `act` would use. A change's Harness Settings offers Act under Autonomous, with the fallback per stage, the allowances and a note on cost and providers; `openspec-ui-cli run` prints why a stage is attempted again.
63 changes: 57 additions & 6 deletions HARNESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,12 +250,15 @@ question.

### `supervisor`

`{ "mode"?: "advise" | "off", "silentAfterSeconds"?: <positive integer>,
"waitingAfterSeconds"?: <positive integer> }`. Optional; absent means
`advise`, 600 and 60. Accepted in both files, and a change's own object is
merged key by key over the global one: a change that turns it off keeps
the workspace's thresholds. Set the mode from either Harness Settings
view; the thresholds are set in the file.
`{ "mode"?: "advise" | "off" | "act", "silentAfterSeconds"?: <positive integer>,
"waitingAfterSeconds"?: <positive integer>, "fallback"?: { "<stage>": ["<agent id>", ...] },
"allowCostIncrease"?: <boolean>, "allowProviderChange"?: <boolean> }`.
Optional; absent means `advise`, 600 and 60, no fallback and nothing
allowed. Accepted in both files, and a change's own object is merged key
by key over the global one: a change that turns it off keeps the
workspace's thresholds. Set the mode from either Harness Settings view;
the thresholds are set in the file. `act`, `fallback` and the two
allowances are a change's own only — see [Act](#act) below.

The supervisor ([ADR 0039](docs/adr/0039-the-supervisor-advises.md)) is
rules over records that already exist. It is not a process and not a
Expand Down Expand Up @@ -297,6 +300,54 @@ number in output is usually a duration or a count. The diagnosis quotes
the line it was found in, so it can be checked. A failure matching none is
diagnosed as unknown, and nothing more is said beneath it.

#### Act

Under `act` (the-supervisor-changes-agents, ADR 0039 decision 4) the
supervisor also does something about a stage whose agent failed, from the
failure's diagnosis and the change's own policy:

| Diagnosis | What it does |
| --- | --- |
| Repeating is likely to help (rate limit, service failure) | Attempts the stage again on the same agent |
| Repeating will not help (not installed, not signed in, blocked, no network) | Moves the stage to the first agent in `fallback.<stage>` that the policy allows and the stage has not run on in this chain |
| Not known | Nothing: the chain ends with the failure, as under `advise` |

Every repeat or move is another attempt of the stage, so it is spent from
`maxStageAttempts`; with no attempt left the chain ends with the failure.
A moved stage runs its new agent with that agent's own defaults: the old
entry's model, effort, budget and custom agent are not carried over.

The policy:

- **`allowProviderChange`** — a move to an agent of another provider
(Anthropic, GitHub, OpenAI, Google, DeepSeek, or the local model). The
change's files and prompts then go to that provider.
- **`allowCostIncrease`** — a move to any agent but the local model.
Nothing here knows one service's price against another's, so every move
but one to the local model counts as one that may cost more.

Both are false where absent, and a fallback the policy refuses is skipped
and named. `act` is refused in the workspace file, and in a change's file
that does not itself set `autonomyLevel: "autonomous"`: under a level that
asks a person, the person decides. A configuration whose `act` would have
one attempt per stage is refused when it resolves. A fallback list names
only stages that run an agent, only registered agents, never
`vscode-chat`, and no agent twice.

What it does is always said and recorded: a progress line ("the supervisor
moved apply from copilot-cli-acp to claude-cli-acp: …"), the next
attempt's `stageStarted` naming the new agent with the reason, and an
audit `message` entry from `supervisor` carrying `supervisorDecision`. A
chain yields no `failed` event for a stage it goes on to attempt again.
Under `advise`, a change's last-run suggestion names the fallback `act`
would move the stage to, and changes nothing.

In a change's Harness Settings, Act is offered only where the change's own
autonomy level is Autonomous; choosing it asks for a second attempt where
the change has one, and shows the fallback fields and the two allowances
with a note on cost and providers. `openspec-ui-cli run` prints the move,
and the reason under the next attempt's heading.

### `branches`

`{ "rebaseWhenBehind"?: <boolean>, "followMain"?: <boolean> }`. Optional;
Expand Down
4 changes: 4 additions & 0 deletions openspec/changes/the-supervisor-changes-agents/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
schema: spec-driven
created: 2026-10-05
follows:
- the-supervisor-advises
126 changes: 126 additions & 0 deletions openspec/changes/the-supervisor-changes-agents/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
## Context

See `proposal.md` and ADR 0039. What exists after `the-supervisor-advises`:

- Every failed run carries a `FailureDiagnosis` with `repeatHelps`
(`no`, `likely`, `unknown`), on its `failed` event and audit entry.
- `supervisor.mode` is `advise` or `off`; `act` is refused today.
- `HarnessChainRunner` runs each stage in an attempt loop bounded by
`maxStageAttempts`, and attempts a stage again only when a ceiling cut
it. A stage's `failed` event is yielded as it arrives, and it is the
chain's terminal event (ADR 0012): nothing may follow it.
- A stage's agent comes from `stepAgents.<stage>`.

## Goals / Non-Goals

**Goals:**

- A chain under `act` survives a failure a repeat or another agent fixes.
- The policy is the person's, written in the change's own file, and every
move is said and recorded.

**Non-Goals:**

- Stopping a run that is still going. The supervisor acts only on a stage
that has already ended in failure; a silent run is still only pointed
out (ADR 0039 decision 3).
- Judging which agent is better. The order is the person's list.
- Moving a model or an effort with the agent: an entry's settings belong
to its agent, as `mergeStepAgent` already holds for a change of agent.
- Acting outside a chain: a single run or a delegated item is not moved.

## Decisions

1. **`act` needs three things in the change's own file.**
- `supervisor.mode: "act"` is refused in the global file, as
`autonomyLevel: "autonomous"` is: it widens what runs unattended.
- It is refused unless the same file sets `autonomyLevel: "autonomous"`:
under a level that asks a person, the person is the one deciding.
- It is refused unless the resolved `maxStageAttempts` is above 1: a
repeat or a move is an attempt of the stage, under the one ceiling
every reason shares (HARNESS.md, `maxStageAttempts`). A setting that
can never act is not accepted quietly.

2. **The policy keys.**
- `supervisor.fallback`: `{ <stage>: [agentId, ...] }` for the stages
that run an agent. Each id must be a registered agent other than
`vscode-chat` (a chain cannot hand a retried stage to the editor's
chat), and appear once. Accepted in either file: a list says what may
be used, and only `act` uses it.
- `supervisor.allowCostIncrease`, `supervisor.allowProviderChange`:
booleans, absent meaning false, per change only (ADR 0018: what widens
authority is never inherited).

3. **A provider per agent, in the registry.** `AgentDescriptor.provider`:
`anthropic` (claude), `github` (copilot), `openai` (codex), `google`
(gemini), `deepseek`, `local` (both local LLM agents), `vscode`
(vscode-chat). A move to another provider sends the change's files to
another company, which is the reason it needs its own allowance.
A move is a cost increase unless it is to a `local` agent: nothing here
knows one service's price against another's, and the safe reading of
"not known" is "may cost more".

4. **The decision is a pure function in `supervisor.ts`.**
`superviseFailure({ stage, current, tried, diagnosis, supervisor })`
returns:
- `{ action: "repeat", agent: current }` where repeating is likely to
help;
- `{ action: "move", agent }` where it will not, naming the first agent
of the stage's fallback list that has not been tried in this chain's
stage and that the policy allows;
- `{ action: "none", why }` otherwise, saying which rule refused each
candidate ("claude-cli-acp is another provider, and
allowProviderChange is false").

5. **The chain holds a stage's failure until it is decided.** `runStage`
no longer yields a `failed` event that came from the stage's agent; it
keeps it on the chain's state and returns `failed`. The attempt loop
then:
- under `act`, with an attempt left and a decision to repeat or move,
yields a `progress` event saying what the supervisor did and why,
records an audit `message` entry from `supervisor`, and starts the
next attempt, whose `stageStarted` names the agent and carries the
reason in `previousAttemptReason`;
- otherwise yields the held `failed` event, unchanged, and the chain
ends as before.
So the terminal contract holds: a `failed` event is still the last
event of the chain it ends, and only one that ends it is yielded.
A moved stage runs its new agent with that agent's own defaults: the
model, effort, budget and custom agent of the old entry are not carried
over.

6. **Under `advise`, the last-run suggestion names the fallback.** Where
the change's last run failed at a stage that cannot simply be repeated,
and the stage has a fallback the policy allows, the suggestion says
which agent `act` would move it to. It still changes nothing.

7. **Surfaces.**
- The change's Harness Settings: the Supervisor choice gains Act, offered
only where the change's own autonomy level is Autonomous, with a note
on cost and providers; a field per stage for its fallback agents; the
two allowances. The global view does not offer Act or the allowances.
Choosing Act where the change would have one attempt per stage sets
`maxStageAttempts` to 2 in the change's file and says so in the note:
with one attempt Act could do nothing, and the configuration would be
refused when it resolves. A change whose autonomy level is moved away
from Autonomous while Act is chosen is told that saving will be
refused.
- The audit entry is a `message` from `supervisor` with
`supervisorDecision` (`action`, `stage`, `from`, `to`, `because`), so no
counter of runs reads it as one.
- The chain's timeline shows the `progress` line and the next stage's
reason, as it shows any repeated attempt.
- `openspec-ui-cli run` prints a repeated or moved stage's reason under
its heading.

## Risks / Trade-offs

- **A move can cost money.** It is off unless the change's file says
`act`, and a move to a paid agent needs `allowCostIncrease` as well.
- **A move can send the change to another company.** It needs
`allowProviderChange`.
- **A repeat of a rate-limited stage may hit the limit again.** It is one
attempt under `maxStageAttempts`, and the second failure ends the chain
if nothing is left.
- **Holding the failure delays it by the decision**, which is a pure
function over what the chain already holds.
66 changes: 66 additions & 0 deletions openspec/changes/the-supervisor-changes-agents/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
## Why

[ADR 0039](../../../docs/adr/0039-the-supervisor-advises.md), decision 4,
and the owner's answers of 2026-10-04: the supervisor may change the agent
of a stage when one fails, where a change's configuration allows it, and
always says so. The policy is configured: which agents a stage falls back
to, and whether a fallback may cost more or go to another provider.

`the-supervisor-advises` diagnoses a failure and says whether repeating it
can help. Today a chain that fails on "not signed in" or "not installed"
ends there, and one cut by a passing rate limit or server error ends there
too: `maxStageAttempts` repeats only a stage a ceiling cut.

## What Changes

- **`supervisor.mode: "act"`**, in a change's own `harness.json` only, and
only with `autonomyLevel: "autonomous"` and `maxStageAttempts` above 1.
Under `act`, when a stage fails, the chain asks the supervisor:
- repeating is likely to help (rate-limited, server error): the stage is
attempted again on the same agent;
- repeating will not help (not installed, not signed in, blocked,
network): the stage moves to the next agent of
`supervisor.fallback.<stage>` that the policy allows;
- otherwise, or with no attempt left, the chain fails as it does today.
- **The policy.** `supervisor.fallback`, an ordered list of agents per
stage, in either file. `supervisor.allowCostIncrease` and
`supervisor.allowProviderChange`, per change only, both false by default.
A move to an agent of another provider (Anthropic, GitHub, OpenAI, Google,
DeepSeek, the local model) needs the second; any move except to the local
model needs the first, since nothing here knows one service's price
against another's.
- **Always said.** The chain's timeline shows the move and why: "the
supervisor moved apply from copilot-cli-acp to claude-cli-acp: the agent
is not signed in". The audit log records it. The move is never silent,
and a policy that refused a fallback says which rule refused it.
- **Under `advise`**, a change whose last run cannot be repeated is told
which fallback agent the policy would allow.
- **From the UI.** A change's Harness Settings offer Act, with a warning
about cost and providers, the fallback agents per stage, and the two
allowances.

## Capabilities

### New Capabilities

(none)

### Modified Capabilities

- `agentic-harness`: `act`, the fallback policy, and how a chain repeats
or moves a failed stage.
- `shared-ui`: the change's Harness Settings for them.
- `ci-cli`: `run` says when the supervisor moved or repeated a stage.

## Impact

- `packages/core`: `harness-config.ts` (the new keys and their rules),
`agents/registry.ts` (each agent's provider), `supervisor.ts` (the
decision), `harness-chain-runner.ts` (holding a stage's failure until the
decision, and acting on it).
- `packages/webui`: the change's Harness Settings.
- `packages/cli`: `render-run.ts`.
- `packages/extension`: regenerated schemas.
- `HARNESS.md`.
- No event kind is added: a move is a `progress` event and the next
`stageStarted`, which already carries the attempt and its reason.
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
## ADDED Requirements

### Requirement: The supervisor acts only where a change allows it

`supervisor.mode: "act"` SHALL be accepted only in a change's own
`harness.json`, only where that file sets `autonomyLevel: "autonomous"`,
and only where the resolved `maxStageAttempts` is above 1. Otherwise it
SHALL be refused where the configuration resolves, naming the rule.

`supervisor.allowCostIncrease` and `supervisor.allowProviderChange` SHALL
be accepted only in a change's own file and SHALL mean false where absent.
`supervisor.fallback` SHALL be accepted in either file, as an ordered list
of registered agents per agent stage, without `vscode-chat` and without a
repeated id.

#### Scenario: Act in the global file

- **WHEN** `openspec/agent-harness.json` sets `supervisor.mode: "act"`
- **THEN** resolving the configuration fails and says act is set per change

#### Scenario: Act without an attempt to spend

- **WHEN** a change's file sets `act` and `maxStageAttempts` resolves to 1
- **THEN** resolving the configuration fails and names `maxStageAttempts`

### Requirement: A failed stage is repeated or moved under act, and said

Under `act`, when a chain's stage fails and an attempt is left, the chain
SHALL:

- attempt the stage again on the same agent where the failure's diagnosis
says repeating is likely to help;
- move the stage to the first agent of `supervisor.fallback.<stage>` not
yet tried in it that the policy allows, where the diagnosis says
repeating will not help;
- otherwise end with the stage's failure, as without `act`.

A move to an agent of another provider SHALL need
`allowProviderChange`, and a move to any agent that is not the local model
SHALL need `allowCostIncrease`. A moved stage SHALL run its new agent with
that agent's defaults.

Every repeat and move SHALL be said in the chain's events and recorded in
the audit log, with the agent, the cause and, for a fallback refused, the
rule that refused it. A chain SHALL yield no `failed` event for a stage it
goes on to attempt again.

#### Scenario: Not signed in, with a fallback allowed

- **WHEN** under `act`, `apply` fails on `copilot-cli-acp` diagnosed as not
signed in, its fallback is `claude-cli-acp`, and both allowances are true
- **THEN** the chain says it moved apply to `claude-cli-acp` because the
agent is not signed in, records it, and attempts apply on
`claude-cli-acp`

#### Scenario: A fallback the policy refuses

- **WHEN** the same happens with `allowProviderChange` false
- **THEN** the chain ends with apply's failure, and says the fallback
`claude-cli-acp` was refused because it is another provider

#### Scenario: A rate limit

- **WHEN** under `act`, a stage fails diagnosed as rate-limited with an
attempt left
- **THEN** the stage is attempted again on the same agent, and the chain
says why

#### Scenario: Advise names the fallback

- **WHEN** under `advise`, a change's last run failed at apply for a cause
repeating cannot fix, and apply has an allowed fallback
- **THEN** the supervisor's suggestion names that agent and changes nothing
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
## ADDED Requirements

### Requirement: The terminal says when the supervisor moved a stage

`openspec-ui-cli run` SHALL print, under a repeated or moved stage's
heading, why it was attempted again and on which agent.

#### Scenario: A moved stage in the terminal

- **WHEN** a chain run from the terminal moves apply to another agent
- **THEN** the heading names the new agent and the attempt, and the line
under it says the supervisor moved it and why
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
## ADDED Requirements

### Requirement: A change's Harness Settings set what the supervisor may do

A change's Harness Settings SHALL offer Act among the supervisor's modes
only where the change's own autonomy level is Autonomous, with a note that
under Act the supervisor may start another agent, which can cost money and
send the change's files to another provider. They SHALL offer each agent
stage's fallback agents and the two allowances, and SHALL save them with
the rest. The global Harness Settings SHALL NOT offer Act or the
allowances.

#### Scenario: Turning Act on

- **WHEN** a person sets a change's autonomy level to Autonomous, chooses
Act, names `claude-cli-acp` as apply's fallback and saves
- **THEN** the change's `harness.json` carries `supervisor.mode: "act"` and
`supervisor.fallback.apply: ["claude-cli-acp"]`, and the note about cost
and providers was shown beside the choice
Loading
Loading