Skip to content

feat(admin): per-model LLM timeout view/set/clear/restore - #1010

Closed
seonghobae wants to merge 2 commits into
mainfrom
feat/admin-per-model-timeout-20260902
Closed

feat(admin): per-model LLM timeout view/set/clear/restore#1010
seonghobae wants to merge 2 commits into
mainfrom
feat/admin-per-model-timeout-20260902

Conversation

@seonghobae

@seonghobae seonghobae commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes the standing admin-web requirement recorded in ContextualWisdomLab/.github's docs/product-goal-directive.md §8: an admin web where operators can view/set/clear/restore per-model LLM timeouts, with units, priority/inheritance, input validation, audit history, and an API contract.

Research first (cloned fresh, no assumptions): no per-model timeout config existed anywhere in this repo -- ModelClient.timeout was one flat instance value (default 90s) applied to every outbound call regardless of model. /admin (admin.py) is already a real, serving operator console (not a stub); admin_ui/ (React+Storybook) is confirmed still the unmodified Vite scaffold, matching this repo's own superseded planning ADR 0036 -- so this PR extends admin.py in place per the operative ADR 0033, and does not touch admin_ui/.

  • TaskOrchestrator.list_model_timeouts/get_model_timeout/set_model_timeout/clear_model_timeout -- follows the existing model_group family's exact pattern: KV-persisted (_StateStore's new "model_timeout_override" keyed kind + a new .delete() method for clear/restore), audited via the existing _append_audit_event, KeyError/ValueError on unknown model / invalid value. MIN_MODEL_TIMEOUT_SECONDS=1 / MAX_MODEL_TIMEOUT_SECONDS=14400 -- the 14400s ceiling matches this org's own already-evidenced NOEMA_LLM_TIMEOUT_SECONDS precedent, not an invented number.
  • GET /api/v1/model_timeouts(/{model}), PATCH/DELETE /api/v1/model_timeouts/{model} (PATCH, not PUT -- matches model_groups' existing convention; there is no do_PUT handler in this repo). Declared in api_contract.py.
  • Real enforcement, not inert config: ModelClient.model_timeout_resolver is wired to a resolver that returns None absent an override (not the resolved default), so chat()/stream_chat() omit the timeout kwarg entirely in the common case -- byte-identical to every existing caller's prior behavior -- and only pass an explicit override when an operator actually set one. Verified with a fake transport capturing the real timeout= reaching _open_provider on both the chat and streaming-chat paths.
  • New "Model timeouts" panel in admin.py's Settings view (bilingual en/ko), mirroring the model_groups panel's render/refresh/save pattern exactly. Purely additive -- no existing line changed, so test_admin_contract.py's 100+ exact-string assertions keep passing unmodified.
  • Planning ADR 0042 records the full design, including a real regression this change introduced and caught locally before push: an earlier version always resolved and passed a concrete timeout, breaking three existing test files' strict-signature ModelClient._send_with_retry/_stream_send mock stand-ins. Fixed by correcting the resolver's semantics (return None absent an override) -- the smaller, more correct fix, not patching every test double.

Test plan

  • python3 -m pytest tests -q -- 3336 passed, 1 skipped (baseline before this PR: 3307 passed, 1 skipped)
  • python3 -m pytest tests/test_model_timeouts.py -q -- 29 new tests (validation, inheritance, audit history, persistence-across-restart, real ModelClient-level timeout wiring for both chat and streaming, HTTP CRUD + auth, admin-console UI-surface assertions)
  • interrogate -v on every touched module -- 100% docstring coverage
  • test_api_contract.py, test_admin_contract.py, test_conventions.py, test_planning_adr_identifiers.py -- all green, unmodified assertions still pass

What's left for the next iteration

  • Batch/embeddings/rerank/transcription/image transport paths still use only the flat default -- chat and streaming chat (the route()/conduct() path this requirement is about) are wired; extending the same resolver to the remaining ModelClient transport methods is straightforward but left for a follow-up.
  • Keyverse SSO for /admin -- a real cross-repo integration (see ContextualWisdomLab/.github ADR-0021 and the sibling keyverse Keyvault PR from this same research pass), designed but not built in this PR.
  • A future KeyverseCredentialBackend implementing this repo's existing CredentialBackend Protocol, per keyverse PR's ADR-0016 -- noted as the natural next consumer of keyverse's new Keyvault, not started here.

🤖 Generated with Claude Code


Devin Review

Closes the standing admin-web requirement in ContextualWisdomLab/.github's
docs/product-goal-directive.md §8: no per-model timeout config existed
anywhere -- ModelClient.timeout was one flat instance value (default 90s)
applied to every outbound call regardless of model.

- TaskOrchestrator gains list_model_timeouts/get_model_timeout/
  set_model_timeout/clear_model_timeout, following the existing
  model_group family's pattern exactly (KV-persisted via a new
  "model_timeout_override" keyed kind + _StateStore.delete, audited via
  the existing _append_audit_event, KeyError/ValueError on unknown model /
  invalid value). MIN=1s/MAX=14400s bounds match the org's own
  NOEMA_LLM_TIMEOUT_SECONDS precedent, not an invented number.
