Skip to content

feat(routing): request-level reasoning_effort + verify mode - #149

Closed
seonghobae wants to merge 10 commits into
mainfrom
feat/reasoning-effort-and-verify-mode
Closed

feat(routing): request-level reasoning_effort + verify mode#149
seonghobae wants to merge 10 commits into
mainfrom
feat/reasoning-effort-and-verify-mode

Conversation

@seonghobae

Copy link
Copy Markdown
Contributor

Summary

Closes a concrete gap between what this repo's cited research (Fugu, Conductor, TRINITY — docs/architecture.md) calls for and what main actually does: reasoning_effort was already accepted at the HTTP edge (OPENAI_PASSTHROUGH_PARAM_KEYS) but silently dropped on the orchestrated route/conduct path, and there was no way to get a single checked judgment without paying for the full four-step conduct() workflow.

  • ModelClient.chat/stream_chat forward reasoning_effort (OpenAI-compatible minimal/low/medium/high) to the provider payload when set, omitted otherwise.
  • Threaded end-to-end: server body → CostRoutingCoordinator.completeTaskOrchestrator.run/complete/_dispatchroute_once/conduct/route_and_verify_invokeModelClient.chat, including the plan-generation and model-judge calls. Folded into the response-cache key so a cached low-effort answer can't be served for a high-effort request. Batch-channel requests intentionally drop it (BatchRequest has no such field) — documented in CostRoutingCoordinator.complete's docstring, not silently wrong.
  • New mode="verify" (TaskOrchestrator.route_and_verify): one worker call plus one checked verifier judgment — for adjudication-shaped requests ("does B follow from A?") that need a verified verdict without the thinker/worker/verifier/synthesizer workflow's cost.
  • _client_chat() call-site helper keeps every existing ModelClient-shaped test double/subclass in this repo working unchanged when reasoning_effort is unset (the default) — no test double needed touching, confirmed against all ~10 that exist in tests/.

Checked against the ~20 other open PRs first (gh pr list --state open) — none claim reasoning_effort forwarding or a partial-conduct mode, so this doesn't duplicate in-flight work.

Test plan

  • tests/test_paper_contracts.py: reasoning_effort reaches every provider call in a conduct() run; omitted by default (no behavior change); verify mode's trace shape (workerverifier, access=[0]); reasoning_effort reaches both calls in route_and_verify.
  • New tests/test_reasoning_effort_and_verify_mode.py: HTTP-level — mode="verify" returns orchestration.mode == "verify"; invalid reasoning_effort400 invalid_reasoning_effort; valid reasoning_effort + mode="route"200.
  • Full suite: python -m pytest -q → 307 passed (no regressions in the ~10 existing ModelClient-shaped test doubles).

🤖 Generated with Claude Code

Closes the test-time-compute-allocation gap between what Fugu, Conductor,
and TRINITY (docs/architecture.md, arXiv:2512.04695, arXiv:2512.04388) call
for and what main actually does: reasoning_effort was accepted at the HTTP
edge but silently dropped on the orchestrated route/conduct path, and there
was no way to get a single checked judgment without paying for the full
four-step conduct() workflow.

- ModelClient.chat/stream_chat forward reasoning_effort (OpenAI-compatible
  minimal/low/medium/high) to the provider payload when set, omitted
  otherwise -- unaffected for providers/callers that never opt in.
- reasoning_effort threads through the whole call chain (server body ->
  CostRoutingCoordinator.complete -> TaskOrchestrator.run/complete/_dispatch
  -> route_once/conduct/route_and_verify -> _invoke -> ModelClient.chat),
  including the plan-generation and model-judge calls, and is folded into
  the response cache key so a cached low-effort answer can't be served for
  a high-effort request. Batch-channel requests intentionally drop it today
  (BatchRequest has no such field) -- documented, not silently wrong.
- New mode="verify" (TaskOrchestrator.route_and_verify): one worker call
  plus one checked verifier judgment, for adjudication-shaped requests
  ("does B follow from A?") that need a verified verdict without the
  thinker/worker/verifier/synthesizer workflow's cost.
- _client_chat() call-site helper keeps every existing ModelClient-shaped
  test double/subclass in this repo working unchanged when reasoning_effort
  is unset (the default) -- no test double needed touching.

Tests: tests/test_paper_contracts.py (reasoning_effort reaches every
provider call in a conduct() run; omitted by default; verify mode's trace
shape and reasoning_effort propagation) and a new
tests/test_reasoning_effort_and_verify_mode.py (HTTP-level: verify mode,
invalid/valid reasoning_effort validation). Full suite: 307 passed.

Does not touch any of the ~20 other open PRs' surface (OpenAI-compat
headers, security/session hardening, pricing/routing) -- verified no
existing open PR claims reasoning_effort or a partial-conduct mode before
starting this.
@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review available on request

  • 🔍 Trigger review

Reviews should be triggered manually for repositories with fewer than 10 stars. Select Trigger review above or comment @coderabbitai review to review the latest changes. For a full review, comment @coderabbitai full review.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bab9b589-7ce1-4379-a49e-c09981967a92

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/reasoning-effort-and-verify-mode

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@seonghobae
seonghobae enabled auto-merge (squash) August 13, 2026 01:09
…s diff

This PR's Semgrep check failed on 5 findings, none introduced by this
change (line numbers only shifted because earlier edits in this branch
added lines above them):

- cost_ledger.py:586,605,625 (sqlalchemy-execute-raw-query): already
  bandit-suppressed (# nosec B608) with the same rationale -- the
  interpolated pieces are a DB-API placeholder character and fixed
  internal column-name constants, never request data; actual values
  always go through the parameterized second argument. Semgrep doesn't
  read bandit's nosec syntax, so it re-flags what bandit already
  accepted. Added the matching # nosemgrep suppression alongside the
  existing nosec comment -- no SQL construction logic changed.
- orchestrator.py (unverified-ssl-context, dynamic-urllib-use-detected):
  same pattern -- both already carry a bandit nosec with an accepted
  rationale (verify_tls=False is an explicit opt-in dev-only argument,
  not a default; the request URL is validated by _provider_url()/
  _validate_provider() -- https-only, path-injection-safe, private/
  loopback/link-local/reserved-IP-rejecting -- before urlopen is ever
  reached). Added the matching # nosemgrep suppression with the same
  rationale spelled out for the urllib case.

Verified locally: 'semgrep --config auto --severity WARNING --severity
ERROR --error' now reports 0 findings on both files (was 5). Full test
suite still 307 passed (comment-only change, no behavior touched).

@opencode-agent opencode-agent Bot 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.

Pull request overview

OpenCode cannot approve yet because required coverage evidence did not pass.

Review outcome

1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence

  • Problem: The required coverage-evidence job result was failure, so OpenCode cannot establish approval sufficiency for this head.

  • Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.

  • Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports success with required evidence or explicit no-source not-applicable evidence.

  • Regression test: Keep the approval branch checking needs.coverage-evidence.result == success before posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present.

  • Result: REQUEST_CHANGES

  • Reason: coverage-evidence result was failure, so required test/docstring evidence was not proven for current head 44381e86c4f41fe1af6fc6303cbfcd06b66b8fa4.

  • Head SHA: 44381e86c4f41fe1af6fc6303cbfcd06b66b8fa4

  • Workflow run: 31702539028

  • Workflow attempt: 1

Coverage evidence

Coverage evidence job did not run or did not publish coverage evidence.

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Changed file (4 files)"]
  S1 --> I1["repository behavior"]
  I1 --> R1["Review risk: Changed file (4 files)"]
  R1 --> V1["required checks"]
  Evidence --> S2["Docs: architecture.md"]
  S2 --> I2["operator or user guidance"]
  I2 --> R2["Review risk: Docs: architecture.md"]
  R2 --> V2["docs review"]
  Evidence --> S3["Test (2 files)"]
  S3 --> I3["regression suite"]
  I3 --> R3["Review risk: Test (2 files)"]
  R3 --> V3["targeted test run"]
Loading

@opencode-agent

opencode-agent Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

OpenCode Review Overview

  • Head SHA: e3f7588ff7b6d683a661cd74f65b407722b6d6cd
  • Workflow run: 32029539248
  • Workflow attempt: 1
  • Gate result: REQUEST_CHANGES (approval step)

Pull request overview

OpenCode cannot approve yet because required coverage evidence did not pass.

Review outcome

1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence

  • Problem: The required coverage-evidence job result was failure, so OpenCode cannot establish approval sufficiency for this head.

  • Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.

  • Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports success with required evidence or explicit no-source not-applicable evidence.

  • Regression test: Keep the approval branch checking needs.coverage-evidence.result == success before posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present.

  • Result: REQUEST_CHANGES

  • Reason: coverage-evidence result was failure, so required test/docstring evidence was not proven for current head e3f7588ff7b6d683a661cd74f65b407722b6d6cd.

  • Head SHA: e3f7588ff7b6d683a661cd74f65b407722b6d6cd

  • Workflow run: 32029539248

  • Workflow attempt: 1

Coverage evidence

Coverage evidence job did not run or did not publish coverage evidence.

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Changed file (5 files)"]
  S1 --> I1["repository behavior"]
  I1 --> R1["Review risk: Changed file (5 files)"]
  R1 --> V1["required checks"]
  Evidence --> S2["Docs: architecture.md"]
  S2 --> I2["operator or user guidance"]
  I2 --> R2["Review risk: Docs: architecture.md"]
  R2 --> V2["docs review"]
  Evidence --> S3["Test (4 files)"]
  S3 --> I3["regression suite"]
  I3 --> R3["Review risk: Test (4 files)"]
  R3 --> V3["targeted test run"]
Loading

@opencode-agent
opencode-agent Bot disabled auto-merge August 13, 2026 18:21
Comment thread .github/workflows/apply-quality-cost-policy.yml Fixed
cursoragent and others added 2 commits August 16, 2026 13:50
The later quality-cost staging scripts were collected by pytest because
stage_quality_cost_policy_test.py matches *_test.py. Importing that module
wrote tests/test_quality_cost_adaptive_default.py during collection, which
then failed the Full unit suite. The apply workflow also used contents:write
(Scorecard Token-Permissions) and regex-patched orchestrator.py into a
U+0001 SyntaxError on red-green-verify.

Keep the already-landed reasoning_effort + verify mode and adaptive
route/verify/conduct dispatch. Ignore scripts/ during collection so helper
modules cannot inject tests again.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
A scripts/*_test.py helper was collected as a test and wrote a failing
file into tests/ during import. Keep collect_ignore covering scripts/
and fuzz/, and document why so the Full unit suite cannot pick up
staging helpers again.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>

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

Verdict: do not merge this head

e3f7588 does thread request-level reasoning_effort on the sync path and adds mode=verify. That wiring is real. The advertised product is not.

A buyer who pays for a checked judgment currently gets a rubber stamp: route_and_verify passes the user prompt in as thinker_output, so _judge_verifier_output fallback-accepts whenever a worker answer exists. Mock/default verify on this head returns accepted=True with reason fallback acceptance from available planner and worker output. The paper contract only asserts "accepted" in result["verification"], so the false-accept stays green.

A rejected verify still returns the worker text as a normal chat.completion with finish_reason: stop. SDK clients that ignore orchestration treat a failed check as a successful answer.

Default mode=auto now substring-matches check / review / confirm / evaluate / assess. Everyday English (Please preview the slide., Add a checkbox to the form., Send the confirmation email.) becomes two billed calls with no request change. routing_decision is not on the OpenAI body, so the caller cannot see why.

docs/architecture.md and the ALLOWED_REASONING_EFFORT comment claim per-role Fugu/Conductor/TRINITY allocation. This diff applies one request-level string to every role. Issue #568 (versioned reasoning_effort_profile, equal-budget ablation, identical snapshot on sync/stream/batch) is not implemented. Leave #568 open.

Batch still drops reasoning_effort with HTTP 202 and no dropped field. The cost ledger still invoices verify as if it were one route call.

Next action for the author

Do not land e3f7588. Either fold the successor honesty fixes into this branch, or close this PR in favor of a successor that:

  1. Fail-closes verify when the verifier report has no explicit accept/reject terms. Do not pass the user prompt as thinker_output.
  2. Does not serve a rejected worker answer as a successful completion. Keep worker text in the trace only.
  3. Matches auto verify hints on word boundaries and drops ambiguous check / review / confirm / evaluate / assess. Add negative tests for preview, checkbox, and confirmation.
  4. Echoes routing_decision and applied-or-dropped reasoning_effort on the chat surface; redact orchestration.verification the same way traces are redacted.
  5. States request-level-only in the architecture note. Do not claim per-role allocation until #568 lands.

Independent non-author approval is still required after those contracts are green. This automation will not approve or merge this head.

Open in Web View Automation 

Sent by Cursor Automation: Fix Issues

"judge",
"adjudicate",
"review",
"check",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Substring match on check / review / confirm turns ordinary English into a billed verify path.

hint in lowered matches review inside preview, check inside checkbox, and confirm inside confirmation. Default mode is auto, so a caller who used to pay for one worker call now pays worker+verifier with no request change.

Drop these ambiguous tokens from VERIFICATION_HINTS. Keep adjudication terms (verify, validate, judge, adjudicate, and the Korean terms) and match ASCII hints on word boundaries. Add negative tests for Please preview the slide., Add a checkbox to the form., and Send the confirmation email. — those must stay route.

)
verifier_latency_ms = (time.perf_counter() - verify_start) * 1000

verification = self._judge_verifier_output(verifier_output, text, answer)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is not a checked judgment.

_judge_verifier_output(verifier_output, text, answer) passes the user prompt as thinker_output. The fallback then accepts whenever both prompt and worker answer exist:

if thinker_output and worker_output:
    return {"accepted": True, "reason": "fallback acceptance from available planner and worker output", ...}

Mock verify on this head returns that exact reason. A buyer paying for mode=verify gets a rubber stamp.

Pass an empty thinker slot (verify has no planner) and fail closed when the verifier report has neither accept nor reject terms. Add a test that a neutral mock verdict is accepted=False.


return {
"mode": "verify",
"answer": answer,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rejected verify still returns the worker answer as the completion.

chat_completion_response then wraps that text with finish_reason: stop. An OpenAI SDK client that ignores orchestration cannot tell a failed check from a successful answer.

When verification.accepted is false, do not put the worker text in answer. Keep it on the verifier/worker trace only, and make the public content a rejection envelope the SDK cannot ignore.

Comment thread docs/architecture.md
- `Orchestrator.route_and_verify` (`mode="verify"`): one worker call plus one checked verifier judgment — for adjudication-shaped requests that need a verified verdict without the full four-step workflow's cost.
- `Orchestrator.conduct`: the workflow path with planner, worker, verifier, and synthesizer steps.
- `WorkflowStep.access`: Conductor-style visibility control.
- `reasoning_effort` (request-level, OpenAI-compatible `minimal`/`low`/`medium`/`high`): threads through every provider call in a request (`ModelClient.chat`/`stream_chat`), so a caller can request higher test-time compute for a single adjudication call while keeping cheap calls (e.g. embeddings) at default effort — the per-role/per-request test-time-compute allocation Fugu, Conductor, and TRINITY call for.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This sentence is false for this diff.

The code applies one request-level reasoning_effort string to thinker, worker, verifier, synthesizer, planner, and judge. Issue #568 requires a versioned per-role reasoning_effort_profile plus equal-budget ablation and the same snapshot on sync/stream/batch. None of that is here.

Say request-level only. Leave #568 open. Do not claim per-role Fugu/Conductor/TRINITY allocation until that contract exists.

# checked verdict, without the full thinker/worker/verifier/synthesizer workflow.
ALLOWED_MODES = {"auto", "route", "conduct", "verify"}
# OpenAI-compatible reasoning-effort levels (Fugu/Conductor/TRINITY test-time-compute
# allocation: role/request-specific reasoning effort, never assumed by default).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same overclaim as docs/architecture.md.

role/request-specific reasoning effort is not what this PR implements. The validator accepts one of minimal|low|medium|high for the whole request. Correct the comment to request-level, omitted by default, never used as a temperature proxy.

and return a job envelope; their cost is recorded on retrieval.
``reasoning_effort`` only applies to the sync path today — ``BatchRequest``
has no reasoning_effort field, so a request routed to the batch channel
drops the hint rather than partially threading it through pg-llm-batch.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A caller who set reasoning_effort=high plus a batch hint gets HTTP 202 and default provider effort.

Documenting the drop in this docstring is not wire honesty. Put reasoning_effort: {requested, status: dropped, reason} on the 202 envelope so the buyer can see the knob was discarded. Do not silently succeed.

@opencode-agent opencode-agent Bot 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.

Pull request overview

OpenCode cannot approve yet because required coverage evidence did not pass.

Review outcome

1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence

  • Problem: The required coverage-evidence job result was failure, so OpenCode cannot establish approval sufficiency for this head.

  • Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.

  • Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports success with required evidence or explicit no-source not-applicable evidence.

  • Regression test: Keep the approval branch checking needs.coverage-evidence.result == success before posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present.

  • Result: REQUEST_CHANGES

  • Reason: coverage-evidence result was failure, so required test/docstring evidence was not proven for current head e3f7588ff7b6d683a661cd74f65b407722b6d6cd.

  • Head SHA: e3f7588ff7b6d683a661cd74f65b407722b6d6cd

  • Workflow run: 32029539248

  • Workflow attempt: 1

Coverage evidence

Coverage evidence job did not run or did not publish coverage evidence.

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Changed file (5 files)"]
  S1 --> I1["repository behavior"]
  I1 --> R1["Review risk: Changed file (5 files)"]
  R1 --> V1["required checks"]
  Evidence --> S2["Docs: architecture.md"]
  S2 --> I2["operator or user guidance"]
  I2 --> R2["Review risk: Docs: architecture.md"]
  R2 --> V2["docs review"]
  Evidence --> S3["Test (4 files)"]
  S3 --> I3["regression suite"]
  I3 --> R3["Review risk: Test (4 files)"]
  R3 --> V3["targeted test run"]
Loading

Copy link
Copy Markdown
Contributor Author

Superseded by the #612#622 verify-mode honesty line. #612 explicitly retained this PR’s request-level reasoning_effort and mode=verify contracts while fixing the rubber-stamp/rejected-output behavior; #622 is the current landing vehicle and closes additional verdict, auto-routing, streaming, ledger, conduct, and model-judge defects. Keeping #149 open would retain the known fail-open predecessor. No checks, reviews, or approvals transfer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants