Skip to content
Draft
Show file tree
Hide file tree
Changes from 83 commits
Commits
Show all changes
137 commits
Select commit Hold shift + click to select a range
8a90464
fix(routing): select concrete free model groups
seonghobae Aug 31, 2026
4a5122b
fix(discovery): remove default network deadlines
seonghobae Aug 31, 2026
a367701
fix(groups): preserve exact model identity
seonghobae Aug 31, 2026
684f188
docs(gaps): align routing baseline with no-timeout policy
seonghobae Aug 31, 2026
a331beb
docs(gaps): distinguish implementation revision from PR head
seonghobae Aug 31, 2026
5abd66e
fix(routing): cancel raced provider transports
seonghobae Aug 31, 2026
643f02b
fix(discovery): migrate exact model identities
seonghobae Aug 31, 2026
248ed66
fix(routing): finalize race and legacy sync once
seonghobae Aug 31, 2026
fec7ac5
fix(discovery): activate refreshed legacy endpoint
seonghobae Aug 31, 2026
85216e7
fix(routing): preserve legacy discovery contracts
seonghobae Aug 31, 2026
5e638a7
fix(discovery): activate migrated legacy agents
seonghobae Aug 31, 2026
0a49fc3
fix(discovery): activate only discovered legacy agents
seonghobae Aug 31, 2026
9348385
fix(discovery): skip manual legacy id collisions
seonghobae Aug 31, 2026
81f3a24
fix(routing): remove readiness network deadlines
seonghobae Aug 31, 2026
94d3ca3
fix(routing): preserve cancellable no-timeout transport
seonghobae Aug 31, 2026
f185f90
fix(transport): make unbounded setup cancellable
seonghobae Aug 31, 2026
f993c2a
fix(transport): bound cancellable resolver ownership
seonghobae Aug 31, 2026
2183040
fix(discovery): release failed resolver ownership
seonghobae Aug 31, 2026
51a3642
fix(readiness): keep slow probes nonblocking
seonghobae Aug 31, 2026
011083c
fix(discovery): protect operator-owned agent identities
seonghobae Aug 31, 2026
dab4246
fix(discovery): preflight durable identity conflicts
seonghobae Aug 31, 2026
c4e7ad6
fix(discovery): canonicalize Unicode legacy IDs
seonghobae Sep 1, 2026
e2deddf
ci(opencode): wait without a readiness deadline
seonghobae Sep 1, 2026
7f92e44
fix(discovery): preserve historical Unicode agent IDs
seonghobae Sep 1, 2026
b7991a8
ci(opencode): stop waiting after gateway exit
seonghobae Sep 1, 2026
5ce6ec5
docs(bytez): correct prefix-free authorization contract
seonghobae Sep 1, 2026
8f0c476
fix(embeddings): quarantine rejected endpoints
seonghobae Sep 1, 2026
60526ba
fix(privacy): enforce OpenRouter ZDR at request time (#953)
seonghobae Sep 1, 2026
375d8c2
fix(embeddings): preserve endpoint quarantine semantics
seonghobae Sep 1, 2026
b51f0b1
fix(embeddings): map quarantine to availability
seonghobae Sep 1, 2026
8569a06
fix(ci): bound gateway readiness wait
seonghobae Sep 1, 2026
3124d7a
fix(ci): reserve maintenance runtime after discovery
seonghobae Sep 1, 2026
edc243f
fix(ci): reserve full maintenance budget
seonghobae Sep 1, 2026
2bc1b08
fix(ci): keep gateway and inference waits unbounded
seonghobae Sep 1, 2026
5e73d6b
fix(network): remove OpenRouter evidence deadlines
seonghobae Sep 1, 2026
e1977a5
fix(network): bound control-plane evidence calls
seonghobae Sep 1, 2026
39c8092
revert: keep control-plane evidence waits unbounded
seonghobae Sep 1, 2026
7e8257a
docs(gap): refresh model-group delivery evidence
seonghobae Sep 1, 2026
117aa18
Merge origin/main into fix/model-group-timeout-openrouter
Sep 1, 2026
70cddd8
fix: repair merge-interaction bugs found while validating tests/main …
Sep 1, 2026
3ca6b7c
test(responses): fix 3 tests stale after orchestrator/free auto-route…
claude Sep 1, 2026
4d2d5b4
test(provider): cover DeepSeek 502 recovery and no default deadline
seonghobae Sep 1, 2026
ef88d16
test: generalize provider retry and inference deadline contract
seonghobae Sep 2, 2026
6bc0834
test: remove model-specific resilience fixture
seonghobae Sep 2, 2026
fd593a3
test(routing): retain default connection-deadline contract
seonghobae Sep 2, 2026
6f588c0
ci: repair PR 971 exact-head findings
seonghobae Sep 2, 2026
fb6d8ba
ci: fit PR971 repair to slim-runner and publish scope
seonghobae Sep 2, 2026
66d49f0
ci: repair PR971 source-fix workflow parse
seonghobae Sep 2, 2026
c1dcff6
test(routing): fail closed on unproven default retries
seonghobae Sep 2, 2026
7f64c80
chore(repair): add PR971 default-retry source fix
seonghobae Sep 2, 2026
ce07141
chore(repair): add PR971 default-retry TDD workflow
seonghobae Sep 2, 2026
ffafb7c
chore(repair): trigger PR971 default-retry source fix
seonghobae Sep 2, 2026
90838d6
fix(repair): make PR971 exact-head repair transactional
seonghobae Sep 2, 2026
7486a1d
fix(ci): make PR971 repair publishable and least-privileged
seonghobae Sep 2, 2026
92ff90b
ci: retrigger PR971 default retry source fix
seonghobae Sep 2, 2026
5b71340
Merge remote-tracking branch 'origin/main' into fix/model-group-timeo…
claude Sep 2, 2026
65d3c60
chore(repair): retrigger PR971 default-retry source fix
seonghobae Sep 2, 2026
2aa206c
fix(routing): remove unproven default provider retry allocation
claude Sep 2, 2026
039d86c
Merge remote-tracking branch 'origin/fix/model-group-timeout-openrout…
claude Sep 2, 2026
e4cacff
ci: run exact-head persisted embedding repair
seonghobae Sep 2, 2026
6c379a4
fix(ci): repair PR971 one-shot YAML and timeout regression
seonghobae Sep 2, 2026
8d19b28
ci: isolate PR971 one-shot repair helper
seonghobae Sep 2, 2026
6fed18c
fix(ci): make PR971 repair workflow parse-safe
seonghobae Sep 2, 2026
56a34ab
fix(embeddings): fix default-timeout OverflowError, recovered-ZDR rev…
claude Sep 2, 2026
bfb2e81
fix(discovery): disable failed configured-gateway probes under a pers…
claude Sep 2, 2026
a7550d0
fix(embeddings): quarantine a repeatedly incomplete sync member, not …
claude Sep 2, 2026
2c8804a
fix(uptime): bound the background OpenRouter uptime fetch independent…
claude Sep 2, 2026
3832a3e
docs(pr971): record dated review findings and self-modifying workflow…
claude Sep 2, 2026
aaa3a68
test(race): preserve failed loser provenance
seonghobae Sep 2, 2026
9aa2eec
fix(race): preserve terminal loser provenance
seonghobae Sep 2, 2026
56127e3
chore(review): stage PR971 exact-head quality repair
seonghobae Sep 2, 2026
79b9cb9
chore(review): run PR971 RED-GREEN owner repair
seonghobae Sep 2, 2026
353cb96
fix(review): fold discovery and provider-diversity regressions into P…
seonghobae Sep 2, 2026
170103c
fix(review): require discovery and failure-domain GREEN
seonghobae Sep 2, 2026
6723792
fix(discovery): bound per-provider model discovery independent of any…
claude Sep 2, 2026
3dd989b
test(discovery): prove bootstrap pool selection is genuinely provider…
claude Sep 2, 2026
b415b41
docs(pr971): record the discovery-deadline and bootstrap-diversity fixes
claude Sep 2, 2026
5f45887
Merge remote-tracking branch 'origin/fix/model-group-timeout-openrout…
claude Sep 2, 2026
9b8f609
chore(review): remove now-superseded PR971 review-quality repair work…
claude Sep 2, 2026
fff6424
chore(ci): run exact-head review-quality owner repair
seonghobae Sep 2, 2026
109df43
test(review): add PR 971 false-negative regressions
seonghobae Sep 2, 2026
be23a2a
chore(ci): add temporary PR 971 repair helper
seonghobae Sep 2, 2026
03b620a
fix(ci): make PR 971 repair workflow parse-safe
seonghobae Sep 2, 2026
e3fa6d9
fix(cost_router): restore request_policy and reject mixed-ZDR embeddi…
claude Sep 2, 2026
12967aa
fix(discovery): bound shared metadata fetches and eliminate a ThreadP…
claude Sep 2, 2026
c07e67b
fix(ci): publish PR 971 repair with workflow-starting token
seonghobae Sep 2, 2026
4949626
fix(server): fail over terminal-failure batch embedding documents
claude Sep 2, 2026
0d77445
fix(discovery): bound OpenRouter free-endpoint fetch to a fixed 8-wor…
claude Sep 2, 2026
9a2e3a4
Merge remote-tracking branch 'origin/fix/model-group-timeout-openrout…
claude Sep 2, 2026
56785d7
test(review): stage current PR 971 false-negative repairs
seonghobae Sep 2, 2026
3d520fb
ci(review): run current exact-head PR 971 repair
seonghobae Sep 2, 2026
fe13068
fix(ci): cancel stale PR971 repair runs
seonghobae Sep 2, 2026
41aaeff
Merge remote-tracking branch 'origin/fix/model-group-timeout-openrout…
claude Sep 2, 2026
d932026
Merge remote-tracking branch 'origin/main' into HEAD
claude Sep 2, 2026
9b28bd2
fix(routing): eliminate a ThreadPoolExecutor shutdown-block in endpoi…
claude Sep 2, 2026
a3afc80
fix(race): settle futures when review callbacks fail
seonghobae Sep 2, 2026
83582ad
test(race): cover callback failure settlement
seonghobae Sep 2, 2026
742b5d5
docs(changelog): trace endpoint race callback settlement
seonghobae Sep 2, 2026
0247eca
chore(ci): retire stale PR 971 source-fix workflow
seonghobae Sep 2, 2026
d388564
chore(ci): remove retired PR 971 source-fix helper
seonghobae Sep 2, 2026
29bc5b7
test: align bootstrap selector oracle comment
seonghobae Sep 2, 2026
59b2fc8
fix(routing): eliminate a ThreadPoolExecutor shutdown-block in provid…
claude Sep 2, 2026
d402ec0
Merge remote-tracking branch 'origin/fix/model-group-timeout-openrout…
claude Sep 2, 2026
d29198e
test: prove blocked embedding worker cannot hold process exit
seonghobae Sep 2, 2026
7dc5ee7
test: keep single stronger embedding process-exit regression
seonghobae Sep 2, 2026
02389e2
test: reject daemon worker submissions after shutdown
seonghobae Sep 2, 2026
8caf844
ci: run one-shot daemon pool shutdown repair
seonghobae Sep 2, 2026
18a76eb
fix(routing): reject _DaemonWorkerPool submissions after shutdown
claude Sep 2, 2026
d87a1cb
Merge remote-tracking branch 'origin/fix/model-group-timeout-openrout…
claude Sep 2, 2026
558c847
docs(changelog): trace daemon pool post-shutdown submit fail-fast fix
claude Sep 2, 2026
df9f380
fix(routing): close daemon worker admission before shutdown drain
claude Sep 2, 2026
09f0249
chore(ci): remove obsolete PR971 daemon-pool-shutdown repair workflow
claude Sep 2, 2026
2510766
docs(gap-baseline): record PR 971 merge-resolution and concurrency-bu…
claude Sep 2, 2026
b05d028
Merge remote-tracking branch 'origin/fix/model-group-timeout-openrout…
claude Sep 2, 2026
770dada
test(bootstrap): reproduce durable report identity mismatch
seonghobae Sep 2, 2026
7369d48
fix(bootstrap): report persisted selected identities
seonghobae Sep 2, 2026
3acc6cd
test(catalog): reproduce durable report identity mismatch
seonghobae Sep 2, 2026
d02c97c
fix(catalog): report persisted selected identities
seonghobae Sep 2, 2026
696fd17
docs(changelog): record durable bootstrap identity contract
seonghobae Sep 2, 2026
121d898
Merge protected main into fix/model-group-timeout-openrouter
seonghobae Sep 2, 2026
2d8e233
docs(gap-baseline): track immutable release + resumable execution gap
claude Sep 2, 2026
df587e1
docs(gap-baseline): fix citations Devin Review flagged on the new entry
claude Sep 2, 2026
8b05b83
merge(main): restack PR 971 and repair exact RED fixtures
seonghobae Sep 6, 2026
521e613
fix(actions): remove merge artifact from hourly workflow
seonghobae Sep 6, 2026
6ddc6b4
test(bootstrap): make identity regressions directly executable
seonghobae Sep 6, 2026
1e59d4f
fix(restack): restore complete orchestrator source blob
seonghobae Sep 6, 2026
98aed1a
test(bootstrap): reproduce durable selection-order loss
seonghobae Sep 6, 2026
457ebb3
fix(bootstrap): preserve durable selector order
seonghobae Sep 6, 2026
d469085
refactor(bootstrap): remove superseded unordered ID seed
seonghobae Sep 6, 2026
eeb9cc1
test(routing): reject ambiguous bootstrap admission
seonghobae Sep 7, 2026
575148b
fix(routing): fail closed on ambiguous bootstrap admission
seonghobae Sep 7, 2026
50b0c86
test(routing): cover equal-price admission boundary
seonghobae Sep 7, 2026
7206c5f
fix(routing): reject unmodeled diversity displacement
seonghobae Sep 8, 2026
838cbcb
test(routing): reject unmodeled full-pool ordering
seonghobae Sep 8, 2026
726911c
docs(gaps): record 2026-09-08 diversity displacement + full-pool orde…
seonghobae Sep 9, 2026
d9c2f57
docs(provider-bootstrap): note provider-spread migration for model-gr…
seonghobae Sep 9, 2026
2f02876
fix(discovery): scope OpenRouter ZDR evidence to its provider
seonghobae Sep 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions .github/workflows/opencode-hourly-loop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ jobs:
loop:
name: Run OpenCode maintenance agent through the local gateway
runs-on: ubuntu-latest
timeout-minutes: 55
permissions:
Comment on lines 72 to 75

@devin-ai-integration devin-ai-integration Bot Sep 2, 2026

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.

🔍 Hourly runs can queue indefinitely

The removed job deadline lets one hung readiness or maintenance run block every later hourly run because concurrency never cancels the incumbent.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed this is a real gap, and traced its history: on main today the loop job still carries an explicit timeout-minutes: 55 (present since the job was introduced in #834, well before this PR). This branch (fix/model-group-timeout-openrouter) removed that job-level bound and replaced it with narrow per-step timeouts on the deterministic setup steps only, deliberately leaving the gateway-start and hourly-loop-agent steps unbounded — so on this PR's current head there is indeed no job-level bound left at all, which is exactly what this thread is flagging.

One correction for the record: this line is not a file that predates and sits outside this PR's diff — .github/workflows/opencode-hourly-loop.yml (including this exact timeout-minutes line) is part of #971's own diff, not a routine main-merge artifact.

I'm fixing this independently on main rather than adding another commit to this already-large, not-merge-ready branch: #1027 restores a job-level timeout-minutes: 300 (5h) on loop — generous headroom above the ~2h+-per-model floor this repo's inference steps need, while staying well under the platform's implicit 360-minute cap so cancel-in-progress: false can no longer let one wedged run block multiple hourly triggers. That also fixes a pre-existing, independent problem: main's current 55-minute cap is already tighter than the "2+ hours per model" floor, so legitimate long runs were already at risk of being killed by their own timeout.

This thread's underlying condition still exists on this branch's current head (no job-level bound), so I'm leaving it unresolved here rather than closing it out — whoever finishes #971 should reconcile its per-step timeout structure against #1027 (likely: keep the per-step timeouts on setup steps, and restore/keep a generous job-level bound like #1027's rather than dropping it).


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fresh owner-boundary correction: do not resolve this by importing #1027's timeout-minutes: 300 into this canonical lane. GitHub's current workflow syntax documents 360 minutes as the default jobs.<job_id>.timeout-minutes, and GitHub-hosted runners have a platform 6-hour job-execution limit. The platform ceiling is real, but CWL's current model contract is also explicit: application/Agent/Gateway model timeout defaults to null, and elapsed time alone must not terminate reasoning/streaming/tool calls. A caller-owned 300-minute job timeout would therefore create an earlier inference deadline rather than repair the queue/hang architecture.

Keep this thread open until a deterministic RED proves both sides at the production boundary: (a) no model-backed Actions client can terminate the inference solely at 300m/other leaf-authored elapsed-time threshold; and (b) an externally killed GitHub-hosted job cannot lose or synthesize the exact-head model execution/result. Smallest GREEN is durable/resumable contextual-orchestrator-owned execution (or equivalent exact-identity checkpoint/re-dispatch mechanism) with Actions reduced to bounded dispatch/observe/reconcile; setup/cleanup/network-health bounds may remain distinct from inference termination. Preserve explicit taxonomy for user cancel, provider termination, audited admin timeout, and infrastructure loss.

Also integrate the still-valid orchestrator/free workflow delta from #1006@c7ca8a06901bc65b7376fbe7a4852267d134cf96 / #1013@0a800f87c6df8bb310e1a09859f44d5451fcc969; protected main@464da4715b495b5eaaa593eba3796e2d976ee0c9 still uses orchestrator/auto. Do not retire either predecessor until one verified successor carries the complete free-pool contract/tests/docs delta.

Current live #971 head has already advanced to 41aaeff951fd5bc49b6e17386944a9b562843e8a and has no materialized exact-head PR workflow runs yet, so predecessor checks/reviews remain non-transferable.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Follow-up: the review discussion on this same file/finding (echoed on both this PR and #1027) surfaced a correction I want to record here too. My first pass on #1027 proposed a timeout-minutes: 300 job-level cap — that was wrong: any finite job-level timeout covering the gateway-start/inference steps is exactly the elapsed-time model cutoff this org's policy forbids (see AGENTS.md/CLAUDE.md — model timeout defaults to unlimited/null; only user cancellation, provider termination, or an explicit audited admin timeout may end a call). Raising the number doesn't fix that, it just moves the violation further out.

The corrected fix (now landed on #1027, #1027) removes the job-level timeout entirely rather than replacing it — which means this PR's branch (fix/model-group-timeout-openrouter) already has the architecturally correct shape here: no job-level cutoff on loop, only narrow per-step timeouts on the deterministic setup steps, gateway-start/inference left genuinely unbounded. #971 and #1027 have converged on the same design independently.

What's still genuinely unaddressed by either PR, and out of scope for both: the underlying concurrency problem this finding named (cancel-in-progress: false means a truly-hung run still occupies the group until GitHub's implicit 360-minute hard ceiling kicks in, and any run superseded during that window doesn't execute). Fixing that for real needs a durable/resumable execution boundary (checkpoint/re-dispatch that preserves exact-head identity and doesn't synthesize a result across the platform's forced termination) — a separate, larger piece of work, not a workflow-file tweak. Leaving this open as a tracked gap rather than resolving it, since the concurrency behavior itself isn't fixed yet.


Generated by Claude Code

contents: write # required for reviewed branch fixes; scoped to this job
pull-requests: write
Expand All @@ -68,29 +67,35 @@ jobs:
NVIDIA_NIM_API_KEY_SUB: ${{ secrets.NVIDIA_NIM_API_KEY_SUB }}
steps:
- name: Checkout repository
timeout-minutes: 5
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # actions/checkout@v7
with:
persist-credentials: true # the agent needs to push fixes/branches

- name: Set up Python
timeout-minutes: 5
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # actions/setup-python@v6
with:
python-version: "3.12"

- name: Set up Node for the OpenCode CLI
timeout-minutes: 5
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # actions/setup-node@v4
with:
node-version: "22"

- name: Install pinned runtime dependencies
timeout-minutes: 10
run: python -m pip install --require-hashes -r requirements.lock

- name: Install OpenCode CLI
timeout-minutes: 10
run: |
npm ci --ignore-scripts
node scripts/ci/install_locked_opencode.mjs

- name: Generate ephemeral loopback gateway token
timeout-minutes: 1
run: |
gateway_token=$(python -c 'import secrets; print(secrets.token_urlsafe(32))')
echo "::add-mask::$gateway_token"
Expand All @@ -106,17 +111,24 @@ jobs:
--auth-token-key CONTEXTUAL_ORCHESTRATOR_TOKEN \
--host 127.0.0.1 --port 8000 \
> /tmp/gateway.log 2>&1 &
for _attempt in $(seq 1 30); do
gateway_pid=$!
while :; do
Comment thread
seonghobae marked this conversation as resolved.
Comment thread
seonghobae marked this conversation as resolved.
if curl -fsS -H "Authorization: Bearer ${CONTEXTUAL_ORCHESTRATOR_TOKEN}" \
http://127.0.0.1:8000/healthz >/dev/null 2>&1; then
echo "gateway ready"
exit 0
fi
if ! kill -0 "$gateway_pid" 2>/dev/null; then
wait "$gateway_pid" || true
echo "::error::gateway exited before becoming healthy"
tail -50 /tmp/gateway.log
exit 1
fi
sleep 2
done
echo "::error::gateway did not become healthy"; tail -50 /tmp/gateway.log; exit 1

- name: Point OpenCode at the local gateway
timeout-minutes: 1
run: |
set -euo pipefail
umask 077
Expand All @@ -142,6 +154,7 @@ jobs:
JSON

- name: Build trusted same-repository PR allowlist
timeout-minutes: 5
env:
GH_TOKEN: ${{ github.token }}
run: |
Expand All @@ -157,5 +170,6 @@ jobs:
"$(cat .github/opencode/hourly-loop-prompt.md)"

- name: Dump gateway log on failure
timeout-minutes: 5
if: failure()
run: tail -100 /tmp/gateway.log
105 changes: 105 additions & 0 deletions .github/workflows/source-fix-971-live-review-quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
name: PR 971 live review quality repair

on:
push:
branches:
- fix/model-group-timeout-openrouter
paths:
- .github/workflows/source-fix-971-live-review-quality.yml
workflow_dispatch:

permissions:
contents: write

concurrency:
group: pr-971-live-review-quality-repair
cancel-in-progress: false

jobs:
repair:
name: RED to GREEN owner repair
runs-on: ubuntu-latest
steps:
- name: Checkout exact writer head
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # actions/checkout@v7
with:
fetch-depth: 0
persist-credentials: true

- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # actions/setup-python@v6
with:
python-version: "3.12"

- name: Set up uv
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
version: "0.12.5"

- name: Guard exact writer head
shell: bash
run: |
set -euo pipefail
remote_head="$(git ls-remote origin "refs/heads/${GITHUB_REF_NAME}" | cut -f1)"
test "$remote_head" = "$GITHUB_SHA"

- name: Verify four causal RED regressions
shell: bash
run: |
set -euo pipefail
nodes=(
"tests/test_pr971_review_quality_regressions.py::test_recovered_zdr_batch_reenters_request_privacy_scope"
"tests/test_pr971_review_quality_regressions.py::test_provider_embedding_batch_rejects_mixed_privacy_identity"
"tests/test_pr971_review_quality_regressions.py::test_terminal_embedding_batch_document_fails_over_before_marking_health"
"tests/test_pr971_review_quality_regressions.py::test_discover_all_models_bounds_every_shared_metadata_fetch"
)
for node in "${nodes[@]}"; do
set +e
uv run --locked --extra api --extra db --extra queue --group dev python -m pytest -q "$node"
rc=$?
set -e
if [ "$rc" -ne 1 ]; then
echo "Expected pytest RED exit 1 for ${node}, got ${rc}" >&2
exit 1
fi
done

- name: Apply causal owner repairs
run: python scripts/ci/pr971_live_review_quality_repair.py
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Outdated

- name: Verify focused GREEN
run: >-
uv run --locked --extra api --extra db --extra queue --group dev
python -m pytest -q
tests/test_pr971_review_quality_regressions.py
tests/test_provider_embedding_batch_backend.py
tests/test_embeddings_model_pool_http_honesty.py
tests/test_model_discovery.py

- name: Verify broader GREEN
run: |
set -euo pipefail
uv run --locked --extra api --extra db --extra queue --group dev python -m pytest -q
python -m compileall -q contextual_orchestrator tests/test_pr971_review_quality_regressions.py
git diff --check

- name: Remove temporary repair machinery and publish
shell: bash
run: |
set -euo pipefail
remote_head="$(git ls-remote origin "refs/heads/${GITHUB_REF_NAME}" | cut -f1)"
test "$remote_head" = "$GITHUB_SHA"
rm .github/workflows/source-fix-971-live-review-quality.yml
rm scripts/ci/pr971_live_review_quality_repair.py
if find .github/workflows -maxdepth 1 -type f -name '*source-fix-971*' -print -quit | grep -q .; then
echo "A source-fix-971 workflow would remain in the publishable tree" >&2
exit 1
fi
git diff --check
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -A
git commit -m "fix(review): enforce embedding privacy health and discovery bounds"
remote_head="$(git ls-remote origin "refs/heads/${GITHUB_REF_NAME}" | cut -f1)"
test "$remote_head" = "$GITHUB_SHA"
git push origin "HEAD:${GITHUB_REF_NAME}"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
1 change: 1 addition & 0 deletions CHANGELOG.d/openrouter-not-evidence-only.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Stopped treating OpenRouter as a whole-account, ZDR-motivated serving exclusion. `PROVIDER_MODEL_SOURCES`'s `openrouter` entry no longer sets `evidence_only=True`: ZDR eligibility is a route/model-level property, never grounds to block an entire provider account from serving, and OpenRouter was the one provider source with genuinely reliable native pricing/`is_free` evidence, so excluding it directly caused `orchestrator/free`'s previously-documented structural emptiness (ADR 0041). Also fixed a backwards side effect of the old flag: `_apply_discovered_model_evidence` could never mark OpenRouter's own rows `zdr_capable=True` even when they exactly matched OpenRouter's own declared ZDR feed. The provider-neutral evidence-application contract from PR #901 (OpenRouter's feed also crediting matching rows from every other provider) is unchanged. Since OpenRouter can multiplex one model id across several backing providers, `ModelClient` now pins every OpenRouter request made under an active `zdr_only` scope with OpenRouter's own documented `"provider": {"zdr": true}` request-time enforcement, applied at the shared `_send`/`_stream_send`/`_send_raw` transport chokepoints, and at the async Batch API path (`_batch_run`) via the same helper on each uploaded JSONL request body.
117 changes: 117 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,19 @@ and this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
retried as if it were a network blip. Fixes the shared classifier itself
(not just the discovery retry call site), so every current and future
caller of `is_transient_error` benefits.
- (Devin review on #953) `_pin_openrouter_zdr` no longer raises a bare
`TypeError` from `dict()` when a caller-supplied `provider` field is
present but not an object (an int, bool, list, or string) under an active
`zdr_only` scope. It now validates the field and raises a named `ValueError`
("provider must be an object with optional OpenRouter routing keys")
instead, matching this codebase's existing convention for malformed
caller-input fields. Every call site sharing this one choke point (chat,
streaming, tools/binary-media passthrough, and the batch JSONL path)
benefits; a valid `provider` object or an absent/`None` one keep their
existing behavior unchanged.
- OpenRouter embedding Batch JSONL now carries the same request-scoped
`provider.zdr=true` enforcement when `zdr_only` selects an attested
OpenRouter embedding agent.
- `batch_route` no longer fabricates a hardcoded, ungated
`{"accepted": True, "verifier_output": ""}` verification verdict for every
batched answer regardless of `policy.realtime_judge`. It now calls the same
Expand Down Expand Up @@ -854,6 +867,110 @@ and this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
otherwise. `_write_sse` relies on `_begin_sse`'s already-set marker rather
than touching it itself, since it is only ever called after a prior
successful header flush.
- (2026-09-02, PR #971) `ModelClient`'s constructor no longer defaults
`max_retries` to a hand-picked `2`: no cited standard, paper, or the org's
own research (Fugu, Conductor, TRINITY) establishes that number, and a
fresh audit found it was never justified. RFC 9110 §9.2.2 constrains *when*
replay can be safe for idempotent semantics, and NIST SP 800-204 discusses
retry/circuit-breaker resilience as a pattern, but neither identifies a
specific numeric retry allocation for this library. The default is now
`0`: a default `ModelClient` allocates zero automatic provider transport
retries, independent of provider, model, or reasoning identity
(`tests/test_no_heuristic_default_transport_retry.py`). Explicit nonzero
retry budgets remain caller-owned configuration, never a library-authored
default (ADR 0001 amendment, 2026-09-02).
- (2026-09-02, PR #971) A default (no configured deadline) synchronous
`/v1/embeddings` request against a provider-backed member returned 503:
`embedding_deadline` collapsed to `+inf`, and `ProviderEmbeddingBatchBackend.wait`
passed that straight into `threading.Event.wait(timeout=...)`, which raises
`OverflowError` for a non-finite timeout on CPython -- directly contradicting
#971's own no-implicit-deadline policy. `wait()` now translates a non-finite
timeout to `None` (block indefinitely) instead
(`tests/test_provider_embedding_batch_backend.py::test_provider_batch_wait_survives_infinite_deadline`).
- (2026-09-02, PR #971) A failed configured-gateway structured-chat probe
recorded the failing model only under its new fingerprinted id
(`agent_id_for`); a persisted agent kept under its pre-fingerprint legacy
id (`legacy_agent_id_for`) was silently dropped from `runtime_models` and
never reached the disable path, leaving a failed legacy endpoint enabled
indefinitely. `_auto_discover_runtime_agents` now accepts either id form
when checking for an existing persisted agent
(`tests/test_auto_discovery_server.py::test_failed_gateway_probe_disables_legacy_id_persisted_agent`).
- (2026-09-02, PR #971) A privacy-scoped (`zdr_only=True`) embedding batch's
pinned `agent_id` was replayed verbatim by `ProviderEmbeddingBatchBackend`
after a process restart recovers a durably queued job, with no
re-validation that the agent still carried the `privacy:zdr` tag -- an
operator could remove ZDR support or repoint the agent between submission
and a resumed execution and the batch would still run. `_run_provider_embeddings`
now re-checks the request's own recorded `zdr_only` against the resolved
agent's current tags at execution time and fails closed if they no longer
match
(`tests/test_provider_embedding_batch_backend.py::test_recovered_privacy_scoped_embedding_batch_revalidates_current_agent_tags`).
- (2026-09-02, PR #971) A synchronous `/v1/embeddings` member result that
came back without raising but was not `completed` (or had no embeddings)
bypassed `orchestrator._record_embedding_failure`, so the circuit breaker
never opened and no `embedding_endpoint_failed` analytics event was
recorded for that failure mode -- a repeatedly incomplete member was
retried forever instead of being quarantined like a raised exception. It
now routes through the same failure recorder
(`tests/test_embeddings_model_pool_http_honesty.py::test_http_embeddings_quarantines_repeated_incomplete_document_with_failure_evidence`).
- (2026-09-02, PR #971) `CostRoutingCoordinator` derived the durable
provider-embedding claim lease from `ModelClient.timeout`, so a durable
(Valkey-backed) job registry raised `ValueError: durable provider backend
claim lease must be positive` at startup whenever the client had no
configured deadline (the default since #971) -- a crash directly caused by
conflating an internal locking heartbeat with the caller's request
deadline. The claim lease now falls back to a fixed, positive default
independent of `ModelClient.timeout`. Separately, `execution_timeout_seconds=None`
previously fell back to the job registry's storage retention window (7
days), silently expiring an intentionally unbounded embedding job; it now
stays genuinely unbounded (`+inf` deadline)
(`tests/test_provider_embedding_batch_backend.py::test_durable_provider_embedding_backend_survives_unbounded_client_timeout`,
`::test_unbounded_execution_timeout_never_substitutes_registry_retention`).
- (2026-09-02, PR #971) `OpenRouterUptimeCollector._fetch_uptime` adopted
#971's inference no-fixed-deadline policy (`timeout=None`) even though it
is unrelated background telemetry polled sequentially on one dedicated
sweep thread that `stop()` cannot interrupt mid-request: one unresponsive
OpenRouter endpoint would hang that thread forever, leaking it and
indefinitely starving every later member of an uptime update. The fetch
now keeps its own fixed, independent bound
(`tests/test_openrouter_uptime.py::test_uptime_fetch_does_not_hang_forever_on_an_unresponsive_endpoint`).
- (2026-09-02, PR #971) `discover_all_models`'s per-provider loop
(`model_discovery.py`) called `discover_provider_models` directly and
in-line, with no separate bound or cancellation mechanism -- a stalled
provider catalog request (a connection accepted but never answered, or
in tests a mock that never returns) blocked discovery of every later,
healthy provider forever, regardless of `DISCOVERY_TIMEOUT_SECONDS`
(which only bounds one socket read at a time and stays `None` by
default). Each provider's discovery attempt now runs on its own daemon
thread bounded by a new, separately configured
`PROVIDER_DISCOVERY_DEADLINE_SECONDS` (default 30.0s, independent of
both `DISCOVERY_TIMEOUT_SECONDS` and `ModelClient.timeout` -- neither is
reused or repurposed): once the deadline elapses the caller stops
waiting, records a `ProviderDiscoveryError(error_code="discovery_timeout")`
for that provider, and moves on; the abandoned thread is daemonized so
it cannot block interpreter shutdown
(`tests/test_model_discovery.py::test_discover_all_models_bounds_a_stalled_provider_so_later_providers_still_complete`).
- (2026-09-02, PR #971) `select_bootstrap_discovered_agents`'s first pass
admitted at most one endpoint per model group (the provider-declared
exact model identity) but never checked provider identity, so several
cheap, distinctly-named models from one provider could fill most or all
of a bootstrap pool before a genuinely independent alternative provider
was ever tried -- an apparently diverse pool (distinct model names) that
was actually one provider's outage away from total failure, contrary to
this repo's own documented "provider-diverse selection" claim for this
function. The first pass now admits at most one endpoint per provider
*and* per model group; once every viable provider has contributed once
(or capacity runs out), a second pass fills remaining slots from
still-untried model groups regardless of provider, and a final pass
falls back to duplicate model-group endpoints only once real diversity
is exhausted. `contextual_orchestrator/provider_bootstrap.py`'s separate,
honestly-named `select_model_group_diverse_models` (which never claimed
provider diversity, and whose own tests deliberately rank a known
same-provider price ahead of diversity) was left unchanged
(`tests/test_discovery_bootstrap_selection.py::test_bootstrap_selector_spans_multiple_providers_before_repeating_one`,
`::test_bootstrap_selector_prefers_model_group_diversity`,
`::test_bootstrap_selector_falls_back_to_duplicate_model_group_when_capacity_remains`,
`tests/test_review_gateway.py::test_build_review_orchestrator_uses_model_group_diversity`).

### Added

Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,8 +290,9 @@ is read from a **KV config store**, never `os.getenv`.
- **Health.** `GET /healthz` is an unauthenticated liveness probe that returns
only service identity and process status; it never discloses worker topology,
backend names, usage volume, or upstream readiness. Admins can use
`GET /api/v1/provider_readiness/latest?refresh=true` for one bounded,
non-retrying chat probe per enabled worker.
`GET /api/v1/provider_readiness/latest?refresh=true` for one explicitly
cancellable, non-retrying chat probe per enabled worker. Concurrent refreshes
return `refresh_in_progress` instead of blocking behind a slow provider.
- **Standalone + optional pg-llm-batch integration.** The hub runs standalone
with the in-memory config store and local batch backend; wiring a Postgres DSN
and an installed/deployed `pg_llm_batch` client activates the KV/secret stores,
Expand Down
Loading
Loading