Skip to content

feat(harness)!: steer the running agent by default - #7377

Closed
waynesun09 wants to merge 6 commits into
steer-receiptsfrom
steer-default
Closed

waynesun09 wants to merge 6 commits into
steer-receiptsfrom
steer-default

Conversation

@waynesun09

@waynesun09 waynesun09 commented Sep 16, 2026

Copy link
Copy Markdown
Member

Closes the stack. Fourth PR; base is #7376.

Steering is on by default from the release that carries this. A harness that says nothing about steer: now watches for follow-up runs and delivers work-item updates — a push, a comment, a stage command — into the running agent instead of letting the queued run redo the work. This follows the 2026-09-14 meeting recorded on #6957, which made run continuation the default with no flag; making steering the default alongside it is the request that produced this stack, and ADR 0113's Decision section in this PR is where that is recorded. #6957's validation criteria still describe steering as off by default and should be read as superseded by ADR 0113.

The opt-out is one spelling. steer: {enabled: false} turns steering off, and nothing else does. enabled is a pointer internally because absent and false have to mean different things: with a plain bool, steer: {max_steers: 3} would unmarshal to false and silently opt a harness out while it is trying to raise the cap. The file already uses the pointer-plus-default shape for every other on-by-default block, so this follows the local pattern. Composition is unchanged — a child that omits the block inherits the base's whole block, so a base opt-out is inherited rather than overwritten by the default; a test pins that direction.

One behaviour change beyond the flag. The runner used to warn "Steering disabled: " on every declined watch, which under opt-in only ever fired for a harness that asked and did not get it. With the default on, that line would fire once per iteration on every local run, every GitLab run, and every behaviour test whose runtime cannot take a message. Declines are now classified in the one eligibility ladder: not in Actions, GitLab, a runtime that cannot take a message, and no work item are ordinary and stay quiet unless the harness named enabled: true; a missing job token or run id is an environment defect and is announced to everyone, because with the default on almost nobody sets enabled: true and a real fault would otherwise go silent fleet-wide. Dropping the defect branch fails two tests; dropping the opt-out guard from the skip check fails two more (on the output assertion — the bypass fails open, so the return value alone tests nothing). Nothing else was gated on the old default; the skip check now runs for every harness that has not opted out, which is the point.

Verification. Three tests fail when the default is flipped back to off; four different tests fail when the reader ignores the pointer and returns the default, so the opt-out is proven independently of the default. internal/harness, internal/cli and internal/steerwatch pass under the race detector.

Before this merges — four things are not yet settled, and the docs now say so rather than claim them.

  1. The fleet agent definitions in feat(agents): re-check the work item once and act on runner mid-run updates agents#1163 are unmerged, so the agents have not been taught the envelope. With the default on, every run would deliver an envelope to definitions that do not yet expect one.
  2. No end-to-end steer inside OpenShell from a real workflow run has been observed. Each runtime's transport was driven in isolation; the whole path has not.
  3. The receipt in fix(cli): authenticate the steer receipt with the job token #7376 is read under the job token's login resolved through the GraphQL viewer query; no offline test can prove that string equals the author GitHub records on the comment. The writer logs the author it gets back, so one live steered run in a test repository settles it.
  4. agents#1163 teaches the envelope to code, fix, review and triage only, so prioritize, retro and scribe must either learn the contract or set steer: {enabled: false} before the default reaches them — an eligible run on a definition that ignores the envelope still acks the delivery and still posts a receipt, so the queued run skips and the update is dropped silently, which is the one failure mode this design may not have.

The default should not reach a release on four unwatched paths. This PR is ready to review now and ready to merge when those are checked off.

BREAKING CHANGE: a harness that omits steer:, or sets only max_steers or poll_interval_seconds, now steers. Add steer: {enabled: false} to keep the previous behaviour. GitLab repositories and runs outside GitHub Actions are unaffected.

Stacked: #7007#6959#7376 → this.

Restacked onto main 6aa078bc7 on top of #7376's 7f38035ac; five commits. The review round also corrected docs/architecture.md's "opt-in per agent" line, replaced the getter-only default test with one that starts a real watcher for a harness with no steer: block, and aligned the two remaining "single pending run" lines in ADR 0113 and steering.md with the base's wording (the pending run is the last to queue, normally but not necessarily the newest event). The four preconditions are release gates — they belong before the release that carries this is cut, and nothing mechanical enforces them; a standalone agents change opting prioritize, retro and scribe out is prepared for the fourth.

@waynesun09
waynesun09 requested a review from a team as a code owner September 16, 2026 16:12
@waynesun09
waynesun09 added this pull request to stack #7259 September 16, 2026 16:12
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

feat(harness)!: steer the running agent by default

✨ Enhancement 📝 Documentation 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Flips ADR 0101 default: harnesses now steer follow-up runs unless they opt out with `steer:
 {enabled: false}`.
• Changes SteerConfig.Enabled to *bool so an absent block, true, and false are
 distinguishable, preventing partial configs (e.g. max_steers only) from silently disabling
 steering.
• Adds SteerExplicitlyEnabled() so the "Steering disabled" warning only fires when a harness
 explicitly asked for steering, avoiding noise now that it's on by default.
• Confirms composition semantics: a child harness with no steer: block inherits a base's opt-out
 rather than the new default, backed by a new test.
• Updates ADRs, contributing guide, harness reference, and agent docs to describe the on-by-default
 behavior and the single opt-out spelling.
Diagram

graph TD
  Y["harness.yaml"] --> S["SteerConfig.Enabled *bool"] --> R{"Enabled set?"}
  R -->|nil| D["DefaultSteerEnabled true"]
  R -->|false| O["Opt-out: steering off"]
  R -->|true| E["Explicit opt-in"]
  D --> W["startSteerWatcher"]
  E --> W
  W --> C{"Watch declined?"}
  C -->|yes and explicit| WARN["Log Steering disabled warning"]
  C -->|yes and default-only| SKIP["Silent skip"]
Loading
High-Level Assessment

Using a *bool with a package-level default constant is the standard, minimal-risk way to distinguish 'unset' from 'false' in Go config structs, consistent with the file's existing pattern for other on-by-default blocks (hooks, sanitizers). No meaningfully different approach (e.g. a tri-state enum or separate 'OptOut' field) offers real advantages here.

Files changed (12) +160 / -62

Enhancement (1) +22 / -2
harness.goChange SteerConfig.Enabled to *bool and add default-on resolution +22/-2

Change SteerConfig.Enabled to *bool and add default-on resolution

• Converts Enabled to *bool, adds DefaultSteerEnabled=true constant, updates SteerEnabled() to use BoolDefault, and adds SteerExplicitlyEnabled() to distinguish an explicit opt-in from the default.

internal/harness/harness.go

Bug fix (1) +12 / -4
steer.goGate 'Steering disabled' warning behind explicit opt-in +12/-4

Gate 'Steering disabled' warning behind explicit opt-in

• Updates comments and changes startSteerWatcher to log the decline warning only when the harness explicitly set 'enabled: true', avoiding warning spam now that steering is on by default.

internal/cli/steer.go

Tests (3) +65 / -7
steer_test.goAdd helper for building SteerConfig.Enabled pointer in tests +4/-1

Add helper for building SteerConfig.Enabled pointer in tests

• Introduces steerBoolPtr helper and updates test harness construction to use a *bool for Enabled.

internal/cli/steer_test.go

compose_test.goAdd composition tests for default-on steering and inherited opt-out +23/-1

Add composition tests for default-on steering and inherited opt-out

• Updates existing test to expect steering enabled when no harness mentions it, and adds a new test proving a child harness inherits a base's opt-out rather than the new default.

internal/harness/compose_test.go

harness_test.goUpdate harness tests for pointer-based Enabled and default-on semantics +38/-5

Update harness tests for pointer-based Enabled and default-on semantics

• Renames and rewrites tests to assert steering is enabled by default, adds tests for explicit opt-out persistence and SteerExplicitlyEnabled behavior.

internal/harness/harness_test.go

Documentation (7) +61 / -49
0101-steer-the-running-agent-on-work-item-updates.mdUpdate ADR 0101 decision and consequences for on-by-default steering +16/-16

Update ADR 0101 decision and consequences for on-by-default steering

• Rewrites the Decision and Consequences sections to state that steering is on by default with a per-harness opt-out via 'steer: {enabled: false}', replacing the prior opt-in framing.

docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md

fix.mdDocument fix agent's default steering behavior for /fs-fix +6/-6

Document fix agent's default steering behavior for /fs-fix

• Explains that a '/fs-fix' comment is now absorbed by an in-flight run by default, with cancel-and-restart reserved for harnesses that opt out.

docs/agents/fix.md

review.mdDocument review agent's default steering behavior for /fs-review +4/-4

Document review agent's default steering behavior for /fs-review

• Updates wording so '/fs-review' comment absorption is described as the default, with opt-out via 'steer: {enabled: false}'.

docs/agents/review.md

triage.mdDocument triage agent's default steering behavior for /fs-triage +4/-4

Document triage agent's default steering behavior for /fs-triage

• Updates wording so '/fs-triage' comment absorption is described as the default, with opt-out via 'steer: {enabled: false}'.

docs/agents/triage.md

steering.mdRewrite steering contributing guide for on-by-default configuration +25/-13

Rewrite steering contributing guide for on-by-default configuration

• Explains the pointer-based 'enabled' field, the opt-out spelling, updated example config, and clarifies that the fleet-agent-envelope, OpenShell end-to-end, and receipt-identity preconditions are still unobserved before the default reaches a release.

docs/contributing/steering.md

bugfix-workflow.mdUpdate user guide to describe steering as default behavior +4/-4

Update user guide to describe steering as default behavior

• Describes mid-run comment delivery as the default and notes the opt-out spelling for repositories that disable steering.

docs/guides/user/bugfix-workflow.md

harness-reference.mdUpdate harness reference default value and description for steer.enabled +2/-2

Update harness reference default value and description for steer.enabled

• Changes the documented default for 'steer.enabled' from false to true and clarifies that 'enabled: false' is the sole opt-out spelling.

docs/reference/harness-reference.md

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 16, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 4:14 PM UTC · Ended 4:33 PM UTC

Commit: 7c73f43 · View workflow run →

@qodo-code-review

qodo-code-review Bot commented Sep 16, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Accepted steering history is rewritten ⊘ Outdated 📜 Skill insight § Compliance
Description
docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md replaces the accepted opt-in
decision with an on-by-default decision instead of preserving the original record and adding a
superseding ADR. The rewrite changes the Decision, its security rationale, and the Consequences for
every repository, so later readers cannot distinguish the original decision from its replacement.
Code

docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md[R50-53]

+Steering is an extension of preserving that is **on by default**, with a per-harness opt-out:
+`steer: {enabled: false}` gets ADR 0113 alone and nothing more, and an absent block is the
+default, which is on. While the run in flight holds the work item it absorbs updates to that item
+itself — the queued follow-up run becomes the *notification*, not the worker — so the queued run
Relevance

●●● Strong

Recent ADR precedents require preserving accepted decisions and recording substantive changes in
superseding ADRs.

PR-#5244
PR-#6769
PR-#2465

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
ADR 0101 is marked Accepted, while the changed Decision reverses steering from opt-in to on by
default and the changed Consequences apply that reversal to every repository. The cited rules
prohibit substantive rewrites of accepted ADRs and require revised architectural decisions to be
recorded in a new superseding ADR.

Rule 1062057: Restrict modifications to accepted ADRs on main
Rule 1062058: Supersede accepted ADRs with new ADRs instead of modifying history
docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md[1-20]
docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md[48-73]
docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md[95-107]
Skill: writing-adrs

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
ADR 0101 was already Accepted, but this change rewrites its opt-in decision and consequences to make steering the default. Accepted architectural history must remain intact and a changed decision must be recorded in a new superseding ADR.

## Fix Focus Areas
- docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md[50-53]
- docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md[67-73]
- docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md[97-107]

## Recommended Fix
Restore ADR 0101's original Decision and Consequences, then add a new correctly numbered ADR that records the on-by-default decision and references ADR 0101 as superseded. Add only a short reciprocal supersession annotation to ADR 0101 and make the corresponding surgical architecture and problem-document updates required for the new accepted ADR.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Three agents ignore live updates ✗ Dismissed 🔗 Cross-repo conflict ≡ Correctness
Description
Harness.SteerEnabled now enables steering for an absent block, but the agents repository
deliberately excludes prioritize, retro, and scribe from the runner-update contract because their
harnesses did not opt in. Eligible GitHub runs for those harnesses can therefore deliver an envelope
that the agent definition ignores, while the queued follow-up may treat the update as consumed.
Code

internal/harness/harness.go[R264-267]

+	if h.Steer == nil {
+		return DefaultSteerEnabled
+	}
+	return BoolDefault(h.Steer.Enabled, DefaultSteerEnabled)
Relevance

●● Moderate

The cross-repository contract risk is substantive and acknowledged, but rollout timing may make
reviewers defer this finding.

PR-#6272
PR-#6909

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The PR enables steering whenever a harness omits the block. The agents repository’s contract test
explicitly says prioritize, retro, and scribe are absent because steering is opt-in, while each
corresponding harness omits a steering opt-out; this assumption becomes invalid under the new
default.

internal/harness/harness.go[257-267]
internal/runtime/steer_session.go[409-418]
External repo: fullsend-ai/agents, scripts/agent-recheck-contract-test.sh [19-30]
External repo: fullsend-ai/agents, harness/prioritize.yaml [13-20]
External repo: fullsend-ai/agents, harness/retro.yaml [13-20]
External repo: fullsend-ai/agents, harness/scribe.yaml [13-20]
External repo: fullsend-ai/agents, agents/prioritize.md [13-34]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Default-on steering activates prioritize, retro, and scribe even though their definitions do not implement the runner-update envelope contract. Coordinate the agents release by either teaching these agents the envelope contract or explicitly opting their harnesses out before this default ships.

## Fix Focus Areas
- internal/harness/harness.go[264-267]
- /cross_repos/agents/harness/prioritize.yaml[13-20]
- /cross_repos/agents/harness/retro.yaml[13-20]
- /cross_repos/agents/harness/scribe.yaml[13-20]
- /cross_repos/agents/scripts/agent-recheck-contract-test.sh[19-30]

## Recommended Fix
Before releasing the new default, update prioritize, retro, and scribe definitions to recognize and safely handle the steering envelope and include them in the contract test. If those definitions are not ready, add `steer: {enabled: false}` to their harnesses and remove each opt-out only after its prompt contract is implemented and tested.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Opt-outs do not cancel active runs ✓ Resolved 🐞 Bug ≡ Correctness
Description
docs/agents/fix.md says an explicit steering opt-out cancels the in-flight fix run and starts
another, but run preservation unconditionally disables cancellation. When a harness uses `steer:
{enabled: false}`, the active run finishes and the queued run subsequently processes current state
instead.
Code

docs/agents/fix.md[R99-100]

+  harness that opts out with `steer: {enabled: false}` cancels the in-flight
+  run and starts a new one instead.
Relevance

●●● Strong

The documentation directly contradicts the documented opt-out behavior; similar documentation
accuracy findings were accepted.

PR-#5457
PR-#3065

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The changed fix-agent documentation promises cancellation, while ADR 0113 states that every stage
uses cancel-in-progress: false; the steering guide also explicitly says an opted-out harness lets
the active run finish before the queued run works.

docs/agents/fix.md[95-100]
docs/ADRs/0113-preserve-the-agent-run-in-flight-on-work-item-updates.md[50-61]
docs/contributing/steering.md[402-406]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The fix-agent documentation incorrectly says that opting out of steering cancels the active run. Run preservation remains unconditional, so the active run finishes and the queued run then handles the current state.

## Fix Focus Areas
- docs/agents/fix.md[99-100]

## Recommended Fix
Replace the cancellation claim with wording that an opted-out harness lets the in-flight run finish and leaves the queued run to process the work item afterward.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Harness users see conflicting defaults ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
The updated steer reference says an absent block enables steering, but the following paragraph
still requires enabled: true and promises a reason for every declined watch even though warnings
are limited to explicit opt-ins. When users rely on the default with an unsupported runtime or
environment, steering remains enabled while the runner deliberately suppresses that diagnostic,
leaving the activation and warning behavior unclear.
Code

docs/reference/harness-reference.md[187]

+**`steer`** — Lets a run already in flight absorb updates to its work item — a push, a comment, a stage command such as `/fs-review` — instead of being cancelled and restarted from nothing ([ADR 0101](../ADRs/0101-steer-the-running-agent-on-work-item-updates.md); field-by-field in [steering.md](../contributing/steering.md#configuration)). On by default, including when the block is absent entirely; `enabled: false` is the opt-out, and it is the only spelling that turns steering off — a block that sets just `max_steers` or `poll_interval_seconds` still steers. Opting out means a run ends at its first result instead of holding its sandbox until it settles.
Relevance

●●● Strong

Reference documentation retains explicit-opt-in wording conflicting with the new default and warning
semantics.

PR-#6772
PR-#7113

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The changed reference establishes that an absent steer key enables steering, while the immediately
following paragraph retains the old explicit-opt-in prerequisite and unconditional warning promise.
The implementation instead defaults a nil value to true and emits declined-watch warnings only for
an explicit true pointer, proving that the surrounding user-facing documentation no longer matches
the changed default and output behavior.

Rule 2748504: Update docs/ references when changing user-facing behavior
docs/reference/harness-reference.md[187-189]
internal/harness/harness.go[257-267]
internal/cli/steer.go[280-290]
docs/reference/harness-reference.md[187-191]
internal/harness/harness.go[257-274]
internal/cli/steer.go[276-290]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The harness reference documents the new default but retains the old opt-in and warning behavior immediately afterward. The reference must consistently describe absent or unspecified `enabled` as on, explicit false as off, and declined-watch warnings as limited to explicit opt-ins.

## Fix Focus Areas
- docs/reference/harness-reference.md[187-189]

## Recommended Fix
Rewrite the paragraph after the `steer` entry so eligibility requires that steering has not been explicitly disabled and that the runtime and environment support steering. State that decline reasons are printed only when the harness explicitly sets `enabled: true`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Default warning behavior is untested ✓ Resolved 📘 Rule violation ▣ Testability
Description
startSteerWatcher now conditionally suppresses the ineligibility warning through
SteerExplicitlyEnabled, but no changed test asserts whether the warning is emitted. The modified
helper makes existing watcher tests use explicit enablement, while the implicit-default path has no
output assertion that would fail if warning suppression regressed.
Code

internal/cli/steer.go[R287-288]

+		if o.harness.SteerExplicitlyEnabled() {
+			o.printer.StepWarn("Steering disabled: " + reason)
Relevance

●● Moderate

Behavior-specific test coverage is often accepted, but recent coverage-only requests have also been
rejected.

PR-#1238
PR-#2860
PR-#6753

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The production branch adds new observable warning behavior, but the modified test helper only adapts
construction to *bool and existing assertions check the returned session rather than printer
output. The cited testing rule requires changed Go logic to have an assertion that verifies its
behavior.

Rule 1062049: Require tests for new or modified Go logic
internal/cli/steer.go[280-290]
internal/cli/steer_test.go[42-47]
internal/cli/steer_test.go[120-129]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new distinction between implicit default steering and explicit `enabled: true` controls operator-visible warnings, but the tests do not assert either output path. A regression could therefore restore noisy warnings for every ineligible default-on run or suppress warnings requested by explicit opt-in.

## Fix Focus Areas
- internal/cli/steer.go[280-290]
- internal/cli/steer_test.go[42-47]
- internal/cli/steer_test.go[120-129]

## Recommended Fix
Add table-driven watcher tests using a captured printer. Verify that an ineligible harness with no `steer` block emits no warning, an explicit `enabled: true` harness emits the reason, and an explicit opt-out starts no watcher and emits no warning.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 70 rules
✅ Cross-repo context — repo relationships
  Explored: repo: fullsend-ai/agents (branch: steer-recheck, sha: 5464a82e)
Review mode: ⚖️ Balanced: This changes runtime steering defaults, configuration semantics, composition inheritance, and watcher behavior across multiple code paths, creating meaningful behavioral and operational risk.

Grey Divider

Tip of the day
💡 Did you know, you can group findings by type and pick your Finding display, from Minimal to Full

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread docs/ADRs/0113-steer-the-running-agent-on-work-item-updates.md
Comment thread docs/reference/harness-reference.md Outdated
Comment thread internal/cli/steer.go Outdated
Comment thread docs/agents/fix.md Outdated
Comment thread internal/harness/harness.go
@github-actions

github-actions Bot commented Sep 16, 2026

Copy link
Copy Markdown

Site preview

Preview: https://515e3fd3-site.fullsend-ai.workers.dev

Commit: 354f82db084e125158d2a1cd07edcfcedfc0eefe

waynesun09 added a commit that referenced this pull request Sep 16, 2026
… agents

Four things from the qodo review on #7377.

fix.md said a harness that opts out "cancels the in-flight fix run and starts a
new one". That has been wrong since #7007 made preserving unconditional: no
stage job cancels a run in flight any more, so an opted-out harness lets the run
finish and the queued run works from the PR's current state. The sentence read
fine, which is why it survived two passes.

Grepping the rest for the same mistake turned up four more places where the
alternative to steering was still described as cancellation — bugfix-workflow.md
("instead of cancelling it"), this page's own opening, the receipt rationale
("costs more than cancelling does today", "where cancel-and-restart produces
one"), and the same comparison in ADR 0101's Decision. None is about the
opt-out, all are stale in the same way: they compare steering against a
behaviour that no longer exists. They now compare it against preserving alone,
which is what an opted-out harness actually gets, and what the queued run does
in either case.

harness-reference.md's eligibility paragraph still required `enabled: true` and
promised a printed reason for every declined watch, both of which this PR
changed. It now names the three conditions — not explicitly disabled, a runtime
that can take a message, a GitHub Actions job with a work item — and says the
reason is printed only when the harness set `enabled: true` itself.

The warning gate had no test. TestStartSteerWatcherDeclineMessage is
table-driven over an ineligible fixture: no block and a block that only tunes
max_steers stay quiet, `enabled: true` prints the reason, `enabled: false` stays
quiet. The max_steers row is there because it is the case the pointer exists for
— it must read as "not an explicit request", not as an opt-out.

The rollout paragraph gains a fourth open precondition. agents#1163 teaches the
envelope to code, fix, review and triage only; prioritize, retro and scribe are
outside it and no harness anywhere sets `steer:`, so with the default on an
eligible run on one of those three delivers an envelope the definition ignores,
the runtime acks it, the receipt is posted, and the queued run skips — the
update is dropped silently. The fix belongs in the agents repo, so this is
recorded as a gate rather than changed here.

ADR 0101 stays at 102 lines of content.

Assisted-by: Claude
Signed-off-by: Wayne Sun <gsun@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 16, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:35 PM UTC · Completed 5:00 PM UTC

Commit: 9f28f23 · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5 · Effort: high · Cost: $6.83

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 16, 2026

Copy link
Copy Markdown

Risk Assessment: moderate (2/5)

Details

Weighted composite (~2.27 -> 2, moderate) anchored to the prior round's 2/5 assessment: low Tier-1 metadata risk (no protected/security/CI/dependency paths, non-bot non-first-time author) dominates at 50% weight, offsetting elevated Tier-2 churn/fix-history in internal/harness/* and a Tier-3 mismatch where the PR's default-on flip supersedes the linked issue's own off-by-default acceptance criterion without a recorded issue comment. This score is informational and does not affect the review verdict, which independently rests on the fail-open finding above.

Previous run

Risk Assessment: moderate (2/5)

Details

Weighted composite (~2.25 -> 2) driven down by low Tier-1 metadata risk (no protected/security/CI/dependency paths, non-first-time non-bot author) despite a heavy-churn, fix-prone harness module in Tier-2 and a breaking on-by-default flip that contradicts the linked issue's own acceptance criterion in Tier-3; the sub-agent's independent recomputation did not anchor to the prior 4/high sticky-comment score, noting the prior score may have weighted the PR's self-applied risk/high label and admitted rollout gaps more heavily than the deterministic Tier1-3 signal buckets support. Orchestrator note: this divergence from the prior two 4/high assessments is presented as-is per the risk-assessment sub-agent's own output; it is informational and does not affect the review verdict, which independently rests on the fail-open finding above.

Previous run (2)

Risk Assessment: high (4/5)

Details

Re-review anchoring preserves the prior high (4/5): Tier 1 signals fall in the same buckets as the prior review despite growth from 14/507 to 19/602 lines (added tests and doc touch-ups), and Tier 2/3 signals still reaffirm risk - internal/harness/* remains a heavy-churn, fix-prone module, and the breaking on-by-default flip still contradicts the linked issue's own validation criterion with four release preconditions explicitly unmet per the PR body.

Previous run (3)

Risk Assessment: high (4/5)

Details

Re-review anchoring preserves the prior high (4/5): Tier 1 signals are unchanged (14 files/507 lines, large blast radius, no protected/security/dependency paths, same non-bot author) despite the branch restack, and Tier 2/3 signals reaffirm rather than mitigate risk - high churn and fix/revert density in internal/harness/*, plus a breaking fleet-wide default flip that still contradicts the linked issue own validation criteria while four release preconditions remain explicitly unmet.

Previous run (4)

Risk Assessment: high (4/5)

Details

Large-blast-radius breaking change that flips a fleet-wide harness default in a file with heavy recent churn, while directly contradicting the linked issue's own validation criteria and shipping despite the author's explicit acknowledgment that four release preconditions remain unmet.

Previous run (5)

Risk Assessment: moderate (2/5)

Details

Re-review anchoring preserved at moderate (2/5): Tier 1 signals remain in the same buckets as the prior assessment (13 vs 12 files, 392 vs 388 lines both in the 300-799 bucket, no protected/security/dependency changes, same non-bot/non-first-time author), Tier 2 confirms the same high-churn, multi-author, fix-heavy harness.go pattern already reflected in the prior score, and Tier 3 remains unchanged; the incremental commit since the prior round only made comment/doc-wording fixes with no logic change, so the composite stays at 2 (moderate), with the breaking default-behavior flip still mitigated by the documented per-harness opt-out.

Previous run (6)

Risk Assessment: moderate (2/5)

Details

Re-review anchoring preserved at moderate (2/5): Tier 1 signals remain in the same buckets as the prior assessment (12 files, 388 vs 304 lines both in the 300-799 bucket, no protected/security/dependency changes, same non-bot/non-first-time author), and Tier 2 confirms the same high-churn, multi-author, fix/revert-heavy core file pattern already reflected in the prior score; the incremental commit only added test coverage and doc-wording fixes to the same file set, so the composite stays at 2 (moderate), unchanged from the prior review, with the breaking-change default flip still mitigated by the documented per-harness opt-out.

Previous run (7)

Risk Assessment: moderate (2/5)

Details

Small doc/config-heavy PR (12 files, 304 lines, no protected/security-sensitive paths, no CI or dependency changes) scores low on Tier 1, but touches a high-churn, multi-author core file (internal/harness/harness.go) with fix/revert history, and is a fleet-wide breaking-change default flip mitigated by an available per-harness opt-out, netting a moderate composite score.

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 16, 2026

Copy link
Copy Markdown

Review

Findings

High

  • [fail-open] internal/harness/harness.go:275DefaultSteerEnabled = true and SteerEnabled() still have no envelope/role/feature-flag gate. startSteerWatcher (internal/cli/steer.go:300-312) keys only off SteerEnabled() and steerEligible(); SteerExplicitlyEnabled() only controls warning volume for ordinary declines — it does not refuse the watcher or withhold a receipt. marker()/steerMarkerFrom (steer.go:406-414) treat a runtime transport ack as proof the update was absorbed; shouldPostSteerReceipt only requires run success; checkSteerAlreadyHandled (steer.go:569) then skips the queued run on a job-token receipt. That is the silent-drop path docs/contributing/steering.md itself names as the one failure mode this design may not have. The four documented rollout preconditions remain unmet in-tree (fleet teaching in feat(agents): re-check the work item once and act on runner mid-run updates agents#1163 unmerged, no live end-to-end OpenShell steer observed, receipt identity match unverified on a live run, prioritize/retro/scribe untaught). The scaffold template (internal/agentnew/templates/agent-body.md.tmpl, pinned by steer_envelope_test.go) only closes the gap for newly generated custom agents, not fleet roles. Nothing in code or config ties the on-by-default flip to those preconditions: documenting the footgun as a release checklist does not remove the code path.
    Remediation: Do not ship DefaultSteerEnabled = true until a runner-side guard makes an untaught agent definition unable to silently drop the queued run (refuse to start the watcher, or withhold the receipt, unless the loaded agent definition contains the steer envelope opening line), or keep the constant false and flip it in a follow-up once the four documented preconditions are checked off, or scope the default to only the roles known to be envelope-aware.

Medium

  • [docs-staleness] docs/guides/user/bring-your-own-agent.md:199docs/cli/agent.md was updated in this PR to describe the new ## Runner updates section that fullsend agent new scaffolds, but docs/guides/user/bring-your-own-agent.md was not (confirmed not part of this PR's 19-file diff). That guide states its sample layout "is what it produces, and what you need to create by hand if you are building a harness from scratch," yet the sample agents/my-agent.md prompt has no ## Runner updates section. Because steering is now on by default for every harness, an agent hand-built from this guide is steered without the prompt instructions needed to recognize a mid-run amendment or to treat the same line inside work-item content as an injection attempt — the same silent-drop / injection-blind-spot failure mode the PR's own rollout gate is concerned about, just for hand-built agents rather than the fleet.
    Remediation: Update docs/guides/user/bring-your-own-agent.md to mention the Runner updates section scaffolded by fullsend agent new, and add a ## Runner updates section to the sample agents/my-agent.md prompt under "Minimum viable agent" (or document opting out via steer: {enabled: false}).

Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run

Review

Findings

High

  • [fail-open] internal/harness/harness.go:275DefaultSteerEnabled = true plus SteerEnabled() (278-282) still has no envelope/role gate. startSteerWatcher (internal/cli/steer.go:301-312) still keys only off SteerEnabled() and steerEligible(); this round only added SteerExplicitlyEnabled() / steerDecline.defect to quiet ordinary warnings, which does not refuse the watcher or withhold a receipt. marker()/steerMarkerFrom (steer.go:425-443) still treat a runtime transport ack as proof the update was absorbed; a successful run then posts a job-token receipt and checkSteerAlreadyHandled (steer.go:569) skips the queued run. That is the silent-drop path steering.md itself names as the one failure mode this design may not have. The four documented rollout preconditions remain unmet (fleet teaching in feat(agents): re-check the work item once and act on runner mid-run updates agents#1163 unmerged, no live end-to-end OpenShell steer observed, receipt identity match unverified on a live run, prioritize/retro/scribe untaught). The scaffold template (internal/agentnew/templates/agent-body.md.tmpl, pinned by steer_envelope_test.go) still only closes the gap for newly generated custom agents, not fleet roles. Nothing in code or config ties the on-by-default flip to those preconditions: no feature flag, environment gate, staged-rollout mechanism, or loaded-definition check for the steer envelope opening line, so the PR's own "must not reach a release on four unwatched paths" text is still enforced by process discipline alone.
    Remediation: Do not ship DefaultSteerEnabled = true until a runner-side guard makes an untaught agent definition unable to silently drop the queued run (refuse to start the watcher, or withhold the receipt, unless the loaded agent definition contains the steer envelope opening line), or keep the constant false and flip it in a follow-up once the four documented preconditions are checked off, or scope the default to only the roles known to be envelope-aware. A release/config flag that stays off until those checks land is an acceptable stand-in for a true absorption check. Scaffold teaching is already done; remaining work is the fleet prioritize/retro/scribe roles (opt out or teach) plus not treating a transport ack as proof the agent actually acted on the update.

Medium

  • [issue-authorization-mismatch] docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md — The PR body cites "the 2026-09-14 meeting recorded on Steer the in-flight agent run on work-item updates instead of cancelling it #6957" as backing for making steering (not just run continuation) the default. Both verbatim 2026-09-14 meeting-update comments on Steer the in-flight agent run on work-item updates instead of cancelling it #6957 only decide "continue runs rather than cancelling them" and "no feature flag for event coalescence" — i.e. run continuation, which is ADR 0113's subject, not ADR 0101's steering-content-delivery mechanism. ADR 0101 and docs/contributing/steering.md do not cite that meeting, Steer the in-flight agent run on work-item updates instead of cancelling it #6957, or any meeting at all — only the PR body's prose asserts the link, and ADR 0101's own Decision section instead grounds "on by default" in the receipts-authentication precondition being satisfied (a self-contained engineering argument). Separately, issue Steer the in-flight agent run on work-item updates instead of cancelling it #6957's own validation criterion ("No stage regresses when harness.steer is off (default)") is superseded by this PR's on-by-default ADR edit, but no comment has been added to Steer the in-flight agent run on work-item updates instead of cancelling it #6957 to record that supersession, so the authorization trail is not discoverable from the issue itself (traceability hygiene; flagged at the same severity across three prior review rounds).
    Remediation: Add an explicit citation in ADR 0101's Decision or Context section identifying the actual authorization source for extending on-by-default from run-continuation (ADR 0113) to steering (ADR 0101), or correct the PR body to stop implying the 2026-09-14 meeting covered steering — state plainly that the meeting authorized only ADR 0113's default and that ADR 0101's on-by-default extension is this PR's own proposal, subject to review under its own merits (and the four listed unmet preconditions). Also add a comment on issue Steer the in-flight agent run on work-item updates instead of cancelling it #6957 noting its validation criteria are superseded by ADR 0101's on-by-default decision, so the authorization trail is discoverable from the issue without relying on this PR's description.

  • [docs-staleness] docs/guides/user/bring-your-own-agent.md:199docs/cli/agent.md was updated to describe the new ## Runner updates section fullsend agent new scaffolds, but docs/guides/user/bring-your-own-agent.md was not (it is not part of this PR's diff). That guide states its sample layout "is what it produces, and what you need to create by hand if you are building a harness from scratch," yet the sample agents/my-agent.md prompt has no ## Runner updates section. Because steering is now on by default for every harness, an agent hand-built from this guide is steered without the prompt instructions needed to recognize a mid-run amendment or to treat the same line inside work-item content as an injection attempt — the same silent-drop / injection-blind-spot failure mode the PR's own rollout gate is concerned about, just for hand-built agents rather than the fleet.
    Remediation: Update docs/guides/user/bring-your-own-agent.md to mention the Runner updates section scaffolded by fullsend agent new, and add a ## Runner updates section to the sample agents/my-agent.md prompt under "Minimum viable agent" (or document opting out via steer: {enabled: false}).

Low

  • [scope-claim-accuracy] docs/contributing/steering.md:453 — The closing sentence "Agents generated by fullsend agent new are not in that gap: the scaffolded body carries the contract." is true only for agents generated after this template change; it does not teach the fleet definitions (precondition 1, feat(agents): re-check the work item once and act on runner mid-run updates agents#1163) or custom agents already scaffolded from the old template. The preceding paragraph still correctly states all four preconditions remain open, so this is a minor over-broad phrasing rather than a change to the gate itself.
    Remediation: Reword to say this closes a narrower, additional gap (newly-scaffolded custom agents from this point forward), does not reduce precondition 1's scope, and that pre-existing scaffolds must be regenerated to gain the Runner updates section.

Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (2)

Review

Findings

High

  • [fail-open] internal/harness/harness.go:275DefaultSteerEnabled = true plus SteerEnabled() (278-282) still has no envelope/role gate: startSteerWatcher (internal/cli/steer.go:301-312) keys only off SteerEnabled() and steerEligible() and never inspects the loaded agent definition. The marker logic (steer.go:406-414) treats a runtime transport ack as proof the update was absorbed; a successful run then posts a job-token receipt and the queued run skips. That is the silent-drop path steering.md itself names as the one failure mode this design may not have. The four documented rollout preconditions remain unmet (fleet teaching in feat(agents): re-check the work item once and act on runner mid-run updates agents#1163 unmerged, no live end-to-end OpenShell steer observed, receipt identity match unverified on a live run, prioritize/retro/scribe untaught). This round's new commit teaches the steer envelope to the fullsend agent new scaffold template (internal/agentnew/templates/agent-body.md.tmpl, pinned by steer_envelope_test.go) — that closes the gap for newly generated custom agents only, not the fleet roles. Nothing in code or config ties the on-by-default flip to the stated preconditions: there is no feature flag, environment gate, or staged-rollout mechanism, so the PR's own "must not reach a release on four unwatched paths" text is enforced by process discipline alone.
    Remediation: Do not ship DefaultSteerEnabled = true until a runner-side guard makes an untaught agent definition unable to silently drop the queued run (refuse to start the watcher, or withhold the receipt, unless the loaded agent definition contains the steer envelope opening line), or keep the constant false and flip it in a follow-up once the four documented preconditions are checked off, or scope the default to only the roles known to be envelope-aware. A release/config flag that stays off until those checks land is an acceptable stand-in for a true absorption check. Scaffold teaching is already done; remaining work is the fleet prioritize/retro/scribe roles (opt out or teach) plus not treating a transport ack as proof the agent actually acted on the update.

Medium

  • [docs-staleness] docs/guides/user/bring-your-own-agent.md:197docs/cli/agent.md was updated this round to describe the new ## Runner updates section fullsend agent new scaffolds, but docs/guides/user/bring-your-own-agent.md was not. That guide states its sample layout "is what it produces, and what you need to create by hand if you are building a harness from scratch," yet the sample agents/my-agent.md prompt has no ## Runner updates section. Because steering is now on by default for every harness, an agent hand-built from this guide is steered without the prompt instructions needed to recognize a mid-run amendment or to treat the same line inside work-item content as an injection attempt — the same silent-drop / injection-blind-spot failure mode the PR's own rollout gate is concerned about, just for hand-built agents rather than the fleet.
    Remediation: Update docs/guides/user/bring-your-own-agent.md to mention the Runner updates section scaffolded by fullsend agent new, and add a ## Runner updates section to the sample agents/my-agent.md prompt under "Minimum viable agent" (or document opting out via steer: {enabled: false}).

Low

  • [missing-test] internal/cli/steer_test.go:944checkSteerAlreadyHandled (steer.go:568) correctly gates on o.harness.SteerEnabled(), which is default-aware (nil Steer ⇒ on). No test exercises the skip-check's success path with a default (nil Steer) harness: TestCheckSteerAlreadyHandled_ReadsTheMarker, _ReadsWithTheJobToken, and _FailureFallsThrough all use baseOpts(t), which sets harness: steerHarness(true) (explicit enabled: true). The watcher-start side got a dedicated TestStartSteerWatcher_DefaultHarnessStartsAWatcher test for exactly this scenario; the skip-check side did not.
    Remediation: Add a checkSteerAlreadyHandled test using steerHarnessDefault() (nil Steer) that asserts the function proceeds to the marker read rather than returning false early.

  • [docs-inconsistency] docs/contributing/steering.md:409 — The "GitLab is not wired" known-limit still says the watcher "is GitHub-only for now and says so when it declines to start." steerEligible classifies GitLab as an ordinary decline (defect: false), and startSteerWatcher prints ordinary declines only when d.defect || o.harness.SteerExplicitlyEnabled(). Under the new on-by-default behavior (the common case going forward), a GitLab run now declines silently, contradicting this sentence. The Configuration section a few paragraphs above already documents the quiet-decline behavior; this Known-limits sentence was not updated to match.
    Remediation: Update the GitLab known-limits sentence to say the watcher is GitHub-only and stays quiet on a GitLab decline unless the harness explicitly set enabled: true (environment defects are still always printed; GitLab is not classified as a defect).

  • [scope-claim-accuracy] docs/contributing/steering.md:460 — The new closing sentence — "Agents generated by fullsend agent new are not in that gap: the scaffolded body carries the contract." — is true only for agents generated after this template change; it does not teach the fleet definitions (precondition 1, feat(agents): re-check the work item once and act on runner mid-run updates agents#1163) or custom agents already scaffolded from the old template. The preceding paragraph still correctly states all four preconditions remain open, so this is a minor over-broad phrasing rather than a change to the gate itself.
    Remediation: Reword to say this closes a narrower, additional gap (newly-scaffolded custom agents from this point forward), does not reduce precondition 1's scope, and that pre-existing scaffolds must be regenerated to gain the Runner updates section.

  • [issue-authorization-mismatch] docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md — Linked issue Steer the in-flight agent run on work-item updates instead of cancelling it #6957's own validation criterion ("No stage regresses when harness.steer is off (default)") is superseded by this PR's on-by-default ADR edit, but no comment has been added to issue Steer the in-flight agent run on work-item updates instead of cancelling it #6957 to record that supersession. ADR 0101 (edited in this PR) is the in-repo source of truth going forward and the PR title correctly carries !, so this is traceability hygiene rather than a live authorization gap — an earlier review round flagged the same fact at the same severity for the same reason.
    Remediation: Add a comment on issue Steer the in-flight agent run on work-item updates instead of cancelling it #6957 noting its validation criteria are superseded by ADR 0101's on-by-default decision, so the authorization trail is discoverable from the issue without relying on this PR's description.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (3)

Review

Findings

High

  • [fail-open] internal/harness/harness.go:275DefaultSteerEnabled = true plus SteerEnabled() (278-282) has no envelope/role gate: every harness that omits steer: starts a watcher. startSteerWatcher keys only off SteerEnabled() and steerEligible(); it never inspects the loaded agent definition. A runtime transport ack (steer.go:406-414) is treated as proof the update was absorbed, and a successful run posts a job-token receipt, so the queued run skips. That is the silent-drop path steering.md:453 itself names as "the one failure mode this design may not have," and this PR's own rollout section lists four unmet preconditions that "belong before the default reaches a release" (feat(agents): re-check the work item once and act on runner mid-run updates agents#1163 unmerged; no live end-to-end steer observed; receipt identity match unverified live; prioritize/retro/scribe untaught). Nothing in this diff or CI ties DefaultSteerEnabled to those gates, the PR is not marked draft, and its own immediate stack base (fix(cli): authenticate the steer receipt with the job token #7376) is still open/unmerged — so this default-flip commit could reach main before its own documented prerequisites are met. The same default is inherited by fullsend agent new: buildHarness (internal/agentnew/render.go) never sets h.Steer, so newly scaffolded (almost certainly untaught) harnesses are opted in with no visible opt-out hint. (Fixed since the prior round: the PR no longer carries a ready-for-merge label — current labels are fullsend-no-fix, risk/high.)
    Remediation: Do not ship DefaultSteerEnabled = true until a runner-side guard makes an untaught agent definition unable to silently drop the queued run (refuse to start the watcher, or withhold the receipt, unless the loaded agent definition contains the steer envelope opening line), or keep the constant false and flip it in a follow-up once the four documented preconditions are checked off, or scope the default to only the roles known to be envelope-aware (code, fix, review, triage) until feat(agents): re-check the work item once and act on runner mid-run updates agents#1163 merges. Separately, have fullsend agent new emit steer: {enabled: false} (or at least a commented steer block) so new scaffolds are not silently opted into an unverified default. Until then, keep this PR blocked/draft so a mergeable default-on commit cannot land ahead of its own rollout gate.

Low

  • [missing-test] internal/cli/steer_test.go:944checkSteerAlreadyHandled (steer.go:568) now correctly gates on o.harness.SteerEnabled(), which is default-aware — the skip-check logic itself is correct. However, no test exercises the skip-check's success path with a default (nil Steer) harness: TestCheckSteerAlreadyHandled_ReadsTheMarker, _ReadsWithTheJobToken, and _FailureFallsThrough all use baseOpts(t), which sets harness: steerHarness(true) (explicit enabled: true). The watcher-start side got a dedicated TestStartSteerWatcher_DefaultHarnessStartsAWatcher test for exactly this scenario; the skip-check side did not.
    Remediation: Add a checkSteerAlreadyHandled test using steerHarnessDefault() (nil Steer) that asserts the function proceeds to the marker read (reusing the ReadsTheMarker fixture) rather than returning false early.

  • [docs-inconsistency] docs/contributing/steering.md:409 — The "GitLab is not wired" known-limit still says the watcher "is GitHub-only for now and says so when it declines to start." steerEligible classifies GitLab as an ordinary decline (defect: false), and startSteerWatcher prints ordinary declines only when d.defect || o.harness.SteerExplicitlyEnabled(). Under the new on-by-default behavior (the common case going forward), a GitLab run now declines silently, contradicting this sentence. Two prior review rounds already fixed the equivalent absolute statements at steering.md:369-372 and harness-reference.md:189 (both now correctly document the environment-defect exception), but this GitLab-specific sentence was missed.
    Remediation: Update the GitLab known-limits sentence to match the new gate: the watcher is GitHub-only and stays quiet on a GitLab decline unless the harness explicitly set enabled: true (environment defects are still always printed; GitLab is not classified as a defect).


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (4)

Review

Findings

High

  • [fail-open] internal/harness/harness.go:275DefaultSteerEnabled = true applies to every harness with no steer: block, and SteerEnabled() has no per-role or envelope-awareness gate. startSteerWatcher keys only off SteerEnabled() plus steerEligible(); the runtime's transport ack (Claude replay echo / pi response / Codex process start) is then treated as proof the update was absorbed, and a successful run posts a job-token-authenticated receipt that makes the queued run skip. Receipt authentication (from the earlier fix(cli): authenticate the steer receipt with the job token #7376 stack PR) closes forged receipts; it does not close the documented failure mode where an UNTAUGHT agent definition still acks the delivery and still posts a legitimate receipt, silently dropping the queued run's update — steering.md itself names this as "the one failure mode this design may not have." That path is now the default for prioritize/retro/scribe (feat(agents): re-check the work item once and act on runner mid-run updates agents#1163, which teaches only code/fix/review/triage, is unmerged) and for every new consumer harness fullsend agent new scaffolds: its templates neither set steer: {enabled: false} nor teach the envelope opening line, so BYOA harnesses are opted into an unverified default with no migration step beyond a human proactively opting out after reading the breaking-change notes. The PR body itself lists four preconditions (agents#1163 merged, a live end-to-end OpenShell steer observed, receipt identity verified live, prioritize/retro/scribe taught) as required "before this merges" / "before the default reaches a release," and nothing in this diff enforces any of them in code or CI — yet the PR carries the ready-for-merge label (verified via the GitHub API), directly contradicting the PR's own stated merge criterion.
    Remediation: Do not ship DefaultSteerEnabled = true fleet-wide until a runner-side guard makes an untaught agent definition unable to silently drop the queued run — e.g., refuse to start the watcher, or withhold the receipt, unless the loaded agent definition contains the steer envelope opening line — or scope the default to only the roles known to be envelope-aware (code, fix, review, triage) and keep it off elsewhere until feat(agents): re-check the work item once and act on runner mid-run updates agents#1163 merges. Separately, have fullsend agent new emit steer: {enabled: false} (or the envelope contract) by default so newly scaffolded consumer harnesses are not silently opted into an unverified default. Remove the ready-for-merge label, or gate the release on the four preconditions the PR body itself lists as unmet.

Low

  • [docs-inconsistency] docs/contributing/steering.md:406 — The "GitLab is not wired" section states the watcher "is GitHub-only for now and says so when it declines to start." In internal/cli/steer.go, a GitLab forge platform is an ordinary decline (defect: false), and startSteerWatcher prints ordinary-decline warnings only when the harness explicitly set enabled: true (if d.defect || o.harness.SteerExplicitlyEnabled()). Under the new on-by-default behavior (no explicit steer: block, the common case going forward), a GitLab run now declines silently, contradicting this sentence.
    Remediation: Clarify that the GitLab decline is announced only when the harness explicitly set enabled: true, matching the caveat already used in the Configuration section of the same document.

  • [docs-inconsistency] docs/contributing/steering.md:369 — This sentence ("The runner announces a declined watch only when the harness set enabled: true itself...") and a similarly absolute sentence in docs/reference/harness-reference.md omit the environment-defect exception. internal/cli/steer.go's steerDecline.defect classification means a missing job token or GITHUB_RUN_ID on an otherwise-eligible GitHub Actions run is unconditionally announced via StepWarn, regardless of whether enabled: true was explicitly set — pinned by TestStartSteerWatcherAnnouncesEnvironmentDefects.
    Remediation: Clarify in both documents that ordinary declines are announced only when enabled: true is explicit, but environment defects (missing job token or run id) are always announced regardless of harness configuration.

  • [missing-authorization] docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md:50 — The Decision section asserts steering is "on by default" as settled fact. The PR body explains this follows "the 2026-09-14 meeting recorded on Steer the in-flight agent run on work-item updates instead of cancelling it #6957" and states "Steer the in-flight agent run on work-item updates instead of cancelling it #6957's validation criteria still describe steering as off by default and should be read as superseded by ADR 0101" — but the PR does not add a comment to issue Steer the in-flight agent run on work-item updates instead of cancelling it #6957 itself, whose Validation criteria section (confirmed live via the GitHub API) still literally reads "No stage regresses when harness.steer is off (default)," the opposite of what this PR ships. A reviewer or future contributor following the normal issue-to-ADR traceability path in this repo, without reading this PR's description, would find issue Steer the in-flight agent run on work-item updates instead of cancelling it #6957 uncorrected and contradicting the new default. This is traceability hygiene rather than a live authorization gap, since ADR 0101 (edited in this PR) is the in-repo source of truth going forward.
    Remediation: Add a comment on issue Steer the in-flight agent run on work-item updates instead of cancelling it #6957 noting its Validation criteria are superseded by ADR 0101's on-by-default decision (and link the 2026-09-14 meeting record if one exists in a durable location), so the authorization trail is discoverable from the issue without relying on this PR's description.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (5)

Looks good to me

Previous run (6)

Review

Findings

Low

  • [stale-comment] internal/harness/harness.go:424 — The Harness struct field comment on Steer still reads // follow-up run watcher (ADR 0101); default off, contradicting DefaultSteerEnabled = true introduced by this same diff and the already-updated SteerConfig type doc comment, which now correctly describes on-by-default behavior.
    Remediation: Update the inline comment on Harness.Steer to something like // follow-up run watcher (ADR 0101); on by default, steer: {enabled: false} to opt out.

  • [schema-documentation-completeness] docs/contributing/harness-fields.md:42 — This is the living contributor-facing reference for harness field classifications and merge rules (AGENTS.md names this file as the doc to update when adding or modifying Harness fields). It omits steer from both its "Fields that stay at top level only" table and its "Merge and inheritance rules" table, even though this PR already added the equivalent row to the sibling user-facing docs/reference/harness-reference.md merge-rules table in response to the prior review round.
    Remediation: Add a steer row to harness-fields.md's "Fields that stay at top level only" table and to its "Merge and inheritance rules" table, mirroring the row already added to docs/reference/harness-reference.md.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (7)

Review

Findings

Medium

  • [test-weakened] internal/cli/steer_test.go:42steerHarness(false) still leaves Steer nil, which under this PR's new default means steering is on, not off. TestStartSteerWatcher_DisabledHarnessStartsNothing and the "steering disabled" case in TestCheckSteerAlreadyHandled_OffPaths no longer exercise the opt-out path — the first passes only because fakeRuntime lacks Steerer (already covered by a separate ineligibility test), and the second now takes the enabled branch. A regression that made startSteerWatcher/checkSteerAlreadyHandled ignore an explicit enabled: false opt-out would slip through.
    Remediation: Construct an explicit opt-out (h.Steer = &harness.SteerConfig{Enabled: steerBoolPtr(false)}) in steerHarness(false), and add a separate nil-Steer fixture if a test needs the default-on/no-block case specifically.

  • [fail-open] internal/harness/harness.go:260DefaultSteerEnabled = true turns on the follow-up-run watcher for every harness that says nothing about steer:. The PR's own body and docs/contributing/steering.md (this diff) disclose four unmet preconditions — fullsend-ai/agents#1163 (envelope teaching) is unmerged, no live end-to-end steer has been observed in OpenShell, the receipt's identity match is unverified live, and prioritize/retro/scribe are not yet taught the envelope. The documented consequence: an eligible run on an untaught definition still acks and posts a receipt, so the queued run skips and the update is silently dropped. This PR lands on the unmerged steer-receipts branch, not main, and nothing in the code enforces these preconditions — only the PR body's checklist does.
    Remediation: Land fullsend-ai/agents#1163 and give prioritize/retro/scribe the envelope contract or an explicit steer: {enabled: false} in the same release train that ships DefaultSteerEnabled = true to main. Merging this PR onto steer-receipts should not be treated as satisfying the preconditions.

  • [cross-repo-compatibility] docs/contributing/steering.md:440 — The schema default flips for every consumer harness the moment a release carries it, including custom/third-party agent definitions unaware of the steer envelope. There's no canary bit in config.yaml; a consumer's only escape is proactively adding steer: {enabled: false} before upgrading. This is the fleet-migration face of the fail-open finding above, not a separate defect.
    Remediation: Ship explicit migration guidance for consumer repos with custom agent definitions to opt out before upgrading, and prefer landing the envelope teaching for untaught fleet roles in the same release train rather than leaving it as a documented gap.

  • [schema-documentation-completeness] docs/reference/harness-reference.md:222 — The "Field merge rules" table has no row for steer, even though this PR makes its inheritance semantics fleet-wide consequential (a silent child inherits the base's whole block, including an opt-out; a child that sets any key replaces the block wholesale — see TestLoadWithBase_SteerChildInheritsBaseOptOut). This PR edits the same file's steer field details but leaves the merge-rules table stale.
    Remediation: Add a steer row, e.g. "Child replaces entirely if set; otherwise inherits base's whole block (including an opt-out)".

Low

  • [stale-comment] internal/harness/harness.go:226 — The SteerConfig type doc comment still says "Disabled by default: enabling it changes how long a run holds its VM," contradicting DefaultSteerEnabled = true and the Enabled field's own updated comment two lines below.
    Remediation: Update the type comment to describe the on-by-default behavior and the enabled: false opt-out.

  • [docs-inconsistency] docs/contributing/steering.md:368 — States the declined-watch warning fires whenever "the harness named steering itself," but the code (SteerExplicitlyEnabled()) only fires it when enabled: true is explicit — steer: {max_steers: 3} stays silent, per the new TestStartSteerWatcherDeclineMessage case. docs/reference/harness-reference.md has the correct, narrower wording.
    Remediation: Align steering.md with the code and with harness-reference.md.

  • [missing-authorization] docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md:50 — The PR body cites "the 2026-09-14 decision recorded on Steer the in-flight agent run on work-item updates instead of cancelling it #6957" as authorizing the on-by-default flip, but the visible Steer the in-flight agent run on work-item updates instead of cancelling it #6957 meeting-update comments discuss run continuation (ADR 0113) rather than explicitly extending to steering's default. This PR is what rewrites ADR 0101's own Decision section, which is normal ADR flow, but the evidentiary trail for a reviewer is thinner than the PR body implies.
    Remediation: Add a comment on Steer the in-flight agent run on work-item updates instead of cancelling it #6957 explicitly extending the decision to steering, or soften the PR body's framing.

  • [scope-exceeded] docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md — Issue Steer the in-flight agent run on work-item updates instead of cancelling it #6957's own "Validation criteria" still say "No stage regresses when harness.steer is off (default)," the opposite of what this PR implements. Traceability hygiene only; ADR 0101 is the source of truth after this change.
    Remediation: Update or comment on Steer the in-flight agent run on work-item updates instead of cancelling it #6957's validation criteria, or point readers to ADR 0101.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR

fullsend-ai-review[bot]

This comment was marked as outdated.

fullsend-ai-review[bot]

This comment was marked as outdated.

waynesun09 added a commit that referenced this pull request Sep 16, 2026
… agents

Four things from the qodo review on #7377.

fix.md said a harness that opts out "cancels the in-flight fix run and starts a
new one". That has been wrong since #7007 made preserving unconditional: no
stage job cancels a run in flight any more, so an opted-out harness lets the run
finish and the queued run works from the PR's current state. The sentence read
fine, which is why it survived two passes.

Grepping the rest for the same mistake turned up four more places where the
alternative to steering was still described as cancellation — bugfix-workflow.md
("instead of cancelling it"), this page's own opening, the receipt rationale
("costs more than cancelling does today", "where cancel-and-restart produces
one"), and the same comparison in ADR 0101's Decision. None is about the
opt-out, all are stale in the same way: they compare steering against a
behaviour that no longer exists. They now compare it against preserving alone,
which is what an opted-out harness actually gets, and what the queued run does
in either case.

harness-reference.md's eligibility paragraph still required `enabled: true` and
promised a printed reason for every declined watch, both of which this PR
changed. It now names the three conditions — not explicitly disabled, a runtime
that can take a message, a GitHub Actions job with a work item — and says the
reason is printed only when the harness set `enabled: true` itself.

The warning gate had no test. TestStartSteerWatcherDeclineMessage is
table-driven over an ineligible fixture: no block and a block that only tunes
max_steers stay quiet, `enabled: true` prints the reason, `enabled: false` stays
quiet. The max_steers row is there because it is the case the pointer exists for
— it must read as "not an explicit request", not as an opt-out.

The rollout paragraph gains a fourth open precondition. agents#1163 teaches the
envelope to code, fix, review and triage only; prioritize, retro and scribe are
outside it and no harness anywhere sets `steer:`, so with the default on an
eligible run on one of those three delivers an envelope the definition ignores,
the runtime acks it, the receipt is posted, and the queued run skips — the
update is dropped silently. The fix belongs in the agents repo, so this is
recorded as a gate rather than changed here.

ADR 0101 stays at 102 lines of content.

Assisted-by: Claude
Signed-off-by: Wayne Sun <gsun@redhat.com>
waynesun09 added a commit that referenced this pull request Sep 16, 2026
…sion

Review comment 5701340330 on #7377.

steerHarness(false) left Steer nil, which this PR made mean *on*. Two tests
were then asserting something other than what their names claim.
TestStartSteerWatcher_DisabledHarnessStartsNothing passed because fakeRuntime
has no Steerer, not because the harness was off, and
TestCheckSteerAlreadyHandled_OffPaths' "steering disabled" case went down the
enabled branch and returned false from a failed timeline read instead.

steerHarness now always writes the key, and steerHarnessDefault covers the
absent-block case that used to be conflated with it.

The disabled-watcher test gets a steerable runtime and asserts the fixture is
otherwise eligible first, so the opt-out is the only thing left that can stop
it. Its companion pins that a default harness is enabled but not explicitly
enabled — the two states the fixture used to blur.

The off-paths test now also asserts nothing was printed. Returning false is not
evidence a guard held: a failed timeline read returns false too, after warning.
Without that assertion the mutation is not caught — dropping the SteerEnabled
check from checkSteerAlreadyHandled leaves the test passing on the return value
while the guarded call reaches the network. With it, the case fails on
"Could not check whether this update was already handled: ... 401 Bad
credentials", which is both the mutation caught and proof the real code never
makes that call.

Three descriptions that had not kept up:

harness.go's SteerConfig type comment still said disabled by default. The field
comment below it was already correct, which is how the type comment survived.

harness-reference.md's field merge table had no steer row at all, so the one
inheritance rule that now matters — a child with no block inherits the base's
opt-out rather than the default — was written down nowhere the table's readers
would look.

steering.md said the declined-watch warning fires when the harness "named
steering itself", which reads as though a cap-only block would qualify. The
code requires enabled: true, as the test row added for exactly that case shows.

Assisted-by: Claude
Signed-off-by: Wayne Sun <gsun@redhat.com>
@waynesun09

Copy link
Copy Markdown
Member Author

Addressed in f92c2ce (rebased onto #7376's e07a57e). The test fixtures now say off explicitly: steerHarness always writes the key and a separate default fixture covers the absent block, TestStartSteerWatcher_DisabledHarnessStartsNothing uses a steerable runtime so only the opt-out can stop the watcher, and TestCheckSteerAlreadyHandled_OffPaths asserts nothing was printed per guard — dropping the opt-out guard now fails it (the return value alone survived the mutation, since the bypass path fails open). The merge-rules table has a steer row, the SteerConfig comment and steering.md's warning wording match the code. The four preconditions are gates in the PR body and steering.md, not satisfied by merging onto steer-receipts; the body's framing of #6957 is softened, and the #6957 validation-criteria note is with the author.

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 16, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:10 PM UTC · Completed 5:41 PM UTC

Commit: f92c2ce · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5 · Effort: high · Cost: $8.70

fullsend-ai-review[bot]

This comment was marked as outdated.

fullsend-ai-review[bot]

This comment was marked as outdated.

@waynesun09

Copy link
Copy Markdown
Member Author

Fixed in c0d78a6: the Harness.Steer field comment now says on by default with the opt-out. harness-fields.md already had steer in both tables (lines 64 and 96); its merge-rules row now also states that an absent block inherits the base's opt-out, matching the reference table.

waynesun09 added a commit that referenced this pull request Sep 16, 2026
…sion

Review comment 5701340330 on #7377.

steerHarness(false) left Steer nil, which this PR made mean *on*. Two tests
were then asserting something other than what their names claim.
TestStartSteerWatcher_DisabledHarnessStartsNothing passed because fakeRuntime
has no Steerer, not because the harness was off, and
TestCheckSteerAlreadyHandled_OffPaths' "steering disabled" case went down the
enabled branch and returned false from a failed timeline read instead.

steerHarness now always writes the key, and steerHarnessDefault covers the
absent-block case that used to be conflated with it.

The disabled-watcher test gets a steerable runtime and asserts the fixture is
otherwise eligible first, so the opt-out is the only thing left that can stop
it. Its companion pins that a default harness is enabled but not explicitly
enabled — the two states the fixture used to blur.

The off-paths test now also asserts nothing was printed. Returning false is not
evidence a guard held: a failed timeline read returns false too, after warning.
Without that assertion the mutation is not caught — dropping the SteerEnabled
check from checkSteerAlreadyHandled leaves the test passing on the return value
while the guarded call reaches the network. With it, the case fails on
"Could not check whether this update was already handled: ... 401 Bad
credentials", which is both the mutation caught and proof the real code never
makes that call.

Three descriptions that had not kept up:

harness.go's SteerConfig type comment still said disabled by default. The field
comment below it was already correct, which is how the type comment survived.

harness-reference.md's field merge table had no steer row at all, so the one
inheritance rule that now matters — a child with no block inherits the base's
opt-out rather than the default — was written down nowhere the table's readers
would look.

steering.md said the declined-watch warning fires when the harness "named
steering itself", which reads as though a cap-only block would qualify. The
code requires enabled: true, as the test row added for exactly that case shows.

Assisted-by: Claude
Signed-off-by: Wayne Sun <gsun@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 16, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 9:49 PM UTC · Completed 10:06 PM UTC

Commit: 01de4ba · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5 · Effort: high · Cost: $5.88

@fullsend-ai-review fullsend-ai-review Bot added risk/moderate PR risk: moderate and removed risk/high PR risk: high labels Sep 16, 2026
fullsend-ai-review[bot]

This comment was marked as outdated.

ADR 0113 made steering opt-in because authenticated receipts did not exist
yet. They do now, so the precondition is satisfied and the 2026-09-14 meeting
recorded on #6957 confirmed steering is on by default alongside run
continuation.

SteerConfig.Enabled becomes a *bool. A plain bool cannot carry this default:
`steer: {max_steers: 3}` would unmarshal to Enabled=false and silently opt the
harness out while it was trying to tune the cap. nil now means on, and
`steer: {enabled: false}` is the only way off — which is also the only spelling
a reader can tell apart from "I said nothing".

The field follows the pattern already used for the other on-by-default blocks
in this file (hooks, sanitizers), including BoolDefault, so nothing new is
introduced.

Composition needs no change and both directions are now covered: a child with
no block still inherits the base's whole block, so a base opt-out is inherited
rather than overwritten by the default, and a child writing
`steer: {enabled: true}` is still whole-block replacement meaning "steer with
the defaults".

SteerExplicitlyEnabled is new, and startSteerWatcher uses it to decide whether
a declined watch is worth a warning. Every decline reason used to imply a
harness that asked for steering and did not get it. With steering on by
default most declines are ordinary — every local run is not in GitHub Actions,
every GitLab run queues instead, a runtime that cannot take a message never
could — and warning once per iteration on runs that never asked would be noise.
The message now fires only when the harness named steering, which is exactly
the set of runs that used to see it.

max_steers (2), poll_interval_seconds (30s) and the settle floor keep their
defaults, and validation is unchanged: it never read Enabled.

BREAKING CHANGE: Steering is on by default from the release that carries this.
A harness that omits the `steer:` block, or sets only `max_steers` or
`poll_interval_seconds`, now watches for follow-up runs and delivers work-item
updates into the running agent. To keep the previous behaviour, add
`steer: {enabled: false}` to the harness. Repositories on GitLab, and runs
outside GitHub Actions, are unaffected: the watcher declines there as before.

Assisted-by: Claude
Signed-off-by: Wayne Sun <gsun@redhat.com>
Every page that described steering described how to switch it on. They now
describe how to switch it off, which is the only choice a harness author has
left to make.

harness-reference.md says the default is true and, in the field note, that
`enabled: false` is the only spelling that turns steering off — a block setting
just `max_steers` or `poll_interval_seconds` still steers. That is the trap the
*bool exists to close, so the reference is where it has to be written down.

steering.md's Configuration section flips the same way and says why `enabled`
is a pointer. Its Rollout order described enabling one harness at a time; that
order existed to hold steering back until its preconditions were met, and they
are met — the fleet agent definitions merged, the envelope settled with them,
each runtime's steer was observed on OpenShell, and the authenticated receipt
is the change below this one. The section now says steering arrives with the
release, and names the one observation still outstanding rather than dropping
the preconditions silently.

review.md, triage.md, fix.md and bugfix-workflow.md each carried a caveat that
absorbing a comment mid-run needs steering turned on. The caveat inverts: the
absorb is what happens, and the opt-out is the exception. fix.md's ordering
flips with it — it led with cancel-and-restart as what a repository sees, which
is now the opted-out case.

ADR 0113's Decision said opt-in; it now says on by default with the per-harness
opt-out, and the receipt paragraph says the satisfied precondition is why the
default is on rather than a switch each harness must find. The Consequences
bullet "nothing changes for a repository that does not opt in" becomes its
honest inverse. The file is not on main, so this is authoring rather than an
amendment to an accepted ADR. It stays at 102 lines of content — where it
already was, still above the writing-adrs 100-line smell line, which this
change neither caused nor fixes.

Assisted-by: Claude
Signed-off-by: Wayne Sun <gsun@redhat.com>
… agents

Four things from the qodo review on #7377.

fix.md said a harness that opts out "cancels the in-flight fix run and starts a
new one". That has been wrong since #7007 made preserving unconditional: no
stage job cancels a run in flight any more, so an opted-out harness lets the run
finish and the queued run works from the PR's current state. The sentence read
fine, which is why it survived two passes. That correction is no longer in this
commit's diff: restacking onto the rewritten base conflicted on exactly that
bullet, and resolving it there was the only way to keep the base's new "one run
waits behind it" wording, so the fix now lands one commit earlier.

Grepping the rest for the same mistake turned up four more places where the
alternative to steering was still described as cancellation — bugfix-workflow.md
("instead of cancelling it"), this page's own opening, the receipt rationale
("costs more than cancelling does today", "where cancel-and-restart produces
one"), and the same comparison in ADR 0113's Decision. None is about the
opt-out, all are stale in the same way: they compare steering against a
behaviour that no longer exists. They now compare it against preserving alone,
which is what an opted-out harness actually gets, and what the queued run does
in either case.

harness-reference.md's eligibility paragraph still required `enabled: true` and
promised a printed reason for every declined watch, both of which this PR
changed. It now names the three conditions — not explicitly disabled, a runtime
that can take a message, a GitHub Actions job with a work item — and says the
reason is printed only when the harness set `enabled: true` itself.

The warning gate had no test. TestStartSteerWatcherDeclineMessage is
table-driven over an ineligible fixture: no block and a block that only tunes
max_steers stay quiet, `enabled: true` prints the reason, `enabled: false` stays
quiet. The max_steers row is there because it is the case the pointer exists for
— it must read as "not an explicit request", not as an opt-out.

The rollout paragraph gains a fourth open precondition. agents#1163 teaches the
envelope to code, fix, review and triage only; prioritize, retro and scribe are
outside it and no harness anywhere sets `steer:`, so with the default on an
eligible run on one of those three delivers an envelope the definition ignores,
the runtime acks it, the receipt is posted, and the queued run skips — the
update is dropped silently. The fix belongs in the agents repo, so this is
recorded as a gate rather than changed here.

ADR 0113 stays at 102 lines of content.

Assisted-by: Claude
Signed-off-by: Wayne Sun <gsun@redhat.com>
…sion

Review comment 5701340330 on #7377.

steerHarness(false) left Steer nil, which this PR made mean *on*. Two tests
were then asserting something other than what their names claim.
TestStartSteerWatcher_DisabledHarnessStartsNothing passed because fakeRuntime
has no Steerer, not because the harness was off, and
TestCheckSteerAlreadyHandled_OffPaths' "steering disabled" case went down the
enabled branch and returned false from a failed timeline read instead.

steerHarness now always writes the key, and steerHarnessDefault covers the
absent-block case that used to be conflated with it.

The disabled-watcher test gets a steerable runtime and asserts the fixture is
otherwise eligible first, so the opt-out is the only thing left that can stop
it. Its companion pins that a default harness is enabled but not explicitly
enabled — the two states the fixture used to blur.

The off-paths test now also asserts nothing was printed. Returning false is not
evidence a guard held: a failed timeline read returns false too, after warning.
Without that assertion the mutation is not caught — dropping the SteerEnabled
check from checkSteerAlreadyHandled leaves the test passing on the return value
while the guarded call reaches the network. With it, the case fails on
"Could not check whether this update was already handled: ... 401 Bad
credentials", which is both the mutation caught and proof the real code never
makes that call.

Three descriptions that had not kept up:

harness.go's SteerConfig type comment still said disabled by default. The field
comment below it was already correct, which is how the type comment survived.

harness-reference.md's field merge table had no steer row at all, so the one
inheritance rule that now matters — a child with no block inherits the base's
opt-out rather than the default — was written down nowhere the table's readers
would look.

steering.md said the declined-watch warning fires when the harness "named
steering itself", which reads as though a cap-only block would qualify. The
code requires enabled: true, as the test row added for exactly that case shows.

Assisted-by: Claude
Signed-off-by: Wayne Sun <gsun@redhat.com>
The Steer field comment still said "default off", two lines from the
type comment that says the opposite. The contributor-facing merge-rules
table now says what an absent block inherits, matching the reference.

Assisted-by: Claude
Signed-off-by: Wayne Sun <gsun@redhat.com>
Scaffolded harnesses are opted in to steering like every other harness, so a
generated agent will be handed runner updates. Until now the template said
nothing about them, which put every scaffolded agent in exactly the gap ADR
0113's rollout names for prioritize, retro and scribe: an agent that does not
recognise the envelope ignores the amendment, the runtime still acks the
delivery, the receipt is still posted, and the queued run skips — the update is
dropped and nobody is told.

The paragraph mirrors the fleet's own "Runner updates" sections in
fullsend-ai/agents#1163 (agents/review.md, agents/fix.md), reduced to what is
true for every role: the line the runner prefixes, that the route job already
authorized the actor behind it, that a widening, narrowing or new head is still
to be acted on, that the result must say what changed, that it grants no tools
or permissions and relaxes no security instruction, and that the same line
inside work-item content is an injection attempt rather than an amendment. It
says "your result" because that is the template's own word for the file it
tells the agent to write.

The test references runtime.SteerEnvelopeOpeningLine rather than spelling the
line out, so a change to the constant fails here instead of silently turning
every steer delivered to a scaffolded agent back into ordinary text. It renders
each role the hosted mint serves, since the paragraph is role-neutral and none
of them may render without it, and it also asserts the injection-defence half —
carrying the line alone would teach recognition without the defence.

The assertion flattens the rendered markdown to one whitespace-normalised line
before matching. The sentence wraps in the template, as it does in the fleet
definitions, and this is the same normalisation fullsend-ai/agents' own contract
test applies (scripts/agent-recheck-contract-test.sh) rather than a rule
invented here — the alternative, forcing the line to stay unwrapped, would make
the template worse to read to satisfy a test.

internal/agentnew does not create an import cycle on internal/runtime: runtime
does not depend on agentnew (`go list -deps ./internal/runtime/` names it
nowhere).

Both golden files are regenerated; the paragraph is the whole of their diff.

docs/cli/agent.md describes what the generated body ships with, so it gains a
clause for the new section. steering.md's rollout gate lists what is still
open, and scaffolded agents are no longer part of it, so they get a closing
sentence there rather than a place in that list.

Assisted-by: Claude
Signed-off-by: Wayne Sun <gsun@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 16, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 10:55 PM UTC · Completed 11:18 PM UTC

Commit: 354f82d · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5 · Effort: high · Cost: $6.80

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the review comment for full details.

// DefaultSteerEnabled is whether the follow-up run watcher runs when the
// harness says nothing about it. On since the release that made run
// continuation unconditional; a harness opts out with enabled: false.
const DefaultSteerEnabled = true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[high] fail-open

DefaultSteerEnabled = true and SteerEnabled() still have no envelope/role/feature-flag gate. startSteerWatcher (internal/cli/steer.go:300-312) keys only off SteerEnabled() and steerEligible(); SteerExplicitlyEnabled() only controls warning volume for ordinary declines, it does not refuse the watcher or withhold a receipt. marker()/steerMarkerFrom (steer.go:406-414) treat a runtime transport ack as proof the update was absorbed; shouldPostSteerReceipt only requires run success; checkSteerAlreadyHandled (steer.go:569) then skips the queued run on a job-token receipt. That is the silent-drop path docs/contributing/steering.md itself names as the one failure mode this design may not have. The four documented rollout preconditions remain unmet in-tree (fleet teaching in fullsend-ai/agents#1163 unmerged, no live end-to-end OpenShell steer observed, receipt identity match unverified on a live run, prioritize/retro/scribe untaught). The scaffold template (internal/agentnew/templates/agent-body.md.tmpl, pinned by steer_envelope_test.go) only closes the gap for newly generated custom agents, not fleet roles. Nothing in code or config ties the on-by-default flip to those preconditions: documenting the footgun as a release checklist does not remove the code path.

Suggested fix: Do not ship DefaultSteerEnabled = true until a runner-side guard makes an untaught agent definition unable to silently drop the queued run (refuse to start the watcher, or withhold the receipt, unless the loaded agent definition contains the steer envelope opening line), or keep the constant false and flip it in a follow-up once the four documented preconditions are checked off, or scope the default to only the roles known to be envelope-aware.

@waynesun09

Copy link
Copy Markdown
Member Author

Replaced by #7461, the same default-on decision as its own PR at the top of the restructured stack (#7007#6959#7447#7451#7456#7461), with ADR 0121 recording it. Every commit was ported there.

@waynesun09 waynesun09 closed this Sep 18, 2026
@waynesun09
waynesun09 removed this pull request from stack #7259 September 18, 2026 15:31
@fullsend-ai-retro

fullsend-ai-retro Bot commented Sep 18, 2026

Copy link
Copy Markdown

🤖 Finished Retro · ✅ Success · Started 3:32 PM UTC · Completed 3:40 PM UTC

Commit: 354f82d · View workflow run →

Runtime: claude · Model: sonnet → claude-sonnet-5 · Effort: high · Cost: $1.47

@fullsend-ai-retro

Copy link
Copy Markdown

PR #7377 ("steer the running agent by default", 4th in a stack rooted in #6957) was CLOSED (not merged) on 2026-09-18 and superseded by a restructured stack ending in #7461, which carries the same default-flip decision under a renumbered ADR (0113→0121). No human reviewer participated on #7377 beyond the author (waynesun09); the only review signal came from two bots. fullsend-ai-review (this org's own agent, resolved from fullsend-ai/agents@main) ran ~8 rounds and correctly, persistently flagged a real High-severity 'fail-open' gap — DefaultSteerEnabled=true has no runner-side guard against an agent definition that doesn't understand the steer envelope silently acking and dropping a work-item update — matching a risk the PR's own description already named as an unresolved release gate. The author never disputed it; it was never code-fixed, only documented/mitigated, and the PR was eventually closed/restructured rather than merged with the gap open. Three separate Medium findings from the third-party qodo-code-review bot were fixed within ~20 minutes of being raised, showing fast author responsiveness to well-scoped feedback. Qodo also raised a High finding (editing an unmerged ADR in place looks like rewriting history) that the author correctly rebutted as a false positive using repo-specific ADR convention the bot didn't know — no action needed there since qodo is a third-party tool outside this org's control. Evidence for existing open issue fullsend-ai/agents#1267 ('pr-review skill re-raises author-deferred findings at full severity every round'): this PR is a second, higher-stakes data point — the fail-open finding was already acknowledged in the PR description as a known, unresolved release gate, yet fullsend-ai-review re-raised it at full severity across ~8 rounds with only updated line numbers, consistent with the exact pattern #1267 describes. Two new proposals below concern process gaps this retro found no existing issue for: (1) the PR's own four 'release gate' preconditions for shipping steering-on-by-default exist only as prose in PR descriptions, carried forward by hand into #7461, with the author explicitly noting 'nothing mechanical enforces them'; (2) issue #6957, the design's origin issue, still states steering defaults to off, which the PR body says is superseded by ADR 0113/0121 but issue #6957 itself carries no note to that effect.

Proposals filed

This branch was successfully deployed

2 active deployments
site-preview 354f82db Deployed Sep 16, 2026 by github-actions[bot]
dev 354f82db Deployed Sep 16, 2026 by waynesun09 via behaviour #13034
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fullsend-no-fix Skip bot-triggered fix agent runs risk/moderate PR risk: moderate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant