feat: establish provider-aware reasoning replay foundation by Zheng-Lu · Pull Request #350 · vllm-project/agentic-api · GitHub
Skip to content

feat: establish provider-aware reasoning replay foundation - #350

Draft
Zheng-Lu wants to merge 18 commits into
vllm-project:mainfrom
Zheng-Lu:feat/335-provider-aware-reasoning
Draft

Zheng-Lu wants to merge 18 commits into
vllm-project:mainfrom
Zheng-Lu:feat/335-provider-aware-reasoning

Conversation

@Zheng-Lu

@Zheng-Lu Zheng-Lu commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Implement the typed reasoning and provenance foundation for #335. Current progress: typed reasoning, fail-closed stored history, server-owned policy/provenance, upstream-reported model evidence, pinned candidate-profile compatibility preflight, the stateless opaque request/isolated transport adapter, stream-owned producer cancellation, pinned provider-reference replay coverage, assistant phase preservation, offline full-executor acceptance, canonical durable tool-round ordering, modeled request-surface preflight, and closed nested HTTP/WebSocket wire guards and matching core preflight are implemented. Opaque replay remains disabled; this draft does not close #335.

Review follow-up (2026-09-24): default plaintext provenance no longer fingerprints credentials; bounded legacy reasoning rows remain continuable without gaining opaque trust; lenient JSON/SSE treat malformed upstream model metadata as unknown evidence; the closed opaque profile rejects prompt_cache_key. The request guard shares the typed field list with core preflight. Full workspace tests, Clippy with -D warnings, formatting, and staged pre-commit hooks pass. Opaque qualification remains gated.

  • Model reasoning text, summaries, status, and opaque state with typed Rust contracts. Preserve exact opaque bytes, redact debug output, and enforce decoded-byte and retained-response bounds. Preserve canonical reasoning while keeping the current vLLM plaintext projection and early rejection of opaque-only continuation.
  • Reject invalid/missing history rows, malformed response metadata/references, and missing or foreign captured conversation responses. Preserve legacy SQL NULL behavior and keep stored values out of error diagnostics.
  • Add typed server configuration responses.reasoning_replay_policy, defaulting to vllm_plaintext. Reserved opaque_responses fails before storage initialization, rehydration, tool discovery, inference, compaction, or external commit.
  • Persist versioned, non-wire provenance per reasoning item. Durable history, transient sessions, forks, and ephemeral-to-durable promotion preserve origin; manual submissions and externally committed outputs cannot claim gateway-observed origin. Fixed-size fingerprints bind policy, configured endpoint, requested model, and optional reported model without retaining original strings. Only the closed opaque profile also binds the effective credential.
  • Carry bounded, typed model metadata through the existing JSON/SSE ingestion path, separately from request-derived public ResponsePayload.model. Only explicit, consistent terminal metadata supplies evidence. Missing/null metadata, nonterminal JSON, lenient EOF completion, and ambiguous lenient streams remain unknown. Malformed model metadata becomes unknown evidence in lenient ingestion and a redacted upstream error in strict ingestion; strict ingestion also rejects changing model names. Model retention is charged once per round.
  • Preserve recorded gateway compatibility: the existing reasoning stream reports gpt-5.6-sol early and echoes requested gpt-5.6 at completion. Lenient execution succeeds with unknown model evidence; strict decoding rejects the conflict. No alias or model-family equivalence is inferred.
  • Add the closed candidate profile openai_gpt_5_4_2026_03_05_v1, pinning the exact OpenAI Responses endpoint and gpt-5.4-2026-03-05 requested/reported snapshot. Validate policy/profile consistency, exact routing, canonical provenance, credential compatibility, nonempty opaque state, and unsupported compaction before inference. Profile fingerprints bind a separate contract domain and cannot match legacy observational identities. Post-ingestion profile provenance requires exact terminal model evidence. Errors use static redacted messages and typed HTTP/SSE mappings. Even a compatible candidate remains unavailable: configuration and request execution still fail the qualification gate.
  • Add a borrowed, typed upstream-only reasoning projection with explicit upstream store: false, preserving exact opaque state, summaries, IDs, optional status, and ordering while omitting plaintext content and internal provenance. Canonical item types and gateway storage policy stay unchanged; the initial plaintext sanitizer remains vLLM-only.
  • Isolate candidate transport from caller-supplied HTTP-client defaults: direct HTTPS only, no redirects, environment proxies, automatic retries, or organization/project/cookie defaults; explicit per-request bearer auth, bounded connect/read timeouts, and bounded idle pooling. Discard non-2xx bodies/headers without reading them; redirects become 502 errors.
  • Select the existing strict ingestion transitions for candidate JSON/SSE. The shared SSE framer rejects invalid UTF-8 and unfinished lines for this candidate, without changing default vLLM framing. Redact invalid-provider diagnostics while retaining typed causes and safe tool-search lifecycle errors. Every candidate round requires exact terminal model evidence, including rounds without reasoning. No availability gate was removed.
  • Remove the nested spawned Responses producer and its abort-only drop guard. The consumer stream now owns and polls orchestration with the existing bounded event receiver; dropping it synchronously disposes local request/tool futures and continuation leases. Drain accepted events in order, emit a typed static panic error without forwarding panic payloads, and preserve persistence/checkpoint publication before completion. WebSocket request tasks still have explicit abort/join and session-idle fences. This closes the specific producer lifetime gap, not all of refactor: extract StreamRelay and bound client delivery #244 or remote side-effect cancellation.
  • Add six recorder-generated pinned OpenAI reference cassettes covering 18 real requests: initial, continuation and independent branch for text and function outputs over JSON, SSE and WebSocket. All use exact gpt-5.4-2026-03-05, store: false, complete item replay, low reasoning effort and concise summaries. The provider accepts continuations using the exact output_item.done opaque bytes retained by gateway ingestion, which differ from the terminal envelope bytes. WebSocket references use a fresh connection per request, not gateway connection-local continuation.
  • Preserve optional typed assistant MessagePhase::{Commentary, FinalAnswer} through completion, output-to-input conversion, storage and rehydration. Live captures exposed that this field was previously dropped. Reject phase on non-assistant input; absent/null legacy phase remains absent. OpenAI phase guidance informs the contract without introducing a second production ingestion path.
  • Extend the existing recorder with explicit immutable stateless checkpoints, branch isolation, item/byte/turn limits, payload-log suppression, a selectable item-completion replay source, and bounded WebSocket capture. Preserve raw captures and honor WebSocket store: false.
  • Replay the pinned JSON/SSE recordings through full ExecuteRequest execution, durable storage, transient forks/promotion, explicit conversations, and a deterministic local MCP tool loop. A bounded, per-context, core-unit-test-only loopback fixture skips availability without weakening profile, routing, credential, provenance, or terminal-model checks. An ordinary opaque transport cannot grant test availability. No runtime configuration or feature flag enables the candidate, and startup, external commit, and compaction gates remain closed. This is offline executor acceptance, not live gateway or actual HTTP/WebSocket handler qualification.
  • Fix a durable opaque-history ordering defect exposed by the tool-loop tests: reasoning must precede its function call and output in later continuations, as it does during the live loop. Reuse canonical round recording and the existing output-to-input conversion, with typed, non-wire RecordedOutputPrefix bookkeeping owned by the turn rather than its session lease. Both persistence modes avoid duplicated public projections while retaining MCP discovery metadata. Default vLLM persistence behavior remains unchanged; no existing history is rewritten.
  • Validate the pinned candidate's modeled request fields before history lookup or tool discovery, and again after effective settings are resolved before each round. The typed allowlist accepts the recorded low/concise reasoning flow, bounded output tokens, and function or local MCP normalization with explicit require_approval: never. It rejects unqualified reasoning/sampling/text settings, automatic truncation, vLLM-only fields, unsupported tool kinds and lossy function extensions with static parameter-specific 400 errors. Default vLLM behavior is unchanged; the availability gate remains closed. The HTTP/WebSocket profile guard rejects unknown and duplicate top-level keys and unknown reasoning keys. Closed nested schemas now cover the pinned user/assistant message, opaque reasoning, function call/output, function/MCP tool, and named-function-choice shapes. Bounded visitors reject duplicate keys inside open function JSON Schema and MCP header documents; empty replay metadata arrays and explicit list caps match the captures. Core preflight applies the same typed input surface to direct callers and rehydrated history. All 18 requests in the six recorder-generated pinned cassettes pass the guard. The availability gate remains closed.
  • Keep normalization, synchronous ingestion, delivery, and orchestration responsibilities separate. No new production parser, queue, worker, or client-emission path is added. Cohesive extractions lower the existing production-file size allowances.

Storage and compatibility

Migration 0006_reasoning_provenance.sql adds nullable items.reasoning_provenance TEXT without backfilling legacy rows. Startup/readiness require the column, including supervisor-managed schemas. Invalid, oversized, unknown-version, or wrong-item-kind provenance fails closed. Fixed non-wire provenance charges participate in response/checkpoint budgets. Reported-model propagation requires no additional migration and does not rewrite old unknown-model fingerprints.

Public JSON/OpenAPI remain free of provenance fields; public completed-response model naming is unchanged. Rust struct-literal callers must supply the new policy/profile/provenance fields and the typed optional model in EventPayload::Response. Raw item insertion is crate-private; typed stores remain the persistence interface. UpstreamRequest import paths remain available; Rust struct literals now require store: None for the default contract and .into() to convert Cow<ResponsesInput> into the typed input view. No additional storage migration is needed for this adapter, stream-owned producer lifecycle, assistant phase, or canonical round-ordering fix. Public Rust RequestContext struct literals must now initialize recorded_output_prefix with RecordedOutputPrefix::default(); split contexts do so without serializing the marker. Item JSON preserves phase directly; public InputMessage and OutputMessage struct literals must now supply phase, while constructors default it to None and existing import paths remain available. Design and migration details are in docs/design/provider-aware-reasoning.md.

Remaining #335 work

  • Complete live gateway acceptance and actual HTTP/WebSocket transport coverage, including connection-local routing. Offline executor acceptance covers durable/transient continuations, forks, promotion, conversations and gateway-executed tool loops. The pinned provider reference captures cover JSON/SSE/WebSocket, but are not live gateway execution. Complete live gateway acceptance and qualify any further request settings, item/tool shapes, tool normalization, and provider terminal/error modes before opening the profile. The pinned nested wire shapes are now closed against silent field loss; store: false cannot bypass executor preflight through transparent HTTP proxying. Ambiguous WebSocket wire failures carry no stream or previous-response ID, so they cannot select a lane or cached checkpoint for eviction. Existing gpt-5.6 captures remain regression evidence, not pinned-model qualification.
  • Keep local plaintext compaction distinct from provider opaque compaction. Candidate preflight rejects local compaction input/triggers and nonempty context_management; provider compaction remains unsupported.
  • Keep qualification opt-in and credentials local. The expanded recorder and pinned reference fixtures are now available, and live access succeeded using the locally configured environment; credentials are not included in fixtures. Future captures must use the recorder and staged validation, never hand-authored YAML. The producer lifetime prerequisite is addressed; broader refactor: extract StreamRelay and bound client delivery #244 delivery/observability and perf: benchmark inline versus worker placement for streaming ingestion #245 placement measurements remain separate.

Test Plan

  • Current slice (0621bce): the complete core/server suites passed at the pushed head with cargo test -p agentic-server-core -p agentic-server --quiet; workspace all-target Clippy with -D warnings and all applicable staged-file pre-commit hooks passed. The server guard replays all 18 requests from six recorder-generated pinned cassettes; HTTP/WebSocket integration rejects nested unknown and duplicate fields, including duplicate keys in open JSON Schema, without leaking caller content. Core tests reject unqualified input shapes and excess list sizes before the closed gate. No live provider calls or cassette edits were made.
  • Current slice (be77904): cargo test -p agentic-server-core -p agentic-server passed before the request-parser extraction, and the complete server suite passed again afterward. Workspace all-target Clippy with -D warnings, format, and all applicable staged-file pre-commit hooks passed. HTTP and WebSocket integration tests cover unknown/duplicate fields, redacted errors, the closed gate, store: false executor routing, and unchanged default proxying; core preflight rejects unknown input-item kinds. No live provider calls or cassette changes.
  • Current slice (c825a66): the full core suite passed with cargo test -p agentic-server-core --offline --quiet --no-fail-fast; workspace all-target Clippy with -D warnings, formatting and applicable staged-file pre-commit hooks passed. A new JSON/SSE matrix rejects 20 unqualified modeled request cases before missing-history lookup or network access, with exact typed parameter errors. A positive unit case preserves the pinned settings without opening the gate; prior recorder-backed executor tests remain green. No provider calls or cassette edits were made.
  • Prior slice (4a617d5): nine offline full-executor acceptance tests pass across pinned JSON/SSE recordings. Cover durable continuations/branches, transient fork isolation and promotion, origin and rotated-credential rejection before network access, ordinary-transport fail-closed behavior, unpolled/active drop, malformed or truncated upstream completion, failed-fork recovery, and two-round MCP tool execution with durable/conversation/session history ordering. A second-round model mismatch persists no response after a completed tool; remote tool side effects are explicitly not rolled back. The turn-prefix unit test checks monotonic deduplication and discovery retention.
  • Reran the complete workspace suite and all-target Clippy after the final changes; all non-ignored tests and applicable staged-file pre-commit hooks pass. This slice made no live provider calls and changed no cassette YAML. All provider response bodies on positive paths are reused from the existing recorder-generated pinned captures; faults are in-memory mutations. User-owned local configuration and unrelated untracked files are excluded.
  • Passed cargo test --workspace --offline --quiet --no-fail-fast, including storage, reasoning, stateful responses, sessions, HTTP/WebSocket, OpenAPI, and compaction/cassette suites.
  • Passed cargo clippy --workspace --all-targets --offline -- -D warnings and all applicable staged-file pre-commit hooks, including formatting and Rust file-size checks.
  • Added 16 adapter/profile regression tests: typed stateless projection and unchanged history, recorded JSON/SSE ingestion, malformed state, missing/out-of-order/duplicate completion, index mismatch, disconnect/backpressure/cancellation, model evidence on rounds without reasoning, and safe tool-search lifecycle preservation. Loopback transport fixtures cover HTTP rejection by the production constructor, redirect rejection, error-body redaction, explicit headers, byte limits, timeouts, and invalid/truncated SSE framing.
  • Added 12 producer lifecycle regression tests (5 unit and 7 cassette-backed integration), replacing the older task-panic test. Cover unpolled/active drop, backpressure, pending I/O and tools, ordered completion, panic redaction/sequence numbers, storage-wait cancellation, persistence-before-completion, and cancelled-fork source provenance. The complete lifecycle integration suite and workspace Clippy were rerun after strengthening the active-fork case; all pre-commit hooks also passed. These tests reuse existing recorder-generated cassettes.
  • Added candidate-profile unit/integration and server TOML tests for closed configuration, exact endpoint/model checks before history lookup/tool discovery/JSON or SSE inference, legacy/manual origin rejection, credential rotation, profile-domain separation, compaction boundaries, unchanged canonical bytes/call order, atomic provenance stamping, and safe error envelopes. Compatibility success never bypasses the availability gate. These are local checks, not provider qualification.
  • New model-evidence tests cover JSON/SSE fingerprint equivalence under an unchanged request alias; reported-model changes; missing/null/conflicting metadata; exact UTF-8 bounds; round isolation; retained-budget exhaustion; upstream disconnect; client backpressure/drop; and rejection before response persistence. The cross-cassette strict audit asserts the exact known alias-rewriting capture/error while retaining its other compatibility checks.
  • Provenance tests cover wire/OpenAPI exclusion, mixed-origin batched storage, SQLite migration/restart/readiness, corrupted history, split-execution resubmission, cancelled forks, and ephemeral-to-durable promotion.
  • Captured and validated six pinned provider-reference cassettes (18 successful real requests), then replayed every body through strict AgentPipeline before promotion. Four reference tests verify exact terminal model evidence, opaque bytes, summaries, phase, item order, typed request projection, branch sources, and in-memory missing-terminal/duplicate-completion/wrong-index faults. The files match recorder staging byte-for-byte; no captured YAML was hand-authored or edited. These references do not qualify live gateway execution.
  • Passed all 23 offline recorder tests, three assistant-phase serialization/storage/validation tests, and the phase OpenAPI schema test. Reran the complete workspace suite, workspace all-target Clippy with warnings denied, formatting and all applicable pre-commit hooks. Existing Qwen/OpenAI/gateway cassette regressions also pass; 11 existing opt-in/ignored tests, including the ignored doctest, remain unrun.
  • Verified with Rust 1.98. Rust 1.85 verification remains blocked by existing locked dependencies requiring Rust 1.86–1.88. No dependency versions changed; the already-locked sha2 library is now also a direct core dependency.

Signed-off-by: Zheng Lu <Lz429671594@gmail.com>
Signed-off-by: Zheng Lu <Lz429671594@gmail.com>
Signed-off-by: Zheng Lu <Lz429671594@gmail.com>
Signed-off-by: Zheng Lu <Lz429671594@gmail.com>
Signed-off-by: Zheng Lu <Lz429671594@gmail.com>
Signed-off-by: Zheng Lu <Lz429671594@gmail.com>
Signed-off-by: Zheng Lu <Lz429671594@gmail.com>
Signed-off-by: Zheng Lu <Lz429671594@gmail.com>
Signed-off-by: Zheng Lu <Lz429671594@gmail.com>
Signed-off-by: Zheng Lu <Lz429671594@gmail.com>
Signed-off-by: Zheng Lu <Lz429671594@gmail.com>
Signed-off-by: Zheng Lu <Lz429671594@gmail.com>
Signed-off-by: Zheng Lu <Lz429671594@gmail.com>
Signed-off-by: Zheng Lu <Lz429671594@gmail.com>
Signed-off-by: Zheng Lu <Lz429671594@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Two critical and two moderate findings remain unresolved.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 2 High severity

Open (2)
What changed in this PR

Establishes typed reasoning, provenance, guarded provider-profile replay, and lifecycle foundations while keeping opaque replay disabled.

Changes:

  • Adds typed reasoning, assistant phases, provenance persistence, and fail-closed storage.
  • Adds request validation, isolated transport, model evidence, and offline replay coverage.
  • Refactors stream ownership, canonical tool history, recorder fixtures, and migrations.

Unresolved findings cover opaque error redaction, retained-size accounting, and duplicate WebSocket lane handling.

File Summary
docs/​deploying/​kubernetes.md Documents migration 0006.
docs/​deploying/​container.md Documents provenance migration requirements.
crates/​agentic-server/​tests/​responses_websocket_test.rs Tests WebSocket profile guards.
crates/​agentic-server/​tests/​opaque_request_test.rs Tests opaque request validation.
crates/​agentic-server/​tests/​input_file_repro.rs Updates request fixtures.
crates/​agentic-server/​tests/​conversations/​regressions.rs Updates conversation fixtures.
crates/​agentic-server/​src/​responses_config.rs Resolves replay configuration.
crates/​agentic-server/​src/​openapi.rs Tests reasoning schemas.
crates/​agentic-server/​src/​handler/​websocket/​responses/​event.rs Updates WebSocket event handling.
crates/​agentic-server/​src/​handler/​websocket/​request.rs Parses and guards WebSocket requests.
crates/​agentic-server/​src/​handler/​websocket/​mod.rs Registers WebSocket request handling.
crates/​agentic-server/​src/​handler/​mod.rs Registers request validation.
crates/​agentic-server/​src/​handler/​http/​responses.rs Applies HTTP admission checks.
crates/​agentic-server/​src/​config_file.rs Adds replay configuration.
crates/​agentic-server-core/​tests/​tool_normalization_test.rs Updates context fixtures.
crates/​agentic-server-core/​tests/​terminal_status_test.rs Updates context fixtures.
crates/​agentic-server-core/​tests/​tenant_isolation_test.rs Updates message fixtures.
crates/​agentic-server-core/​tests/​storage_integration.rs Tests fail-closed storage.
crates/​agentic-server-core/​tests/​stateful_responses_integration.rs Updates reasoning assertions.
crates/​agentic-server-core/​tests/​shell_tool_test.rs Updates context fixtures.
crates/​agentic-server-core/​tests/​relayed_stream_validation_test.rs Tests model validation.
crates/​agentic-server-core/​tests/​reasoning_types_test.rs Tests typed reasoning.
crates/​agentic-server-core/​tests/​reasoning_test.rs Tests reasoning replay.
crates/​agentic-server-core/​tests/​reasoning_provenance_types_test.rs Tests provenance contracts.
crates/​agentic-server-core/​tests/​reasoning_provenance_migration_test.rs Tests provenance migration.
crates/​agentic-server-core/​tests/​postgres_tenant_isolation_test.rs Updates PostgreSQL fixtures.
crates/​agentic-server-core/​tests/​postgres_storage_integration.rs Updates PostgreSQL storage tests.
crates/​agentic-server-core/​tests/​message_phase_test.rs Tests assistant phases.
crates/​agentic-server-core/​tests/​event_normalizer_test.rs Updates event assertions.
crates/​agentic-server-core/​tests/​cassettes/​test_record_tool_search.py Updates recorder fixtures.
crates/​agentic-server-core/​tests/​cassettes/​recorder_history.py Adds bounded replay checkpoints.
crates/​agentic-server-core/​tests/​cassettes/​README.md Documents pinned recordings.
crates/​agentic-server-core/​src/​types/​upstream_request.rs Defines upstream request types.
crates/​agentic-server-core/​src/​types/​upstream_input/​tests.rs Tests upstream projections.
crates/​agentic-server-core/​src/​types/​upstream_input.rs Adds opaque input projection.
crates/​agentic-server-core/​src/​types/​upstream_identity.rs Models upstream identity.
crates/​agentic-server-core/​src/​types/​turn_history.rs Tracks output prefixes.
crates/​agentic-server-core/​src/​types/​request_response/​serde_helpers.rs Refactors serialization helpers.
crates/​agentic-server-core/​src/​types/​request_response.rs Adds typed request integration.
crates/​agentic-server-core/​src/​types/​reasoning_profile.rs Defines the candidate profile.
crates/​agentic-server-core/​src/​types/​mod.rs Exports typed modules.
crates/​agentic-server-core/​src/​types/​io/​output.rs Adds typed output reasoning.
crates/​agentic-server-core/​src/​types/​io/​mod.rs Exports I/O types.
crates/​agentic-server-core/​src/​types/​io/​message.rs Adds message phases.
crates/​agentic-server-core/​src/​types/​io/​input.rs Adds phase-aware inputs.
crates/​agentic-server-core/​src/​types/​input_conversion.rs Updates input conversion.
crates/​agentic-server-core/​src/​storage/​types/​response.rs Makes response decoding fail closed.
crates/​agentic-server-core/​src/​storage/​types/​item.rs Adds provenance-aware items.
crates/​agentic-server-core/​src/​storage/​types/​errors.rs Adds safe storage errors.
crates/​agentic-server-core/​src/​storage/​schema/​readiness.rs Requires provenance readiness.
crates/​agentic-server-core/​src/​storage/​schema.rs Updates schema checks.
crates/​agentic-server-core/​src/​storage/​response.rs Validates stored responses.
crates/​agentic-server-core/​src/​storage/​models/​response.rs Parses stored responses strictly.
crates/​agentic-server-core/​src/​storage/​models/​item/​legacy_reasoning.rs Supports bounded legacy reasoning.
crates/​agentic-server-core/​src/​storage/​models/​item/​insert.rs Adds provenance-aware inserts.
crates/​agentic-server-core/​src/​storage/​models/​conversation.rs Loads item provenance.
crates/​agentic-server-core/​src/​storage/​conversation/​api.rs Persists typed inserts.
crates/​agentic-server-core/​src/​storage/​conversation.rs Validates conversation rehydration.
crates/​agentic-server-core/​src/​lib.rs Exports typed contracts.
crates/​agentic-server-core/​src/​executor/​session/​checkpoint_size.rs Accounts for provenance retention.
crates/​agentic-server-core/​src/​executor/​session.rs Integrates retained-byte accounting.
crates/​agentic-server-core/​src/​executor/​response_budget.rs Accounts for reasoning sizes.
crates/​agentic-server-core/​src/​executor/​request.rs Adds transport and policy state.
crates/​agentic-server-core/​src/​executor/​replay/​tests.rs Tests replay identities.
crates/​agentic-server-core/​src/​executor/​replay/​profile.rs Validates profile compatibility.
crates/​agentic-server-core/​src/​executor/​rehydrate.rs Validates replayable history.
crates/​agentic-server-core/​src/​executor/​qualification.rs Registers qualification tests.
crates/​agentic-server-core/​src/​executor/​prepare.rs Updates execution fixtures.
crates/​agentic-server-core/​src/​executor/​pipeline/​tests.rs Updates ingestion tests.
crates/​agentic-server-core/​src/​executor/​pipeline/​ingest.rs Propagates model evidence.
crates/​agentic-server-core/​src/​executor/​pipeline/​driver_tests.rs Tests pipeline metadata.
crates/​agentic-server-core/​src/​executor/​pipeline.rs Carries typed ingestion data.
crates/​agentic-server-core/​src/​executor/​persist.rs Handles external provenance.
crates/​agentic-server-core/​src/​executor/​modes/​response.rs Persists response history.
crates/​agentic-server-core/​src/​executor/​modes/​conversation.rs Persists conversation history.
crates/​agentic-server-core/​src/​executor/​mod.rs Registers executor modules.
crates/​agentic-server-core/​src/​executor/​inference/​transport.rs Adds isolated transport.
crates/​agentic-server-core/​src/​executor/​error/​opaque.rs Defines opaque error handling.
crates/​agentic-server-core/​src/​executor/​error.rs Maps executor errors.
crates/​agentic-server-core/​src/​executor/​engine/​usage.rs Accumulates round usage.
crates/​agentic-server-core/​src/​executor/​engine/​streaming/​producer/​tests.rs Tests producer lifecycle.
crates/​agentic-server-core/​src/​executor/​engine/​streaming/​producer.rs Owns producer cancellation.
crates/​agentic-server-core/​src/​executor/​engine/​streaming.rs Integrates stream ownership.
crates/​agentic-server-core/​src/​executor/​engine/​round.rs Records round provenance.
crates/​agentic-server-core/​src/​executor/​engine/​history.rs Canonicalizes round history.
crates/​agentic-server-core/​src/​executor/​compaction/​reasoning.rs Accounts for reasoning compaction.
crates/​agentic-server-core/​src/​executor/​compaction/​context.rs Updates context checks.
crates/​agentic-server-core/​src/​executor/​compaction.rs Integrates replay-aware compaction.
crates/​agentic-server-core/​src/​executor/​accumulator/​upstream_identity.rs Tracks model evidence.
crates/​agentic-server-core/​src/​executor/​accumulator/​json.rs Parses JSON model metadata.
crates/​agentic-server-core/​src/​executor/​accumulator/​identity.rs Tracks call identity.
crates/​agentic-server-core/​src/​executor/​accumulator/​budget_tests.rs Tests retention budgets.
crates/​agentic-server-core/​src/​events/​types.rs Adds response metadata.
crates/​agentic-server-core/​src/​events/​shell.rs Adds shell event types.
crates/​agentic-server-core/​src/​events/​normalize.rs Normalizes model metadata.
crates/​agentic-server-core/​src/​events/​mod.rs Registers event modules.
crates/​agentic-server-core/​src/​config.rs Adds replay configuration.
crates/​agentic-server-core/​migrations/​0006_reasoning_provenance.sql Adds provenance storage.
crates/​agentic-server-core/​Cargo.toml Adds hashing dependency.
crates/​agentic-server-core/​benches/​storage_crud.rs Updates storage fixtures.
crates/​agentic-server-core/​benches/​storage_concurrent.rs Updates concurrent storage fixtures.
crates/​agentic-llm-d/​src/​context.rs Initializes request context state.
Cargo.toml Adds workspace dependency.
Cargo.lock Locks dependency versions.
.rust-file-sizes.json Updates file-size baselines.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread crates/agentic-server-core/src/executor/accumulator/json.rs
Comment thread crates/agentic-server-core/src/executor/engine/streaming.rs
Signed-off-by: Zheng Lu <Lz429671594@gmail.com>
@maralbahari

Copy link
Copy Markdown
Collaborator

@Zheng-Lu I'll convert the PR to draft. please breakdown the PR into smaller pieces with a more reflective PR titles, the description references so many issues as well.

@maralbahari
maralbahari marked this pull request as draft September 25, 2026 14:02
Signed-off-by: Zheng Lu <Lz429671594@gmail.com>
Renumber the reasoning provenance migration to 0007 after main's
0006_item_references. Stored rows now keep public item IDs, so tests that
persist the same recorded items twice use distinct IDs.

Signed-off-by: Zheng Lu <Lz429671594@gmail.com>
@Zheng-Lu

Copy link
Copy Markdown
Contributor Author

@Zheng-Lu I'll convert the PR to draft. please breakdown the PR into smaller pieces with a more reflective PR titles, the description references so many issues as well.

Proposed split:
Sizes are approximate added lines; about 1.4k more of docs and glue code spread across all of them.

# Title Size Depends on
1 fix: preserve assistant message phase through history ~200 nothing
2 refactor: poll the Responses producer inside the client stream (#244) ~600 nothing
3 feat: type reasoning items and fail closed on corrupt stored history ~1.0k nothing
4 feat: persist server-owned reasoning provenance (migration 0007) ~1.4k 3
5 feat: record upstream-reported model evidence ~1.0k 4
6 test: pinned OpenAI reasoning reference recordings and recorder support ~3.8k, mostly recorded YAML 1, 3
7 feat: closed opaque reasoning candidate profile ~4.2k 1–6

Each PR would reference only #335; PR 2 references #244 instead.

PR 1 to 3 have no dependencies on each other, so I'll open those first. Should 7 wait as a draft until the profile can actually be enabled?

haoshan98 pushed a commit that referenced this pull request Oct 2, 2026
…384)

Part of #335 (split from #350)

## Summary

- Replace the untyped JSON in `ReasoningOutput` with typed reasoning
content, summaries and status, plus a bounded `OpaqueReasoning` string
for `encrypted_content`: redacted `Debug`, 16 MiB ceiling. Public JSON
field names are unchanged. The shared retained-response budget now
charges reasoning parts and opaque bytes.
- Keep the existing vLLM replay policy: plaintext is joined, summaries
are left out of the upstream copy, and opaque-only continuation is
rejected before inference.
- Stop silently dropping stored context. Undecodable history rows,
missing referenced rows, and malformed history references or response
metadata now return typed storage errors. The errors never include
stored values. SQL NULL keeps its legacy meaning.
- Keep reasoning stored by earlier releases readable and continuable.
One compatibility decoder mirrors the schema earlier releases accepted.
It keeps plaintext (as `reasoning_text`) and every summary part, string
opaque state, status and agent attribution that satisfies the typed
schema. It covers history rows, stored response snapshots and
multi-agent tree histories, on both `previous_response_id` and
`conversation` continuation. Anything earlier releases could not have
stored still fails closed, and so does an item whose only replay state
the decoder would drop: opaque-only continuation is still rejected
before it reaches the model. Dropped opaque state is logged with its
byte count, never its content.

Compatibility: request input and strict upstream ingestion now reject
reasoning items that don't match the typed schema, for example a
non-string `encrypted_content` or an unknown `status`; earlier releases
accepted them. The default (lenient) upstream path keeps such an item,
using the same decoder, instead of relaying the invalid values: the item
keeps only the fields the typed schema accepts. No migration is needed,
and existing rows are not rewritten.

## Test Plan

- `cargo test -p agentic-server-core -p agentic-server`: 1,979 passed, 0
failed, 18 ignored. `cargo clippy --workspace --all-targets -- -D
warnings`, `cargo fmt --check` and pre-commit (including Rust file
sizes) pass.
- `legacy_reasoning_test.rs` rewrites stored rows and stored response
snapshots to shapes earlier releases stored:
- Rows decode with their valid fields kept. A `previous_response_id`
continuation completes and replays the plaintext to vLLM as
`reasoning_text`.
- A rewritten snapshot is retrievable through
`ResponseHandler::retrieve` (which serves `GET /v1/responses/{id}`). It
is continuable by both `previous_response_id` and `conversation`.
- Shapes earlier releases could not have stored fail closed: rows with
`InvalidHistoryItem`, snapshots with `InvalidResponseMetadata`. The
shapes are a content part without a string `type`, non-array content, a
non-string `status` and a malformed `agent`.
- Opaque-only reasoning never reaches the model, whether it is continued
by `previous_response_id` or by `conversation`. String state is rejected
with the existing 400. Object state, which earlier releases accepted,
fails closed with `InvalidHistoryItem`. In both cases only the stored
turn reaches the mock upstream, and the object case fails without the
fix.
- Negative control: before the shared decoder, the snapshot test fails
because retrieval errors, and the row test fails because the rows are
accepted. Before `ConversationStore` used it, only the `conversation`
continuation fails.
- A `storage/types/response.rs` unit test does the same for multi-agent
tree histories. It also fails before the shared decoder.
- A `types/io/reasoning/legacy.rs` unit test checks dropped opaque
state. Non-string state and state over the ceiling are logged with their
byte count and reason only. Kept or absent state logs nothing. The test
fails with the warning removed.
- `accumulator/tests.rs`: lenient JSON and SSE ingestion keep a
reasoning item with the invalid fields dropped, and strict ingestion
rejects the same item on both paths. Shapes earlier releases rejected
are dropped from JSON, and in SSE the completion is ignored and the
opened item is kept, byte-for-byte as on `main` (checked against
`main`).
- `reasoning_types_test.rs` covers schema rejection, nullability, exact
opaque round trips, redaction and the size ceiling.
`storage_response_integrity_test.rs` and the storage suites cover
invalid rows, missing rows, bad references and bad metadata, all with
redacted errors.
- The existing recorder-generated OpenAI and gateway reasoning cassettes
replay unchanged. No cassette was added or edited.

---------

Signed-off-by: Zheng Lu <Lz429671594@gmail.com>
maralbahari added a commit that referenced this pull request Oct 20, 2026
## summary

fixes #385.

stored response history used each item's public id as the `items.id`
primary key. a client that sent a previous response's `output` back as
`input` with `store: true` (the default) hit `UNIQUE constraint failed:
items.id` and a 500, even though the request is valid:

Persistence(Storage(Database(Database(SqliteError { code: 1555, message:
"UNIQUE constraint failed: items.id" }))))

`item::create_in_tx` now gives each response-history occurrence a fresh
`item_…` row id and keeps the public id in a new nullable
`items.public_id` column. `Item::public_id()` still returns the public
id, so clients see stable ids, and repeated occurrences keep every
position. conversation inserts keep their membership checks, sequence
allocation, and reference resolution in the same function.

conversation inserts, cursors, retrieval and detach resolve an item by
public identity (`COALESCE(public_id, reference_id, id)`). the lookup is
indexed on that expression with the tenant, because a plain `public_id`
index is not usable by the resolved predicate. the join resolves one
source row per incoming item first (a `source_rows` CTE): several rows
can share a public identity, and joining on the identity alone fans out
into duplicate row ids, which then fails on `items.id`.

this supersedes #388, which used the same mechanism but no longer
compiles on main and left the conversation resolution untouched. thanks
to @RainieLLM for the original approach.

## test plan

- `resubmitted_item_history_test` replays the recorded two-turn `OpenAI`
responses (`text_only/responses/`, streaming and non-streaming). it
asserts the upstream request the second turn sends, then the rehydrated
order, roles, content, public ids, and distinct row ids. the
resubmission case is constructed from the same recording and keeps every
submitted occurrence.
- `stateful_conversation_integration` asserts the same for the recorded
two-turn conversation path (`text_only/conversation/`, streaming and
non-streaming): rehydrated history, public ids, row keys, and the
upstream history.
- `conversation_storage_regressions` covers repeated occurrences of one
public id, cross-tenant non-reuse, and batch rollback.
- `conversations_api_cassette_test` covers the conversation path,
including
`edge_replay::gateway_matches_all_openai_edge_case_status_codes_and_bodies`,
which posts one stored public id twice and caught the fan-out.
- reverting the resolution predicate reproduces `UNIQUE constraint
failed: items.id`, and removing the single-source reduction reproduces
`UNIQUE constraint failed: items.conversation_id, items.seq`.
- index check on sqlite with 100k existing items in one tenant:
inserting 100 items takes 1.0 ms with the expression index and 640 ms
without it. the plan changes from `SEARCH source USING INDEX
idx_items_tenant_id` to `SEARCH source USING INDEX
idx_items_public_identity (tenant_id=? AND <expr>=?)`.
- also ran `cargo fmt -- --check`, `cargo clippy -p agentic-server-core
--all-targets -- -D warnings`, `cargo test --workspace`, and the
production file-size check.

## notes

- the migration is `0007_response_item_public_ids.sql`. open #350 also
proposes a `0007`; whichever merges second renumbers to `0008`.
- consolidating the insert into `item.rs` crosses the 500-line
production cap, so `.rust-file-sizes.json` records a reviewed exception
(510) with the reason.
- existing rows keep the public id as their row id and a null
`public_id`; `Item::public_id()` falls back to `reference_id` and then
`id`, so no backfill is needed.
- postgres was not exercised locally (no server available). the
expression index uses standard `CREATE INDEX` syntax, and the ci
`postgres` job applies the migration and runs the storage tests.

---------

Signed-off-by: Abhinandan <abhinandanverma551@gmail.com>
Signed-off-by: maral <maralbahari.98@gmail.com>
Co-authored-by: maral <maralbahari.98@gmail.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make persisted reasoning replay aware of upstream capabilities

3 participants