Skip to content

refactor(runtime): add the steer interface, off by default - #6959

Open
waynesun09 wants to merge 7 commits into
preserve-runsfrom
steer-followup-runs
Open

waynesun09 wants to merge 7 commits into
preserve-runsfrom
steer-followup-runs

Conversation

@waynesun09

@waynesun09 waynesun09 commented Sep 3, 2026 •

Copy link
Copy Markdown
Member

What this is now

The interface slice of the steering stack: the runtime capability a steer is delivered through, the harness block that configures it, and the two ADRs that decide it. Steering is off by default here — nothing in this PR turns it on, and no caller invokes it yet.

This PR was previously the whole of steering. It has been cut down: the watcher, provenance checks, the receipt and skip check, fullsend steer, and the on-by-default flip now live in the PRs stacked above this one, each with its own ADR.

Builds on #7007 (ADR 0106 implementation). Tracking issue: #6957 — which is also
the authority for the one protected-path edit here, the AGENTS.md topic-index
row pointing at docs/contributing/steering.md.

Implements the design recorded in #6957 (closed as completed; this stack ships it).

What lands

  • runtime.Steerer — an optional capability, Steer + Settle. RunParams.Steerable asks a steerable runtime to keep its session open; Run then returns only after Settle and the agent's current turn. A runtime that does not implement it is unchanged.
  • Three steerers. Claude Code and pi take a message into the live session through a mailbox their stdin is tailing; codex exec has no live channel, so the runner stops and restarts the sandbox, which ends the turn and keeps its disk state, and resumes the same thread.
  • Acknowledged delivery. A steer counts as delivered only when the runtime echoes that specific message, matched by the message's own identity rather than by counting — the mailbox sits in a directory the agent can write to, so an echo matching nothing outstanding is ignored.
  • The steer: harness block — enabled, max_steers, poll_interval_seconds, with base composition and validation. enabled is a pointer so absent and false stay distinguishable.
  • ADR 0113 (umbrella): steer rather than only preserve. ADR 0117: the interface and the mailbox transport.
  • docs/normative/steer-envelope/v1 — the envelope pinned as a versioned contract, because the runner writes those bytes and the agent definitions in fullsend-ai/agents match on them.

Notes for review

  • ADR 0106 is annotated, not superseded. Its Decision says fullsend run does not poll for later events; a steered run does. The annotation names that clause and nothing else — steering still invokes no run of its own, every event still creates its pending run, and the timeout window is unchanged. 0106's deferral of content provenance and actor authority stands, and is the mandate of the ADR in the PR above this one.
  • DefaultSteerEnabled is false. The flip is the last PR in the stack, and waits on the PRs between.
  • max_steers and poll_interval_seconds are parsed and validated here but have no consumer yet — the code that spends the cap and paces the interval arrives with the change that looks for updates. The field comments and the docs say so rather than describing them as live.
  • steering.md is a skeleton: concurrency, the contract, the transport, configuration. The provenance, watcher, ceilings and receipt sections arrive with the PRs that implement them.
  • Caller obligation, documented on the interface: the runner must hold its sandbox write lock across every Steer and Settle. Both write into the running sandbox — the mailbox append and the feeder kill, or codex's sandbox stop and start, which end every process in the sandbox and would otherwise cut off a credential refresher's upload mid-write.
  • Two decisions made in review. On pi, steering and model fallback are exclusive, and fallback wins: a steered session and its mailbox are bound to one launch of pi, and a fallback relaunches pi on the next model. So a pi run whose model chain can fall back is not steerable, logs one line saying why, and leaves its updates to the queued run. A steerable launch also clears the loader variables a .env could use to run code inside the agent and forge its delivery echo (NODE_OPTIONS/NODE_PATH/LD_* on Claude Code, LD_* on pi). The ordinary pi launch is tracked on pi launch does not clear LD_* loader variables after sourcing .env #7580.

Verification

go build ./..., go vet ./..., go test -race ./internal/runtime/... ./internal/harness/... all clean. pre-commit run --from-ref <base> --to-ref HEAD passes, ADR and markdown-link lints included; every commit was hook-validated in isolation as it was made. No file in this PR references steerwatch, the receipt, the skip check, or fullsend steer.

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 3, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 12:56 PM UTC · Completed 1:33 PM UTC

Commit: 4d175a6 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $21.98

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Steer in-flight agent runs on work-item updates

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Absorbs authorized work-item updates into running agent sessions instead of restarting.
• Verifies follow-up workflow provenance and skips queued work already consumed.
• Adds default-off steering for Claude, Codex, and pi runtimes.
Diagram

sequenceDiagram
    actor User as Maintainer
    participant Shim as Dispatch Shim
    participant Actions as GitHub Actions
    participant Runner as Agent Runner
    participant Watcher as Steer Watcher
    participant Forge as Forge API
    participant Runtime as Agent Runtime

    User->>Shim: Update work item
    Shim->>Actions: Authorize and route
    Actions-->>Runner: Queue follow-up run
    Runner->>Watcher: Start with job token
    loop Until settled
        Watcher->>Actions: Poll follow-up runs
        Actions-->>Watcher: Runs and jobs
        Watcher->>Watcher: Verify provenance
        Watcher->>Forge: Read current item
        Forge-->>Watcher: Work-item delta
        Watcher->>Runtime: Steer live session
        Runtime-->>Watcher: Delivery acknowledgement
    end
    Watcher->>Runtime: Settle session
    Runner->>Forge: Publish consumed marker
    Actions->>Runner: Start queued run
    Runner->>Forge: Read consumed marker
    Runner-->>Actions: Exit when handled
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Cancel and restart every run
  • ➕ Retains the existing simple concurrency model
  • ➕ Always starts from a fresh checkout and session
  • ➖ Repeats expensive context gathering and model work
  • ➖ Discards useful in-flight progress
  • ➖ Can repeatedly restart during bursty updates
2. External push relay
  • ➕ Could deliver updates immediately without polling
  • ➕ Could support multiple forge providers through one transport
  • ➖ Introduces a new public service and authentication boundary
  • ➖ Requires durable delivery, replay protection, and operational ownership
  • ➖ Duplicates authorization already recorded by workflow runs
3. Runner-managed checkout refresh
  • ➕ Read-only agents would immediately see the latest repository state
  • ➕ Reduces reliance on agents following fetch instructions
  • ➖ Can overwrite uncommitted work in mutation stages
  • ➖ Requires stage-specific workspace policy and conflict handling
  • ➖ Does not address comments, labels, reviews, or task amendments alone

Recommendation: The PR's follow-up-run watcher is the best fit for GitHub Actions because it reuses existing dispatch authorization and requires only outbound runner access. Keep the implementation default-off and roll it out runtime-by-runtime; cancellation remains the safest fallback where steering or provenance checks are unavailable.

Files changed (46) +7537 / -100

Enhancement (22) +3539 / -79
root.goRegister the steer subcommand +1/-0

Register the steer subcommand

• Adds the new 'fullsend steer' command to the root CLI.

internal/cli/root.go

run.goIntegrate steering into agent execution +104/-31

Integrate steering into agent execution

• Starts the follow-up watcher, marks runtime runs steerable, exports run baseline facts, and skips follow-up runs already consumed. It also attaches consumed markers to completion comments and centralizes prompt-text sanitization.

internal/cli/run.go

steer.goCoordinate watcher lifecycle and skip checks +404/-0

Coordinate watcher lifecycle and skip checks

• Adds steering eligibility, deadlines, sandbox-locked delivery and settlement, turn-end signaling, run baseline exports, and App-authored marker checks.

internal/cli/steer.go

steercmd.goAdd the fullsend steer command +186/-0

Add the fullsend steer command

• Parses GitHub work-item URLs and posts authorized '/fs-steer' comments with optional review, fix, or triage targeting. GitLab is recognized but explicitly unsupported.

internal/cli/steercmd.go

claude.goKeep Claude sessions open for live steering +106/-7

Keep Claude sessions open for live steering

• Launches steerable Claude sessions through a mailbox feeder, captures session IDs and acknowledgements, and aggregates metrics across multiple results. Non-steerable command behavior remains unchanged.

internal/runtime/claude.go

claude_progress.goParse Claude session and replay events +24/-2

Parse Claude session and replay events

• Surfaces Claude session IDs and replayed user-message acknowledgements. Resets result tracking when a steered turn begins so partial metrics remain recoverable.

internal/runtime/claude_progress.go

claude_steer.goImplement Claude mailbox steering +175/-0

Implement Claude mailbox steering

• Encodes stream-JSON user messages, appends steers to the live mailbox, settles the feeder safely, and aggregates per-turn usage with cumulative cost semantics.

internal/runtime/claude_steer.go

codex_run.goRun steerable Codex resume loops +199/-28

Run steerable Codex resume loops

• Refactors Codex execution into per-process turns and resumes the same thread after an interrupt. Preserves artifacts and aggregates metrics across resumed processes.

internal/runtime/codex_run.go

codex_steer.goImplement Codex interrupt-and-resume steering +291/-0

Implement Codex interrupt-and-resume steering

• Queues steers, interrupts active Codex processes through the stray-process sweep, resumes the captured thread, and records delivered updates. Settlement leaves the current turn running.

internal/runtime/codex_steer.go

event.goAdd session and delivery events +22/-2

Add session and delivery events

• Extends initialization events with session IDs and introduces replay events representing confirmed user-message delivery.

internal/runtime/event.go

pi_progress.goEmit per-prompt results in pi RPC mode +57/-0

Emit per-prompt results in pi RPC mode

• Adds steering-aware parsing that emits results after each settled prompt and recognizes successful RPC prompt acknowledgements without duplicating results at EOF.

internal/runtime/pi_progress.go

pi_run.goRun steerable pi sessions in RPC mode +114/-9

Run steerable pi sessions in RPC mode

• Launches pi with a runner-generated session ID and mailbox-fed RPC input. Captures acknowledgements, session metrics, and one cumulative result per prompt.

internal/runtime/pi_run.go

pi_steer.goImplement live pi RPC steering +93/-0

Implement live pi RPC steering

• Encodes mailbox prompts with 'streamingBehavior: steer', appends them to active sessions, and safely closes settled feeders.

internal/runtime/pi_steer.go

runtime.goExpose steerable runs and session metrics +12/-0

Expose steerable runs and session metrics

• Adds 'RunParams.Steerable' plus runtime session IDs and delivered steer records to run metrics.

internal/runtime/runtime.go

steer.goDefine the runtime steering contract +81/-0

Define the runtime steering contract

• Introduces 'Steerer', steer messages, results, unsupported errors, and caller locking requirements shared by runtime adapters.

internal/runtime/steer.go

steer_session.goManage live runtime steer feeds +370/-0

Manage live runtime steer feeds

• Implements the shared mailbox feeder registry and acknowledgement-based settlement state machine. It also renders the provenance-aware runner update envelope.

internal/runtime/steer_session.go

statuscomment.goAttach steering markers to completion comments +18/-0

Attach steering markers to completion comments

• Stores consumed steering metadata in terminal status comments while removing the marker from visible status text.

internal/statuscomment/statuscomment.go

steermarker.goEncode and locate consumed-run markers +125/-0

Encode and locate consumed-run markers

• Builds, parses, validates, and retrieves App-authored markers containing consumed follow-up run IDs and settled head SHAs.

internal/statuscomment/steermarker.go

actions.goAdd the follow-up Actions API client +172/-0

Add the follow-up Actions API client

• Reads workflow runs and jobs with bounded requests, authenticated by the captured job token, and returns candidates in creation order.

internal/steerwatch/actions.go

delta.goBuild sanitized work-item update deltas +255/-0

Build sanitized work-item update deltas

• Detects PR heads, comments, reviews, and issue metadata changes while excluding bot activity. It sanitizes and bounds the agent-facing update text.

internal/steerwatch/delta.go

provenance.goValidate follow-up run provenance +169/-0

Validate follow-up run provenance

• Requires the expected shim, allowed event, matching reusable-workflow chain, successful Route job, selected stage, freshness, and work-item binding.

internal/steerwatch/provenance.go

watcher.goImplement the follow-up run watcher +561/-0

Implement the follow-up run watcher

• Polls and verifies follow-up runs, folds simultaneous updates, computes deltas, enforces time and cost ceilings, delivers steers, and always settles sessions. Tracks seen and consumed runs separately for safe skipping.

internal/steerwatch/watcher.go

Refactor (1) +38 / -0
unicode.goShare agent prompt sanitization +38/-0

Share agent prompt sanitization

• Extracts Unicode sanitization for validation feedback and steering text while preserving benign compatibility characters.

internal/security/unicode.go

Tests (15) +3513 / -11
run_test.goAdapt bootstrap tests for run facts +4/-4

Adapt bootstrap tests for run facts

• Updates existing bootstrap environment tests for the new run-baseline argument.

internal/cli/run_test.go

steer_test.goTest CLI steering orchestration +494/-0

Test CLI steering orchestration

• Covers eligibility, deadlines, watcher startup and settlement, run facts, stage ambiguity, and fail-open consumed-marker checks.

internal/cli/steer_test.go

steercmd_test.goTest steer command parsing and posting +204/-0

Test steer command parsing and posting

• Covers work-item URL forms, stage validation, generated comment bodies, authentication failures, and comment-posting errors.

internal/cli/steercmd_test.go

harness_test.goTest steering configuration defaults +76/-0

Test steering configuration defaults

• Verifies YAML loading, opt-in behavior, defaults, explicit values, and validation bounds.

internal/harness/harness_test.go

claude_progress_test.goTest Claude steering stream events +94/-0

Test Claude steering stream events

• Covers session headers, replay acknowledgements, tool-result discrimination, and token salvage after earlier results.

internal/runtime/claude_progress_test.go

claude_steer_test.goTest Claude steering lifecycle and metrics +499/-0

Test Claude steering lifecycle and metrics

• Exercises command construction, envelope semantics, mailbox races, delivery acknowledgements, feeder shutdown, and multi-turn metric aggregation.

internal/runtime/claude_steer_test.go

codex_steer_test.goTest Codex steering queues and resumes +327/-0

Test Codex steering queues and resumes

• Covers early-steer handling, FIFO delivery, failed interrupts, settlement, resume command ordering, and cross-process metric aggregation.

internal/runtime/codex_steer_test.go

pi_steer_test.goTest pi RPC steering behavior +281/-0

Test pi RPC steering behavior

• Covers hardened command construction, prompt encoding, acknowledgements, per-prompt results, session IDs, and settlement.

internal/runtime/pi_steer_test.go

workflow_call_alignment_test.goValidate steering workflow contracts +82/-7

Validate steering workflow contracts

• Updates concurrency parsing for expression values and verifies gated cancellation, '/fs-steer' authorization, instruction stripping, and shim run names.

internal/scaffold/workflow_call_alignment_test.go

unicode_test.goTest shared prompt sanitization +30/-0

Test shared prompt sanitization

• Verifies clean text preservation, compatibility-character handling, and removal of non-rendering characters.

internal/security/unicode_test.go

steermarker_test.goTest steering marker safety and selection +193/-0

Test steering marker safety and selection

• Covers canonical rendering, malformed data, forged authors, latest-marker selection, completion integration, and visible-body filtering.

internal/statuscomment/steermarker_test.go

delta_test.goTest work-item delta construction +255/-0

Test work-item delta construction

• Covers PR and issue baselines, bot filtering, label changes, head movement, Unicode sanitization, truncation, and forge failures.

internal/steerwatch/delta_test.go

provenance_test.goTest provenance rejection boundaries +270/-0

Test provenance rejection boundaries

• Exercises workflow-chain equality, route and stage selection, work-item binding, replay rejection, foreign workflows, and ambiguous stage resolution.

internal/steerwatch/provenance_test.go

steerwatch_test.goProvide steer watcher integration fixtures +297/-0

Provide steer watcher integration fixtures

• Adds fake Actions endpoints, forge readers, delivery recorders, and shared fixtures for watcher and provenance tests.

internal/steerwatch/steerwatch_test.go

watcher_test.goTest watcher delivery and settlement +407/-0

Test watcher delivery and settlement

• Covers successful and failed delivery, candidate deduplication, steer caps, ticker and turn-end polling, deadlines, cancellation, API failures, and settlement guarantees.

internal/steerwatch/watcher_test.go

Documentation (5) +310 / -0
0101-steer-the-running-agent-on-work-item-updates.mdDocument the in-flight steering architecture +295/-0

Document the in-flight steering architecture

• Records the steering decision, provenance checks, runtime contract, limits, skip-marker protocol, security implications, and default-off rollout plan.

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

README.mdList the steer CLI command +1/-0

List the steer CLI command

• Adds 'fullsend steer' to the CLI command reference with a link to ADR 0101.

docs/cli/README.md

harness-fields.mdClassify steering as an overlay field +1/-0

Classify steering as an overlay field

• Documents 'steer' as an operational harness field eligible for overlay configuration.

docs/contributing/harness-fields.md

harness-reference.mdDocument steering harness settings +12/-0

Document steering harness settings

• Describes the default-off 'steer' block, runtime and repository prerequisites, steer cap, and polling interval.

docs/reference/harness-reference.md

runtimes.mdAdd runtime steering support matrix +1/-0

Add runtime steering support matrix

• Documents live steering for Claude and pi and interrupt-and-resume steering for Codex.

docs/runtimes.md

Other (3) +137 / -10
reusable-dispatch.ymlRoute steering commands and gate cancellation +64/-10

Route steering commands and gate cancellation

• Adds authorized '/fs-steer' routing with stage-specific permission floors. Stage concurrency stops cancelling in-flight runs only when 'FULLSEND_STEER=true', and fix instructions strip steering prefixes.

.github/workflows/reusable-dispatch.yml

harness.goAdd steering harness configuration +65/-0

Add steering harness configuration

• Introduces default-off steering configuration, default limits, poll interval resolution, and validation for invalid values.

internal/harness/harness.go

shim-per-repo.yamlBind shim runs to work items +8/-0

Bind shim runs to work items

• Adds a server-visible run name containing the repository and work-item number for provenance matching.

internal/scaffold/fullsend-repo/templates/shim-per-repo.yaml

@qodo-code-review

qodo-code-review Bot commented Sep 3, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (1) 📜 Skill insights (0)

Grey Divider


Action required

1. Accepted ADR lacks architecture update ✓ Resolved
Description
ADR 0101 is introduced with status: Accepted, but this PR contains no corresponding
docs/architecture.md change linking the decision under the relevant component. An accepted
architectural decision must update the living architecture overview in the same PR.
Code

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

+title: "101. Steer the running agent on work-item updates instead of cancelling the run"
+status: Accepted
Relevance

●●● Strong

Recent ADR precedent accepts findings requiring related architecture or problem-document updates.

PR-#6926
PR-#5016

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new ADR is explicitly Accepted at lines 2–3 and 18–20. PR Compliance IDs 1062062 and 1062100
require a same-PR architecture overview update, but docs/architecture.md is absent from the change
set.

Rule 1062062: Update architecture overview and problem docs when ADR is accepted
docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md[1-20]
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
The PR adds accepted ADR 0101 without updating `docs/architecture.md`.

## Issue Context
Add a surgical `Decided:` entry or short paragraph under the dispatch/runtime component, linking to ADR 0101. Also annotate any architecture open question resolved by this decision.

## Fix Focus Areas
- docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md[1-20]
- docs/architecture.md[1-1]

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


2. Runs consumed before delivery ✓ Resolved
Description
pollAndSteer marks a follow-up run consumed as soon as Steer returns, but Claude and pi only
append to a mailbox and Codex only queues the message; their actual delivery acknowledgements occur
later. If the runtime fails or times out before the echo or resume starts, the terminal marker still
causes the queued follow-up run to skip, silently losing the update.
Code

internal/steerwatch/watcher.go[474]

+	w.markSteered(accepted)
Relevance

●●● Strong

Accepted reliability precedents address state advancement after partial operations and preventing
lost or duplicated effects.

PR-#6711
PR-#6861

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The watcher calls markSteered immediately after Steer returns. Claude's implementation returns
after appendLine, while live delivery is only recorded by noteEcho; Codex returns after
enqueueing and records delivery later in nextCodexTurn, yet the CLI builds the skip marker from
the watcher's consumed IDs.

internal/steerwatch/watcher.go[454-479]
internal/runtime/claude_steer.go[62-71]
internal/runtime/steer_session.go[201-226]
internal/runtime/codex_steer.go[198-207]
internal/runtime/codex_run.go[584-592]
internal/cli/run.go[2214-2218]

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

## Issue description
Do not mark a follow-up workflow run consumed until the runtime confirms that its message reached the agent. Add an acknowledgement path for live mailbox echoes and Codex resume startup, and leave unacknowledged runs for the queued follow-up job.

## Issue Context
The watcher currently treats a successful queue or mailbox write as delivery. Runtime `SteerResult` records already distinguish the later point at which a live message is echoed or a Codex resume begins, but the marker is built directly from the watcher's earlier consumed set.

## Fix Focus Areas
- internal/steerwatch/watcher.go[454-479]
- internal/runtime/claude_steer.go[62-71]
- internal/runtime/steer_session.go[201-226]
- internal/runtime/codex_steer.go[198-207]
- internal/runtime/codex_run.go[584-592]
- internal/cli/run.go[2214-2218]

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


3. Issue snapshot never advances ✓ Resolved
Description
After a successful issue steer, buildDelta continues comparing title, body, and labels with the
run-start snapshot instead of the last delivered state. Later updates therefore repeat old changes
or omit a revert to the original value; if another visible change makes that steer non-empty, the
follow-up run is consumed and skipped despite the omitted metadata update.
Code

internal/steerwatch/delta.go[R124-127]

+	if issue.Title != w.cfg.Item.Title {
+		d.lines = append(d.lines, fmt.Sprintf("Title is now: %s", issue.Title))
+	}
+	if issue.Body != w.cfg.Item.Body {
Relevance

●●● Strong

Accepted bug precedents favor correcting stale cumulative snapshots and preserving accurate later
deltas.

PR-#6924

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The issue delta compares current metadata with w.cfg.Item, while successful delivery advances only
w.baseline; only PR head state receives a corresponding update. Thus the comparison baseline
remains the initial issue state across every steer.

internal/steerwatch/delta.go[120-140]
internal/steerwatch/watcher.go[430-479]
internal/steerwatch/watcher.go[495-512]

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

## Issue description
Issue title, body, and label deltas are always calculated against the run-start snapshot. Persist the latest successfully delivered issue state so subsequent steers describe only new changes, including reversions to an earlier value.

## Issue Context
`markSteered` advances the timestamp baseline and PR head only; it does not advance the issue metadata snapshot used by `buildDelta`. Update the snapshot only after successful delivery so failed steers remain retryable.

## Fix Focus Areas
- internal/steerwatch/delta.go[120-140]
- internal/steerwatch/watcher.go[430-479]
- internal/steerwatch/watcher.go[495-512]

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



Remediation recommended

4. Consequences lack required bullets ✓ Resolved
Description
The ADR's Consequences section consists of eight multi-sentence prose blocks rather than 3–5
one-sentence bullet points. This violates the required ADR consequence format.
Code

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

+## Consequences
+
+**What each stage's contract becomes.** Review produces one review per *settled* head rather than
+per dispatched head; the steered turn re-diffs A..B in-session, which is the incremental review
Relevance

●●● Strong

ADR formatting and concise consequence structure are regularly enforced in accepted review feedback.

PR-#2743
PR-#6083

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1062091 requires 3–5 one-sentence bullets, but the new section uses long prose
paragraphs beginning at line 243 and continuing through the end of the ADR.

docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md[241-295]
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's Consequences section does not use the required 3–5 one-sentence bullets.

## Issue Context
Summarize only the most important positive and negative consequences; move detailed operational analysis to a linked document.

## Fix Focus Areas
- docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md[241-295]

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


5. ADR 0041 lacks link ✓ Resolved
Description
The Context states that ADR 0101 follows ADR 0041 but mentions it as plain text rather than linking
to the existing ADR file. Related ADRs must be explicitly cross-referenced with a link in Context.
Code

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

+[#6573](https://github.com/fullsend-ai/fullsend/issues/6573)), and as an agent missing an update
+that arrived while it worked ([#1207](https://github.com/fullsend-ai/fullsend/issues/1207)).
+[#1637](https://github.com/fullsend-ai/fullsend/issues/1637) asked for the concurrency and cancel
+semantics to be written down after ADR 0041; this ADR is that content.
Relevance

●●● Strong

Recent ADR reviews explicitly require linked cross-references in Context.

PR-#5798
PR-#6926
PR-#1549

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Line 36 explicitly says this decision follows ADR 0041, establishing a relationship, while PR
Compliance ID 1062094 requires an explicit cross-reference link in Context.

docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md[33-36]
docs/ADRs/0041-synchronous-workflow-call-event-dispatch.md[1-20]
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 references related ADR 0041 in Context without a Markdown link.

## Issue Context
Link the reference to the existing `0041-synchronous-workflow-call-event-dispatch.md` ADR.

## Fix Focus Areas
- docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md[33-36]

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


6. steerCommentPoster bypasses forge.Client ✓ Resolved
Description
The CLI introduces a single-use local interface whose only production implementation is constructed
directly with gh.New(token). The comment operation should use an injected forge.Client rather
than a parallel abstraction created solely for this command.
Code

internal/cli/steercmd.go[R109-112]

+// steerCommentPoster is the write the command needs. github.LiveClient
+// satisfies it.
+type steerCommentPoster interface {
+	CreateIssueComment(ctx context.Context, owner, repo string, number int, body string) (*forge.IssueComment, error)
Relevance

●●● Strong

Historical architecture reviews accept findings that isolate runtime-specific abstractions and
preserve shared contracts.

PR-#1780
PR-#5643

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The added interface has one production implementation, gh.New(token), and one production call
path. This violates both the shared forge abstraction requirement and the prohibition on
abstractions without multiple production usages or implementors.

Rule 1062052: Route all git forge operations through forge.Client
Rule 1062065: Avoid single-use abstractions; require reuse across multiple call paths
internal/cli/steercmd.go[109-116]
internal/cli/steercmd.go[166-170]
internal/forge/forge.go[435-437]

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

## Issue description
`steerCommentPoster` is a single-use abstraction that bypasses the repository's shared `forge.Client` contract.

## Issue Context
The only production factory returns `github.LiveClient`; alternative implementations exist only in tests. Inject a `forge.Client` through the CLI composition path and use its comment operation directly.

## Fix Focus Areas
- internal/cli/steercmd.go[109-116]
- internal/cli/steercmd.go[166-170]
- internal/forge/forge.go[435-437]

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


View medium (5)
7. Actions API bypasses forge layer ✓ Resolved
Description
internal/steerwatch adds a raw HTTP client with a hardcoded GitHub API root and GitHub-specific
headers outside internal/forge/github. This bypasses forge.Client and violates the required
location for direct GitHub API calls.
Code

internal/steerwatch/actions.go[R26-28]

+// defaultAPIBase is the GitHub REST root. Tests point this at an httptest
+// server instead.
+const defaultAPIBase = "https://api.github.com"
Relevance

●● Moderate

The architecture rule is explicit, but no close historical finding exists for this new package and
API boundary.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new package hardcodes https://api.github.com, constructs requests with GitHub-specific
headers, and calls http.Client.Do directly. Rules 1062052 and 1062054 require these operations to
flow through forge.Client and reside under internal/forge/github.

Rule 1062052: Route all git forge operations through forge.Client
Rule 1062054: Restrict direct GitHub API calls to internal/forge/github
internal/steerwatch/actions.go[26-28]
internal/steerwatch/actions.go[103-129]
internal/forge/forge.go[435-437]

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 steering watcher performs direct GitHub Actions REST requests outside the approved forge adapter.

## Issue Context
Add the required workflow-run operations to the forge abstraction and implement GitHub-specific HTTP behavior under `internal/forge/github`. Inject that abstraction into the watcher rather than constructing an HTTP client there.

## Fix Focus Areas
- internal/steerwatch/actions.go[26-28]
- internal/steerwatch/actions.go[81-117]
- internal/steerwatch/watcher.go[128-154]
- internal/forge/forge.go[435-437]

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


8. Context has excess paragraphs ✓ Resolved
Description
The ADR Context contains six substantive paragraphs, exceeding the required 1–3 short paragraphs.
The extended operational explanation should be summarized and linked from a shorter Context section.
Code

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

+Every stage job in `reusable-dispatch.yml` declares `cancel-in-progress: true`. When a second
+event lands on the same work item while an agent is running — a push during a review, a comment
+during triage — the run in flight is killed and a fresh one starts from nothing. Everything the
+first run read is thrown away, and the same diff is read again from scratch.
Relevance

●● Moderate

ADR structure is reviewed closely, but no decisive paragraph-count precedent was found.

PR-#2743
PR-#6083

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The Context section spans lines 24–51 and contains six paragraphs, while PR Compliance ID 1062090
permits at most three short paragraphs.

docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md[22-51]
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's Context exceeds the required 1–3 short paragraphs.

## Issue Context
Retain a brief statement of the cancellation problem and link to the relevant problem documents or detailed design material rather than reproducing the full analysis.

## Fix Focus Areas
- docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md[22-51]

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


9. Per-org change lacks ADR callout 📘 Rule violation § Compliance
Description
The new ADR and watcher comments discuss the deprecated per-org dispatch path, but the PR
description does not disclose that deprecated functionality is being touched or reference ADR 0044.
Any such change must be explicitly called out even when it does not add per-org behavior.
Code

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

+The watcher asks the forge what the work item is, at startup, rather than reading it from the
+job's environment. `PR_HEAD_SHA` is set only on the deprecated per-org dispatch path, so a
+per-repo run has neither a head SHA nor any way to tell a pull request from an issue. Guessing
+wrong is not cosmetic: an issue-shaped baseline of empty title, body and labels makes every delta
Relevance

●● Moderate

The deprecated-path disclosure requirement is specific and compliance-oriented, but lacks a close
historical match.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The added ADR says PR_HEAD_SHA is set only on the deprecated per-org dispatch path, and the
watcher adds the same per-org-specific reasoning. PR Compliance ID 2795055 requires the PR
description to identify such changes and reference ADR 0044, which the supplied description does not
do.

Rule 2795055: Flag and avoid changes to deprecated per-org installation mode content (ADR 0044)
docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md[161-168]
internal/steerwatch/watcher.go[239-241]

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 changes reference deprecated per-org installation behavior without the required PR-description callout.

## Issue Context
Update the PR description to state that deprecated per-org dispatch behavior is referenced or affected and link to ADR 0044. Confirm that no new per-org-only capability is introduced.

## Fix Focus Areas
- docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md[161-164]
- internal/steerwatch/watcher.go[239-241]

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

Dismiss ↗ | View ↗


10. Runtime guide consultation undocumented ✓ Resolved
Description
The PR adds steering behavior to the Claude, Codex, and Pi runtime backends, but neither the PR
description nor the diff references docs/contributing/runtime-implementation.md. Behavioral
backend changes require explicit evidence that the runtime implementation guide was consulted and
any affected contracts were reviewed.
Code

internal/runtime/claude_steer.go[R174-175]

+// Ensure ClaudeRuntime implements Steerer.
+var _ Steerer = ClaudeRuntime{}
Relevance

●● Moderate

Runtime contract changes receive review, but guide-consultation documentation lacks a close
precedent.

PR-#1780
PR-#6926

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The added compile-time assertions show that all three production runtime backends now implement the
new Steerer behavior. PR Compliance ID 2889480 requires the PR description or diff to show
consultation of the implementation guide, but the guide is neither referenced nor modified.

Rule 2889480: Consult runtime implementation guide when modifying runtime.Runtime backends
internal/runtime/claude_steer.go[174-175]
internal/runtime/codex_steer.go[290-291]
internal/runtime/pi_steer.go[92-93]

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

## Issue description
Significant runtime backend behavior was added without documenting consultation of the runtime implementation guide.

## Issue Context
State in the PR description that `docs/contributing/runtime-implementation.md` was consulted and identify whether steering affects its security matrix, runtime interfaces, sandbox hooks, wire protocol, or workspace layout. Update that guide where necessary.

## Fix Focus Areas
- internal/runtime/claude_steer.go[174-175]
- internal/runtime/codex_steer.go[290-291]
- internal/runtime/pi_steer.go[92-93]
- docs/contributing/runtime-implementation.md[1-1]

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


11. Concurrency comments changed alone ✓ Resolved
Description
The workflow's concurrency documentation is rewritten in a comment-only hunk with no related
non-comment code changed in that hunk. The checklist prohibits standalone comment modifications
outside a hunk containing the associated code change.
Code

.github/workflows/reusable-dispatch.yml[R17-20]

+# Concurrency: each stage job declares a per-role group (fullsend-{stage}-...).
+# Roles operate independently — review dispatches do not cancel triage, code,
+# fix, etc. Within a role, cancel-in-progress is gated on the FULLSEND_STEER
+# repository variable (ADR 0101): unset, or anything but "true", keeps today's
Relevance

●● Moderate

Comment-only changes are sometimes accepted, but this repository also rejects stylistic comment-only
edits.

PR-#3079
PR-#5244

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Lines 17–24 modify only YAML comments; the actual cancel-in-progress expression is changed in a
separate hunk beginning near line 641. PR Compliance ID 1062072 requires a related non-comment code
change in the same comment hunk.

Rule 1062072: Do not add or modify comments outside code lines changed for the issue
.github/workflows/reusable-dispatch.yml[17-24]
.github/workflows/reusable-dispatch.yml[641-644]

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 concurrency explanation is modified in a comment-only diff hunk.

## Issue Context
Place the documentation adjacent to the relevant changed concurrency expression so the comment and functional change appear together, or avoid modifying the standalone header comment.

## Fix Focus Areas
- .github/workflows/reusable-dispatch.yml[17-24]
- .github/workflows/reusable-dispatch.yml[641-644]

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



Informational

12. ADR exceeds 100 lines ✓ Resolved
Description
The new ADR contains 283 content lines after its frontmatter, substantially exceeding the 100-line
maximum. This makes the decision record too large and indicates that detailed design material should
be moved elsewhere.
Code

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

+the same one a local resume needs.
Relevance

● Weak

A closely matching ADR length reduction finding was explicitly rejected in a recent ADR review.

PR-#2582

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1062092 limits ADR content excluding frontmatter to 100 lines, while this newly
added ADR runs from line 14 through line 295.

docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md[1-295]
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 exceeds the permitted 100 lines of content.

## Issue Context
Keep the point-in-time architectural decision concise and move detailed runtime, protocol, rollout, and implementation contracts into linked design or normative documents.

## Fix Focus Areas
- docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md[14-295]

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


Grey Divider

Context sources
✅ Compliance rules (platform): 72 rules
Review mode: ⚖️ Balanced

Grey Divider

Tip of the day
💡 Did you know, you can route each severity your way: inline, summary, both, or drop

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md Outdated
Comment thread docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md Outdated
Comment thread docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md Outdated
Comment thread docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md Outdated
Comment thread docs/ADRs/0101-steer-the-running-agent-on-work-item-updates.md Outdated
Comment thread internal/steerwatch/actions.go Outdated
Comment thread internal/cli/steercmd.go Outdated
Comment thread .github/workflows/reusable-dispatch.yml Outdated
Comment thread internal/steerwatch/delta.go Outdated
Comment thread internal/steerwatch/watcher.go Outdated
@github-actions

github-actions Bot commented Sep 3, 2026 •

Copy link
Copy Markdown

Site preview

Preview: https://e235161b-site.fullsend-ai.workers.dev

Commit: 841eeca111cf856684e35ff9cc6cd958db965ea3

@fullsend-ai-review fullsend-ai-review Bot added the risk/high PR risk: high label Sep 3, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 3, 2026 •

Copy link
Copy Markdown

Risk Assessment: moderate (2/5)

Details

Score preserved at 2 (moderate) under re-review anchoring: Tier 1 metadata signals remain in identical scoring brackets (39 files, 7,242 lines, 1 protected path in AGENTS.md, zero dependency changes, established author), Tier 2 churn in runtime files is largely confined to new steer files without regression history, and Tier 3 confirms an off-by-default opt-in interface slice with high rollback safety.

Previous run

Risk Assessment: moderate (2/5)

Details

Score preserved at 2 (moderate) under re-review anchoring: Tier 1 metadata signals remain in identical scoring brackets (39 files, 7,240 lines, 1 protected path in AGENTS.md, zero dependency changes, established author), Tier 2 churn in runtime files is largely confined to new steer files without regression history, and Tier 3 confirms an off-by-default opt-in interface slice with high rollback safety.

Previous run (2)

Risk Assessment: moderate (2/5)

Details

Score preserved at 2 (moderate) under re-review anchoring: Tier 1 metadata scoring brackets remain unchanged despite minor file growth (39 files, 7,228 lines, one protected path in AGENTS.md, no CI or dependency changes, established author), Tier 2 churn on pre-existing runtime files is offset by newly added steer files with no regression history, and Tier 3 confirms an off-by-default opt-in interface slice with high rollback safety.

Previous run (3)

Risk Assessment: moderate (2/5)

Details

Score preserved at 2 (moderate) under re-review anchoring: Tier 1 metadata (38 files, 6,788 lines, one protected-path touch in AGENTS.md, no CI or dependency changes, established author) remains identical to the prior run, Tier 2 churn on pre-existing runtime files is offset by newly added steer files with no regression history, and Tier 3 confirms an off-by-default opt-in interface slice with high rollback safety.

Previous run (4)

Risk Assessment: moderate (2/5)

Details

Score preserved at 2 (moderate) under re-review anchoring: Tier 1 metadata (38 files, 6,778 lines, one protected-path touch in AGENTS.md, no CI or dependency changes, established author) remains identical to the prior run, Tier 2 churn on pre-existing runtime files is offset by newly added steer files with no regression history, and Tier 3 confirms an off-by-default opt-in interface slice with high rollback safety.

Previous run (5)

Risk Assessment: moderate (2/5)

Details

Score preserved at 2 (moderate) under re-review anchoring: Tier 1 metadata (38 files, 6,776 lines, one protected-path touch in AGENTS.md, no CI or dependency changes, established author) remains identical to the prior run, Tier 2 churn on pre-existing runtime files is offset by newly added steer files with no regression history, and Tier 3 confirms an off-by-default opt-in interface slice with high rollback safety.

Previous run (6)

Risk Assessment: moderate (2/5)

Details

Score preserved at 2 (moderate) under re-review anchoring: Tier 1 metadata (38 files, 6,771 lines, one protected-path touch in AGENTS.md, no CI or dependency changes, established author) remains identical to the prior run, Tier 2 churn on pre-existing runtime files is offset by newly added steer files with no regression history, and Tier 3 confirms an off-by-default opt-in interface slice with high rollback safety.

Previous run (7)

Risk Assessment: moderate (2/5)

Details

Score preserved at 2 (moderate) under re-review anchoring: Tier 1 metadata (38 files, 6,771 lines, one doc-index protected-path touch in AGENTS.md, no CI or dependency changes, established non-bot author) remains unchanged in risk bracket; Tier 2 reflects elevated churn and author contention on existing runtime files while newly introduced steer files carry no prior history; and Tier 3 confirms an off-by-default opt-in interface slice (DefaultSteerEnabled=false) with high rollback safety, yielding a composite score that rounds to 2.

Previous run (8)

Risk Assessment: moderate (2/5)

Details

Score preserved at 2 (moderate) under re-review anchoring: Tier 1 metadata (38 files, 6,767 lines, one doc-index protected-path touch in AGENTS.md, no CI or dependency changes, established non-bot author) remains within the same risk bracket as the prior assessment; Tier 2 reflects elevated churn and author contention on existing runtime files while new steer files have no prior history; and Tier 3 confirms an off-by-default opt-in interface slice (DefaultSteerEnabled=false) with high rollback safety, yielding a composite score that rounds to 2.

Previous run (9)

Risk Assessment: moderate (2/5)

Details

Score preserved at 2 (moderate) under re-review anchoring: Tier 1 metadata (39 files, 6,486 lines, one doc-index protected-path touch in AGENTS.md, no CI or dependency changes, established non-bot author) remains consistent with the prior assessment; Tier 2 reflects elevated churn and multi-author contention on existing runner hotspot files while new steer files have no prior history on main; and Tier 3 confirms an off-by-default opt-in interface slice (DefaultSteerEnabled=false) with strong rollback safety, yielding a composite score of 2.13 that rounds to 2.

Previous run (10)

Risk Assessment: moderate (2/5)

Details

Score preserved at 2 (moderate) under re-review anchoring: Tier 1 metadata (39 files, 6,366 lines, one doc-index protected-path touch in AGENTS.md, no CI or dependency changes, established non-bot author) remains consistent with the prior assessment; Tier 2 reflects elevated churn and coupling on existing runner hotspots while new steer implementations have no history; Tier 3 confirms an off-by-default opt-in interface slice (DefaultSteerEnabled=false) with strong rollback safety, yielding a composite score that rounds to 2.

Previous run (11)

Risk Assessment: moderate (2/5)

Details

Score preserved at 2 (moderate) under re-review anchoring: Tier 1 metadata (39 files, 6,337 lines, one doc-index protected-path touch in AGENTS.md, no CI or dependency changes, established non-bot author) remains consistent with the prior assessment; Tier 2 reflects elevated churn and coupling on existing runner hotspots while new steer implementations have no history; Tier 3 confirms an off-by-default opt-in interface slice (DefaultSteerEnabled=false) with strong rollback safety, yielding a composite score of 2.31 that rounds to 2.

Previous run (12)

Risk Assessment: moderate (2/5)

Details

Score preserved at 2 (moderate) under re-review anchoring: Tier 1 metadata (39 files, ~6.2k lines, one authorized protected-path touch, no CI/dependency changes, established non-bot author) remains unchanged from the prior review; Tier 2 shows the same high-churn/multi-author pattern on pre-existing hotspots (harness.go, pi_run.go) while the new steer-* files have no history yet, matching the prior assessment's described profile; Tier 3 still reflects a closed, explicitly scoped, off-by-default interface slice with clear opt-in rollback safety and no warning-level labels.

Previous run (13)

Risk Assessment: moderate (2/5)

Details

Score preserved at 2 (moderate) under re-review anchoring: Tier 1 metadata (39 files, ~6.2k lines, one authorized protected-path touch, no CI/dependency changes, established non-bot author) remains unchanged from the prior review; Tier 2 shows the same high-churn/multi-author pattern on pre-existing hotspots (harness.go, pi_run.go) while the new steer-* files have no history yet, matching the prior assessments described profile; Tier 3 still reflects a closed, explicitly scoped, off-by-default interface slice with clear opt-in rollback safety and no warning-level labels.

Previous run (14)

Risk Assessment: moderate (2/5)

Details

Score preserved at 2 (moderate) under re-review anchoring: Tier 1 metadata (39 files, 5921 lines, one authorized protected-path touch, no CI/dependency changes, established non-bot author) is essentially unchanged from the prior review; Tier 2 shows high churn/multi-author activity on the touched harness.go hotspot but no missing change-coupling and very recent last-touch dates; Tier 3 reflects an explicitly scoped, off-by-default interface slice with clear opt-in rollback safety. No signal diverges materially from the prior assessment's described characteristics.

Previous run (15)

Risk Assessment: moderate (2/5)

Details

Score preserved at 2 (moderate) under re-review anchoring: Tier 1 metadata (39 files, 5929 lines, one authorized protected-path touch, no CI/dependency changes, established non-bot author) is unchanged from the prior review; Tier 2 shows high churn/multi-author/fix-revert activity on the touched harness/runtime hotspot files but all are very recently modified with no coupling, revert, or hack-sentiment signals beyond what the prior review already factored in; Tier 3 reflects an explicitly scoped, off-by-default interface slice with clear opt-in rollback safety.

Previous run (16)

Risk Assessment: moderate (2/5)

Details

Score preserved at 2 (moderate) under re-review anchoring: Tier 1 metadata (39 files, 5928 lines, one authorized protected-path touch, no dependency/CI changes, established non-bot author) is unchanged from the prior review, and Tier 2 churn/multi-author/fix-revert history on the touched runtime/harness hotspot files is consistent with risk already factored into the prior score rather than a new driver; Tier 3 reflects an explicitly scoped, off-by-default interface slice with clear rollback safety. Weighted composite ~2.25 rounds to 2.

Previous run (17)

Risk Assessment: moderate (2/5)

Details

Large diff footprint and one protected-path touch push metadata risk up, and the touched runtime/harness files show active, multi-author churn, but the change adds no CI or dependency risk, ships with strong test coverage, and is an explicitly scoped, off-by-default interface slice with clear rollback safety and a documented, authorized protected-path edit.

Previous run (18)

Risk Assessment: moderate (2/5)

Details

Composite ~2.36 (0.50x2.125 Tier1 + 0.30x3.0 Tier2 + 0.20x2.0 Tier3) rounds to moderate (2/5): a large but low-risk diff (no CI/dependency changes, one protected-path touch, zero security-sensitive paths per the Tier1 script, established non-bot author) offset by real churn/multi-author/fix-revert and coupling history on the pre-existing runtime/harness files this PR extends, tempered by very recent last-touch dates, zero revert/hack-sentiment signals, and an opt-in feature flag that is off by default and intentionally scoped as a documented narrow slice of the linked issue's larger staged plan. Unchanged from the prior assessment of this same revision (score 2); Tier 1 signals (file count range, protected-path count, dependency status, author type) are identical to the prior run. Note: this review's independent security-triage classifier flagged 27 of 39 files as "security-critical" using broader content/path heuristics for the steer mailbox/interrupt mechanism (distinct from the narrower Tier 1 SECURITY_SENSITIVE_COUNT path-pattern signal, which is 0) — that finding does not change the composite score but is relevant context for reviewers.

Previous run (19)

Risk Assessment: moderate (2/5)

Details

Composite ≈2.20 rounds to moderate (2/5): Tier 1 reflects a large but otherwise low-risk diff (no CI/dependency changes, one protected-path touch, zero security-sensitive files, established non-bot author); Tier 2 shows real churn/multi-author/fix-revert history on the pre-existing runtime/harness files this PR extends, offset by very recent last-touch dates and zero revert/hack-sentiment signals; Tier 3 stays low because the new steer capability ships off-by-default/opt-in and this is an explicitly intentional narrower slice of a larger staged feature rather than scope creep. Unchanged from the immediately preceding assessment of this same revision.

Previous run (20)

Risk Assessment: moderate (2/5)

Details

Tier 1 metadata (2.13) reflects a large but otherwise low-risk diff: no CI or dependency changes, one protected-path touch, zero security-sensitive files, established non-bot author, moderate test ratio. Tier 2 (2.86) confirms real churn/multi-author/fix-revert history on the pre-existing runtime/harness files this PR extends, offset by very recent last-touch dates and zero revert/hack-sentiment signals. Tier 3 (1.25) stays low because the new steer capability ships off-by-default/opt-in and the PR is an explicitly intentional narrower slice of a larger staged feature rather than scope creep. Weighted composite (0.52.13 + 0.32.86 + 0.2*1.25 ≈ 2.17) rounds to moderate (2/5), unchanged from the immediately preceding re-review of this same revision (Tier 1 signals unchanged; no Tier 2/3 signal provides a specific reason to diverge).

Previous run (21)

Risk Assessment: moderate (2/5)

Details

Adjusted down from the prior 3/5 (elevated) because this revision carries a linked issue (#6957), enabling full three-tier weighting (50/30/20) instead of the prior no-issue 62/38 split, and SECURITY_SENSITIVE_COUNT is 0 (contradicting the prior rationale's characterization); Tier 1 (2.13) reflects a large but otherwise low-risk diff (no CI/dependency changes, established author, moderate test ratio, one protected-path touch), Tier 2 (2.57) confirms real churn/multi-author/fix-revert history on the pre-existing runtime/harness files this PR extends, and Tier 3 (1.67) is low because the new steer capability is off-by-default/opt-in and the issue's larger scope is an intentionally staged partial implementation, yielding a weighted composite of ~2.17 that rounds to moderate (2/5).

Previous run (22)

Risk Assessment: elevated (3/5)

Details

Elevated from the prior review's moderate score: this revision now touches a protected path (AGENTS.md) and concentrates 26 of 39 files in security-sensitive steer/harness authorization surfaces, pushing Tier 1 up; Tier 2 confirms sustained high churn, multi-author contention, and fix/revert rates on the pre-existing runtime/harness files this PR extends. With no linked issue, weights redistribute to 62% Tier1/38% Tier2, yielding a weighted composite that rounds to elevated (3/5).

Previous run (23)

Risk Assessment: moderate (2/5)

Details

No linked issue exists so weights redistribute to Tier1 62% / Tier2 38%; Tier1 stays low (avg ~1.9/5) because protected-path, security-sensitive, CI-workflow, and dependency signals are all zero/false, with only diff size (large, 5293 lines/36 files) and a low test ratio (0.22) pulling individual dimensions up, while Tier2 lands moderate (avg ~2.6/5) driven by genuinely high churn and conventional fix-commit frequency on the pre-existing shared harness/runtime files this PR touches, offset by very recent last-modified dates and near-zero revert/workaround sentiment hits. This independently reproduces the prior assessment score of 2 (moderate); no signal provides a specific reason to move off moderate despite the PR file count being trimmed from ~79 to 36 files in this restructuring.

Previous run (24)

Risk Assessment: moderate (2/5)

Details

Composite recomputed independently at 2/5 (Tier1=2, Tier2=3, Tier3=2): Tier1 stays low because protected-path, security-sensitive, CI-workflow, and dependency signals are all zero/false, with only diff size (large, 5061 lines/36 files) and low test ratio (0.22) pulling individual dimensions up; Tier2 is moderate, driven by genuinely high churn/fix-grep and multi-author-contention on the pre-existing shared runtime/harness files this PR touches, offset by very recent last-modified dates and zero workaround-sentiment/revert-frequency hits; Tier3 is mild since the PR is an intentional, narrowly-scoped, additive, opt-in slice (steer default-disabled, no caller wired yet) of a larger breaking-change-labeled umbrella issue. This matches the prior assessment score of 2 -- Tier 1 signals are unchanged from the prior run despite the diff growing slightly, and no Tier 2/3 signal provides a specific reason to move off moderate.

Previous run (25)

Risk Assessment: moderate (2/5)

Details

Independent recomputation for the restructured 36-file/4807-line diff drops from the prior elevated (3/5): protected_path_count is now 0 and ci_workflow_changed is false (no .github/workflows/ or AGENTS.md content changes in this diff, unlike the pre-split PR), pulling Tier 1 down despite the change-size sub-score remaining high; Tier 2 stays moderate due to continued churn in shared harness/runtime files; Tier 3 is mild since the PR is an intentional, scoped, opt-in slice of a larger issue. The composite genuinely moves to moderate (2/5) because the PR was split and no longer touches protected CI/governance paths.

Previous run (26)

Risk Assessment: elevated (3/5)

Details

Independent recomputation confirms the prior elevated score: Tier 1 sits in top-bucket ranges (79 files, 12,615 lines, 2 protected CI workflow paths, no dependency changes, established human author), Tier 2 shows sustained high churn/fix-revert/multi-author activity concentrated in the same hotspot files diluted somewhat by many brand-new files with no history, and Tier 3 stays moderate — design matches the linked issue but full real-world validation is explicitly still pending while the feature ships fully default-off. Weighted composite rounds to 3, matching the prior review; no signal changed enough to move off elevated.

Previous run (27)

Risk Assessment: elevated (3/5)

Details

Re-review anchoring holds at elevated (3/5): Tier 1 metadata (78 files/12,518 lines, 3 protected CI-adjacent paths, no dependency changes) sits in the same top-bucket ranges as the prior assessment; Tier 2 dilution reflects many brand-new files rather than reduced risk, with the same two hotspots (internal/forge/github/github.go, internal/runtime/codex_run.go) still showing high churn/fix-revert/multi-author patterns; Tier 3 stays moderate with issue #6957 deliberately left open pending real-world validation but the feature shipped fully default-off. No signal justifies moving off the prior elevated score.

Previous run (28)

Risk Assessment: elevated (3/5)

Details

Re-review anchoring holds at elevated (3/5): Tier 1 metadata (73 files/12,409 lines, 3 protected CI-adjacent paths, no dependency changes) sits in the same top-bucket ranges as the prior assessment; Tier 2 confirms internal/forge/github/github.go and internal/runtime/codex_run.go remain the same high-churn, high fix/revert, multi-author hotspots; Tier 3 stays moderate, with issue #6957 deliberately left open pending real-world validation but the feature shipped fully default-off, so no signal justifies moving off the prior elevated score.

Previous run (29)

Risk Assessment: elevated (3/5)

Details

Re-review anchoring holds: independently computed Tier 1 signals (73 files/12,498 lines, 3 protected paths, CI workflow touched, 0.27 test ratio) land in the same top-bucket ranges as the prior elevated assessment, Tier 2 confirms the same high-churn, multi-author hotspots with no new concerning file, and Tier 3 stays low with matching issue scope and no risky labels, so the score is preserved at elevated (3/5).

Previous run (30)

Risk Assessment: elevated (3/5)

Details

Held at elevated (3/5), unchanged from the prior re-review. Tier 1 metadata moved only within the same scoring buckets since the last assessment (68→70 files, ~11,729→12,350 lines, both already at the top bucket; 2 protected CI workflow paths unchanged; no dependency-file changes; test ratio 0.31→0.29 stays in the same bucket; non-first-time, non-bot author). Tier 2 confirms internal/cli/run.go, internal/forge/github/github.go, and .github/workflows/reusable-dispatch.yml remain the same high-churn, multi-author hotspots already driving the prior score, with no new concerning files introduced. Tier 3 stays low: the linked issue's scope matches the PR, carries no warning-level labels, and the change ships behind a verified default-off flag. This review's own findings corroborate the score: a forge-abstraction-deviation left unresolved for two review rounds and the protected-CI-path change are the concrete drivers of a merge-review-worthy but not high/critical risk profile; no new high-severity code defects were found and several previously flagged issues (steer-budget carryover, workflow-run pagination, receipt collision, console-rendering regression) were independently verified fixed in this revision.

Previous run (31)

Risk Assessment: elevated (3/5)

Details

Diff has grown slightly to 68 files / ~11,729 lines, still touching 2 protected CI workflow paths (fullsend.yaml, reusable-dispatch.yml) with a large blast radius across runtime, dispatch, CLI, and forge layers. internal/cli/run.go and internal/forge/github/github.go remain high-churn hotspots, reproducing the prior review's top risk driver unchanged. This is offset by a verified default-off gate (steerEligible hard-requires FULLSEND_PRESERVE_RUNS=true), no new dependency-file or supply-chain changes, a solid test ratio (~31% of changed files are tests), an experienced non-first-time author, and a thorough ADR-driven design (ADR 0101) with an explicit staged rollout gated behind a separate agents-repo PR. Score held at elevated (3/5), unchanged from the immediately-prior re-review.

Previous run (32)

Risk Assessment: elevated (3/5)

Details

Very large diff (65 files, ~11.4k lines, 2 protected CI paths, blast radius large) and a modified hotspot (internal/cli/run.go, internal/forge/github/github.go) with heavy historical fix/revert churn push risk up, offset by a verified default-off opt-in flag, no security-sensitive or dependency-file changes beyond the workflow files already counted as protected paths, solid test ratio, an experienced non-first-time author, and a thorough ADR-driven design with a staged rollout plan, landing the composite at elevated (3) rather than high.

Previous run (33)

Risk Assessment: high (4/5)

Details

Tier 1 metadata is essentially unchanged from the prior assessment (65 files, ~11.4k lines, protected-path count maxed at 2, CI workflow touched, no dependency files, non-bot/non-first-time author), so per re-review anchoring the score is preserved at 4/high; Tier 2 reinforces this with heavy churn/fix-revert rates and change-coupling gaps on the two touched files with prior git history; Tier 3 shows a breaking change to in-flight agent-run control stacked on an unmerged base PR, addressing an issue whose own author states validation criteria are not yet met, though the capability itself is safely gated behind a default-off harness.steer.enabled flag confirmed in the diff.

Previous run (34)

Risk Assessment: high (4/5)

Details

Score anchored at 4 (high) per re-review anchoring policy. Tier 1 signals essentially unchanged: FILES_CHANGED remains 65, LINES_CHANGED increased marginally from 10936 to 11414, PROTECTED_PATH_COUNT at maximum threshold, CI_WORKFLOW_CHANGED true. Tier 2 churn identical to prior (internal/cli 291, internal/runtime 158 commits in 30d). Breaking behavioral change to in-flight agent run control with the issue still open.

Previous run (35)

Risk Assessment: high (4/5)

Details

High risk anchored from prior assessment: Tier 1 signals essentially unchanged (65 files, 10936 lines, PROTECTED_PATH_COUNT at maximum threshold), Tier 2 churn near-identical (internal/cli 291 and internal/runtime 158 commits in 30d), breaking-change designation, and no mitigating factors justify lowering the score.

Previous run (36)

Risk Assessment: high (4/5)

Details

High risk maintained from prior assessment: PR grew from 46 to 64 files (10920 lines), PROTECTED_PATH_COUNT at maximum threshold, Tier 2 churn signals near-identical (internal/cli 295 and internal/runtime 159 commits in 30d), and no mitigating factors emerged to justify a lower score.

Previous run (37)

Risk Assessment: high (4/5)

Details

High risk due to a breaking change, very large blast radius (46 files, 7637 lines across 9 packages including a new internal/steerwatch/ package), active modification of high-churn areas (internal/cli/ with 298 commits in 30 days, internal/runtime/ with 118), CI workflow changes, and cross-component scope spanning runner and dispatch. The 0.33 test file ratio provides partial mitigation but is modest for a change of this magnitude.

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 3, 2026 •

Copy link
Copy Markdown

Review

Findings

Medium

Low

  • [message-authentication] internal/runtime/pi_steer.go:83 — Partially resolved. pi still keys an outstanding delivery on a UUID written into the agent-writable mailbox (id at pi_steer.go:78, passed as the append-line key at :83). parsePiStream's rpc handler (pi_progress.go:704-723) still forwards only id/command/success — no body — and steerFeed.noteEcho (steer_session.go:287) records a SteerResult on that id match. An agent that truncates the mailbox and rewrites a line under the same id can make RunMetrics.Steers report an operator's amendment as delivered while pi consumed different text; Claude Code is not exposed the same way because its key is the verbatim content. This is the disclosed KNOWN LIMIT at steer_session.go:274-284; the underlying code is byte-identical to the prior review. It remains unreachable today: DefaultSteerEnabled is false (harness.go:283) and no production caller sets RunParams.Steerable. This round updated docs/contributing/steering.md (~lines 94-96) with the pi-specific caveat that a matched pi ack shows id acceptance, not body binding — but docs/ADRs/0117-steer-interface-in-sandbox-mailbox.md was not updated and still states (lines 81-82) that delivery is "matched by the message's own identity rather than by counting," with no pi-specific caveat that identity is an agent-rewritable rpc id unbound to the consumed body.
    Remediation: Qualify the identity-matching sentence in ADR 0117 the same way steering.md now does, so a future receipt path cannot read the ADR as a content-binding guarantee for pi. Do not treat a pi id-only ack as receipt-quality payload authentication in any later consumer of RunMetrics.Steers.

  • [sub-agent-failure] N/A — The challenger sub-agent returned an empty adjudicated_findings array after being given one non-empty finding (the message-authentication finding above) to adjudicate. Per review policy, a full removal from a non-empty input is treated as a challenger failure rather than a legitimate adjudication, so the pre-challenger finding set is used unchanged here. For transparency, the challenger's stated reasoning was that the finding is a docs-completeness nit on an Accepted ADR describing an already-documented, currently unreachable protocol limit — worth weighing when triaging the finding above, but not accepted as grounds for removal under this policy.


This is a re-review (prior head 9f8155b95814b32f400e17a75c3fc2fa2f1e3fd6; current head 841eeca111cf856684e35ff9cc6cd958db965ea3). The forge compare API reports 24 commits / 60 files between the two revisions because the shared preserve-runs base branch advanced underneath this PR (rebase), not because this PR grew. Isolating this PR's own base-to-head patch at both revisions (rather than raw file content, which mixes in that unrelated base-branch drift) shows only 1 of this PR's 39 files carries a genuine PR-authored change since the prior review:

  • docs/contributing/steering.md — added the pi-specific caveat above, addressing part of the prior round's LOW message-authentication remediation.

A second file, internal/runtime/codex_bootstrap_test.go, differs in raw content between the two head snapshots, but this is base-branch drift absorbed by the rebase (an unrelated FULLSEND_TOOL_ALLOWLIST feature that landed on the base branch) — this PR's own diff to that file (the steer-interrupt sandbox-lifecycle test scaffolding) is unchanged since the prior review.

correctness (full scope, mandatory every round) and style-conventions (trivial scope, no prior findings) independently re-examined this revision and found nothing. docs-currency was dispatched because a documentation file changed since the prior review; it confirmed the new steering.md caveat is accurate against pi_steer.go/pi_progress.go, and re-confirmed the two previously-fixed doc gaps (max_steers range, wire-format versioning) remain fixed. security re-examined the unchanged pi_steer.go/steer_session.go code and the new steering.md text, and concluded the prior LOW finding is only partially addressed — steering.md now carries the caveat but ADR 0117 does not — narrowing the remediation accordingly. intent-coherence and cross-repo-contracts were not re-dispatched: neither had prior findings, and the only change since the prior review (a documentation caveat) does not implicate new behavior the linked issue makes claims about, nor touch public API/interface/schema/CLI surface.

Previous run

Review

Findings

Medium

Low

  • [message-authentication] internal/runtime/pi_steer.go:80 — pi keys an outstanding delivery on a UUID written into the agent-writable mailbox (steer-inbox.ndjson at pi_steer.go:80, passed as the append-line key). parsePiStream's rpc response handler (pi_progress.go:704-723) forwards only id/command/success — no body — and steerFeed.noteEcho (steer_session.go:287) records a SteerResult on that id match. An agent that truncates the mailbox and rewrites a line under the same id can therefore make RunMetrics.Steers report an operator's amendment as delivered while pi actually consumed different text; Claude Code is not exposed the same way because its key is the verbatim content. This is exactly the KNOWN LIMIT disclosed at steer_session.go:274-284, which states it cannot be closed with pi's current ack and records the gap rather than defending against it. It is not reachable today: DefaultSteerEnabled is false (internal/harness/harness.go:283) and no production caller sets RunParams.Steerable. docs/contributing/steering.md (~91-93) and ADR 0117 (~81-82) still describe delivery as echoing "that specific message, matched by the message's own identity," with no pi-specific caveat that identity is an agent-rewritable rpc id unbound to the consumed body. This finding, its underlying code, and its severity are unchanged across many prior review rounds of this PR.
    Remediation: Do not treat a pi id-only ack as receipt-quality payload authentication in any later consumer of RunMetrics.Steers. The implementation already defers a code-level close (the KNOWN LIMIT comment); this PR is not required to stop recording SteerResult or add a truncate-rewrite regression test. Qualify the identity-matching language in docs/contributing/steering.md (and ADR 0117, still unmerged) so a future receipt path cannot read those docs as a content-binding guarantee for pi.

This is a re-review (prior head 9c8d16eabe74ed76f3299d28e6be8ec6c8bd5960; current head 9f8155b95814b32f400e17a75c3fc2fa2f1e3fd6). The forge compare API reports 35 commits / 95 files between the two revisions because the shared preserve-runs base branch advanced underneath this PR (rebase), not because this PR grew. Isolating this PR's own base-to-head patch at both revisions (rather than raw file content, which mixes in that unrelated base-branch drift) shows only 2 of this PR's 39 files carry a genuine PR-authored change since the prior review:

  • docs/normative/steer-envelope/v1/README.md — added an "Open Questions" section
  • docs/reference/harness-reference.md — documented the 0–50 range for max_steers

Both changes are fixes for the two LOW findings raised in the prior review round (wire-format-versioning and missing-doc, respectively). This round's cross-repo-contracts and docs-currency sub-agents independently confirmed both fixes against the current file content and did not re-raise them. internal/runtime/pi_steer.go and internal/runtime/steer_session.go — where the remaining message-authentication LOW finding lives — are unchanged; this round's security sub-agent independently re-verified the finding still holds and the challenger kept it at LOW (unreachable today, and the gap is already disclosed in the code's own KNOWN LIMIT comment), narrowing its remediation to the documentation caveat that is still missing. correctness (full scope, mandatory every round) and style-conventions (trivial scope, no prior findings) independently re-examined this revision and found nothing.

Previous run (2)

Review

Findings

Medium

Low

  • [message-authentication] internal/runtime/pi_steer.go:85 — pi's Steerer.Steer keys an outstanding delivery on a UUID written into the agent-writable mailbox (steer-inbox.ndjson); steerFeed.noteEcho (internal/runtime/steer_session.go:287) records a SteerResult as soon as pi's rpc ack carries that id, with no binding to the consumed message body. An agent that truncates the mailbox and rewrites a line under the same id can therefore make RunMetrics.Steers report an operator's amendment as delivered while the agent actually consumed different text. Claude Code is not exposed (its key is the verbatim content). This is precisely the case the code's own KNOWN LIMIT comment at internal/runtime/steer_session.go:274-284 discloses and explicitly defers, stating it cannot be closed with pi's current ack. It is not reachable today — DefaultSteerEnabled is false and no caller in this PR sets RunParams.Steerable — but it becomes load-bearing the moment a later PR treats SteerResult as a receipt that can skip the queued follow-up run. docs/contributing/steering.md and ADR 0117 still phrase the delivery defense as identity-matching without a pi-specific caveat.
    Remediation: Do not treat a pi id-only ack as receipt-quality payload authentication. Either stop recording SteerResult for pi until the ack binds the consumed body, or tag pi live deliveries as unauthenticated (a distinct mode/field) and document that a receipt system must not skip the queued run on that signal alone. Add a regression test that truncates the mailbox and rewrites the same rpc id with different text, asserting no receipt-quality SteerResult is recorded. Qualify the identity-matching language in steering.md / ADR 0117 for pi's case.

  • [missing-doc] docs/reference/harness-reference.md:200 — steer.max_steers is validated in internal/harness/harness.go with an upper bound (maxSteerMaxSteers = 50; "steer.max_steers must be at most 50, got %d"), but neither the YAML sample comment (line 133) nor the field-details prose (line 200) documents this 0–50 range. The sibling field poll_interval_seconds documents its 0–600 ceiling in both the sample comment and the prose. A user setting max_steers above 50 will hit an undocumented validation error.
    Remediation: Document the ceiling next to the default in the YAML sample comment, and state the 0–50 range (0 or unset → default 2) in the steer field-details paragraph, matching how poll_interval_seconds is documented.

  • [wire-format-versioning] docs/normative/steer-envelope/v1/README.md:40 — The envelope's binding "opening line" carries no in-band version marker; versioning is entirely out-of-band (a v1/v2 directory convention plus an instruction that the runner and the fullsend-ai/agents repo must change their definitions in the same deploy window). The document itself states the failure mode if the two repos ever deploy out of lockstep is silent: "the definitions will read its message as ordinary content." This is a disclosed, intentional tradeoff of a test-pinned, string-matched contract rather than a hidden bug, but it is a durable compatibility gap for a contract this PR explicitly describes as consumed cross-repo, and the document has no Open Questions entry noting the tradeoff was considered.
    Remediation: Not urgent, and do not change the pinned opening line in this PR (that would itself be a breaking cross-repo change). Consider adding an Open Questions note in the v1 README that an in-band version discriminator was considered and deferred, so the tradeoff is visible to future readers before a v2 is needed.


This is a re-review (prior head 49d716be7ccebe5b06dabea6b3f28e67932160d6; current head 9c8d16eabe74ed76f3299d28e6be8ec6c8bd5960, 135 commits apart per the forge compare API because the shared preserve-runs base branch advanced). Comparing this PR's own diff hunks (base-to-head) at both revisions — rather than raw file content, which is contaminated by unrelated base-branch drift — shows 17 of this PR's 39 files received new commits since the prior review: the codex run/steer path, claude.go, internal/harness/compose.go, internal/runtime/steer.go, internal/sandbox/sandbox.go, and several docs/ADR files. AGENTS.md's own diff hunk is unchanged from the prior review.

Both prior LOW findings (internal/runtime/codex_steer.go requeueFront, and unquoted RepoDir in internal/runtime/claude.go) were independently re-checked against the current code by this round's correctness sub-agent and found fixed — requeueFront now requeues unconditionally, and buildRunCommand now quotes RepoDir and the env path. security, intent-coherence, docs-currency, and cross-repo-contracts — none of which had prior findings — were re-dispatched this round because the files that changed since the prior review are directly relevant to each dimension (sandbox/env-handling code for security, the ADRs and core Steerer interface for intent-coherence, several doc files for docs-currency, and the exported Steerer interface plus the new docs/normative/steer-envelope/v1 contract for cross-repo-contracts). style-conventions ran at trivial scope (no prior findings) and found nothing.

All three LOW findings above were adversarially challenged. The challenger downgraded the security sub-agent's original MEDIUM finding on pi_steer.go's mailbox-ack authentication to LOW, and the docs-currency sub-agent's original MEDIUM finding on max_steers documentation to LOW, in both cases because the underlying issue — while real — is either already disclosed in the code's own comments and currently unreachable (the security case), or a comment-completeness gap rather than a behavioral defect (the docs case). The challenger also removed a fourth, INFO-level cross-repo-contracts finding about Steerer interface ordering guarantees, on the grounds that the interface's existing "CALLER OBLIGATION" lock-holding documentation already implies the ordering guarantee the finding asked for.


Labels: PR adds substantial harness config surface (steer: block in internal/harness), touches internal/sandbox lifecycle code, and introduces two new ADRs, none of which are currently labeled.


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

Medium

Low

  • [logic-error] internal/runtime/codex_steer.go:350 — requeueFront still returns without requeueing when q.settled is set, and its own comment claims this matches confirmDelivery's behavior. That comment is stale: confirmDelivery requeues via requeueFrontLocked unconditionally, including after settle, with an explicit note that skipping the requeue previously dropped an older message while a newer one queued behind it was still delivered out of order. The only caller of requeueFront is nextCodexTurn's tid=="" arm; the production Run loop only calls nextCodexTurn after q.currentThreadID() is already confirmed non-empty (it breaks first otherwise) and threadID is first-wins/never cleared, so this arm is not reachable from Run today. The inconsistency between the comment and confirmDelivery's documented, tested invariant is still worth fixing so the pattern is not later cargo-culted back onto confirmDelivery or exercised by a future caller.
    Remediation: Make requeueFront requeue unconditionally (delete the settled early-return), matching confirmDelivery / requeueFrontLocked, and correct the comment so it no longer claims confirmDelivery drops on settle.

  • [logic-error] internal/runtime/claude.go:456 — buildRunCommand interpolates params.RepoDir (and the .env path) with fmt.Sprintf and no shellQuote, on both the pre-existing non-steerable launch (cd %s && . %s && claude) and the new steerable branch this PR adds (cd %s && %s && %s && . %s && ...). pi and codex both quote RepoDir with shellQuote. RepoDir is runner-controlled (the sandbox workspace repo path) so a metacharacter in practice is unlikely, but the new steerable string is a second unquoted copy of the same pattern touched by this diff's refactor.
    Remediation: Quote params.RepoDir and the env-file path with shellQuote on both Claude launch strings, matching pi and codex.


The forge compare API reports this revision as "diverged" from the prior review (9 ahead / 9 behind, 45 files) because the shared preserve-runs base branch moved (mergeable_state: behind) — none of those 45 files carry a PR-authored change. Direct byte comparison of file content at the prior-reviewed head SHA (c35c90dafeff9ed67b0693315f5f380965bbb820) against the current head SHA, for every one of this PR's own 38 changed files, found zero differences — all 38 are byte-identical to the prior review; the remaining files in the compare API's list (e.g. docs/agents/fix.md, docs/cli/run.md) belong to unrelated commits that landed on the base branch and are not part of this PR at all. Per re-review policy, security, intent-coherence, docs-currency, and cross-repo-contracts were not re-dispatched: none had prior findings, and there is no changed content since the prior review that would independently qualify them.

correctness was dispatched at full scope regardless (mandatory on every round) and, examining the unchanged code fresh, surfaced two logic-error findings not raised in any prior round — a stale/incorrect code comment paired with an inconsistent early-return in the codex steer queue, and an unquoted RepoDir interpolation on the Claude launch path shared with pi/codex's quoted equivalents. Both were adversarially challenged: the codex-queue finding was downgraded from medium to low after verifying the flagged code path is unreachable from the production Run loop today (the underlying comment/behavior inconsistency is still real and worth fixing); the Claude quoting finding was kept as-is. style-conventions (trivial scope, no prior findings) returned no findings on this unchanged code.


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

Medium


Only 2 of the 38 changed files differ from the prior review (head SHA 022f7e75831557d5c2d32033c94679f50a332ba8), verified by direct byte comparison against that revision rather than relying solely on the forge compare API (which reports diverged due to unrelated churn on the shared preserve-runs base branch). Both are pure documentation clarifications with no code impact:

  • docs/ADRs/0113-steer-the-running-agent-on-work-item-updates.md — adds a closing paragraph stating that an absorbed update never passes through the harness pre-script; per-event gates (payload validation, merged/draft checks, author skip-lists, iteration/size caps) do not apply to it, and the runner's own gates (Route job authorization, envelope sanitizer/size bounds, max_steers) apply instead.
  • docs/reference/harness-reference.md — clarifies that max_steers counts steer deliveries, not raw events (one delivery folds in every update seen within a single poll window), and notes absorbed updates skip the pre-script.

The remaining 36 files, the PR title, and all commit message prefixes (refactor(runtime)/refactor(harness), consistent with COMMITS.md) are unchanged from the prior round.

Correctness (full scope, mandatory) and style-conventions (trivial scope, per re-review policy for a dimension with no unresolved prior findings) independently re-examined this revision and returned no findings. Docs-currency was dispatched because documentation files changed since the prior review; it confirmed the two clarifications are internally consistent with each other and with the rest of the unchanged documentation, and cross-checked the max_steers/poll_interval_seconds defaults against internal/harness/harness.go. Security, intent-coherence, and cross-repo-contracts were not re-dispatched: none had prior findings, and the only changes since the prior review — two documentation clarifications — touch neither auth/permissions/secrets/config surface, public API/interface surface, nor issue-scope-relevant behavior.

Previous run (5)

Review

Findings

Medium


Note on prior findings not observed in the current diff: the previously-flagged injection finding (internal/runtime/steer_session.go:335, id-only pi delivery-ack matching) is not carried forward this round. This round's challenger re-verified the claim against the current code with fresh evidence — DefaultSteerEnabled is false (internal/harness/harness.go:283), the only production assignment to RunParams.Steerable is piSteerGate (internal/runtime/pi_run.go:1189), which can only narrow an already-set flag, Steerable: true appears only in tests, and internal/runtime/runtime.go:37-38 documents that no caller sets Steerable yet, so RunMetrics.Steers is empty on every run today — and concluded the underlying primitive, while real, is unreachable dead code on this revision rather than a live defect, and removed it. steer_session.go and pi_progress.go remain byte-identical to the prior-reviewed head SHA; nothing about the underlying code changed this round, only the adjudication.

Only 3 files changed since the prior review (head SHA 29b7f281104357a8e526d7281f4f925a1b808c04), all pure documentation clarifications with no code impact: docs/ADRs/0117-steer-interface-in-sandbox-mailbox.md, docs/contributing/steering.md, and docs/runtimes.md — each updated to note that the OpenShell sandbox-stop fix (NVIDIA/OpenShell#3036) has shipped in OpenShell v0.1.0, but fullsend's CI still pins OpenShell 0.0.116, so the 45-second stop cost still holds until a separate version-pin bump. The remaining 35 files, including the PR title and all 7 commit messages (refactor/docs prefixes throughout, consistent with COMMITS.md), are unchanged from the prior round. Correctness, docs-currency, and style-conventions independently re-examined the diff this round and returned no findings; security re-verified the persisting design and, via the challenger, concluded the finding above is no longer warranted. Intent-coherence and cross-repo-contracts were not re-dispatched: neither had prior findings, and the only PR-authored change since the prior review (three documentation clarifications) touches neither public API/interface surface nor issue-scope-relevant behavior.

Previous run (6)

Review

Findings

Medium

Low

  • [injection] internal/runtime/steer_session.go:335 — Pi delivery-ack matching is still id-only: matchLocked treats key == id when id != "". pi's rpc ack (pi_progress.go:722) emits UserReplayEvent{ID: resp.ID} with empty Content, so the content-matching arm never runs on that path. noteEcho already records this as a KNOWN LIMIT: truncating steer-inbox.ndjson and rewriting a line with the same rpc id can substitute agent-authored body while the runner still records a SteerResult. Claude is not exposed the same way (its key is the verbatim content). DefaultSteerEnabled remains false, no production caller sets RunParams.Steerable (only tests, and piSteerGate, which only narrows an already-set flag), and nothing outside runtime tests consumes RunMetrics.Steers as a skip/receipt signal, so this remains residual risk of the matching primitive rather than a reachable defect in this diff. steer_session.go and pi_progress.go are byte-identical to the prior-reviewed head SHA — this finding has persisted, unresolved, across many prior review rounds of this PR.
    Remediation: Before any receipt/skip-check treats a pi ack as authoritative, bind the ack to the consumed body (hash) rather than id alone, or move the mailbox outside agent write reach.

  • [sub-agent-failure] N/A — The challenger sub-agent returned an empty adjudicated_findings array after being given one non-empty finding (the injection finding above) to adjudicate. Per review policy, a full removal from a non-empty input is treated as a challenger failure rather than a legitimate adjudication, so the pre-challenger finding set is used unchanged here. For transparency, the challenger's stated reasoning was that the finding restates an author-documented, already-known transport limitation on a disabled, uncalled interface — worth weighing when triaging the injection finding above, but not accepted as grounds for removal under this policy.


Note on prior findings not observed in the current diff: the previously-flagged pattern-inconsistency finding (internal/runtime/codex_steer.go:397, defaultCodexInterrupt lacking the required Fn suffix, doc-comment convention, and t.Cleanup in its test) is fixed at this head — codex_steer.go (definition and call site) now uses defaultCodexInterruptFn with an updated doc comment including "Override in tests to...", and codex_steer_test.go restores it via t.Cleanup instead of defer. This is a genuine PR-authored fix, confirmed by direct comparison against the prior-reviewed head SHA (b9f2edc6633da380eea1c0926b70ec51702abb32) and independently verified by this round's style-conventions sub-agent, which found no other issues. Correctness returned no findings this round, independently re-examining the full diff rather than relying on the prior round's clean result. Security independently re-verified the persisting id-only ack-matching design and found no new issues.

The orchestrator additionally verified, by diffing actual file contents at the prior-reviewed head SHA against the current head SHA, that 36 of the 38 changed files carry no PR-authored change since the prior review — only internal/runtime/codex_steer.go and internal/runtime/codex_steer_test.go changed (the naming fix above). The differences observed between the two snapshots for the other files (docs/architecture.md, docs/contributing/runtime-implementation.md, docs/glossary.md, docs/guides/user/customizing-agents.md, docs/runtimes.md, internal/runtime/claude.go, internal/runtime/codex_bootstrap_test.go, internal/runtime/codex_run.go, internal/runtime/runtime.go) are base-branch drift from unrelated PRs merged into the shared preserve-runs base branch since the prior review, not this PR's own commits — this PR was rebased onto an advanced base between review rounds (mergeable_state: behind). Intent-coherence, docs-currency, and cross-repo-contracts were not re-dispatched this round: none had prior findings, and the only PR-authored change since the prior review (the codex_steer.go/codex_steer_test.go naming fix) touches neither documentation, public API/interface surface, nor issue-scope-relevant behavior.

Previous run (7)

Review

Findings

Medium

Low

  • [injection] internal/runtime/steer_session.go:335 — Pi delivery-ack matching is id-only: matchLocked treats key == id when id != "". pi's rpc ack (pi_progress.go:722) emits UserReplayEvent{ID: resp.ID} with empty Content, so the content-matching arm never runs on that path. noteEcho already records this as a KNOWN LIMIT: truncating steer-inbox.ndjson and rewriting a line with the same rpc id can substitute agent-authored body while the runner still records a SteerResult. Claude is not exposed the same way (its key is the verbatim content). DefaultSteerEnabled remains false, no production caller sets RunParams.Steerable (only tests, and piSteerGate, which only narrows an already-set flag), and nothing outside runtime tests consumes RunMetrics.Steers as a skip/receipt signal, so this is residual risk of the matching primitive rather than a reachable defect in this diff.
    Remediation: Before any receipt/skip-check treats a pi ack as authoritative, bind the ack to the consumed body (hash) rather than id alone, or move the mailbox outside agent write reach.

  • [pattern-inconsistency] internal/runtime/codex_steer.go:397 — defaultCodexInterrupt is a new package-level function-value test seam (installed by newCodexSteerQueue, overridden by TestCodexRun_TurnThatCompletedBeforeTheStopKeepsItsVerdict) that does not follow docs/contributing/go-code.md's "Injectable function variables (test seams)" convention: it lacks the required XxxFn/xxxFn suffix, its doc comment has no "Override in tests to..." sentence, and the test restores it with defer (codex_steer_test.go:1110) rather than t.Cleanup. Contrast internal/sandbox/sandbox.go's RetrySleepFn, which follows all three rules.
    Remediation: Rename defaultCodexInterrupt to defaultCodexInterruptFn in internal/runtime/codex_steer.go (and its call site at line 94). Update its doc comment to start with the variable name and include "Override in tests to...". In internal/runtime/codex_steer_test.go:1110, restore the previous value with t.Cleanup instead of defer.


Note on prior findings not observed in the current diff: the previously-flagged conventional-commits finding (PR title and commits using feat(runtime)/feat(harness) prefixes for internal, off-by-default abstractions) is fixed at this head — the PR title and all 7 commits now use refactor(runtime), refactor(harness), or docs(adr) prefixes, none use feat. Correctness, intent-coherence, docs-currency, and cross-repo-contracts returned no findings this round — correctness independently re-verified the previously-fixed steerable-turn context-cancellation and guard-exit-code (97/98) handling in codex_run.go remains correct; docs-currency confirmed documentation (harness reference, architecture doc, glossary, steering.md, normative steer-envelope spec) remains consistent with the Steerer interface, the steer: harness block, and the pinned steer-envelope opening line; cross-repo-contracts confirmed the Steerer interface, RunParams/RunMetrics additions, and the normative spec remain additive-only and internally consistent with no breaking change.


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 (8)

Review

Findings

Medium

Low

  • [injection] internal/runtime/steer_session.go:338 — Pi delivery-ack matching is still id-only: matchLocked treats key == id when id != "". pi's rpc ack (pi_progress.go:722) emits UserReplayEvent{ID: resp.ID} with empty Content, so the content-matching arm never runs on that path. noteEcho (steer_session.go:274-287) still records this as a KNOWN LIMIT: truncating steer-inbox.ndjson and rewriting a line with the same rpc id can substitute agent-authored body while the runner still records a SteerResult. Claude is not exposed the same way (its key is the verbatim content). DefaultSteerEnabled remains false, no production caller sets RunParams.Steerable (only tests, and piSteerGate, which only narrows an already-set flag), and RunMetrics.Steers has no skip/receipt consumer yet, so this is a residual risk of the matching primitive rather than a reachable defect in this diff. This exact finding has persisted, unresolved, across many prior review rounds of this PR.
    Remediation: Before any receipt/skip-check treats a pi ack as authoritative, bind the ack to the consumed body (hash) rather than id alone, or move the mailbox outside agent write reach.

  • [conventional-commits] PR title / commit messages — The PR title and 4 of its 7 commits use the feat(runtime)/feat(harness) Conventional Commits prefix (e.g. "feat(runtime): add the steer interface, off by default"). Per this repo's COMMITS.md (binding per AGENTS.md): "feat is wrong for: ... Adding internal packages, helpers, or abstractions that don't change user-visible behavior → refactor" and "When in doubt, prefer refactor or chore over feat or fix." DefaultSteerEnabled remains false at this head (internal/harness/harness.go) and no production caller sets RunParams.Steerable. GoReleaser builds release notes from merged PR titles, so this dormant, opt-in-only capability would appear under Features for a release where no user actually observes any behavior change. This finding has persisted, unresolved, across the last several review rounds of this PR.
    Remediation: Change the PR title (and ideally the commit message prefixes) from feat(runtime)/feat(harness) to refactor(runtime)/refactor(harness), consistent with COMMITS.md's guidance for internal abstractions that do not yet change user-visible behavior. The prefix can revert to feat on the stacked PR that actually flips DefaultSteerEnabled and wires in a caller.


Note on prior findings not observed in the current diff: the previously-flagged incorrect-doc finding on poll_interval_seconds's documented range in docs/reference/harness-reference.md is not observed in the current diff — the range description now reads "(0–600; 0 or unset takes the default of 30)", consistent with Validate() in internal/harness/harness.go. Correctness and intent-coherence returned no findings this round. Docs-currency confirmed the harness reference, architecture doc, glossary, and other modified documentation accurately reflect the Steerer interface, the per-runtime support matrix, and the harness steer: block. Cross-repo-contracts confirmed the Steerer interface and the RunParams/RunMetrics additions remain additive-only with no breaking change, mirroring the existing ProviderResolver optional-capability pattern, and that the steer-envelope normative spec's DRAFT/binding distinction is unchanged.

Previous run (9)

Review

Findings

Medium

Low

  • [injection] internal/runtime/steer_session.go:335 — Pi delivery-ack matching is still id-only: matchLocked treats key == id when id != "". pi's rpc ack (pi_progress.go:722) emits UserReplayEvent{ID: resp.ID} with empty Content, so the content-matching arm never runs on that path. noteEcho (steer_session.go:274-287) still records this as a KNOWN LIMIT: truncating steer-inbox.ndjson and rewriting a line with the same rpc id can substitute agent-authored body while the runner still records a SteerResult. Claude is not exposed the same way (its key is the verbatim content). DefaultSteerEnabled remains false, no production caller sets RunParams.Steerable (only tests), and RunMetrics.Steers has no skip/receipt consumer yet, so this is a residual risk of the matching primitive rather than a reachable defect in this diff. This exact finding has persisted, unresolved, across many prior review rounds of this PR; steer_session.go and pi_progress.go are unchanged since the prior review's head SHA.
    Remediation: Before any receipt/skip-check treats a pi ack as authoritative, bind the ack to the consumed body (hash) rather than id alone, or move the mailbox outside agent write reach.

  • [conventional-commits] PR title / commit messages — The PR title and commits use the feat(runtime) Conventional Commits prefix ("feat(runtime): add the steer interface, off by default"). Per this repo's COMMITS.md (binding per AGENTS.md): "feat is wrong for: ... Adding internal packages, helpers, or abstractions that don't change user-visible behavior → refactor" and "When in doubt, prefer refactor or chore over feat or fix." DefaultSteerEnabled remains false at this head (internal/harness/harness.go) and no production caller sets RunParams.Steerable. GoReleaser builds release notes from merged PR titles, so this dormant, opt-in-only capability would appear under Features for a release where no user actually observes any behavior change. This finding has persisted, unresolved, across the last several review rounds of this PR.
    Remediation: Change the PR title (and ideally the commit message prefixes) from feat(runtime) to refactor(runtime), consistent with COMMITS.md's guidance for internal abstractions that do not yet change user-visible behavior. The prefix can revert to feat on the stacked PR that actually flips DefaultSteerEnabled and wires in a caller.

  • [incorrect-doc] docs/reference/harness-reference.md:200 — The prose documents poll_interval_seconds as accepting the range "(1–600)", but Validate() in internal/harness/harness.go only rejects negative values (PollIntervalSeconds < 0) and values whose resolved duration exceeds maxSteerPollInterval (600s); poll_interval_seconds: 0 is accepted and falls back to DefaultSteerPollInterval (30s) via SteerPollInterval() (PollIntervalSeconds <= 0 takes the default). The documented floor of 1 does not match the schema's actual accepted floor of 0. This is a public .fullsend/config.yaml/harness-yaml field whose valid-value set other tooling or harness authors may rely on verbatim from this reference page.
    Remediation: Update the range description to reflect that 0 (unset, defaults to 30s) through 600 are valid, e.g. "poll_interval_seconds (0–600, 0 meaning unset/default)", or add an explicit lower-bound validation of 1 in code if a true minimum of 1 is intended.


Note on prior findings not observed in the current diff: the previously-flagged codex_run.go guard-exit finding (a steered run's integrity-guard exit codes 97/98 being treated as a retryable resume miss and silently replaced by lastGood's prior success) is fixed at this head — Run now checks out.exitCode == codexHooksMissingExit || out.exitCode == codexConfigTamperedExit immediately after each turn and returns that verdict directly, before lastGood can be consulted (codex_run.go:611-616), and a resume with no thread ID also breaks before nextCodexTurn is reached. TestCodexRun_SteeredResumeGuardExitIsTheVerdict and TestCodexRun_SteeredFirstTurnGuardExitReturnsAtOnce cover both cases. Intent-coherence and docs-currency returned no findings this round — intent-coherence confirmed the PR traces cleanly to tracking issue #6957 and the interface-slice scoping (steering off by default, DefaultSteerEnabled = false) is a deliberate, documented design decision with no scope creep; docs-currency confirmed the harness reference, runtime capability matrix, architecture doc, and glossary are all updated consistently with the new steer: block and Steerer interface (the one prose inaccuracy on poll_interval_seconds's range is captured above). Cross-repo-contracts confirmed the Steerer interface and related RunParams/RunMetrics/InitEvent changes are additive only, with no backward-compatibility issue.


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 (10)

Review

Findings

Medium

  • [protected-path] AGENTS.md:43 — This PR modifies AGENTS.md (a protected/governance path), adding a Steering row to the "Topic-specific guidance" index table pointing at docs/contributing/steering.md. The edit matches AGENTS.md's own indexing convention for documenting new docs/contributing/ topics, and it is the only protected-path change among the 39 files in this PR. The PR body cites tracking issue Steer the in-flight agent run on work-item updates instead of cancelling it #6957 as the authority for this exact edit, and the PR is legitimately stacked on the still-open PR feat(dispatch)!: never cancel the agent run in flight on a work-item update #7007 (verified: this PR's base SHA equals feat(dispatch)!: never cancel the agent run in flight on a work-item update #7007's head SHA). Human approval is always required for protected-path changes regardless of context.
    Remediation: A human reviewer must explicitly review and approve this AGENTS.md change before merge. No code change is required — this is a process/governance gate, not a defect.

  • [logic-error] internal/runtime/codex_run.go:636 — The steerable Codex loop treats a process that never opened a thread, and a process that exited the config/hooks integrity guards, as just another turn to wait or retry on. After runCodexTurn returns, the loop always banks counters and calls nextCodexTurn unless err != nil. Guard failures (codexHooksMissingExit=97, codexConfigTamperedExit=98) come back with err == nil and ProcessState set — the launch shell exits before codex exec runs — so they take this path. (1) On the first turn, with no thread.started observed, q.threadID stays empty and nextCodexTurn parks in waitForWork until Settle or a later steer, so a steerable run whose hooks/config guard already refused to start burns the rest of the run budget instead of surfacing 97/98 immediately. (2) After a successful first turn, a resume that hits 97/98 also has an empty per-process thread id, so confirmDelivery(false) requeues it as a transient resume miss (up to maxCodexResumeAttempts); when the loop ends, out.threadID == "" with lastGood != nil replaces that outcome with the previous successful turn, so codexVerdict never sees 97/98 and the run reports success. These exit codes exist to refuse an unhooked or redirected process, not to be retried as a missed steer and then dropped from the verdict. No test drives a steerable Run through a 97/98 exit. This is currently unreachable in production (DefaultSteerEnabled is false and no caller sets RunParams.Steerable), but the loop is new in this PR and lastGood silently overriding an integrity-guard refusal is a fail-open of exactly the checks those exit codes exist to enforce, once steering is wired up by a later PR in the stack.
    Remediation: After each steerable process, abort before nextCodexTurn when there is nothing to resume onto or when the process is a hard guard failure: if q.currentThreadID() is still empty, settle and break so the first-turn 97/98/crash is the run verdict; if out.exitCode is codexHooksMissingExit or codexConfigTamperedExit, settle and return r.codexVerdict(out) even when lastGood is set (do not let lastGood hide a refuse-to-run exit, and do not treat it as a retryable resume miss). Add tests: (a) first steerable turn exits 97 with no thread — Run returns that error without waiting on waitForWork; (b) a completed first turn plus a resume that exits 97 — Run returns the guard error, not lastGood's success, and does not relaunch the resume up to the retry cap.

Low

  • [injection] internal/runtime/steer_session.go:335 — Pi delivery-ack matching is still id-only: matchLocked treats key == id when id != "". pi's rpc ack (pi_progress.go) emits UserReplayEvent{ID: resp.ID} with empty Content, so the content-matching arm never runs on that path. noteEcho still records this as a KNOWN LIMIT: truncating steer-inbox.ndjson and rewriting a line with the same rpc id can substitute agent-authored body while the runner still records a SteerResult. Claude is not exposed the same way (its key is the verbatim content). DefaultSteerEnabled remains false, no production caller sets RunParams.Steerable (only tests, and piSteerGate, which preserves the incoming flag), and RunMetrics.Steers has no skip/receipt consumer yet, so this is a residual risk of the matching primitive rather than a reachable defect in this diff. This exact finding has persisted, unresolved, across many prior review rounds of this PR.
    Remediation: Before any receipt/skip-check treats a pi ack as authoritative, bind the ack to the consumed body (hash) rather than id alone, or move the mailbox outside agent write reach.

  • [conventional-commits] PR title / commit messages — The PR title and commits use the feat(runtime) Conventional Commits prefix ("feat(runtime): add the steer interface, off by default"). Per this repo's COMMITS.md (binding per AGENTS.md): "feat is wrong for: ... Adding internal packages, helpers, or abstractions that don't change user-visible behavior → refactor" and "When in doubt, prefer refactor or chore over feat or fix." The PR description itself states steering is off by default and no caller invokes it yet — verified at the current head: DefaultSteerEnabled remains false in internal/harness/harness.go, and no production caller sets RunParams.Steerable. GoReleaser builds release notes from merged PR titles, so this dormant, opt-in-only capability would appear under Features for a release where no user actually observes any behavior change. COMMITS.md treats a Conventional Commits prefix mismatch as a required change, not a cosmetic one. This finding has persisted, unresolved, across the last several review rounds of this PR.
    Remediation: Change the PR title (and ideally the commit message prefixes) from feat(runtime) to refactor(runtime), consistent with COMMITS.md's guidance for internal abstractions that do not yet change user-visible behavior. The prefix can revert to feat on the stacked PR that actually flips DefaultSteerEnabled and wires in a caller.


Note on prior findings not observed in the current diff / not carried forward: the previously-flagged nextCodexTurn context-cancellation gap (a queued steer surviving a cancelled-but-not-expired context and spawning a doomed resume) is fixed at this head — the loop now checks ctx.Err() before takePending/stakeDelivery, codexTurnTimeout also fails on a cancelled context, and TestNextCodexTurn_CancelledContextLeavesQueuedSteer covers the queued-steer case. Loader-env-unset hygiene across Claude/pi/Codex steerable launches (NODE_OPTIONS/NODE_PATH/LD_PRELOAD/LD_LIBRARY_PATH/LD_AUDIT, plus Codex's Python variants) was independently re-checked and shows no regression from the fixes landed in earlier rounds. Intent-coherence, docs-currency, and cross-repo-contracts returned no findings this round — intent-coherence confirmed the PR traces cleanly to tracking issue #6957, that slicing the epic so DefaultSteerEnabled stays false in this PR is a deliberate, documented design decision, and found no scope creep; cross-repo-contracts confirmed Steerer is an additive, optional capability interface (mirroring the existing ProviderResolver pattern) that does not force changes on existing Runtime implementations, and that the normative steer-envelope spec's DRAFT/binding distinction is unchanged from the version settled in an earlier round.


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 fullsend-ai-review Bot added component/runner Agent runner behavior and lifecycle component/dispatch Workflow dispatch and triggers labels Sep 3, 2026
@waynesun09

Copy link
Copy Markdown
Member Author

functional-tests is red on main itself, not from this PR: a workflow_dispatch control on main (run 33763731846) fails identically (4/4 policy_denied on the first Vertex call), and the last run that actually executed the step and passed was 33704432608 (01:38Z). Every green functional run on other PRs since then skipped the step for lack of relevant changes. Tracked in #6962; bisect runs on this branch's runtime-only and pre-wiring refs failed the same way and those refs are deleted.

waynesun09 added a commit that referenced this pull request Sep 3, 2026
Two review findings on #6959, one cosmetic and one that would have lost
updates.

1. closeFeedIf was duplicated verbatim on ClaudeRuntime and PiRuntime.
   Extracted as steerCloseFeedIf in steer_session.go, next to the state
   machine whose decision it acts on. Both runtimes call it; behaviour is
   unchanged, and it now has direct tests (the two copies had none, being
   reachable only from Run's stream handler).

2. Confirming steer-2's Qodo fix surfaced a real defect on the codex path.
   recordDelivery was called from nextCodexTurn, BEFORE the resumed process
   launched, so a resume that failed to start — or one codex refused
   because the thread was gone — still appended a SteerResult. With the
   marker now rebuilt from RunMetrics.Steers, that would mark the follow-up
   run consumed, the queued run would skip it, and the update would be lost
   outright rather than merely delayed. Delivery is now two-phase:
   nextCodexTurn stakes the message with the resume's start time, and
   confirmDelivery records it only once that process reported a thread of
   its own. codex emits thread.started on a resume — verified live, the
   resumed process repeats the original thread_id — so an empty thread id
   is proof the resume never took. It is confirmed on the error path too,
   which is precisely where the resume did not happen. DeliveredAt is still
   the resume's start, not its end, because that is when the message enters
   the thread.

Verified for the reviewer's question, all three runtimes append a
SteerResult only after the delivery signal and never on a failed one, and
FollowUpRunID is carried from the SteerMessage in every case:

  - Claude: appended in noteEcho, driven by the --replay-user-messages
    echo (isReplay). appendLine increments nothing and queues nothing
    unless the mailbox write returned exit 0, so a failed write can never
    acquire a result. Already covered; the failed-write test now also
    asserts steerResults() stays empty.
  - pi: same shared machinery, its ack being the rpc `response` with
    command=prompt and success=true (a success=false response is
    deliberately not an ack, already tested). Added two pi-level tests —
    nothing recorded at append time, recorded on the steer's own ack with
    the right run id, and nothing recorded when the write fails — because
    the reviewer asked per runtime and pi had none of its own.
  - Codex: as above, plus tests that an unconfirmed resume records nothing,
    that a discarded delivery cannot be resurrected by a later confirm, and
    that confirming with nothing staked (every run's first turn) is a
    no-op.

steerCloseFeedIf, stakeDelivery and confirmDelivery are at 100%.

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

fullsend-ai-review Bot commented Sep 3, 2026 •

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 3:30 PM UTC · Ended 4:01 PM UTC

Commit: 748dd20 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 3, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure (validation failed after 2 iteration(s)) · Started 4:04 PM UTC · Completed 4:46 PM UTC

Commit: 0dbfade · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:15 PM UTC · Completed 4:42 PM UTC

Commit: 0e91fe5 · View workflow run →

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

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:56 PM UTC · Completed 6:16 PM UTC

Commit: 1c4dd8a · View workflow run →

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

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 7:42 PM UTC · Completed 8:05 PM UTC

Commit: 0604fb4 · View workflow run →

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

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 9:13 PM UTC · Completed 9:35 PM UTC

Commit: b9f2edc · View workflow run →

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

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 3:31 AM UTC · Completed 3:52 AM UTC

Commit: 29b7f28 · View workflow run →

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

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 1:06 PM UTC · Completed 1:26 PM UTC

Commit: 022f7e7 · View workflow run →

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

fullsend-ai-review[bot]

This comment was marked as outdated.

@maruiz93 maruiz93 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think there might be some issues by enabling steering that we should declare in the consequences due to skipping pre-script.

the item moved; the receipt that lets the queued run skip its work; and whether steering is on by
default.

## Consequences

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I understand correctly, one of the consequences is that the new input doesn't pass through the pre-script. I believe this is important enough to be written in here.

This could have behaviour and security considerations on agents so they should be explicitly declared so agent creators are aware of them.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agreed, and added in c35c90d. ADR 0113 now has a Consequences bullet for this (in 4b8a665):

  • The pre-script runs once, on the host, for the event that dispatched the run. An update the run absorbs goes straight into the sandbox, so no per-event gate a pre-script implements applies to it: payload validation, a merged or draft check, an author skip-list, an iteration cap, an instruction size cap.
  • The runner applies its own gates instead: the Route job's authorization of the event, the split into one amendment and context, the envelope's sanitizer and size bounds, the exclusion of fullsend's own output, and max_steers.
  • An agent creator whose gate must hold for every event keeps it in the dispatch arm, enforces it in the agent from the envelope, or leaves steering off for that harness. The post-script is unaffected.

The steer section of harness-reference.md now says the same in one sentence, so agent creators see it where they configure steering.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The head is now 49d716b, moved only by a rebase. The ADR 0113 bullet is unchanged, now in ade3a6d.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The head is now 9c8d16e. The ADR 0113 pre-script bullet is unchanged, now in 4b7b48e. The head moved for a rebase onto main and for the Codex interrupt update on OpenShell 0.1.2.

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 7:47 PM UTC · Completed 8:05 PM UTC

Commit: c35c90d · View workflow run →

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

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 9:00 PM UTC · Completed 9:21 PM UTC

Commit: 49d716b · View workflow run →

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

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 11:48 PM UTC · Ended 11:58 PM UTC

Commit: 96c9000 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 11:59 PM UTC · Completed 12:25 AM UTC

Commit: 9c8d16e · View workflow run →

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

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 3:45 PM UTC · Completed 4:14 PM UTC

Commit: 9f8155b · View workflow run →

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

fullsend-ai-review[bot]

This comment was marked as outdated.

A runtime that can take a message into a session already running implements
Steerer. RunParams.Steerable asks for that session to be kept open; Run then
returns only after Settle and the agent's current turn, so a runtime that does
not implement it is unchanged.

Delivery is acknowledged rather than assumed: a steer counts as delivered only
when the runtime echoes that specific message back, matched by the message's
own identity. The mailbox the live runtimes read sits in a directory the agent
can write to, so an echo matching nothing outstanding is ignored rather than
counted.

The envelope renders the runner's message as the bytes the agent definitions
match on, versioned separately as a contract between the two repositories.

Assisted-by: Claude
Signed-off-by: Wayne Sun <gsun@redhat.com>
Claude Code reads stream-json input, so the session is started with stdin
connected to an in-sandbox feeder tailing a mailbox file and Steer appends one
line to it. `tail -n +1 -f` starts at the first line, so the opening prompt is
delivered by the same channel as every later steer, and the file stays open for
each append.

The ack is --replay-user-messages: the runtime re-emits each consumed stdin
line with isReplay set. Tool results arrive as "user" lines too, so only the
replay flag distinguishes them — counting "user" lines would let the run settle
with a steer still unread in the mailbox.

Assisted-by: Claude
Signed-off-by: Wayne Sun <gsun@redhat.com>
pi takes a message into a running session over rpc, so the same mailbox feeds
its stdin and Steer appends one line. pi's ack carries the id the runner sent,
which is a stronger match than content: the steer feed pairs each echo with the
message it acknowledges and ignores one that matches nothing outstanding.

agent_settled is pi's end-of-prompt marker rather than an end-of-run signal, so
on a steerable run it ends the turn and not the session.

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

codex exec has no live input channel, so a steer stops the current process and
resumes the same thread with the message as the next prompt. That is why
Steerer is two methods rather than one send: the interrupt and the settle are
different operations on this runtime.

A resume is recorded only once the new process reports a thread of its own.
codex emits thread.started on a resume, so an empty thread id means the resume
never took and the steer did not reach the agent; recording it at stake time
would claim an update had been acted on when it never was.

The interrupt stops the sandbox and starts it again (new sandbox.Stop and
sandbox.Start primitives with phase waits): an exec's processes are not
expected to exit with the caller, and a stop is the released way to end them
while keeping the disk state the resume reads. The turn is marked before the
stop, so its relay-closed exit counts as the runner's own interrupt, not an
agent failure or a missed resume. On the pinned OpenShell 0.0.116 a stop
takes about 46 s, exported as CodexSteerInterruptCost for settle budgets.
The caller holds the sandbox lock across Steer, so the credential
refreshers wait the stop out instead of being cut off mid-write.

A process the integrity guards refuse (exit 97 or 98) is the run's verdict:
it is not retried as a missed resume, and an earlier turn's success does not
stand in for it. A first turn that opens no thread ends the run, since there
is nothing to resume onto.

Assisted-by: Claude
Signed-off-by: Wayne Sun <gsun@redhat.com>
`steer:` configures whether a run absorbs updates to its work item while it is
still working, and the ceilings on doing so: max_steers per run and the poll
interval. It composes like security — a child replaces the base's block whole
rather than field by field — so a child that sets one key gets the defaults for
the rest instead of a silent mixture of two layers.

enabled is a pointer so that absent and false mean different things. A block
tuning only max_steers says nothing about whether steering is on, so it takes
the default rather than reading as an opt-in or an opt-out, and the same config
keeps its meaning when the default changes.

Off by default while the surfaces steering depends on land.

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

ADR 0106 stops the cancellation, and this ADR addresses what preserving alone
leaves standing: output computed from state the subject has moved past, a
queued run that redoes the same work, and no way for a person to reach a run
already working.

It decides only that steering happens. The interface, provenance and actor
authority, how a run learns its item moved, the receipt, and the default are
five decisions of their own, named here in prose and recorded separately.

ADR 0106 is annotated rather than superseded: a steered run revisits its
no-polling clause and nothing else — it still invokes no run of its own, every
event still creates its pending run, and the timeout window is unchanged.

Assisted-by: Claude
Signed-off-by: Wayne Sun <gsun@redhat.com>
The sandbox is the constraint: the runner reaches it only through exec and
upload, and an exec is a sibling of the agent process rather than its parent,
so there is no stdin to write to from outside. ADR 0117 records the options
that fails on — restart, upload, exec stdin — and the one that does not: a
mailbox the agent's own stdin is already tailing.

The envelope is pinned as a v1 contract because two repositories read it: the
runner writes those bytes and the agent definitions match on them, so fixing
them in either would make a change there a silent break here.

steering.md carries only what this change ships — concurrency, the contract,
the transport and the configuration. The sections on how a run learns its item
moved, and on what the queued run reads, arrive with those changes.

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

fullsend-ai-review Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 6:55 PM UTC · Completed 7:19 PM UTC

Commit: 841eeca · View workflow run →

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

@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.

Comment thread AGENTS.md
Comment thread internal/runtime/pi_steer.go

This branch was successfully deployed

1 active and 1 inactive deployments
site-preview — 841eeca1 Deployed Sep 30, 2026 by github-actions[bot]
dev — 841eeca1 Deployed Sep 30, 2026 by waynesun09 via behaviour #14684
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component/dispatch Workflow dispatch and triggers component/harness Agent harness, config, and skills loading component/runner Agent runner behavior and lifecycle component/sandbox OpenShell sandbox environment requires-manual-review Review requires human judgment risk/moderate PR risk: moderate type/adr

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants