Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
35a428c
fix(gateway): remove implicit model request timeout
seonghobae Sep 4, 2026
60c04f9
Merge remote-tracking branch 'origin/main' into codex/goal-39-unbound…
seonghobae Sep 4, 2026
a60ed89
Merge remote-tracking branch 'origin/main' into codex/goal-39-unbound…
seonghobae Sep 4, 2026
28082a1
Merge branch 'main' into codex/goal-39-unbounded-model-timeout
seonghobae Sep 4, 2026
284447f
fix(embeddings): preserve unbounded completion wait
seonghobae Sep 4, 2026
661ce8d
chore(gateway): integrate protected main into timeout repair
seonghobae Sep 6, 2026
35465a5
test(gateway): require durable model timeout policy
seonghobae Sep 6, 2026
c276fce
feat(gateway): persist administrator model timeout policy
seonghobae Sep 6, 2026
94c6856
test(gateway): cover timeout migration and numeric storage
seonghobae Sep 6, 2026
dac678c
test(gateway): prove persisted timeout rows rather than seed replay
seonghobae Sep 6, 2026
439da2e
fix(gateway): normalize timeout seconds before durable binding
seonghobae Sep 6, 2026
6236e98
test(gateway): reject unaudited model timeout mutation
seonghobae Sep 6, 2026
32b0199
docs(gateway): track timeout policy and audit acceptance gaps
seonghobae Sep 6, 2026
197e888
test(gateway): require transactional timeout history
seonghobae Sep 6, 2026
4e839ce
fix(gateway): commit timeout policy and history atomically
seonghobae Sep 6, 2026
c387943
test(gateway): cover timeout rollback and stale policy writers
seonghobae Sep 6, 2026
cb24fa9
docs(gateway): record transactional timeout evidence and remaining gates
seonghobae Sep 6, 2026
ef76ade
test(gateway): reject stale timeout policy after value restoration
seonghobae Sep 6, 2026
d911a38
fix(gateway): compare durable timeout policy revisions
seonghobae Sep 6, 2026
bceaeb2
test(gateway): reject mixed timeout value and revision snapshots
seonghobae Sep 6, 2026
e5e9c96
fix(gateway): read model configuration from one database snapshot
seonghobae Sep 6, 2026
5765307
docs(gateway): record timeout revision and snapshot proofs
seonghobae Sep 6, 2026
cbab94c
test(gateway): retain authenticated principal in timeout history
seonghobae Sep 6, 2026
e0dc420
test(gateway): authorize the timeout history actor fixture
seonghobae Sep 6, 2026
c1b372d
test(gateway): supply caller address to actor authorization
seonghobae Sep 6, 2026
c08a5fd
feat(gateway): retain opaque timeout policy actor evidence
seonghobae Sep 6, 2026
fc23402
test(gateway): validate timeout actor migration and secret boundaries
seonghobae Sep 6, 2026
9c01b57
docs(gateway): distinguish timeout actor evidence from HTTP authoriza…
seonghobae Sep 6, 2026
2919652
test(gateway): require revision-bound timeout restoration
seonghobae Sep 6, 2026
37bca9c
feat(gateway): restore model timeout as a revision-checked change
seonghobae Sep 6, 2026
62ba3c3
test(gateway): cover timeout restore ownership and rollback
seonghobae Sep 6, 2026
6aece30
docs(gateway): record revision-bound timeout restore evidence
seonghobae Sep 6, 2026
e0eab78
test(gateway): reject stale timeout in ordinary pool edits
seonghobae Sep 6, 2026
9701dec
fix(gateway): guard timeout policy in every durable pool save
seonghobae Sep 6, 2026
28313ef
docs(gateway): record ordinary save policy protection
seonghobae Sep 6, 2026
1edf0fb
test(gateway): preserve serving state when pool edits fail
seonghobae Sep 6, 2026
befe04c
fix(gateway): publish pool edits only after durable saves
seonghobae Sep 6, 2026
8a412b8
docs(gateway): record rejected edit publication evidence
seonghobae Sep 6, 2026
ad337e1
test(gateway): require rollback on late pool batch conflicts
seonghobae Sep 6, 2026
36fc35d
fix(gateway): commit pool batch changes in one transaction
seonghobae Sep 6, 2026
81145d4
docs(gateway): record atomic pool batch evidence
seonghobae Sep 6, 2026
a2951f6
test(gateway): verify timeout conflict on authenticated HTTP edits
seonghobae Sep 6, 2026
3612ab9
docs(gateway): distinguish HTTP conflict checks from timeout activation
seonghobae Sep 6, 2026
e794ed3
docs(gateway): link canonical runtime acceptance request
seonghobae Sep 6, 2026
b80e64b
test(admin): require durable timeout policy read without runtime acti…
seonghobae Sep 6, 2026
2e12ed4
feat(admin): read durable timeout policy without activating execution…
seonghobae Sep 6, 2026
6774dab
test(admin): bind timeout policy reads to admin scope and OpenAPI
seonghobae Sep 6, 2026
b0d1fda
docs(admin): distinguish policy visibility from runtime timeout enfor…
seonghobae Sep 6, 2026
80b3aa3
test(admin): require model-scoped stable timeout history pagination
seonghobae Sep 6, 2026
5dc69bc
feat(admin): expose bounded model timeout audit history
seonghobae Sep 6, 2026
f9505a5
test(admin): verify timeout history HTTP bounds and document schema
seonghobae Sep 6, 2026
b128834
docs(admin): record bounded read-only timeout audit evidence
seonghobae Sep 6, 2026
10225f4
test(server): require concurrent error log response correlation
seonghobae Sep 6, 2026
4b73933
test(server): inject generic handler failure after auth boundary
seonghobae Sep 6, 2026
0e7c03b
fix(server): correlate sanitized error logs with response IDs
seonghobae Sep 6, 2026
8c20f1e
test(server): keep untrusted error details out of correlation logs
seonghobae Sep 6, 2026
443aa5f
docs(server): record Strix timeout and correlation regression evidence
seonghobae Sep 6, 2026
4b1c108
test(server): reproduce SDK replay of terminal tool errors
seonghobae Sep 6, 2026
fc94faa
fix(server): prevent SDK replay of explicit tool stops
seonghobae Sep 6, 2026
5c82b9c
test(server): verify tool stop header without optional SDK
seonghobae Sep 6, 2026
ea5555d
docs(server): record explicit tool stop SDK retry contract
seonghobae Sep 6, 2026
08e5193
test(gateway): require typed nonreplayable transport outcomes
seonghobae Sep 6, 2026
260767d
test(server): prevent SDK replay of unknown provider outcomes
seonghobae Sep 6, 2026
fe1e85a
fix(gateway): surface unknown outcomes without enabling replay
seonghobae Sep 6, 2026
76d1caa
test(gateway): verify SDK to HTTP unknown outcome boundary
seonghobae Sep 6, 2026
4ceafa0
docs(gateway): record unknown outcome no-replay evidence
seonghobae Sep 6, 2026
a35a3c6
test(logging): require bounded upstream status evidence
seonghobae Sep 6, 2026
b20f994
fix(logging): retain numeric provider status without raw diagnostics
seonghobae Sep 6, 2026
0b949aa
test(logging): preserve typed status and avoid exception stringification
seonghobae Sep 6, 2026
73f8977
docs(logging): record bounded upstream status evidence
seonghobae Sep 6, 2026
b81739f
test(logging): require omission rather than partial diagnostic masking
seonghobae Sep 6, 2026
4316515
docs(logging): record full regression and canonical sanitizer prerequ…
seonghobae Sep 6, 2026
e1ff51b
test(timeout): preserve policy across unrelated writes
seonghobae Sep 7, 2026
b2a38b4
fix(timeout): isolate policy writes from pool updates
seonghobae Sep 7, 2026
9179873
test(timeout): verify isolated writes and disabled tombstones
seonghobae Sep 7, 2026
f50755b
style(tests): clean timeout integration checks
seonghobae Sep 7, 2026
78a71c0
fix(router): distinguish pre-send admission timeouts
seonghobae Sep 7, 2026
1ccc959
test(sdk): require pinned integration probes in locked CI
seonghobae Sep 7, 2026
a95d3c8
fix(audit): record timeout policy changes and history access
seonghobae Sep 7, 2026
7685d3f
fix(admin): make model timeout audit entries readable
seonghobae Sep 7, 2026
2b189ad
fix(gateway): apply per-model timeout on the serving path
seonghobae Sep 7, 2026
76c0475
test(local): accept per-model timeout on waiter mock
seonghobae Sep 7, 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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ and this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html)

