Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
ecc93ac
feat(routing): persist bounded group observations
seonghobae Aug 28, 2026
9129931
fix(routing): preserve failure ledger updates
seonghobae Aug 28, 2026
f0f1f29
fix(routing): bound shared observation refreshes
seonghobae Aug 28, 2026
dda44c8
fix(routing): preserve completed streams on telemetry errors
seonghobae Aug 28, 2026
5450ff4
docs(gap): preserve historical routing snapshots
seonghobae Aug 28, 2026
b99b599
fix(routing): serialize durable observation state
seonghobae Aug 28, 2026
2bd7d33
fix(routing): preserve provider failure causes
seonghobae Aug 28, 2026
4094a9c
fix(routing): bound observation lock scope
seonghobae Aug 28, 2026
b6ad17c
fix(routing): preserve quality failover
seonghobae Aug 28, 2026
b9c3f58
chore: run one-shot conflict resolver for PR #911
seonghobae Aug 29, 2026
d703d9d
chore: rerun conflict diagnostics for PR #911
seonghobae Aug 29, 2026
1e7c59c
chore: semantically resolve PR #911 conflicts
seonghobae Aug 29, 2026
5635c5e
chore: merge protected main into PR #911
github-actions[bot] Aug 29, 2026
0f95650
chore: request protected checks for PR #911
seonghobae Aug 29, 2026
4393d97
chore: finalize protected check trigger for PR #911
seonghobae Aug 29, 2026
4f0a143
chore: refresh PR #911 against current protected main
seonghobae Aug 29, 2026
da0b18e
chore: merge protected main into PR #911
github-actions[bot] Aug 29, 2026
1e14b2f
fix(pr911): serialize routing observation refresh and renumber ADR
seonghobae Aug 29, 2026
1e7a05e
fix(routing): isolate durable observations by member context
seonghobae Aug 30, 2026
1e2db85
chore: merge protected main into PR #911; fix quality-ledger test fake
claude Aug 30, 2026
52fff51
chore: merge concurrent PR #911 head update (context-key isolation fix)
claude Aug 30, 2026
b140eda
fix(responses): don't let blank seed/top_logprobs force provider-only…
claude Aug 30, 2026
f5c7acc
fix(errors): redact upstream diagnostics at the shared boundary
seonghobae Aug 30, 2026
e6183fb
Merge remote-tracking branch 'origin/main' into pr-911-conflict-resolve
claude Aug 30, 2026
53d0d38
Merge remote-tracking branch 'origin/main' into pr-911-refresh
claude Aug 30, 2026
5438b31
Merge remote-tracking branch 'origin/main' into pr-911-refresh2
claude Aug 30, 2026
a5257e5
fix(routing): preserve completion ordering in durable observations
seonghobae Aug 30, 2026
6afe07e
Merge remote-tracking branch 'origin/main' into feat/durable-routing-…
claude Aug 30, 2026
bc97efe
fix(routing): retain shared observation windows across peers
seonghobae Aug 30, 2026
6c7dc4f
fix(routing): keep shared retention monotonic
seonghobae Aug 30, 2026
64143e0
fix(routing): tolerate removed members in durable observations
seonghobae Aug 30, 2026
52518bd
fix(routing): bind durable observations to current agent context
seonghobae Aug 30, 2026
5ddedc1
fix(routing): ignore late observations for removed members
seonghobae Aug 30, 2026
f4ce7ab
fix(review): close remaining pr911 follow-up findings
seonghobae Aug 31, 2026
75def49
fix(routing): fence late observation updates
seonghobae Aug 31, 2026
62884e7
Merge origin/main into feat/durable-routing-observations-20260829
seonghobae Aug 31, 2026
66496dc
fix: isolate durable routing observations
seonghobae Aug 31, 2026
3262f0b
fix: expire stale routing window registrations
seonghobae Aug 31, 2026
728aa52
fix: keep idle routing registrations alive
seonghobae Aug 31, 2026
5d43fa0
fix(routing): bind retention heartbeat to owner lifecycle
seonghobae Aug 31, 2026
ba3b299
Merge main into feat/durable-routing-observations-20260829
seonghobae Sep 3, 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
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,10 @@ and this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html)

### Added

- An opt-in, time-window-only SQLite observation store for measured model-group
routing, configured with `--routing-observation-window-seconds` alongside
`--state-db`; it shares completed attempt evidence across gateway processes
without inventing decay or cross-model equivalence (ADR 0042).
- Verbose/debug logging (ADR 0005): a new stdlib-only `debug_logging.py`
module, a `--log-level {DEBUG,INFO,WARNING,ERROR,CRITICAL}` CLI flag with a
`--verbose`/`--debug` shorthand (default unchanged: `WARNING`), and new
Expand Down Expand Up @@ -982,6 +986,18 @@ and this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html)

### Fixed

- A blank-string `seed` or `top_logprobs` on `/v1/responses` no longer forces
the provider-only (non-streamed) execution path for `orchestrator/auto` /
`orchestrator/free`. Both omit-equivalence checks now pop the field instead
of leaving the raw blank string behind, matching the existing Chat
Completions convention and keeping the streamed-orchestration decision
(`_responses_virtual_requires_provider_path`) honest about what was
actually supplied.
- The opt-in durable routing-observation store now prunes rows by the shared
database's largest registered routing-observation window, not by whichever
writer happens to have the shortest local window. Mixed-window gateway
processes therefore keep physically bounded storage without erasing active
evidence required by a longer-window peer.
- `TaskOrchestrator._invoke`'s route/Conduct primary chat call now classifies
a `ProviderUpstreamError` (5xx, 429, network) directly from its own
already-computed `retryable` flag (`tool_fallback.classify_provider_transport_failure`)
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ HTTP serving is hardened for local lab use:
- `/healthz` is a minimal unauthenticated process probe; use the administrator-authenticated `/readyz` endpoint for secret-free orchestration, sync-routing, and optional batch dependency status. Liveness stays available during optional dependency degradation.
- Full orchestration traces are not returned by default. Set `include_orchestration_trace: true` per standard Chat request or start with `--expose-trace-by-default` when the caller is trusted. Requests that also use `tools` or `response_format` still fail with `unsupported_trace_disclosure`; remove the trace flag for structured or tool requests.
- State is in-memory by default. Pass `--state-db PATH` (or `CONTEXTUAL_ORCHESTRATOR_STATE_DB`) to persist workflow runs, evaluation runs, audit, and analytics to a stdlib sqlite file so they survive a restart; without it, behavior is unchanged.
- Model-group routing observations remain process-local unless an operator explicitly passes `--routing-observation-window-seconds SECONDS` with `--state-db PATH`. That opt-in SQLite store shares only completed success/failure and measured latency/token observations inside the selected time window; it applies no decay or cross-model quality weighting. Replay remains per-router by its configured window, while transactional pruning uses the shared database's largest registered routing-observation window so a short-window process does not delete evidence still required by a longer-window peer. Non-stream success-observation persistence errors fail closed; provider-failure recording preserves the original failure and logs the durable-evidence outage. If a stream has already emitted provider bytes, a write failure is logged and cannot change the completed response.
- Response caching is off by default. Pass `--cache-ttl SECONDS` to serve identical requests (same messages + mode) from an in-memory TTL+LRU cache and skip the provider calls; `0` disables it.
- `ModelClient.batch_chat(agent, {custom_id: messages})` runs many requests through the provider's Batch API (async, 24h completion window, typically ~50% cheaper) — suited to evaluation/benchmark workloads, not latency-sensitive chat. The mock path answers synchronously.

Expand Down
8 changes: 8 additions & 0 deletions contextual_orchestrator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@
ResponseCacheProvider,
build_response_cache_key,
)
from .routing_observation_store import (
RoutingObservation,
RoutingObservationStore,
SqliteRoutingObservationStore,
)
from .tool_fallback import (
MAX_TOOL_RETRY_ATTEMPTS,
ToolExecutionError,
Expand Down Expand Up @@ -149,6 +154,9 @@
"ResponseCacheProvider",
"RedisResponseCacheProvider",
"build_response_cache_key",
"RoutingObservation",
"RoutingObservationStore",
"SqliteRoutingObservationStore",
# routing / batch
"RoutingPolicy",
"RoutingHints",
Expand Down
9 changes: 9 additions & 0 deletions contextual_orchestrator/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -945,6 +945,12 @@ def main(argv: list[str] | None = None) -> None:
parser.add_argument("--agents", default="examples/agents.mock.json", help="Agent config JSON.")
parser.add_argument("--state-db", default=os.environ.get("CONTEXTUAL_ORCHESTRATOR_STATE_DB", "") or None,
help="Optional sqlite path to persist runs/audit/analytics across restarts (default: in-memory).")
parser.add_argument(
"--routing-observation-window-seconds",
type=_positive_int,
default=None,
help="Persist measured routing observations in state-db for this explicit time window; decay is not applied.",
)
parser.add_argument("--mode", choices=["auto", "route", "conduct"], default="auto")
parser.add_argument("--serve", action="store_true", help="Run the chat completions HTTP server.")
parser.add_argument(
Expand Down Expand Up @@ -1056,6 +1062,8 @@ def main(argv: list[str] | None = None) -> None:
)
_add_log_level_arguments(parser)
args = parser.parse_args(arguments)
if args.routing_observation_window_seconds is not None and not args.state_db:
parser.error("--routing-observation-window-seconds requires --state-db")

client = ModelClient(
ca_bundle=args.provider_ca_bundle,
Expand All @@ -1069,6 +1077,7 @@ def main(argv: list[str] | None = None) -> None:
load_agents(args.agents),
client=client,
state_db=args.state_db,
routing_observation_window_seconds=args.routing_observation_window_seconds,
agents_db=args.agents_db,
budget_max_output_tokens=args.budget_max_output_tokens,
budget_max_cost_usd=args.budget_max_cost_usd,
Expand Down
Loading
Loading