- New API: GET /api/v1/model_timeouts(/{model}), PATCH/DELETE
  /api/v1/model_timeouts/{model} (PATCH to match this repo's existing
  model_groups convention -- there is no do_PUT handler), declared in
  api_contract.py.
- Real enforcement: ModelClient.model_timeout_resolver is wired to a
  resolver that returns None absent an override (not the resolved
  default), so chat()/stream_chat() omit the timeout kwarg entirely in the
  common case -- identical to every existing caller's prior behavior --
  and only pass an explicit timeout when an operator actually set one.
  Verified against a fake transport capturing the real timeout= reaching
  _open_provider for both the chat and streaming-chat paths.
- New "Model timeouts" panel in admin.py's existing Settings view
  (bilingual en/ko), following the model_groups panel's exact
  render/refresh/save pattern. Purely additive -- no existing line
  changed, so test_admin_contract.py's 100+ exact-string assertions still
  pass unmodified. Extends the existing /admin console per planning ADR
  0033 (operative: defer React/Storybook until a concrete trigger is
  met -- none is met here); does not touch the unbuilt admin_ui/ scaffold.
- Planning ADR 0042 records the design, including a regression this
  change introduced and fixed before push: an earlier version always
  resolved and passed a concrete timeout, which broke three existing
  test files' strict-signature ModelClient._send_with_retry/_stream_send
  mock stand-ins -- fixed by correcting the resolver's semantics (return
  None absent an override), not by patching every test double.

29 new tests (tests/test_model_timeouts.py). Full suite: 3336 passed, 1
skipped (was 3307 passed, 1 skipped before this change). 100% docstring
coverage (interrogate) on every touched module.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 26 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: ebad36b2-cf79-44ad-856c-a641d3454bec

📥 Commits

Reviewing files that changed from the base of the PR and between 8839081 and 56a6e45.

📒 Files selected for processing (8)
  • README.md
  • contextual_orchestrator/admin.py
  • contextual_orchestrator/api_contract.py
  • contextual_orchestrator/orchestrator.py
  • contextual_orchestrator/server.py
  • docs/planning/adrs/0042-per-model-timeout-admin-surface.md
  • docs/product-technical-gap-baseline.md
  • tests/test_model_timeouts.py

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.

@devin-ai-integration devin-ai-integration 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.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 6 potential issues.

Devin Review

Comment on lines +1958 to +1962
timeout_override = self._resolve_timeout(agent)
return (
self._send_with_retry(agent, payload, destination)
if timeout_override is None
else self._send_with_retry(agent, payload, destination, timeout=timeout_override)

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.

🟡 Local queue ignores model timeout

Under local contention, _local_provider_slot uses the default before _resolve_timeout reads the override. Requests can fail at the old deadline.

Prompt for agents
Resolve the model-specific timeout before entering _local_provider_slot in both ModelClient.chat and ModelClient.stream_chat. Use the effective override for local queue acquisition as well as provider I/O, while preserving the existing no-override call shape for _send_with_retry and _stream_send. Add contention tests where the override is longer and shorter than ModelClient.timeout so the queue deadline and transport timeout follow the same model setting.
Devin Review

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

Comment on lines +3957 to +3963
# Admin-editable per-model timeout overrides (model name -> seconds).
# Absent entries inherit self.client.timeout. Wired into the client as
# a resolver so every chat/stream call already routed through
# ModelClient picks up operator changes with no other call-site change.
self._model_timeout_overrides: dict[str, float] = {}
if isinstance(self.client, ModelClient):
self.client.model_timeout_resolver = self._model_timeout_override_for

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.

🟡 Passthrough requests ignore model timeouts

Chat requests using tools call proxy_send, bypassing the new resolver. They keep the flat timeout despite an administrator's per-model override.

Prompt for agents
Apply the per-model timeout resolver to ModelClient's chat-compatible passthrough paths, including proxy_send/proxy_send_once for chat/completions and Responses requests. Thread the resolved timeout through _send_raw_with_retry and _open_provider while retaining capability-probe timeout behavior and the no-override call shape. Cover explicit-model tool and response-format requests through the HTTP API.
Devin Review

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

Comment on lines +6259 to +6262
previous = self._model_timeout_overrides.get(model)
self._model_timeout_overrides[model] = value
if self._store is not None:
self._store.save("model_timeout_override", model, {"model": model, "timeout_seconds": value})

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.

🟡 Failed saves still change timeouts

set_model_timeout mutates live state before persistence commits. A storage failure returns an error while changing the timeout until restart.

Prompt for agents
Make set_model_timeout and clear_model_timeout atomic across durable storage and the in-memory override registry. Persist first or roll back the in-memory mutation when _StateStore fails, and serialize concurrent set/clear operations so runtime state, returned status, audit records, and restart state cannot diverge. Add failure-injection tests for both save and delete paths.
Devin Review

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

Comment on lines +1 to +27
---
id: "0042"
title: "Per-model LLM request timeout: admin-editable override, audited, inherited"
status: accepted
proposed_date: "2026-09-02"
accepted_date: "2026-09-02"
deciders:
- "repository maintainer"
affected_components:
- "contextual_orchestrator/orchestrator.py"
- "contextual_orchestrator/server.py"
- "contextual_orchestrator/api_contract.py"
- "contextual_orchestrator/admin.py"
related:
- path: "docs/planning/adrs/0033-admin-console-ui-tooling-boundary.md"
relation: informational
success_criteria:
- metric: "operator can view/set/clear per-model timeout"
target: "GET/PATCH/DELETE /api/v1/model_timeouts(/{model}) and a Settings-view panel in admin.py all round-trip"
source: "tests/test_model_timeouts.py"
- metric: "an override changes real outbound call behavior, not just stored config"
target: "ModelClient.chat/stream_chat pass the resolved per-model timeout into _send_with_retry/_stream_send -> _open_provider"
source: "tests/test_model_timeouts.py::test_chat_call_uses_the_resolved_per_model_timeout, ::test_stream_chat_uses_the_resolved_per_model_timeout"
- metric: "no override leaves every existing caller's exact prior call shape unchanged"
target: "the resolver wired into ModelClient returns None (not the resolved default) absent an override, so call sites omit the timeout kwarg entirely in the common case"
source: "tests/test_model_timeouts.py::test_wired_resolver_returns_none_for_a_model_with_no_override, ::test_bare_model_client_with_no_resolver_passes_no_timeout_override"
---

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.

🔍 Research grounding is absent

This substantive feature adds no paper, citation, link, or redistribution note. Repository governance requires research grounding for feature PRs.

Devin Review

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

Comment on lines +6188 to +6190
def _known_model_names(self) -> set[str]:
"""Return the distinct provider model identifiers configured on any agent."""
return {agent.model for agent in self.candidates}

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.

📝 Info: Shared model names share overrides

Overrides key on agent.model, so every provider connection exposing one model identifier inherits the same setting. This matches the per-model API contract.

Devin Review

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

Comment on lines +6239 to +6252
def list_model_timeouts(self) -> list[dict[str, Any]]:
"""Return every configured model's effective timeout and override status."""
names = sorted(self._known_model_names() | set(self._model_timeout_overrides))
return [self._model_timeout_payload(name) for name in names]

def get_model_timeout(self, model: str) -> dict[str, Any]:
"""Return one model's timeout detail plus its recent set/clear audit history.

Raises ``KeyError`` when ``model`` matches no configured agent and has
no (now-orphaned) override on record.
"""
if model not in self._known_model_names() and model not in self._model_timeout_overrides:
raise KeyError(model)
return self._model_timeout_payload(model, with_audit=True)

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.

📝 Info: Orphaned overrides stay manageable

Removed models remain listed while an override exists. Operators can inspect and clear stale state, while new settings still require a configured model.

Devin Review

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

…anel

Live-browser verification of the model-timeout panel from the prior commit
(stood up the dev server, drove real Save/Restore/error/bilingual/mobile
flows against real API responses -- not just contract-test assertions)
surfaced two real bugs neither test_admin_contract.py nor
test_model_timeouts.py could catch, since both assert markup/behavior, not
actual rendering:

- "Restore default" had no visual difference between disabled and enabled
  -- .btn:disabled had no CSS anywhere in admin.py; this panel is the first
  thing in the console to ever set `disabled` on a .btn. Added
  `.btn:disabled { opacity: .5; cursor: not-allowed; filter: none; }`.
- The shared Audit tab did not reflect new model_timeout_set/
  model_timeout_cleared events without a full page reload. Verified
  server-side the events *were* recorded correctly
  (curl /admin/state showed them); state.recent_audit_events was only ever
  populated by load()'s initial fetch. Added refreshAuditEvents() (re-fetch
  /admin/state, re-render the Audit table), called after a successful
  save/clear.

Scoped to this panel's own actions only -- model_groups' save/delete has
the identical staleness, but that is a pre-existing, wider pattern this
PR did not introduce and fixing it broadly is out of scope here.

Also verified live: bilingual (en/ko) rendering with no raw i18n keys,
localized aria-label, validation-error feedback, and mobile (375px) layout
-- the panel's table scrolls within its own .panel (overflow-x:auto from
the shared stylesheet) rather than the page, and every control stays
reachable and the fixed disabled-state styling stays visible at that width.

tests/test_admin_contract.py + tests/test_model_timeouts.py: 31/31 passed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@seonghobae

Copy link
Copy Markdown
Contributor Author

Live-render verification (not just contract-test inspection)

Stood up contextual-orchestrator locally (python -m contextual_orchestrator --serve --agents examples/agents.mock.json --auth-token ..., per this repo's own README dev-server command — .superset/run.sh's --insecure-disable-auth is stale/deprecated, real auth is always required now) and drove the new "Model timeouts" panel in a real browser against the real /admin/state + /api/v1/model_timeouts API, not mocked data.

Verified against this org's UX checklist:

  • List view: 3 mock models render with real effective timeout / source chip (Default/Override) / editable input placeholder from the live API.
  • Edit/save flow: typed 120, clicked Save → row updates to 120s / Override (green chip) immediately, success message "Timeout saved. New requests to this model use it immediately." renders.
  • Restore/clear flow: clicked "Restore default" on the overridden row → reverts to 90s / Default, success message "Override cleared. This model now uses the default timeout."
  • Validation/error feedback: typed -5, clicked Save → server-side validation rejects it, error surfaces as "model timeout must be between 1 and 14400 seconds" in the same feedback region.
  • Bilingual (en/ko): switched the console's language selector live — title, table headers, chips, buttons, description text, and the input's aria-label all render correctly in Korean (모델 타임아웃 / 저장 / 기본값으로 복원 / 새 타임아웃(초)) with no raw i18n keys leaking through.
  • Accessibility: input has a real, localized aria-label; keyboard-driven <select>-based view navigation works; focus order follows DOM order (no tabindex gaps introduced).
  • Responsive (375×812 mobile): the panel's table overflows its own .panel container (which already has overflow-x:auto from the shared stylesheet) rather than the page — scrolled programmatically and confirmed every Save/Restore control is reachable and the disabled-state styling stays visible at that width.

Two real bugs found in the live render (contract tests didn't catch either) — fixed in this same commit

  1. Disabled "Restore default" was visually identical to an enabled button. .btn:disabled had no CSS anywhere in admin.py — this panel is the first thing in the whole console to ever set disabled on a .btn, so the gap was latent until now. Added .btn:disabled { opacity: .5; cursor: not-allowed; filter: none; }. Confirmed live: the button is now clearly faded when a model has no override, and full-opacity/clickable once one exists.
  2. The shared Audit tab didn't reflect new model_timeout_set/model_timeout_cleared events without a full page reload. state.recent_audit_events was only ever populated by the initial /admin/state fetch in load(); saving/clearing a timeout never refreshed it. Verified server-side the events were being recorded correctly (curl /admin/state showed them) while the Audit tab UI stayed on "Run a workflow to create your first audit event." Added refreshAuditEvents() (re-fetches /admin/state, re-renders the Audit table) called after a successful save/clear. Confirmed live: switching to Audit immediately after a Save now shows model_timeout_set with the correct detail, no reload needed.

Scoped this fix to the model-timeout panel's own save/clear actions only — the identical staleness exists for model_group save/delete too, but that's a pre-existing, wider pattern across the whole console (not something this PR introduced) and fixing it broadly is out of scope here.

Not fixed, noted as pre-existing and out of scope: the feedback <p role="status"> region (both mine and the pre-existing model_groups one) doesn't color-code success vs. error text — both render as plain black. Inherited from the existing model_groups pattern I followed; fixing it well would mean touching the shared feedback convention app-wide, not just this panel.

Full suite still green after both fixes (python3 -m pytest tests -q); tests/test_admin_contract.py and tests/test_model_timeouts.py re-verified passing (31/31).

@devin-ai-integration devin-ai-integration 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.

Devin Review found 2 new potential issues.

Devin Review

if (!response.ok) throw new Error(payload.error?.message || "Could not save model timeout");
els.modelTimeoutFeedback.textContent = t("model_timeout_saved");
await refreshModelTimeouts();
await refreshAuditEvents();

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.

🟡 Successful timeout changes appear failed

When refreshAuditEvents fails after a timeout update succeeds, the shared handler replaces the success message with an error. Operators can repeat an applied change.

Prompt for agents
Treat the audit-panel refresh as best-effort after a successful timeout mutation. In contextual_orchestrator/admin.py, saveModelTimeout and clearModelTimeout currently let refreshAuditEvents failures reject the whole action after the server has committed it. Preserve the success result and message when the follow-up /admin/state fetch or JSON parsing fails, while optionally showing a separate audit-refresh warning.
Devin Review

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

const response = await apiFetch("/admin/state");
if (!response.ok) return;
const payload = await response.json();
state.recent_audit_events = payload.recent_audit_events || [];

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.

🟡 Concurrent edits stale audit history

Overlapping timeout actions let refreshAuditEvents apply responses out of order. An older response can hide newer audit events until the next reload.

Prompt for agents
Prevent stale /admin/state responses from overwriting newer audit history in contextual_orchestrator/admin.py. Multiple saveModelTimeout or clearModelTimeout calls can overlap, and refreshAuditEvents currently applies every response. Serialize these refreshes, cancel superseded requests, or track a monotonically increasing request generation and apply only the newest response.
Devin Review

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

@seonghobae seonghobae added documentation Improvements or additions to documentation priority: medium Normal-priority or P2 work status: needs-review Open pull request requiring current-head review or checks type: feature New or expanded product capability labels Sep 2, 2026 — with ChatGPT Codex Connector

Copy link
Copy Markdown
Contributor Author

No-heuristics RCA / supersession: this PR is not safe to land under the current routing contract. Its core serving behavior makes an operator-authored per-model wall-clock value a test-time-compute allocation, and _validate_model_timeout_seconds() admits it through repository-authored 1s / 14400s bounds. The PR itself documents 14400 only by analogy to the historical Noema timeout; that precedent is not a mathematical/statistical model, authoritative allocation standard, or experimentally identified timeout policy. The canonical timeout owner is #971, which instead establishes the universal no-implicit-inference-timeout contract (ModelClient.timeout=None by default; explicit externally governed cancellation/deadline only) and is already carrying test-first repair machinery. This PR would reintroduce exactly the per-model timeout authority that #971 removes.

I also re-fetched the current review evidence before acting. The branch still has substantive implementation findings (local queue path ignores the override, passthrough/tool requests bypass it, failed persistence can leave the live timeout mutated, and admin refresh races can misreport/stale audit state). Those findings are not being suppressed or bypassed.

Closing/superseding this PR preserves the branch's independently useful admin/persistence/API exploration for a future evidence-backed allocator, but the current manual timeout-setting semantics must not become production authority. Do not delete the global/provider integration or reinterpret OPENAI_API_KEY injection here; this closure is solely about unsupported test-time-compute allocation. If a research-/standard-backed timeout allocator with executable provenance is later implemented, the UI/persistence work can be selectively reused behind that owner rather than reviving the 1/14400 rule.

@seonghobae seonghobae closed this Sep 2, 2026
@seonghobae

Copy link
Copy Markdown
Contributor Author

Repair-policy recheck of this closure (2026-09-02).

Per the org's repair-not-close policy ("close is reserved for: explicit user
instruction, no diff, a malicious change, or all valid delta verified as
inherited by a successor/merged PR"), this closure was rechecked to confirm
which of those four applies and whether any delta needs to move to a
successor PR.

File-level fact, independently re-verified against a fresh clone (main
8839081, #971 head 92ff90b, #1010 head 56a6e45):
#971 inherits
none of this PR's delta. git diff main...971 (53 files, +3328/-318) contains
zero case-insensitive occurrences of model_timeout anywhere in the diff.
admin.py is untouched by #971; its one api_contract.py line is an
unrelated provider_readiness summary-string edit; server.py's 53 changed
lines are DNS/cancellation plumbing, not /api/v1/model_timeouts routing;
orchestrator.py's 406 changed lines add cancellation/ZDR-pinning/provider-probe-timeout
removal, not MIN/MAX_MODEL_TIMEOUT_SECONDS, model_timeout_resolver, or any
TaskOrchestrator.*_model_timeout method. tests/test_model_timeouts.py and
docs/planning/adrs/0042-*.md do not exist on #971 at all. So #971 is not
a file-level successor to this PR, and the "all valid delta verified as
inherited by a successor" branch of the policy does not apply here.

But the closure is still valid, independent of that branch, because it
satisfies the policy's separate "explicit user instruction" ground.
This
was not an agent's unverified "looks superseded" inference — the repo owner
(closed_by: seonghobae, account type User) personally reviewed and closed
this PR the same day with a first-person, evidence-based rationale that goes
beyond the single MIN/MAX_MODEL_TIMEOUT_SECONDS bound:

the current manual timeout-setting semantics must not become production
authority ... The branch still has substantive implementation findings
(local queue path ignores the override, passthrough/tool requests bypass
it, failed persistence can leave the live timeout mutated, and admin
refresh races can misreport/stale audit state) ... If a research-/standard-backed
timeout allocator with executable provenance is later implemented, the
UI/persistence work can be selectively reused behind that owner rather than
reviving the 1/14400 rule.

That is a categorical objection to this PR's live-enforcement wiring becoming
production authority for outbound-call allocation, plus four distinct
unresolved correctness findings in that wiring — not only a complaint about
the specific 14400s ceiling's derivation. Reopening a new PR now that ports
model_timeout_resolver back into ModelClient.chat/stream_chat, even with
a relabeled bound, would reintroduce exactly the mechanism this closure
rejected and would still carry the four unresolved findings above, on the
same day the owner explicitly ruled against it. No successor PR is being
opened for that reason.

Delta is preserved, not orphaned. Nothing here is deleted: this PR's two
commits (523867fa "per-model LLM timeout view/set/clear/restore", 56a6e45f
"disabled state + live audit refresh") remain fully intact on this closed
PR's branch, and the closer's own comment already records the exact
reuse condition — selective reuse of the admin/persistence/API exploration
"behind" a future research-/standard-backed timeout allocator (e.g. once
#971's no-implicit-timeout policy, or a successor to it, actually lands and
establishes what a bounded override should mean). That is this PR's
traceable successor path; it is a future, gated action, not one due today.

Recorded in ContextualWisdomLab/.github
docs/product-technical-gap-baseline.md,
2026-09-02 entry.

🤖 Generated with Claude Code

seonghobae added a commit to ContextualWisdomLab/.github that referenced this pull request Sep 3, 2026
…yvault) (#1675)

* docs(adr): record ecosystem admin-web architecture (Keyverse SSO + Keyvault)

Cross-repo research pass (owner request: "관리자 웹 개발 (noema,
contextual-orchestrator, keyverse) 및 상호 연계 준비") across all three
named repos, cloned fresh -- not assumed -- before any design work.

Records: Keyverse as the shared SSO provider for every admin web (design
only, not yet wired); each repo's admin web as a thin frontend over its
own backend (no shared cross-repo frontend package, matching
contextual-orchestrator's own ADR 0033 reasoning); the Keyverse-as-Keyvault
bounded-context decision and why service ABAC/RBAC and "login credential
store" are NOT rebuilt from scratch (PR #103 already covers the former;
the latter is Keyvault + per-service Anti-Corruption Layers, not a new
module); and why noema got no code change this iteration (no
admin-relevant HTTP surface exists yet to build a console on).

Points to the two implemented slices from this same pass:
ContextualWisdomLab/contextual-orchestrator#1010 (per-model LLM timeout
admin surface, closing docs/product-goal-directive.md §8) and
ContextualWisdomLab/keyverse#129 (Keyvault: namespaced encrypted-at-rest
secrets store, plus ADRs 0014-0016 for the three-capability Keyverse
research).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs(adr-0021): correct stale claim that contextual-orchestrator#1010 shipped

