-
Notifications
You must be signed in to change notification settings - Fork 1
feat(routing): add stateless candidate controls #983
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
seonghobae
wants to merge
39
commits into
main
Choose a base branch
from
feat/stateless-candidate-controls
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
39 commits
Select commit
Hold shift + click to select a range
37456ac
feat(routing): add stateless candidate controls
seonghobae 473bb0d
docs(routing): show candidate control request
seonghobae be3eebf
test(routing): cover Responses candidate controls
seonghobae 9942b62
fix(stream): preserve omitted routing compatibility
seonghobae a426755
fix(routing): enforce candidate controls end to end
seonghobae e564871
fix(routing): validate request-local candidates
seonghobae 97fd106
fix(routing): preflight conduct candidate roles
seonghobae 8a5c0d1
fix(routing): detach response-only evidence
seonghobae 922ca7e
fix(routing): require exact candidate IDs
seonghobae ccd9a9b
fix(routing): keep rejected preflight provider-free
seonghobae 40dbfbd
fix(api): reject terminal candidate whitespace
seonghobae ab7a813
fix(routing): keep candidate controls request-local
seonghobae 6c8fca8
fix(responses): preflight conduct candidate pins
seonghobae c33cd6b
fix(routing): preflight auto candidate roles
seonghobae 19e7752
fix: align candidate preflight with proxy path
seonghobae 38c8aaf
Merge remote-tracking branch 'origin/main' into fix/983-continue
claude 828a1b8
fix(routing): close endpoint/candidate-pin and worker-fallback eviden…
claude 226244d
fix(routing): narrow auto-mode candidate-pin preflight to the roles t…
claude 1cc5c03
fix(routing): normalize batch hints under candidate control, share tr…
claude 31655f6
fix(routing): extend candidate-control batch precedence to Responses,…
claude 6183433
fix(routing): resolve served candidate by step identity, not text mat…
claude 3536c2b
fix(routing): share the candidate-routing scope across triage and con…
claude f234a9e
fix(routing): thread served-step identity through structured synthesi…
claude e8ed2ea
fix(routing): carry answering_step_id through run(), fix candidate_id…
claude ef5ae01
test(routing): reject heuristic candidate-control limits
seonghobae 3d045c6
build(repair): add exact PR983 no-heuristics source fix
seonghobae 6d93c16
ci(repair): add PR983 no-heuristics source fix
seonghobae 5166349
chore(repair): trigger PR983 no-heuristics source fix
seonghobae b05bf90
fix(routing): never trust provider-supplied _candidate_routing/orches…
claude f1674b4
chore(repair): retrigger PR983 no-heuristics on current head
seonghobae 34f47a1
fix(routing): remove heuristic candidate-control limits
claude 65d9cc2
Merge remote-tracking branch 'origin/main' into fix/983-continue
claude 7652707
fix(routing): scope unconditional served_agent_id to active candidate…
claude 05eb95f
fix(api): remove stale maxItems:32 from exclude_candidate_ids schema
claude 811ff84
Merge remote-tracking branch 'origin/main' into feat/stateless-candid…
claude 1889e1a
fix(fuzz): remove stale 32-item exclude_candidate_ids cap from the fu…
claude 56ac62f
Merge remote-tracking branch 'origin/main' into feat/stateless-candid…
claude 47011b8
fix(routing): model judge must not select a verifier-excluded sole ca…
claude 53aa9c5
fix(routing): let ZDR-only triage fall back to the pinned candidate
claude File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,7 @@ | |
|
|
||
| from __future__ import annotations | ||
|
|
||
| import contextlib | ||
| import hashlib | ||
| import re | ||
| from contextvars import ContextVar | ||
|
|
@@ -543,6 +544,7 @@ def complete( | |
| provider_request: Optional[Dict[str, Any]] = None, | ||
| provider_endpoint: str = "chat/completions", | ||
| zdr_only: bool = False, | ||
| candidate_scope_open: bool = False, | ||
| ) -> Dict[str, Any]: | ||
| """Route a request (sync or batch) and record its usage + cost. | ||
|
|
||
|
|
@@ -554,17 +556,47 @@ def complete( | |
| Each trace step backed by valid provider token counts is ``measured``. | ||
| A missing count is recorded with an ``unavailable`` status and numeric | ||
| storage sentinels; API usage and cost remain null. | ||
|
|
||
| ``candidate_scope_open=True`` tells the ``messages``-based sync path | ||
| (``provider_request is None``) that the caller already has a | ||
| ``candidate_routing_policy`` scope open -- e.g. because it ran | ||
| :meth:`TaskOrchestrator.would_route`'s triage call under that scope | ||
| before deciding to conduct rather than route -- so this call must not | ||
| open a second, independent scope that would discard the triage | ||
| attempt from ``attempted_candidate_ids`` (#983). Ignored on the | ||
| ``provider_request`` path, which always scopes itself around its own | ||
| ``proxy_completion`` call. | ||
| """ | ||
| if not isinstance(cache_bypass, bool): | ||
| raise TypeError("cache_bypass must be a boolean") | ||
| if type(zdr_only) is not bool: | ||
| raise TypeError("zdr_only must be a boolean") | ||
| routing_controls = hints if isinstance(hints, dict) else {} | ||
| # TaskOrchestrator._has_active_candidate_controls is the single | ||
| # source of truth: it detects an active control by key *presence*, | ||
| # not truthiness, so an explicitly malformed value (candidate_id= | ||
| # None, exclude_candidate_ids=None or a non-list/tuple) still | ||
| # forces the sync path below, giving candidate_routing_policy's | ||
| # real validation a chance to reject it instead of silently falling | ||
| # through the batch branch's early return and dropping the | ||
| # malformed control entirely. An explicit empty exclude_candidate_ids | ||
| # list/tuple is the one genuine no-op (#983 Devin/CodeRabbit | ||
| # finding: direct Python API callers can lose or bypass routing | ||
| # validation). The same predicate also gates whether this method's | ||
| # own provider-response evidence handling below may trust a | ||
| # `_candidate_routing` field (#983 Devin finding: "Provider fields | ||
| # forge routing evidence"). | ||
| has_candidate_controls = self.orchestrator._has_active_candidate_controls( | ||
| routing_controls | ||
| ) | ||
| routing_hints = hints if isinstance(hints, RoutingHints) else RoutingHints.from_mapping(hints) | ||
| try: | ||
| prompt_tokens = self.token_counter.count_messages(messages, model_name) | ||
| except TokenCountUnavailable: | ||
| prompt_tokens = None | ||
| decision = self.policy.decide(routing_hints, prompt_tokens) | ||
| if has_candidate_controls and decision.channel == "batch": | ||
| decision = replace(decision, channel="sync", reason="candidate controls require sync routing") | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
|
||
|
|
||
| if decision.channel == "batch" and provider_request is None: | ||
| request = BatchRequest( | ||
|
|
@@ -600,7 +632,17 @@ def complete( | |
| } | ||
| race_token = self._race_usage_context.set(race_context) | ||
| try: | ||
| with self.orchestrator.request_policy(zdr_only): | ||
| with self.orchestrator.request_policy(zdr_only), self.orchestrator.candidate_routing_policy( | ||
| routing_controls, | ||
| model_name=model_name, | ||
| required_roles=("thinker", "worker", "verifier", "synthesizer") | ||
| if self.orchestrator.proxy_completion_requires_conduct( | ||
| provider_request, | ||
| endpoint=provider_endpoint, | ||
| single_agent=False, | ||
| ) | ||
| else ("worker",), | ||
|
seonghobae marked this conversation as resolved.
|
||
| ): | ||
|
seonghobae marked this conversation as resolved.
|
||
| provider_response = self.orchestrator.proxy_completion( | ||
| provider_request, | ||
| endpoint=provider_endpoint, | ||
|
|
@@ -621,6 +663,17 @@ def complete( | |
| ): | ||
| raise RuntimeError("provider completion omitted orchestration lineage") | ||
| result = dict(self.orchestrator.get_workflow_run(lineage["workflow_run_id"])) | ||
| routing_evidence = provider_response.pop("_candidate_routing", None) | ||
| # Only republish gateway-computed evidence: the raw provider | ||
| # response is untrusted (#983 Devin finding: "Provider fields | ||
| # forge routing evidence"). Without an active candidate control | ||
| # on this request, proxy_completion() never sets | ||
| # `_candidate_routing` itself, so a `_candidate_routing` field | ||
| # observed here with no active control can only have arrived | ||
| # already-present on the provider's own response body -- never | ||
| # trust it as gateway evidence in that case. | ||
| if has_candidate_controls and routing_evidence is not None: | ||
| lineage["routing"] = routing_evidence | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
|
||
| race_records = list(race_context["records"]) | ||
| records = list(race_records) | ||
| # The caller's request prompt is attributed at most once per | ||
|
|
@@ -734,8 +787,28 @@ def complete( | |
| } | ||
| race_token = self._race_usage_context.set(race_context) | ||
| try: | ||
| with self.orchestrator.request_policy(zdr_only): | ||
| # candidate_scope_open=True means the caller already has a | ||
| # candidate_routing_policy scope open (see the docstring above); | ||
| # entering a second, independent one here would reset the | ||
| # attempted-candidate ContextVar and discard whatever the caller | ||
| # already recorded under it, so reuse a no-op context instead. | ||
| candidate_scope = ( | ||
| contextlib.nullcontext() | ||
| if candidate_scope_open | ||
| else self.orchestrator.candidate_routing_policy( | ||
| routing_controls, | ||
| model_name=model_name, | ||
| required_roles=self.orchestrator.candidate_pin_required_roles( | ||
| mode, model_name | ||
| ), | ||
| ) | ||
| ) | ||
| with self.orchestrator.request_policy(zdr_only), candidate_scope: | ||
| result = self.orchestrator.run(messages, **run_kwargs) | ||
| routing_evidence = self.orchestrator._candidate_routing_evidence(result) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| if routing_evidence is not None: | ||
| result = dict(result) | ||
| result["candidate_routing"] = routing_evidence | ||
|
seonghobae marked this conversation as resolved.
seonghobae marked this conversation as resolved.
devin-ai-integration[bot] marked this conversation as resolved.
|
||
| if isinstance(result.get("workflow_run_id"), str): | ||
| race_context["workflow_run_id"] = result["workflow_run_id"] | ||
| race_context["workflow_ready"] = True | ||
|
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.