### Fixed

- Model, Agent, gateway, and structured-output repair requests now default to
no application timeout. Explicit probe, discovery, benchmark, and operator
limits remain bounded.
- Workflow workers now preserve the caller message array exactly once, while
the added envelope carries only the subtask and Conductor-style prior-step
access list instead of duplicating the task or source attachments.
Expand Down
5 changes: 4 additions & 1 deletion contextual_orchestrator/cost_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,10 @@ def _run_embedding_shard(

def _provider_embedding_backend(self) -> ProviderEmbeddingBatchBackend:
client = getattr(self.orchestrator, "client", None)
client_timeout = float(getattr(client, "timeout", 0))
configured_timeout = getattr(client, "timeout", None)
client_timeout = (
float(configured_timeout) if configured_timeout is not None else 0.0
Comment thread
seonghobae marked this conversation as resolved.
)
return ProviderEmbeddingBatchBackend(
self._run_provider_embeddings,
job_registry=self.job_registry,
Expand Down
12 changes: 8 additions & 4 deletions contextual_orchestrator/endpoint_race.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def race_first_valid(
attempts: list[EndpointAttempt[T]],
*,
validate: Callable[[T], bool],
deadline_seconds: float,
deadline_seconds: float | None,
max_concurrency: int,
on_attempt_complete: Callable[[str, T | None, BaseException | None], None] | None = None,
) -> RaceOutcome[T]:
Expand All @@ -94,7 +94,7 @@ def race_first_valid(
raise ValueError("immediate_race requires concurrency capacity of at least two")
if max_concurrency < len(attempts):
raise ValueError("immediate_race capacity must cover every declared endpoint")
if deadline_seconds <= 0:
if deadline_seconds is not None and deadline_seconds <= 0:
raise ValueError("deadline_seconds must be positive")
contract = attempts[0].contract
if any(attempt.contract != contract for attempt in attempts[1:]):
Expand Down Expand Up @@ -128,8 +128,12 @@ def execute(attempt: EndpointAttempt[T]) -> T:
last_error: BaseException | None = None
try:
while pending:
remaining = deadline_seconds - (time.monotonic() - started)
if remaining <= 0:
remaining = (
None
if deadline_seconds is None
else deadline_seconds - (time.monotonic() - started)
)
if remaining is not None and remaining <= 0:
raise TimeoutError("equivalent endpoint race exceeded its deadline")
done, pending = wait(pending, timeout=remaining, return_when=FIRST_COMPLETED)
if not done:
Expand Down
10 changes: 5 additions & 5 deletions contextual_orchestrator/orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -1030,15 +1030,15 @@ def _local_provider_state(base_url: str) -> _LocalProviderState:
def _local_provider_slot(
agent: ModelAgent,
capacity: int,
timeout: float,
timeout: float | None,
):
"""Bound local requests and serialize model switches on a shared endpoint."""
if not _is_local_provider_url(agent.base_url):
yield
return

state = _local_provider_state(agent.base_url)
deadline = time.monotonic() + max(float(timeout), 0.0)
deadline = None if timeout is None else time.monotonic() + max(float(timeout), 0.0)
with state.condition:
while True:
if state.active == 0:
Expand All @@ -1051,8 +1051,8 @@ def _local_provider_slot(
state.active += 1
break

remaining = deadline - time.monotonic()
if remaining <= 0:
remaining = None if deadline is None else deadline - time.monotonic()
if remaining is not None and remaining <= 0:
raise TimeoutError("local provider endpoint is busy past its request deadline")
state.condition.wait(remaining)

Expand Down Expand Up @@ -1693,7 +1693,7 @@ class ModelClient:

def __init__(
self,
timeout: int = 90,
timeout: float | None = None,
max_output_tokens: int = 2048,
max_retries: int = 2,
local_max_retries: int = 0,
Expand Down
19 changes: 13 additions & 6 deletions contextual_orchestrator/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -7286,25 +7286,32 @@ def register_video_job(agent: ModelAgent, provider_result: dict[str, Any]) -> di
if not attribution.get("service"):
attribution["service"] = "embeddings_api"
started_at = time.perf_counter()
embedding_deadline = time.monotonic() + float(
orchestrator.client.timeout
configured_timeout = orchestrator.client.timeout
embedding_deadline = (
Comment on lines 7312 to +7316

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Honor the selected model timeout for embeddings

When the server is built with a finite ModelClient.timeout and an embedding agent has a larger administrator-configured model_timeout_seconds, this deadline is still derived from the client-wide fallback. complete_embeddings_batch is consequently cancelled when the shared client timeout expires even though the selected model's transport policy permits more time, so a legacy 90-second client setting defeats a per-model 2-hour override and applies the same cap across every member. Resolve the timeout from each selected embedding agent rather than constructing the synchronous deadline from orchestrator.client.timeout.

Useful? React with 👍 / 👎.

None
if configured_timeout is None
else time.monotonic() + float(configured_timeout)
)
document = None
last_embedding_error: Exception | None = None
for embedding_agent in embedding_agents:
remaining_timeout = embedding_deadline - time.monotonic()
if remaining_timeout <= 0:
remaining_timeout = (
None
if embedding_deadline is None
else embedding_deadline - time.monotonic()
)
if remaining_timeout is not None and remaining_timeout <= 0:
break
attempt_started_at = time.perf_counter()
try:
document = self._run(lambda agent=embedding_agent: coordinator.complete_embeddings_batch(
document = self._run(lambda agent=embedding_agent, wait_timeout=remaining_timeout: coordinator.complete_embeddings_batch(
inputs,
model=agent.model,
attribution=attribution,
metadata={"actor_scope": "inference", "endpoint_alias": "embeddings"},
zdr_only=zdr_only,
agent_id=agent.id,
wait_timeout=remaining_timeout,
wait_timeout=wait_timeout,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
owner_id=security.principal_id(self.headers),
))
except Exception as exc: # noqa: BLE001 - measured member failover
Expand Down
16 changes: 16 additions & 0 deletions tests/test_orchestrator_client_boundaries.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,22 @@ def test_batch_results_must_be_a_mapping() -> None:
# -- local provider slot concurrency ------------------------------------------


def test_default_model_timeout_is_unbounded() -> None:
"""Model and repair requests inherit no application wall-clock cap."""
assert ModelClient().timeout is None


def test_local_slot_accepts_unbounded_waits() -> None:
"""The local-agent coordinator preserves the shared unbounded default."""
agent = ModelAgent(
id="unbounded_slot_agent",
model="unbounded-slot-model",
base_url="local://127.0.0.1:59343/v1",
)
with _local_provider_slot(agent, 1, None):
pass


def test_slot_shrinks_capacity_for_same_model_and_resets_when_empty() -> None:
"""Concurrent same-model holders shrink capacity; last release resets."""
url = "local://127.0.0.1:59341/v1"
Expand Down
14 changes: 14 additions & 0 deletions tests/test_provider_embedding_batch_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,20 @@ def count_text(self, text, model):
return len(text.split())


def test_default_client_keeps_batch_lifecycle_separate_from_model_timeout() -> None:
"""A null model timeout does not break the existing batch-retention boundary."""
coordinator = CostRoutingCoordinator(
TaskOrchestrator([], allow_empty_agents=True),
embedding_token_counter=_SyntheticExactCounter(),
)

backend = coordinator._provider_embedding_backend()

assert backend._execution_timeout_seconds == 604_800
assert backend._claim_lease_seconds is None
backend.close()


def test_unknown_tokenizer_uses_authoritative_provider_usage() -> None:
"""A byte-safe request completes only after the provider supplies exact usage."""
agent = ModelAgent(
Expand Down
Loading