Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
8737f58
fix(server): correlate error responses with safe request logs
seonghobae Sep 8, 2026
217b21f
test(server): assert exact safe error correlation identity
seonghobae Sep 8, 2026
2cb6db5
test(server): verify error correlation over real HTTP
seonghobae Sep 8, 2026
d7bba88
fix(persistence): roll back failed state replacements
seonghobae Sep 9, 2026
1beb72c
docs(persistence): record state-write rollback reproduction
seonghobae Sep 9, 2026
aa67418
docs: separate atomicity and release-cycle guidance
seonghobae Sep 9, 2026
f1abe1e
test(persistence): verify rollback survives reopening
seonghobae Sep 9, 2026
877d511
docs(gap): track persistence fix and skipped review evidence
seonghobae Sep 9, 2026
716e012
test(persistence): cover deferred commit rollback
seonghobae Sep 9, 2026
4316be8
docs(persistence): record full-suite and commit-failure evidence
seonghobae Sep 9, 2026
f7f0556
test(telemetry): reproduce missing provider request correlation
seonghobae Sep 9, 2026
c7468ec
fix(telemetry): bind provider attempts to HTTP request identity
seonghobae Sep 9, 2026
8b82235
fix(telemetry): correlate retry and terminal provider diagnostics
seonghobae Sep 9, 2026
7b7b320
docs(telemetry): record correlation evidence and collector boundary
seonghobae Sep 9, 2026
df39ff8
fix(telemetry): identify successful HTTP request summaries
seonghobae Sep 9, 2026
130a049
test(telemetry): prove identity rotation on a reused socket
seonghobae Sep 9, 2026
467353c
test(telemetry): isolate overlapping same-session HTTP requests
seonghobae Sep 9, 2026
6b24fe9
docs(telemetry): distinguish second 429 incident from timeout
seonghobae Sep 9, 2026
7cb97ec
docs(telemetry): record combined HTTP and isolation verification
seonghobae Sep 9, 2026
f467863
docs: record exact-revision collector contract evidence
seonghobae Sep 9, 2026
f588ca8
docs: record completed suite and bounded visual inspection
seonghobae Sep 9, 2026
f30fa78
docs: reconcile diagnostic gap with exact-head evidence
seonghobae Sep 9, 2026
89ccb04
docs: verify success-summary collector contract
seonghobae Sep 9, 2026
7744468
docs: retain integrated suite failure evidence
seonghobae Sep 9, 2026
01db64a
docs: distinguish isolated retry from full-suite verification
seonghobae Sep 9, 2026
b655fe1
docs(gap): reconcile verified success-summary contract
seonghobae Sep 9, 2026
fbb933c
merge: retain rollback repair with current KPI evidence base
seonghobae Sep 9, 2026
85a0b2d
test: require durable initial decision receipt over HTTP
seonghobae Sep 9, 2026
c11df64
merge: retain rollback delta and activate stacked quality checks
seonghobae Sep 9, 2026
129a665
Merge remote-tracking branch 'origin/autoresearch/20260909-kpi-loop' …
seonghobae Sep 9, 2026
814b9ea
feat: connect opt-in Rust decision receipt to HTTP routing candidate
seonghobae Sep 9, 2026
920df5a
test: require persisted admission before chat and SSE dispatch
seonghobae Sep 9, 2026
df6eb4a
feat: retain admission denominator and expose incomplete receipt joins
seonghobae Sep 9, 2026
01ce903
test: verify race receipt identity and incomplete admission evidence
seonghobae Sep 9, 2026
aeb66e9
fix: preserve one admission across embedding failover and bound recei…
seonghobae Sep 9, 2026
07957ee
Merge branch 'codex/state-save-rollback-20260909' into codex/decision…
seonghobae Sep 9, 2026
6e4f87a
fix: index receipt cohorts and distinguish repeated race decisions
seonghobae Sep 9, 2026
fe2db40
Merge branch 'codex/error-request-correlation-20260908' into codex/de…
seonghobae Sep 9, 2026
2bd3fcd
docs: record indexed receipt and identity integration evidence
seonghobae Sep 9, 2026
05b512e
Merge commit '0ef13edc' into codex/decision-latency-receipts-20260909
seonghobae Sep 9, 2026
8c4edbc
feat: separate auxiliary provider timing from task decisions
seonghobae Sep 9, 2026
0e6cd58
fix: export bounded auxiliary timing diagnostics
seonghobae Sep 9, 2026
21d1e88
fix: retain cache-hit admissions without provider timings
seonghobae Sep 9, 2026
3478b90
test: cover diagnostic cohort export cap boundaries
seonghobae Sep 9, 2026
6ddddb8
fix: keep generated planning inside task decision interval
seonghobae Sep 9, 2026
9707a5e
fix: distinguish post-decision evidence embedding observations
seonghobae Sep 9, 2026
bbe7eae
build: verify locked native receipts before CI acceptance
seonghobae Sep 9, 2026
0b10b55
fix: preserve unmeasured streaming handler compatibility
seonghobae Sep 9, 2026
daa5e36
fix: preserve trusted request identity in typed SSE failures
seonghobae Sep 9, 2026
bafbaf9
fix: admit streaming triage and retain typed selection failures
seonghobae Sep 9, 2026
2f3a086
Merge commit 'daa5e3636342644c49a037e705e2ecf85e642c71' into codex/de…
seonghobae Sep 9, 2026
3b6dd47
fix: finalize admitted request errors without erasing acknowledgements
seonghobae Sep 9, 2026
c734567
fix: reserve chat streaming capacity before classification
seonghobae Sep 9, 2026
6b40db4
fix: retain durable workflow request origin identity
seonghobae Sep 9, 2026
4cf7fea
docs: record exact workflow linkage reproduction environment
seonghobae Sep 9, 2026
af8d732
docs: repair locked full-suite environment prerequisite
seonghobae Sep 9, 2026
387aa21
test: reproduce missing durable batch request lineage
seonghobae Sep 9, 2026
9b7f491
docs: record deferred batch lineage red evidence
seonghobae Sep 9, 2026
919c945
test: require explicit batch lineage write failure outcome
seonghobae Sep 9, 2026
9356ac6
fix: retain durable batch submission identity associations
seonghobae Sep 9, 2026
b565173
test: preserve repeated batch origins and standalone compatibility
seonghobae Sep 9, 2026
b5baf42
test: reject redundant remote batch registry write
seonghobae Sep 9, 2026
6223962
fix: avoid second remote registry write for lineage status
seonghobae Sep 9, 2026
f61f206
docs: qualify batch lineage event and failure evidence
seonghobae Sep 9, 2026
3773f0d
test: distinguish registry snapshots from durable lineage proof
seonghobae Sep 9, 2026
6966974
test: retain remote batch handle after registry failure
seonghobae Sep 9, 2026
d95ad53
fix: preserve applied batch outcome across registry failures
seonghobae Sep 9, 2026
369ea1e
fix: keep registry write result outside persisted snapshot
seonghobae Sep 9, 2026
18af294
test: distinguish absent and partially written batch registry
seonghobae Sep 9, 2026
b12981d
test: inspect canonical registry key in partial write probe
seonghobae Sep 9, 2026
d71bcc0
test: reproduce missing owner-bound batch restart recovery
seonghobae Sep 9, 2026
6b40cce
fix: recover owner-bound remote batch descriptors from durable events
seonghobae Sep 9, 2026
e8d8c8a
fix: use existing attribution dimensions normalizer
seonghobae Sep 9, 2026
65b4160
test: reject unsafe batch recovery descriptors and result identities
seonghobae Sep 9, 2026
853e860
fix: validate batch recovery shape and preserve configured expiry
seonghobae Sep 9, 2026
b4abdd6
docs: record owner-bound batch restart recovery evidence
seonghobae Sep 9, 2026
e372bc5
test: reject inconsistent recovery identities and survive registry ou…
seonghobae Sep 9, 2026
7fb1a71
fix: isolate authorized batch recovery from unavailable registry
seonghobae Sep 9, 2026
83394af
test: bind recovery to deployment and reject malformed job fields
seonghobae Sep 9, 2026
f4d036b
test: recover backend metadata despite healthy coordinator handle
seonghobae Sep 9, 2026
6cb530a
fix: recover partial registry state and expose descriptor availability
seonghobae Sep 9, 2026
42ad396
test: reject duplicate persisted items and verify recovery polling
seonghobae Sep 9, 2026
aaa9b13
fix: require exact persisted batch item cardinality
seonghobae Sep 9, 2026
17cfa61
test: retain applied remote submission after backend registry failure
seonghobae Sep 9, 2026
8ddfeb9
fix: return applied batch handle despite backend registry failure
seonghobae Sep 9, 2026
df638d6
test: preserve healthy active batch beyond recovery expiry
seonghobae Sep 9, 2026
a6b9485
fix: preserve healthy batch registry lifecycle during recovery
seonghobae Sep 9, 2026
3f98f92
docs: record batch recovery review repairs and deployment binding
seonghobae Sep 9, 2026
08a660d
test: prevent healthy registry from bypassing deployment binding
seonghobae Sep 9, 2026
cd38d9c
fix: validate deployment binding before healthy batch fast path
seonghobae Sep 9, 2026
2d68f70
test: preserve explicitly unbound legacy batch compatibility
seonghobae Sep 9, 2026
c06615a
test: preserve endpoint binding on healthy batch metadata
seonghobae Sep 9, 2026
4506675
fix: enforce exact endpoint on bound batch metadata
seonghobae Sep 9, 2026
73404f8
docs: freeze reviewed batch recovery acceptance scope
seonghobae Sep 9, 2026
82892ed
Merge pull request #1112 from ContextualWisdomLab/codex/decision-late…
seonghobae Sep 9, 2026
5a9d8fb
Merge pull request #1113 from ContextualWisdomLab/codex/outcome-reque…
seonghobae Sep 9, 2026
53dcd1b
Merge pull request #1115 from ContextualWisdomLab/codex/batch-request…
seonghobae Sep 9, 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
17 changes: 14 additions & 3 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,16 @@ jobs:
with:
version: "0.12.5"

- name: Build locked native decision measurement
run: |
set -euo pipefail
rustup show active-toolchain
uv sync --locked --extra api --extra db --extra queue --group dev --group native-build
uv run --no-sync maturin develop --locked --release --features pyo3/extension-module --manifest-path rust/decision_receipt/Cargo.toml
uv run --no-sync maturin build --locked --release --features pyo3/extension-module --manifest-path rust/decision_receipt/Cargo.toml --out "$RUNNER_TEMP/decision-wheels"

- name: Run full test suite
run: uv run --locked --extra api --extra db --extra queue --group dev python -m pytest -q
run: uv run --no-sync python -m pytest -q

- name: Install hash-locked quality tools
run: |
Expand Down Expand Up @@ -74,12 +82,15 @@ jobs:
set -euo pipefail
rm -rf dist "$RUNNER_TEMP/nim-wheel-site"
python -m pip wheel --no-deps --no-build-isolation . --wheel-dir dist
python scripts/verify_decision_wheel_manifest.py dist/contextual_orchestrator-*.whl "$RUNNER_TEMP"/decision-wheels/*.whl
python -m pip install --no-deps \
--target "$RUNNER_TEMP/nim-wheel-site" \
dist/contextual_orchestrator-*.whl
dist/contextual_orchestrator-*.whl "$RUNNER_TEMP"/decision-wheels/*.whl
cd "$RUNNER_TEMP"
PYTHONPATH="$RUNNER_TEMP/nim-wheel-site" \
python -c "import contextual_orchestrator; import contextual_orchestrator.nim_benchmark"
python -c "import pathlib, contextual_orchestrator as core; import contextual_orchestrator.nim_benchmark; import contextual_orchestrator._decision_receipt as native; root = pathlib.Path('nim-wheel-site').resolve(); assert pathlib.Path(core.__file__).is_relative_to(root); assert pathlib.Path(native.__file__).is_relative_to(root)"
PYTHONPATH="$RUNNER_TEMP/nim-wheel-site" \
python -m pytest --noconftest --import-mode=importlib "$GITHUB_WORKSPACE/tests/test_decision_receipts.py" -q

fuzz:
name: Property and coverage-guided fuzzing
Expand Down
11 changes: 11 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# AGENTS.md

Deferred batch lineage: read `docs/doctoring/batch_request_lineage.md` for the
HTTP reproduction, atomic submission-event projection, and remote/local failure
boundary. Do not retry a remotely submitted job after local lineage failure.

Workflow origin identity and persistence limitations are documented in
`docs/doctoring/workflow_request_link.md`; preserve origin on replacements and reload.

Cross-agent conventions for `contextual-orchestrator`, readable by any coding
agent (Claude, Codex, Cursor, opencode, …). Keep this file tool-agnostic.

Expand Down Expand Up @@ -50,6 +57,10 @@ push or open a PR.

### Code exploration

- Provider logs need server-generated per-request identity, not a session hash.
Preserve context cleanup and validate the central collector before adoption.
Reproduction and exact evidence: `docs/doctoring/provider_request_correlation.md`.

- This repo has **no `.codegraph/` index**, so use normal search
(grep/ripgrep/find, file reads) to locate and understand code. If a
`.codegraph/` directory is ever added at the repo root, prefer CodeGraph
Expand Down
11 changes: 11 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# CLAUDE.md

Batch request lineage evidence and unresolved registry failure semantics live
in `docs/doctoring/batch_request_lineage.md`; HTTP 201 alone does not establish
durable lineage. Preserve job-scoped item IDs and original submission identity.

See `docs/doctoring/workflow_request_link.md` for request-to-workflow correlation
tests, cache semantics, and the distinction between in-memory and durable outcomes.

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Read AGENTS.md first
Expand Down Expand Up @@ -27,6 +34,10 @@ This file complements AGENTS.md with commands and architecture; where they diffe

## Common commands

For missing provider/error correlation, run the focused telemetry and debug-log
tests in `docs/doctoring/provider_request_correlation.md`. A green local producer
test does not prove the central collector preserves the new field.

```bash
# Install (pinned, hash-locked — always this two-step form)
python -m pip install --require-hashes -r requirements.lock
Expand Down
5 changes: 5 additions & 0 deletions contextual_orchestrator/batch_job_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,11 @@ def __init__(self, client: Any = None, *, retention_seconds: int = DEFAULT_RETEN
)
self._local_locks_guard = threading.Lock()

@property
def retention_seconds(self) -> int:
"""Return the configured registry retention for durable recovery expiry."""
return self._retention_seconds

def lock(
self,
name: str,
Expand Down
92 changes: 84 additions & 8 deletions contextual_orchestrator/batch_routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,15 @@ class BatchJob:
# Prompt-token fallback estimates are safe metadata, stored atomically with
# the job handle rather than retaining submitted prompt text.
prompt_token_estimates: Dict[str, int] = field(default_factory=dict)
# Finalized before the registry snapshot is written; the append-only event
# supplies the durable request association independently of that registry.
request_link_status: str = "unavailable"
recovery_status: str = "unavailable"
# Deliberately not a dataclass field: HSET may succeed before expiry fails,
# so an operation result must never be serialized into its own snapshot.
registry_persistence_status = "unavailable"
backend_registry_persistence_status = "unavailable"
recovered_request_metadata = None


@dataclass
Expand Down Expand Up @@ -453,17 +462,75 @@ def __init__(
endpoint: str = "/v1/chat/completions",
payload_assembler: Any = None,
job_registry: Any = None,
recovery_identity: str | None = None,
) -> None:
self._client = client
self._endpoint_alias = endpoint_alias
self._endpoint = endpoint
self._assembler = payload_assembler
if recovery_identity is not None and (not isinstance(recovery_identity, str) or not recovery_identity.strip()):
raise ValueError("recovery identity must be a nonempty operator-controlled identifier")
self._recovery_identity = recovery_identity
# Tracked requests survive a restart when a Valkey-backed registry
# is injected; a plain dict preserves the historical behavior.
self._jobs: Dict[str, Dict[str, Any]] = (
job_registry.mapping("pg_llm_batch_jobs") if job_registry is not None else {}
)

@property
def recovery_enabled(self) -> bool:
"""Whether the operator supplied a stable deployment/account binding."""
return self._recovery_identity is not None

def has_job_metadata(self, job: BatchJob) -> bool:
"""Check whether active registry metadata supports the existing job."""
try:
document = self._jobs.get(job.job_id)
except Exception:
return False
return (isinstance(document, dict)
and document.get("endpoint_alias") == self._endpoint_alias
and document.get("recovery_identity") == self._recovery_identity
and (document.get("endpoint") == self._endpoint
or ("endpoint" not in document and self._recovery_identity is None))
and isinstance(document.get("requests"), dict)
and len(document["requests"]) == job.request_count)

def recovery_descriptor(self, requests: List[BatchRequest]) -> Dict[str, Any]:
"""Describe exact target and item metadata without submitted prompt text."""
from .cost_ledger import AttributionDimensions
return {
"recovery_identity": self._recovery_identity,
"backend_name": self.name,
"endpoint_alias": self._endpoint_alias,
"endpoint": self._endpoint,
"items": [{"custom_id": item.custom_id, "model": item.model,
"mode": item.mode, "attribution": AttributionDimensions.from_mapping(item.attribution).as_dict()}
for item in requests],
}

def restore_descriptor(self, job: BatchJob, descriptor: Dict[str, Any]) -> None:
"""Restore prompt-free item identity only for this exact configured target."""
if (not isinstance(descriptor, dict) or descriptor.get("backend_name") != self.name
or self._recovery_identity is None
or descriptor.get("recovery_identity") != self._recovery_identity
or descriptor.get("endpoint_alias") != self._endpoint_alias
or descriptor.get("endpoint") != self._endpoint):
raise ValueError("batch target mismatch")
items = descriptor.get("items")
if not isinstance(items, list) or len(items) != job.request_count:
raise ValueError("batch item count mismatch")
restored = {}
for item in items:
if not isinstance(item, dict) or set(item) != {"custom_id", "model", "mode", "attribution"}:
raise ValueError("invalid batch item descriptor")
if any(not isinstance(item[field], str) or not item[field] for field in ("custom_id", "model", "mode")):
raise ValueError("invalid batch item identity")
if item["custom_id"] in restored or not isinstance(item["attribution"], dict):
raise ValueError("invalid batch item metadata")
restored[item["custom_id"]] = {**item, "messages": []}
job.recovered_request_metadata = restored

def _assemble_payload(self, requests: List[BatchRequest]) -> str:
if self._assembler is not None:
return self._assembler.assemble(
Expand Down Expand Up @@ -496,18 +563,26 @@ async def _submit() -> Dict[str, Any]:
# Tracked requests are stored as JSON primitives (not dataclass
# instances) so the registry can be a JSON-backed Valkey mapping;
# retrieve() rebuilds the dataclass view it needs.
self._jobs[batch_id] = {
"endpoint_alias": self._endpoint_alias,
"requests": {
request.custom_id: dataclasses.asdict(request) for request in requests
},
}
return BatchJob(
registry_status = "stored"
try:
self._jobs[batch_id] = {
"endpoint_alias": self._endpoint_alias,
"recovery_identity": self._recovery_identity,
"endpoint": self._endpoint,
"requests": {
request.custom_id: dataclasses.asdict(request) for request in requests
},
}
except Exception:
registry_status = "write_failed"
job = BatchJob(
job_id=batch_id,
backend=self.name,
status=job_payload.get("status", "validating"),
request_count=len(requests),
)
job.backend_registry_persistence_status = registry_status
return job

def poll(self, job: BatchJob) -> Dict[str, Any]:
"""Poll batch status via the pg-llm-batch client."""
Expand Down Expand Up @@ -542,7 +617,8 @@ async def _download() -> Dict[str, Any]:
reason,
)
raise BatchDownloadError(job.job_id, reason)
tracked = self._jobs.get(job.job_id, {}).get("requests", {})
tracked = (job.recovered_request_metadata if job.recovered_request_metadata is not None
else self._jobs.get(job.job_id, {}).get("requests", {}))
responses = _validated_download_responses(
payload,
expected_custom_ids=set(tracked),
Expand Down
91 changes: 87 additions & 4 deletions contextual_orchestrator/cost_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@

import hashlib
import re
import time
from contextvars import ContextVar
from dataclasses import replace
from dataclasses import asdict, replace
from threading import Lock
from typing import Any, Dict, List, Optional
from .decision_receipts import record_initial_selection

from .batch_routing import (
BatchBackend,
Expand All @@ -37,6 +39,7 @@
LocalBatchBackend,
LocalEmbeddingBatchBackend,
ProviderEmbeddingBatchBackend,
PgLlmBatchBackend,
RoutingHints,
RoutingPolicy,
)
Expand Down Expand Up @@ -972,6 +975,7 @@ def submit_batch(
requests: List[BatchRequest],
metadata: Optional[Dict[str, Any]] = None,
owner_id: Optional[str] = None,
request_id: Optional[str] = None,
) -> BatchJob:
"""Submit a batch, resolve its targets, and bind its authenticated owner."""
try:
Expand All @@ -995,7 +999,36 @@ def submit_batch(
job = self.batch_backend.submit(prepared_requests, metadata=metadata)
job.owner_id = owner_id
job.prompt_token_estimates = prompt_token_estimates
self._batch_jobs[job.job_id] = job
if request_id is not None and self.orchestrator._store is not None:
try:
# One append-only submission envelope commits all item links
# together. A later retrieval never rewrites this origin.
self.orchestrator._store.save("batch_request_link", job.job_id, {
"request_id": request_id,
"batch_job_id": job.job_id,
"custom_ids": [request.custom_id for request in prepared_requests],
"owner_id": owner_id,
"recovery_descriptor": ({
"job": asdict(job),
"expires_at": job.submitted_at + self._job_registry.retention_seconds,
"backend": self.batch_backend.recovery_descriptor(prepared_requests),
} if isinstance(self.batch_backend, PgLlmBatchBackend) else None),
}, durable=True)
except Exception:
# The upstream submission already happened. Preserve its handle
# and report incomplete lineage instead of inviting a resubmit.
job.request_link_status = "write_failed"
else:
job.request_link_status = "durable"
if isinstance(self.batch_backend, PgLlmBatchBackend) and self.batch_backend.recovery_enabled:
job.recovery_status = "durable_descriptor"
job.registry_persistence_status = "stored"
try:
self._batch_jobs[job.job_id] = job
except Exception:
# Submission already applied remotely; an HSET/expiry failure may
# itself be partially applied. Return the handle without replay.
job.registry_persistence_status = "write_failed"
return job

def _resolve_batch_request(self, request: BatchRequest) -> BatchRequest:
Expand Down Expand Up @@ -1046,7 +1079,7 @@ def retrieve_batch(self, job_id: str, *, owner_id: Optional[str] = None) -> Dict
not self._batch_item_usage_valid(item)
and item.custom_id not in prompt_token_estimates
for item in items
)
) and job.recovered_request_metadata is None
request_by_custom_id = (
self._legacy_batch_requests(job) if needs_legacy_lookup else {}
)
Expand Down Expand Up @@ -1286,7 +1319,53 @@ def _resolve_batch_provider_model(self, item: BatchResultItem) -> tuple[str, str
return provider, item.model

def _require_job(self, job_id: str, *, owner_id: Optional[str] = None) -> BatchJob:
job = self._batch_jobs.get(job_id)
try:
job = self._batch_jobs.get(job_id)
except Exception:
job = None
if job is not None and job.owner_id != owner_id:
raise KeyError(f"batch job {job_id!r} not found")
if job is not None and isinstance(self.batch_backend, PgLlmBatchBackend):
if self.batch_backend.has_job_metadata(job):
return job
# Missing or differently bound metadata cannot use the ordinary
# retrieval path; only a validated durable descriptor may recover.
job = None
if (owner_id is not None and self.orchestrator._store is not None
and (job is None or (isinstance(self.batch_backend, PgLlmBatchBackend)
and self.batch_backend.recovery_enabled))):
record = self.orchestrator._store.load_latest_key("batch_request_link", job_id)
if (isinstance(record, dict) and record.get("owner_id") == owner_id
and record.get("batch_job_id") == job_id
and isinstance(self.batch_backend, PgLlmBatchBackend)):
descriptor = record.get("recovery_descriptor")
try:
if not isinstance(descriptor, dict) or type(descriptor.get("expires_at")) is not int:
raise ValueError("invalid descriptor")
if descriptor["expires_at"] <= time.time():
raise ValueError("expired descriptor")
recovered = BatchJob(**descriptor["job"])
if recovered.job_id != job_id or recovered.owner_id != owner_id or recovered.backend != self.batch_backend.name:
raise ValueError("mismatched descriptor")
custom_ids = record.get("custom_ids")
if (type(recovered.request_count) is not int or recovered.request_count < 1
or not isinstance(custom_ids, list)
or any(not isinstance(item, str) or not item for item in custom_ids)
or len(custom_ids) != recovered.request_count
or len(set(custom_ids)) != recovered.request_count):
raise ValueError("invalid recovery item identities")
estimates = recovered.prompt_token_estimates
if (not isinstance(estimates, dict) or not set(estimates).issubset(custom_ids)
or any(type(value) is not int or value < 0 for value in estimates.values())):
raise ValueError("invalid recovery estimates")
self.batch_backend.restore_descriptor(recovered, descriptor["backend"])
if set(recovered.recovered_request_metadata) != set(custom_ids):
raise ValueError("mismatched recovery items")
recovered.request_link_status = "durable"
recovered.recovery_status = "durable_descriptor"
job = recovered
except (KeyError, TypeError, ValueError):
job = None
if job is None or job.owner_id != owner_id:
raise KeyError(f"batch job {job_id!r} not found")
return job
Expand Down Expand Up @@ -1332,6 +1411,8 @@ def submit_embeddings_batch(
if callable(reserve) and callable(start):
job = reserve(requests, metadata=metadata)
else:
if resolved_agent_id is not None:
record_initial_selection([resolved_agent_id], "embedding_submission")
job = backend.submit(requests, metadata=metadata)
self._embedding_models[job.job_id] = resolved_model
self._embedding_owners[job.job_id] = owner_id
Expand All @@ -1341,6 +1422,8 @@ def submit_embeddings_batch(
self._embedding_part_limits[job.job_id] = part_limits
self._embedding_jobs[job.job_id] = job
if callable(reserve) and callable(start):
if resolved_agent_id is not None:
record_initial_selection([resolved_agent_id], "embedding_submission")
start(job)
return job

Expand Down
Loading