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
8 changes: 8 additions & 0 deletions .changeset/applying-a-configuration-turns-act-off.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@openspec-ui/core": patch
"@openspec-ui/webui": patch
"@openspec-ui/server": patch
"openspec-ui-vscode": patch
---

Applying a named configuration to a change under the supervisor's Act no longer fails (applying-a-configuration-turns-act-off). Every named configuration sets Semi-autonomous, and Act acts only under Autonomous, so applying one - or saving a change's Harness Settings with another autonomy level - turns Act off instead of refusing the write, keeps the fallback agents and allowances, and says so where it says what was applied or saved. A file written by hand that sets both Act and another level is not run, but it now opens in the change's Harness Settings, which put it right on Save.
12 changes: 12 additions & 0 deletions HARNESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,18 @@ 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.

**An edit that lowers the level turns Act off**
(applying-a-configuration-turns-act-off). Applying a named configuration
to a change - every one sets Semi-autonomous - or saving a change's
Harness Settings with an autonomy level other than Autonomous removes
`supervisor.mode` where it was `act`, so the change follows the
workspace's mode, and says so where it says what was applied or saved.
The fallback agents and the two allowances stay in the file: choose
Autonomous and Act again to bring it back as it was. Only a file written
by hand that says both `act` and another level is not run and is not
written, since nothing chose between them; it still opens in the change's
Harness Settings, which say Act turns off and put it right on Save.

### `branches`

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

`the-supervisor-changes-agents` accepts `supervisor.mode: "act"` only in a
change's own file that also sets `autonomyLevel: "autonomous"`
(ADR 0018: what widens authority is per-change only). The rule is checked
when a file is written and when it resolves. Applying a named
configuration lays the configuration over the change's file and keeps
every other key (applying-a-template-keeps-the-rest); every named
configuration sets `semi-autonomous`; so the result kept Act and lost
Autonomous, and was refused.

## Decisions

1. **An edit that lowers the level turns Act off.** One function in core,
`withoutActItCannotUse(config)`, removes `supervisor.mode` where it is
`act` and the same config's `autonomyLevel` is not `autonomous`, and
says whether it did. A `supervisor` left empty is removed. The fallback
agents and the allowances stay: they do nothing without Act, and they
are what a person set.
- *Alternative: refuse with a clearer message.* Rejected by the owner:
applying a configuration is a decision to change the level, and the
configuration should apply.
- *Alternative: keep Autonomous instead.* Rejected: it would widen what
the configuration says, which is the direction ADR 0018 forbids
guessing in.
2. **Where it applies.** `changeTemplateConfigToWrite`, the one function
every surface applies a configuration to a change through (extension,
standalone settings view, standalone run dialog), and
`changeConfigToSave`, which a change's Harness Settings save through.
3. **It is said.** The extension's applied note and the settings view's
applied message add that the supervisor's Act is off, because it acts
only under Autonomous and the configuration sets another level. The
settings view's note under the supervisor, which said saving would be
refused, says Act turns off when the form is saved.
4. **A hand-written contradiction is refused where it would do something,
and opens where it can be put right.** Found in the owner's check: the
rule was part of reading a file, so a file written by hand with Act and
a lower level could not even open in its Harness Settings, the one
place that would fix it. It moves to `actWithoutAutonomousProblem`,
checked where a change's file is written and where it resolves for a
run (which names both ways out), and not where it is read. The
settings view then opens it, says Act turns off on saving, and offers
Save with nothing else changed. The product still does not pick for a
file that says both: it refuses to run it and to write it.

## Risks / Trade-offs

- **A person may not notice Act went off.** It is said on the surface that
applied or saved it, and the change's Harness Settings show the mode.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
## Why

Reported by the owner on 2026-10-05: in a workspace whose changes run
under `supervisor.mode: "act"`, applying any named configuration to a
change failed with "supervisor.mode "act" needs autonomyLevel
"autonomous" in the same file". Every named configuration sets
`autonomyLevel: "semi-autonomous"`, and applying one keeps the rest of the
change's file, `supervisor` included; the file it produced broke a rule
the product itself states, so the write was refused. The same happened in
a change's Harness Settings when the autonomy level was moved away from
Autonomous while Act was chosen: the view only warned that saving would
be refused.

The owner's rule: configurations change freely, by rules a person can
follow, and are refused only where refusing is truly needed.

## What Changes

- Where a person's own edit takes a change's autonomy level below
Autonomous - a named configuration applied, or the level changed in the
change's Harness Settings - an Act the change had is turned off rather
than refused: the supervisor's mode is removed, so the change follows
the workspace's (Advise unless the workspace says Off). The fallback
agents and the two allowances are kept, so choosing Act again under
Autonomous restores what was set.
- Every surface that does this says so where it says what was applied or
saved.
- A file written by hand that sets Act without Autonomous is refused
where it would act - a run - and where it is written, but it opens in
its Harness Settings, which put it right on saving: nothing chose
between the two, and only a person's save does.

## Capabilities

### Modified Capabilities

- `agentic-harness`: applying a named configuration turns off an Act it
leaves without Autonomous.
- `shared-ui`: a change's Harness Settings turn Act off, and say so, when
the autonomy level leaves Autonomous.

## Impact

- `packages/core`: `harness-templates.ts` (the rule as one function, and
`changeTemplateConfigToWrite` applying it).
- `packages/webui`: `harness-settings-parts.tsx` (`changeConfigToSave`, the
applied message), `ChangeHarnessSettingsView.tsx` (the note).
- `packages/extension`: `commands.ts` (the applied note).
- `HARNESS.md`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
## ADDED Requirements

### Requirement: Applying a configuration turns off an Act it leaves without Autonomous

Applying a named configuration to a change SHALL NOT fail because the
change's file has `supervisor.mode: "act"` and the configuration sets an
autonomy level other than `autonomous`. The written file SHALL instead
carry no `supervisor.mode`, SHALL keep the change's `supervisor.fallback`,
`allowCostIncrease` and `allowProviderChange`, and the surface that
applied it SHALL say that Act was turned off and why.

A change's file that sets `act` with another level SHALL be refused where
it is written and where it resolves for a run, naming how to put it
right, and SHALL NOT be refused where it is read, so that its Harness
Settings open.

#### Scenario: A named configuration over a change under Act

- **WHEN** a change's file sets `autonomyLevel: "autonomous"`,
`maxStageAttempts: 2` and `supervisor` with `mode: "act"` and a fallback
for apply, and Balanced is applied to it
- **THEN** the file is written with `autonomyLevel: "semi-autonomous"`, a
`supervisor` with the fallback and no `mode`, and the applied note says
the supervisor's Act is off because Balanced sets Semi-autonomous

#### Scenario: A hand-written contradiction

- **WHEN** a change's `harness.json` with `supervisor.mode: "act"` and
`autonomyLevel: "semi-autonomous"` is written through the product
- **THEN** the write is refused, naming the rule

#### Scenario: The same contradiction, written by hand

- **WHEN** such a file is already on disk
- **THEN** reading it succeeds, and resolving it for a run fails, saying
to save the change's Harness Settings to turn Act off or to set
`autonomyLevel: "autonomous"`
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
## ADDED Requirements

### Requirement: A change's Harness Settings turn Act off when the level leaves Autonomous

When a change's Harness Settings have Act chosen and the autonomy level
is set to anything but Autonomous, by hand or by a named configuration,
the view SHALL say that Act turns off when the form is saved, and saving
SHALL write the change's file without `supervisor.mode`, keeping its
fallback agents and allowances. Saving SHALL NOT be refused for it.

#### Scenario: The level moved away from Autonomous

- **WHEN** a change under Act has its autonomy level set to Inherit in its
Harness Settings and is saved
- **THEN** the note under the supervisor said Act turns off on saving, and
the saved file has no `supervisor.mode` and the same fallback agents

#### Scenario: A file written by hand with Act and another level

- **WHEN** a change's Harness Settings are opened on a file that sets
`act` with `semi-autonomous`
- **THEN** they open, say Act turns off on saving, and offer Save without
any other edit; saving writes the file without `supervisor.mode`
76 changes: 76 additions & 0 deletions openspec/changes/applying-a-configuration-turns-act-off/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
Reported by the owner on 2026-10-05: applying any named configuration to
a change under Act failed. Rule: configurations change freely by rules a
person can follow, and are refused only where truly needed.

## 1. Core

- [x] 1.1 `harness-config.ts`: `withoutActItCannotUse` (design.md
decision 1). Tests in `harness-config.test.ts`: act with another level
loses its mode and keeps fallback and allowances; act under autonomous
unchanged; a `supervisor` left empty is removed; a hand-written file
with act and another level is still refused.
Placed in `harness-templates.ts`, beside the function that applies
configurations, since `harness-config.ts` is not browser-safe; with
`actTurnedOff` and `actTurnedOffNote` for the surfaces. Four cases in
`harness-templates.test.ts`; the hand-written refusal in
`harness-config.test.ts`. 266 passed across both.
- [x] 1.2 `harness-templates.ts`: `changeTemplateConfigToWrite` applies it.
Test in `harness-templates.test.ts`: every change-scope configuration
over a change under Act writes a file the validator accepts, with no
mode and the fallback kept.
Every change-scope configuration over a change under Act is written
and accepted by `writeChangeHarnessConfig` (`harness-config.test.ts`),
with no mode and the fallback kept (`harness-templates.test.ts`).
- [x] 1.3 Found in the owner's check of 4.3: a file written by hand with
Act and another level did not open in its Harness Settings ("Load
failed"), since the rule was part of reading it. `harness-config.ts`:
`actWithoutAutonomousProblem`, checked on write and on resolve, not on
read (design.md decision 4); the settings view offers Save on such a
file with nothing else changed. Tests: `harness-config.test.ts` (read
succeeds, resolve refuses and names the way out), 267 passed with
`harness-templates`; `ChangeHarnessSettingsView.test.tsx`, 32 passed.

## 2. Surfaces

- [x] 2.1 `packages/webui`: `changeConfigToSave` applies it; the
supervisor's note says Act turns off on saving; the applied message
says so after a configuration. Tests in
`ChangeHarnessSettingsView.test.tsx`.
`changeConfigToSave` and the applied message; the standalone run
dialog's applied note too (`applyTemplateToChange` returns whether Act
went off). Two cases replace the old "will be refused" one; 31 passed
in the file, webui 713.
- [x] 2.2 `packages/extension`: the applied note says Act was turned off.
Test beside the command's existing tests.
One case in `commands.test.ts`, 163 passed.

## 3. Documents

- [x] 3.1 `HARNESS.md`, `supervisor`, Act: what turns it off.
"An edit that lowers the level turns Act off".
- [x] 3.2 A changeset: core, webui, server, extension, patch.
`.changeset/applying-a-configuration-turns-act-off.md`.

## 4. Checks

- [x] 4.1 `npm run typecheck && npm run lint`, and every test project, each
on its own where the root run would exceed a background limit.
Typecheck clean; lint 0 errors (3 warnings in lines this change did
not touch); seven script tests pass. Core 2101, core-git-subprocess 67,
cli 201, webui 713, server 122, extension 503 passed. After 1.3: core
2102, core-git-subprocess 67, cli 201, webui 714, server 122, extension
503 passed; typecheck and lint as before.
- [x] 4.2 `openspec validate applying-a-configuration-turns-act-off
--strict`, and the merge gate with the worktree's absolute path as
`--cwd`.
Valid; the gate with `--cwd C:/Prog/.worktrees/OpenSpec-UI/applying-a-configuration-turns-act-off`
reports only 4.3 open (Human-only).
- [x] 4.3 **Human-only**: in the owner's workspace, apply a named
configuration to a change under Act, and change its autonomy level in
its Harness Settings; both apply, and say Act is off.
Closed by the owner on 2026-10-06 in an Extension Development Host on a
copy of their workspace's changes: a named configuration applied and an
autonomy level changed over Act, both saved and said Act is off; Act
came back with its fallbacks; the hand-written contradiction opened and
was put right on Save. Ticked on the owner's word in chat: everything
works, close it.
2 changes: 1 addition & 1 deletion packages/core/src/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export type { ChangeDateEvidence, ChangeDateSource, ChangeDates, DatedFact } fro
// Pure over a resolved config, with only type imports from the Node-side
// modules — safe in the browser bundle, unlike harness-config.js itself.
export { findHarnessConfigLimits, groupHarnessFindings } from "./harness-config-findings.js";
export { agentForEveryStageToWrite, changeTemplateConfigToWrite, HARNESS_TEMPLATES, stepAgentsForTemplate, templateConfigToWrite, templatesForScope } from "./harness-templates.js";
export { actTurnedOff, actTurnedOffNote, agentForEveryStageToWrite, changeTemplateConfigToWrite, HARNESS_TEMPLATES, stepAgentsForTemplate, templateConfigToWrite, templatesForScope, withoutActItCannotUse } from "./harness-templates.js";
export { HARNESS_EFFORT_LEVELS, effortLevelCollisions, resolveEffortLevel } from "./harness-effort-level.js";
export type { HarnessEffortLevel, ResolvedEffort } from "./harness-effort-level.js";
export { recommendTemplate } from "./harness-recommendation.js";
Expand Down
29 changes: 28 additions & 1 deletion packages/core/src/harness-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {
// merged a stage entry by spread and kept `customAgent`, `mergeStepAgent`
// named three fields and dropped it, so the same entry came out of the two
// differently. See a-stage-override-keeps-its-custom-agent.
import { HARNESS_TEMPLATES, templateConfigToWrite } from "./harness-templates.js";
import { changeTemplateConfigToWrite, HARNESS_TEMPLATES, templateConfigToWrite, templatesForScope } from "./harness-templates.js";

// suite-survives-a-loaded-machine:
// measured 2026-09-05 for this file alone at 734ms test time (3.91s wall)
Expand Down Expand Up @@ -1104,6 +1104,33 @@ describe("supervisor act", () => {
await expect(write({ apply: [] })).rejects.toThrow(/non-empty list/);
});

// applying-a-configuration-turns-act-off 1.1, 1.2: what a named
// configuration writes over a change under Act is a file that is accepted;
// the same contradiction written by hand is still refused.
it("accepts what a named configuration writes over Act, and refuses the contradiction by hand", async () => {
const root = await temporaryRoot();
const own = { autonomyLevel: "autonomous" as const, maxStageAttempts: 2, supervisor: act };
await writeChangeHarnessConfig(root, "demo", own);
for (const template of templatesForScope("change")) {
await writeChangeHarnessConfig(root, "demo", changeTemplateConfigToWrite(template, await resolveHarnessConfig(root), own));
}
await expect(writeChangeHarnessConfig(root, "demo", { ...own, autonomyLevel: "semi-autonomous" }))
.rejects.toThrow(/needs autonomyLevel "autonomous" in the same file/);
});

// applying-a-configuration-turns-act-off: a hand-written contradiction
// opens, so it can be put right in its Harness Settings, and nothing runs
// on it.
it("reads a hand-written Act without Autonomous, and refuses to resolve it for a run", async () => {
const root = await temporaryRoot();
const file = path.join(root, "openspec", "changes", "demo", "harness.json");
await mkdir(path.dirname(file), { recursive: true });
await writeFile(file, JSON.stringify({ autonomyLevel: "semi-autonomous", maxStageAttempts: 2, supervisor: act }), "utf8");

expect((await readChangeHarnessConfig(root, "demo"))?.supervisor?.mode).toBe("act");
await expect(resolveHarnessConfig(root, "demo")).rejects.toThrow(/Open the change's Harness Settings and save to turn Act off/);
});

it("refuses the allowances in the workspace's file", async () => {
const root = await temporaryRoot();
await expect(writeGlobalHarnessConfig(root, { supervisor: { allowCostIncrease: true } } as never))
Expand Down
Loading
Loading