Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
title: "Dispatch settings must reach the bridged implementer, not its native wrapper"
date: "2026-10-17"
track: bug
category: integration
module: plugins/flow-next/scripts/flowctl.py
tags: [judge, tier, effort, bridge, dispatch]
problem_type: integration
symptoms: Tier line claims an effort the bridged implementer never received
root_cause: effort routed to native spawn; routing-block bridge resolved later in the worker
resolution_type: fix
related_to: [bug/integration/a-new-terminal-status-needs-every-2026-10-05, bug/integration/bundled-snapshot-pr-listing-truncation-2026-09-26, bug/integration/review-backend-adapters-move-2026-10-05]
---

## Problem
A dispatch setting computed by the conductor (worker reasoning effort) was routed to the native worker spawn whenever no explicit implementer was passed. When the project routing block named a bridged implementer, worker Phase 1b launched the bridge without that setting, so the native wrapper ran at the chosen effort and the real implementer did not, while the Tier line claimed it was applied. A role-pinned effort also short-circuited the bridged case and dropped the requested effort.

## What Didn't Work
Deciding "bridged" only from the conductor's own inputs (explicit model or mechanical fast model): the routing-block implementer is resolved later, inside the worker, and the conductor never passes it to the judge.

## Solution
A role pin applies only to the native spawn (`if role_effort and not bridged` in `judge_tier_effort`, flowctl.py); bridged implementers carry the effort in `<model> at <effort>`. worker-bridge.md takes the effort from IMPLEMENTER, else the routing block, else the level the TIER_LINE applied.

## Prevention
For any per-dispatch setting, trace it to the process that actually does the work on every reach path (native spawn, explicit bridge, routing-block bridge resolved by the worker), and add a test case per path that asserts where the value lands.
215 changes: 212 additions & 3 deletions .flow/specs/fn-299-worker-reasoning-effort-follows-the.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,223 @@
"default_sync": null,
"depends_on_epics": [],
"id": "fn-299-worker-reasoning-effort-follows-the",
"impl_review_rounds": {},
"impl_review_rounds": {
"fn-299-worker-reasoning-effort-follows-the.1": 0
},
"next_task": 1,
"no_plan": true,
"plan_review_rounds": 0,
"plan_review_status": "unknown",
"plan_reviewed_at": null,
"ready": true,
"review_attempts": [
{
"artifact_sha256": "31e6758d83181b940195c00c43552351277ec04aaee076841cc27355c15f1500",
"backend": "codex",
"base_sha": "04a03c6d3685e3ee6bafe56cd9a9dbb576ebe4f8",
"counter_kind": "impl",
"effort": "high",
"failure_class": null,
"finalized": {
"digest": "complete",
"receipt": "complete",
"status": "not_applicable"
},
"findings_digest": {
"backend": "codex",
"digest_truncated": false,
"items": [
{
"chainRoot": "finding-6926e2a591c6a11b85400007e910addb",
"classification": "introduced",
"findingId": "finding-6926e2a591c6a11b85400007e910addb",
"firstSeenThisRound": true,
"severity": "P1",
"status": "open"
},
{
"chainRoot": "finding-f704db02254192a87db666974a02833a",
"classification": "introduced",
"findingId": "finding-f704db02254192a87db666974a02833a",
"firstSeenThisRound": true,
"severity": "P2",
"status": "open"
},
{
"chainRoot": "finding-b4d5cfe847157efd77f423ab616fd53c",
"classification": "introduced",
"findingId": "finding-b4d5cfe847157efd77f423ab616fd53c",
"firstSeenThisRound": true,
"severity": "P2",
"status": "open"
}
],
"reviewKind": "implementation"
},
"forced": false,
"hash_epoch": 0,
"head_sha": "bb9f1a8673f13ef6f8be7295e8609de76270a967",
"head_sha_observed": true,
"kind": "impl",
"model": "gpt-6-astra",
"outcome": "verdict",
"output_bytes": 2590,
"output_sha256": "c6559bcb29f935ca6746c853e81f0de0cd1dc925457c2bb624ca9dc00cf05316",
"reservation_id": "2918e9c11af24129b6acadfadad77f51",
"round_consumed": true,
"scope": "impl:fn-299-worker-reasoning-effort-follows-the.1",
"superseded_by": null,
"task": "fn-299-worker-reasoning-effort-follows-the.1",
"timestamp": "2026-10-16T20:37:16.791397Z",
"verdict": "NEEDS_WORK"
},
{
"artifact_sha256": "24f32dad5523cff45ced96609eb4488d8dfe571f2775d76602203959a31e2363",
"backend": "codex",
"base_sha": "04a03c6d3685e3ee6bafe56cd9a9dbb576ebe4f8",
"counter_kind": "impl",
"effort": "high",
"failure_class": null,
"finalized": {
"digest": "complete",
"receipt": "complete",
"status": "not_applicable"
},
"findings_digest": {
"backend": "codex",
"digest_truncated": false,
"items": [
{
"chainRoot": "finding-6926e2a591c6a11b85400007e910addb",
"classification": "introduced",
"findingId": "finding-6926e2a591c6a11b85400007e910addb",
"firstSeenThisRound": false,
"severity": "P1",
"status": "fixed"
},
{
"chainRoot": "finding-f704db02254192a87db666974a02833a",
"classification": "introduced",
"findingId": "finding-f704db02254192a87db666974a02833a",
"firstSeenThisRound": false,
"severity": "P2",
"status": "fixed"
},
{
"chainRoot": "finding-b4d5cfe847157efd77f423ab616fd53c",
"classification": "introduced",
"findingId": "finding-b4d5cfe847157efd77f423ab616fd53c",
"firstSeenThisRound": false,
"severity": "P2",
"status": "not_fixed"
},
{
"chainRoot": "finding-5571b6b18f4eec7e3e0464b298ce32a9",
"classification": "introduced",
"findingId": "finding-5571b6b18f4eec7e3e0464b298ce32a9",
"firstSeenThisRound": true,
"severity": "P2",
"status": "open"
}
],
"reviewKind": "implementation"
},
"forced": false,
"hash_epoch": 0,
"head_sha": "3fca663b35a42f7de00fb1af06019858a40efc01",
"head_sha_observed": true,
"kind": "impl",
"model": "gpt-6-astra",
"outcome": "verdict",
"output_bytes": 1938,
"output_sha256": "f1a79bb49d0c9bf1d7375706996e99df3d3cb02b06f090357c5d72153d0dbafa",
"reservation_id": "151748d85c9e4e2b987750b3d69878e2",
"round_consumed": true,
"scope": "impl:fn-299-worker-reasoning-effort-follows-the.1",
"superseded_by": null,
"task": "fn-299-worker-reasoning-effort-follows-the.1",
"timestamp": "2026-10-16T22:01:10.791794Z",
"verdict": "NEEDS_WORK"
},
{
"artifact_sha256": "15695b6b22e0b8fe3db3a6ab621bf0fb2aaa6e6aa48d275061a43247f178723a",
"backend": "codex",
"base_sha": "04a03c6d3685e3ee6bafe56cd9a9dbb576ebe4f8",
"counter_kind": "impl",
"effort": "high",
"failure_class": null,
"finalized": {
"digest": "complete",
"receipt": "complete",
"status": "not_applicable"
},
"findings_digest": {
"backend": "codex",
"digest_truncated": false,
"items": [
{
"chainRoot": "finding-6926e2a591c6a11b85400007e910addb",
"classification": "introduced",
"findingId": "finding-6926e2a591c6a11b85400007e910addb",
"firstSeenThisRound": false,
"severity": "P1",
"status": "fixed"
},
{
"chainRoot": "finding-f704db02254192a87db666974a02833a",
"classification": "introduced",
"findingId": "finding-f704db02254192a87db666974a02833a",
"firstSeenThisRound": false,
"severity": "P2",
"status": "fixed"
},
{
"chainRoot": "finding-b4d5cfe847157efd77f423ab616fd53c",
"classification": "introduced",
"findingId": "finding-b4d5cfe847157efd77f423ab616fd53c",
"firstSeenThisRound": false,
"severity": "P2",
"status": "fixed"
},
{
"chainRoot": "finding-5571b6b18f4eec7e3e0464b298ce32a9",
"classification": "introduced",
"findingId": "finding-5571b6b18f4eec7e3e0464b298ce32a9",
"firstSeenThisRound": false,
"severity": "P2",
"status": "fixed"
}
],
"reviewKind": "implementation"
},
"forced": false,
"hash_epoch": 0,
"head_sha": "8f3b80b770ae9e34e09e9ba47bd358f904132fad",
"head_sha_observed": true,
"kind": "impl",
"model": "gpt-6-astra",
"outcome": "verdict",
"output_bytes": 1768,
"output_sha256": "4b49fd07a50ddaa6268e52064cb231febaf6d94658b7db01efefacd89227dc80",
"reservation_id": "9d8cda3e86f14b908774c9fdf4a5b415",
"round_consumed": true,
"scope": "impl:fn-299-worker-reasoning-effort-follows-the.1",
"superseded_by": null,
"task": "fn-299-worker-reasoning-effort-follows-the.1",
"timestamp": "2026-10-17T01:15:57.439025Z",
"verdict": "SHIP"
}
],
"review_hash_epoch": {
"impl:fn-299-worker-reasoning-effort-follows-the.1": 1
},
"review_pending_rounds": {},
"review_reservations": {},
"review_transport_failures": {
"impl:fn-299-worker-reasoning-effort-follows-the.1": 0
},
"spec_path": ".flow/specs/fn-299-worker-reasoning-effort-follows-the.md",
"status": "open",
"status": "done",
"title": "Worker reasoning effort follows the dispatch tier",
"tracker": {
"baseHashFlow": null,
Expand All @@ -28,5 +237,5 @@
"mergeBaseTracker": null,
"url": null
},
"updated_at": "2026-10-15T09:52:38.487878Z"
"updated_at": "2026-10-17T01:20:12.328932Z"
}
15 changes: 15 additions & 0 deletions .flow/tasks/fn-299-worker-reasoning-effort-follows-the.1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"assignee": null,
"claim_note": "",
"claimed_at": null,
"created_at": "2026-10-16T20:25:47.344205Z",
"depends_on": [],
"id": "fn-299-worker-reasoning-effort-follows-the.1",
"implicit_owner": true,
"priority": null,
"spec": "fn-299-worker-reasoning-effort-follows-the",
"spec_path": ".flow/tasks/fn-299-worker-reasoning-effort-follows-the.1.md",
"status": "done",
"title": "Implement Worker reasoning effort follows the dispatch tier",
"updated_at": "2026-10-16T20:25:47.344205Z"
}
31 changes: 31 additions & 0 deletions .flow/tasks/fn-299-worker-reasoning-effort-follows-the.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
satisfies: [R1, R2, R3, R4, R5, R6]
---
# fn-299-worker-reasoning-effort-follows-the.1 Implement Worker reasoning effort follows the dispatch tier

## Description
TBD

## Acceptance
Every R-ID in the parent spec's ## Acceptance Criteria is satisfied; judge this task against the spec's criteria directly.

## Done summary
Worker reasoning effort now follows the dispatch tier: `flowctl judge --preset tier --task` takes the host's effort levels and default (`--effort-levels`, `--default-effort`) plus `--explicit-effort` / `--role-effort`, returns `spawn_effort` (one level below the default for a confident mechanical answer, one above for a confident intelligent one, the default otherwise and without a judge), carries it as `<model> at <effort>` for a bridged implementer, and names the effort applied or why not on the `Tier:` line; scouts are unchanged. The judge-tier recipe, multi-task 3c (and its Codex SECTION3C copy), worker-bridge, judge/flowctl docs, the Claude Code and Codex reach pages, and the changelog state the behaviour.

Tests: `test_judge_consumers.py` covers R1 bounds and floors, R2 precedence (requested, role pin, explicit model keeps tier effort), R3 no-judge default, R4 not-applied lines and bridge transport, R5 line text, and the CLI path without a key.

Decisions: the 0.8 confidence floor that gates the model tier gates effort in both directions (an intelligent answer below 0.8 keeps the default). A requested effort covers the invocation and, below it, the routing block's implementer line; the conductor resolves which, per routing precedence. A role-pinned effort applies only to a native spawn. A rejected level is retried once without effort (conductor prose); flowctl cannot observe the rejection.

OPEN_ITEM: R6 evidence is partial. Worker replay (claude-opus-5-5, 3 judge-confident mechanical fixtures x 5 reps per arm): pass rate 15/15 at low vs 15/15 at medium; mechanical medians 55.0/43.0/130.9 s vs 76.1/67.2/138.0 s, but the pre-registered paired verdict is INCONCLUSIVE (pooled gap 10.0 s against a 9.7 s floor, within-cell spread 29-88 s). End-to-end large-feature control (3 draws each, same time): review rounds median 2 vs 2, wall median 1,886 vs 1,992 s, all hidden checks pass, but null by construction: no draw spawned a worker or made a tier call. Only about 1% of historical tasks are judge-confident mechanical and 2% intelligent. Whether to accept this or run a multi-task `--auto` study on Claude Code 2.1.296+ is the maintainer's call.

Follow-up: the end-to-end harness pins Claude Code 2.1.283, whose Agent spawn has no effort parameter; an opt-in `FN271_CLAUDE_DIR` override was added there, uncommitted.

stage: impl-review - ran (codex, 3 rounds: NEEDS_WORK -> NEEDS_WORK (R6 only) -> SHIP)

Tier: session (jev intelligent 0.63) (model: claude-opus-5-5)

stage: plan-sync - skipped(config: planSync.enabled != true)
## Evidence
- Commits: bb9f1a8673f13ef6f8be7295e8609de76270a967, 3fca663b35a42f7de00fb1af06019858a40efc01, 8f3b80b770ae9e34e09e9ba47bd358f904132fad, bdbafacbe99588d3c98038ac17dc61b49597969f
- Tests: python3 -m unittest plugins/flow-next/tests/test_judge_consumers.py, python3 -m unittest plugins/flow-next/tests/test_work_reached_path_routes.py plugins/flow-next/tests/test_tracker_caller_execution.py plugins/flow-next/tests/test_review_convergence_journal.py plugins/flow-next/tests/test_startup_bootstrap.py, ./scripts/sync-codex.sh --check, uvx ruff@0.16.0 check plugins/flow-next/scripts/flowctl.py plugins/flow-next/tests/test_judge_consumers.py
- PRs:
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Flow-Next changed shape with 5.0.0. One command, `/flow-next:flow`, reads whatev

### Changed

- **Workers think less on routine tasks and more on hard ones.** The tier call that already runs before each worker spawn now sets the worker's reasoning effort as well as its model: a confidently mechanical task runs one level below the host's default effort, a confidently intelligent one a level above, and everything else at the default. An effort you name in the invocation or the routing block, or one the worker role pins, still wins, and naming only a model keeps the tier's effort. Without a TypeSafe key every worker runs at the default, as before. The effort reaches the worker through the host's spawn effort parameter (Claude Code, Codex) or a bridged implementer's effort setting; elsewhere the worker runs as before. The `Tier:` line, kept in each task's done summary, names the effort applied or why it was not.
- **Defect fixes remove the fault, not its symptom, and prove it where it was reported.** Before designing a fix, the defect route now decides whether the state that triggered the crash may legitimately occur; when it should never exist, the fix goes where that state was produced instead of a guard, retry or swallowed error where it surfaced. A failure that appears only after a restart suspects persisted state (config, caches, locks, serialized records) before code. Base and head observations run through the surface the report came from (the command, the request, the call or the app), so a unit test of the changed branch is supporting evidence, not the proof.

- **A measured number says what bounds it and what else it could be measuring.** When flow speeds something up once, climbs a metric, or measures a prompt change, the number now passes a short set of questions before anyone acts on it: what bounds it (from a profile or system counters, not a guess from the code), what else it could be measuring (fast errors, a warm cache on one side, work that never ran, an untuned side), whether the gain is possible given that piece's share of the run, and whether the work happened. Each side runs at least five times, alternated, and the claim reads `faster`, `slower`, `no measurable difference` or `inconclusive`, with the run count, range and limiter. The hill-climb harness prints error and work counts next to the metric and rejects a faster number with less work done, proves its case reproduces the complaint against easier workloads, and tries the cheapest ideas first (do not do the work, do not do it again, do less of it) before concurrency or rewrites. A prompt eval's verdict carries how many draws on each side finished. Other routes load nothing new.
Expand Down
7 changes: 6 additions & 1 deletion plugins/flow-next/codex/docs/flow-next/flowctl.md
Original file line number Diff line number Diff line change
Expand Up @@ -1236,7 +1236,12 @@ over 32k tokens at four characters per token is rejected without sending.
The command never writes state, answers, or credentials to disk.

`--task` assembles tier inputs from the task and returns `tier_line`,
`spawn_model` and `implementer`. Unknown tasks fail.
`spawn_model`, `spawn_effort` and `implementer`. Unknown tasks fail. For a worker
the caller passes the host facts `--effort-levels <lowest,...,highest>` and
`--default-effort <level>`, plus `--explicit-effort` or `--role-effort` when one
applies; `spawn_effort` is the level for the spawn's effort parameter, or null
for the host default. A bridged implementer carries the effort as
`<model> at <effort>` in `implementer`.

`--spec` assembles route facts from the live spec and repository. A failed
PR probe returns `pr_probe_failed: true` and no `decision`, so the caller
Expand Down
Loading
Loading