PR #1010 (the ADR's decision item 6, the timeout-admin-surface slice) was
opened at 03:40:12Z, this ADR PR at 03:40:12Z, and #1010 was subsequently
closed unmerged by the repo owner at 05:10:46Z the same day on a categorical
objection to its live-enforcement wiring becoming production authority, plus
four distinct unresolved correctness findings -- already repair-policy
rechecked and confirmed a valid closure with delta preserved, not orphaned.

Adds an Update section rather than rewriting the original decision record, so
the ADR doesn't merge into main citing a closed PR as an implemented slice.
Decisions 1-5 (SSO/Keyvault/ABAC-RBAC/credential-store shape) are unaffected;
only item 6's implementation claim was stale.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(adr): renumber ADR-0021 to ADR-0026 to resolve a numbering collision

docs/adr/0021-hourly-review-repair-single-file-consolidation.md landed on
main after this PR branched, so this ADR's own "0021 is the next free
number" claim went stale. 0026 is the next free number after the current
highest (0025, the CodeQL dispatch ADR). Renamed the file and updated its
own title heading; no other file in the repo references the old number
or filename.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
seonghobae added a commit that referenced this pull request Sep 3, 2026
Resolves the admin.py conflict between this branch's own audit-refresh
fix (item 25: re-fetch /admin/state after a model_groups save/delete,
matching PR #1010's fix for the sibling model-timeouts panel) and
main's independently-landed, more complete fix for the exact same bug.

Verified main's refreshModelGroupViews()/refreshAuditEvents() is a
strict superset of this branch's own fix: it refreshes both model
groups and audit events with per-call try/catch and a user-visible
warning on partial failure (this branch's sequential-await version had
no such handling -- a refresh failure would have overwritten the
"saved successfully" message with a misleading error), and it also
fixes the color-coding gap this branch's own PR description explicitly
left as a separate unfixed item (main sets .style.color; this branch's
CSS-class-based setModelGroupFeedback() helper was a working
alternative, but tests/test_admin_contract.py's executable Node
behavioral test asserts els.modelGroupFeedback.style.color directly,
so main's mechanism is what's actually load-bearing here).

Kept main's implementation for all three admin.py conflict blocks,
removed the now-dead setModelGroupFeedback() helper and its
.feedback-success/.feedback-error CSS rules (zero remaining callers),
and updated the 9 substring assertions in the non-behavioral half of
test_admin_contract.py's test_admin_surface_exists_for_enterprise_operations
that referenced the removed helper/classes to assert main's actual
mechanism instead. The deeper, executable
test_model_group_mutations_refresh_audit_events (unconflicted, already
covers the exact behavior) needed no changes.

Full suite: 3391 passed, 1 skipped. admin.py 100% coverage,
interrogate 100%.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation priority: medium Normal-priority or P2 work status: needs-review Open pull request requiring current-head review or checks type: feature New or expanded product capability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant