Skip to content

feat: add Responses file_search protocol and execution - #486

Merged
franciscojavierarceo merged 6 commits into
mainfrom
codex/file-search-responses-protocol
Nov 8, 2026
Merged

franciscojavierarceo merged 6 commits into
mainfrom
codex/file-search-responses-protocol

Conversation

@franciscojavierarceo

@franciscojavierarceo franciscojavierarceo commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Add typed Responses file_search declarations, output items, optional result fields, and file citation annotations, with strict event and streaming lifecycle validation. Register the built-in tool in the gateway tool loop so it searches the configured file-search service, keeps retrieved passages private for model continuation, exposes file_search_call.results only when requested, and preserves citations in completed and streamed responses. Persist the model-facing search call and result for previous_response_id continuation.

This PR includes the execution layer merged from #34. #483 (pluggable storage), #484 (sqlite-vec), and #485 (Files/vector-store HTTP APIs) are merged. PostgreSQL/pgvector indexing remains in draft #278.

Test Plan

  • Full CI passed on 45c729f: Rust Clippy and tests, the OpenAI Python client file API integration test, SQLite vector provider test, CLI end-to-end test, Python, pre-commit, DCO, PostgreSQL, Kubernetes, embedded interpreter, container smoke test, and Linux/macOS wheel builds.
  • Added partial file_search_call.results deserialization regression, typed citation JSON/SSE relay regression, and generated OpenAPI schema assertions.
  • The gateway execution integration tests cover blocking/streaming search, citations, opt-in results, and continuation with a mocked upstream.
  • A recorder-generated live file-search cassette remains outstanding: this environment has no live provider credential, and the repository's cassette workflow prohibits hand-written captured YAML.

franciscojavierarceo added a commit that referenced this pull request Nov 3, 2026
## Summary

Add typed file, vector-store, and search contracts; pluggable
`FileSearchRepository` and `FileBlobStore` interfaces; portable SQL
metadata and bounded exact retrieval; and local blob storage. Store
listing batches current attachment counts and usage. Durable,
token-fenced cleanup intents cover uploads and deletions; expired leases
cannot let a publisher expose missing content. Recovery retains
abandoned-upload tombstones for later sweeps when a delayed write
finishes after an initial cleanup pass. Local staging is addressable by
file ID, and cancellation drains publication before cleanup. This is the
independently compilable foundation of the file-search stack; HTTP
routes and periodic cleanup are in #485. CI uses the macOS 15 Apple
Silicon runner because GitHub is retiring macOS 14 and its scheduled
brownout canceled wheel validation.

Review order: #483 → #484 → #485 → #486 → #34. PostgreSQL vector
indexing remains in follow-up #278.

## Test Plan

- `cargo test --offline -p agentic-server-core --test
file_search_service`: 31 passed, 1 PostgreSQL test ignored locally.
Includes two-service upload/recovery interleavings, restart recovery
after metadata rejection plus failed blob removal, delayed-staging
cancellation, and a late-write/failed-rollback regression.
- `cargo test --offline -p agentic-server-core --lib
storage::local_files::tests`: 5 passed, including absent-directory and
deterministic staging cleanup.
- `cargo test --offline -p agentic-server-core --lib
storage::schema::tests`: 6 passed, 4 PostgreSQL tests ignored locally;
PostgreSQL CI exercises the final schema.
- `cargo clippy --offline --all-targets -- -D warnings`, `cargo fmt --
--check`, and repository pre-commit hooks: passed.
- The official OpenAI Python SDK HTTP integration test is in #485 and
passed locally against the rebuilt final stack.

---------

Signed-off-by: Francisco Javier Arceo <4163062+franciscojavierarceo@users.noreply.github.com>
@franciscojavierarceo
franciscojavierarceo force-pushed the codex/file-search-responses-protocol branch 2 times, most recently from 5e2b78b to 2e628a1 Compare November 4, 2026 00:21
@franciscojavierarceo
franciscojavierarceo force-pushed the codex/file-search-responses-protocol branch from e7c0c83 to 1984b9f Compare November 4, 2026 11:51
@franciscojavierarceo
franciscojavierarceo force-pushed the codex/file-search-responses-protocol branch from 1984b9f to 0e2e73e Compare November 4, 2026 11:58
@franciscojavierarceo
franciscojavierarceo force-pushed the codex/file-search-responses-protocol branch from 0e2e73e to 8183d07 Compare November 5, 2026 03:20
@franciscojavierarceo
franciscojavierarceo force-pushed the codex/file-search-responses-protocol branch from 8183d07 to 9e919b4 Compare November 5, 2026 05:05
@franciscojavierarceo
franciscojavierarceo force-pushed the codex/file-search-responses-protocol branch from 9e919b4 to 1420da9 Compare November 6, 2026 02:09
@franciscojavierarceo
franciscojavierarceo requested a balanced review from Copilot November 6, 2026 02:15
@franciscojavierarceo
franciscojavierarceo force-pushed the codex/file-search-responses-protocol branch from 1420da9 to f0478c3 Compare November 6, 2026 02:27

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.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

4 open issues
What changed in this PR

Adds typed file_search request/response and citation models, wires them through strict event/stream lifecycle handling, and exposes the new shapes in OpenAPI.

Changes:

  • Introduces typed file_search tool params plus typed output items (file_search_call) and file_citation annotations.
  • Extends streaming/event validation, accumulator lifecycle, and response budgeting to preserve and account for file-search items.
  • Updates OpenAPI schemas and tests to validate the new public wire shapes.
File Description
crates/​agentic-server/​src/​openapi.rs Registers file-search schemas and updates OpenAPI validation tests.
crates/​agentic-server-core/​tests/​support/​mod.rs Updates test helpers to handle FileSearchCall output items.
crates/​agentic-server-core/​tests/​relayed_stream_validation_test.rs Adds regression test ensuring file citations survive JSON + SSE relay decoding.
crates/​agentic-server-core/​src/​types/​tools/​params/​file_search.rs New typed file-search tool parameter struct + OpenAPI schema helper.
crates/​agentic-server-core/​src/​types/​tools/​params.rs Moves FileSearchToolParam into its own module; adds strict schema handling and tests.
crates/​agentic-server-core/​src/​types/​io/​tools.rs Adds file_search tool choice/selector normalization + OpenAPI schema updates.
crates/​agentic-server-core/​src/​types/​io/​output/​history.rs Extracts history-related logic for output items; includes file-search handling.
crates/​agentic-server-core/​src/​types/​io/​output/​file_search.rs New public types for file_search_call results + file_citation annotation.
crates/​agentic-server-core/​src/​types/​io/​output.rs Adds file_search_call output item variant + schema and lifecycle integrations.
crates/​agentic-server-core/​src/​types/​io/​mod.rs Re-exports new file-search output/citation types.
crates/​agentic-server-core/​src/​types/​io/​item/​item_id.rs Adds id/prefix handling for FileSearchCall.
crates/​agentic-server-core/​src/​types/​io/​input/​file_text.rs Tweaks error message to clarify Responses input limitations.
crates/​agentic-server-core/​src/​executor/​translate/​context.rs Ensures async-call marking logic includes file-search output items.
crates/​agentic-server-core/​src/​executor/​response_budget/​file_search.rs Adds retained-size accounting for file-search output items/results.
crates/​agentic-server-core/​src/​executor/​response_budget.rs Wires file-search retained-size accounting into overall budgeting.
crates/​agentic-server-core/​src/​executor/​item_lifecycle.rs Includes FileSearchCall in lifecycle classification.
crates/​agentic-server-core/​src/​executor/​gateway/​lifecycle.rs Emits synthetic file-search start/completion events for gateway streaming.
crates/​agentic-server-core/​src/​executor/​gateway/​admission.rs Adds refusal behavior for file-search output items.
crates/​agentic-server-core/​src/​executor/​accumulator/​tests.rs Adds strict/lenient lifecycle tests for file-search streaming.
crates/​agentic-server-core/​src/​executor/​accumulator/​slot.rs Includes FileSearchCall in slot-map handling.
crates/​agentic-server-core/​src/​executor/​accumulator/​active/​size.rs Extracts retained-size logic for active streaming items; includes file-search.
crates/​agentic-server-core/​src/​executor/​accumulator/​active.rs Adds FileSearchCall as an active streaming item variant.
crates/​agentic-server-core/​src/​events/​validate.rs Validates file-search event types map to the correct item type.
crates/​agentic-server-core/​src/​events/​types/​item.rs Adds file_search_call SSE item type.
crates/​agentic-server-core/​src/​events/​types.rs Adds response.file_search_call.in_progress event type + mappings and tests.
crates/​agentic-server-core/​src/​events/​normalize.rs Ensures file-search events route to the right payload normalization.
crates/​agentic-llm-d/​tests/​split_execution_integration.rs Adjusts integration test stream to use a future/unknown tool call item type.

Review effort: Lite

Comment thread crates/agentic-server/src/openapi.rs Resolved
@franciscojavierarceo
franciscojavierarceo force-pushed the codex/file-search-responses-protocol branch from b4485be to 5179091 Compare November 6, 2026 03:45
franciscojavierarceo added a commit that referenced this pull request Nov 6, 2026
## Summary

Add a `FileSearchRepository` backed by sqlite-vec for semantic cosine
retrieval on a file-backed SQLite database. It keeps portable SQL
metadata and published-chunk visibility, normalizes embeddings before
indexing, and records a provider identity so stores cannot silently
switch indexes. Keyword and hybrid search continue through the bounded
exact ranker. SQLite metadata and vector rows now publish in one
transaction, so concurrent replicas cannot remove a winning attachment.
Orphan cleanup rechecks rows under a writer lock and immediately
reclaims stale rows and per-store tables; search skips empty stores and
unpublished rows, and uses bounded exact ranking when sqlite-vec's
4,096-candidate limit is exceeded. The loadable extension path is
deployment-controlled; #485 wires its configuration.

BEIR-sized corpus testing exposed the old 64 MiB serialized-corpus limit
and top-k edge cases with duplicate chunks and near ties. This branch
keeps the default exact-search limit at 64 MiB and gives sqlite-vec a
256 MiB limit, validates vector-index coverage, and adaptively fetches
candidates to preserve the requested document count.

Review order: #483 → #484 → #485 → #486 → #34. PostgreSQL vector
indexing remains in #278.

## Test Plan

- Review follow-up: the portable vector map is migration-owned;
extension-specific index SQL lives in
`storage/file_search/sqlite_vec_index.rs`, publication has one
implementation, and writes use `BEGIN IMMEDIATE` without a process-wide
mutex. Focused storage and schema tests passed; the original 3
sqlite-vec integration tests passed, including immediate orphan-row and
orphan-table recovery.
- `SQLITE_VEC_EXTENSION_PATH=/tmp/vec0.so cargo test -p
agentic-server-core --test sqlite_vec_repository -- --ignored`: 4 passed
in CI with sqlite-vec 0.1.9, covering publication, extreme finite
embeddings, restart, filtered and duplicate-chunk search, deletion,
concurrent attachment conflicts, unpublished rows, empty stores, and
searches beyond the 4,096-candidate limit.
- `cargo test -p agentic-server-core --lib`: 1,370 passed, 6 ignored.
`cargo clippy -p agentic-server-core --all-targets -- -D warnings`,
`cargo fmt -- --check`, and pre-commit hooks passed.
- Maral's capacity regression: a new sqlite-vec integration test
publishes a serialized store above 64 MiB, then exercises keyword,
hybrid, and filtered semantic searches against it. The Rust CI run
passed this test and the full ignored sqlite-vec integration suite.
- CI downloads the pinned Linux sqlite-vec release asset, verifies its
SHA-256, and runs the ignored integration tests.

### BEIR retrieval evidence (2026-11-03, benchmark commit `fd6b574`)

Compared the provider's semantic search with [OGX's published
vector-mode
results](https://ogx-ai.github.io/docs/building_applications/rag_benchmarks).
Used the official BEIR **test** corpora, queries, and relevance
judgments, title + body text, `cl100k_base` 512-token chunks with
128-token overlap, `nomic-ai/nomic-embed-text-v1.5` embeddings, and
top-10 document scoring with `pytrec_eval`. The 512/128 setting comes
from [OGX's documented benchmark
config](https://ogx-ai.github.io/docs/building_applications/rag_benchmarks),
but this was a local preparation, not [OGX's full API ingestion
path](https://github.com/ogx-ai/ogx/tree/12a51077cdd4f89b16df5eb6de12a88b0bfcf7df/benchmarking/rag).
A local harness loaded the prepared chunks into the actual
`SqliteVecRepository` and called its `search` method for every judged
query. sqlite-vec 0.1.9 ran on CPU.

| Dataset | Documents / chunks / queries | nDCG@10 OGX vector → ours |
Recall@10 OGX vector → ours | MAP@10 OGX vector → ours |
| --- | ---: | ---: | ---: | ---: |
| NFCorpus | 3,633 / 4,921 / 323 | 0.3106 → **0.3101** | 0.1482 → 0.1462
| 0.1153 → 0.1137 |
| SciFact | 5,183 / 6,604 / 300 | 0.6943 → **0.6968** | 0.8369 → 0.8296
| 0.6442 → 0.6496 |
| ArguAna | 8,674 / 9,278 / 1,406 | 0.3765 → **0.3928** | 0.7610 →
0.8037 | 0.2542 → 0.2624 |

All 2,029 searches succeeded. An independent exhaustive NumPy cosine
calculation on the same embeddings gave identical aggregate nDCG@10 for
SciFact and ArguAna; NFCorpus differed by 0.000084. This validates
sqlite-vec against an exact-cosine oracle on our prepared vectors. The
proximity to OGX's published vector scores is indicative, not exact
parity: OGX's current batch ingestion path applies whitespace cleaning
and appears to use 800/400-token file-processor defaults for automatic
chunking, despite the benchmark config documenting 512/128. The
published run does not include materialized chunk/embedding or per-query
result fixtures, so its exact input and rankings cannot yet be compared.

**Scope and limit:** This is a repository-level semantic retrieval test
with prepared embeddings; it does not exercise the Files/Vector Stores
HTTP APIs, automatic ingestion, hybrid reranking, Responses
`file_search`, or end-to-end answer generation. OGX's fourth BEIR
dataset, FiQA, produces 63,129 chunks and exceeds this branch's
10,000-chunk corpus bound, so full four-dataset parity remains
unvalidated.

---------

Signed-off-by: Francisco Javier Arceo <4163062+franciscojavierarceo@users.noreply.github.com>
@franciscojavierarceo
franciscojavierarceo force-pushed the codex/file-search-responses-protocol branch 2 times, most recently from fa49123 to a222dd5 Compare November 6, 2026 04:38
@franciscojavierarceo
franciscojavierarceo force-pushed the codex/file-search-responses-protocol branch 2 times, most recently from 4ef8354 to 09843d7 Compare November 6, 2026 13:41
@franciscojavierarceo
franciscojavierarceo force-pushed the codex/file-search-responses-protocol branch from 09843d7 to 04a5a1e Compare November 6, 2026 18:48
franciscojavierarceo added a commit that referenced this pull request Nov 7, 2026
## Summary

Expose the Files and vector-store upload, lifecycle, attachment, and search routes over the pluggable core service. Add server configuration for local file bytes, embeddings, and an optional trusted sqlite-vec extension path. Wire the routes into authentication and OpenAPI, with deployment documentation. Reconcile abandoned blob cleanup intents at startup and periodically while the gateway runs. Local routes enforce the configured API key and request-body ceiling; deletion tolerates a cleanup marker already removed by reconciliation, and startup logs provider setup diagnostics. This layer does not register the Responses `file_search` built-in tool.

Review order: #485 → #486 → #34. #483 and #484 are merged. PostgreSQL vector indexing remains in #278.

## Test Plan

- `cargo test -p agentic-server --test file_search_http_test`: 6 passed, including missing, incorrect, and valid API-key cases and configured multipart/JSON body limits.
- `cargo test -p agentic-server-core --test file_search_service`: 32 passed, 1 ignored, including the deletion cleanup-marker race regression.
- Full workspace `cargo test --offline` passed on the restacked #34 head containing these fixes.
- `cargo test -p agentic-server --lib openapi`: 19 passed.
- `cargo test -p agentic-server --bin agentic-server config_file::tests`: 37 passed.
- `cargo build --offline -p agentic-server --bin agentic-server` and `cargo clippy --offline -p agentic-server -p agentic-server-core --all-targets -- -D warnings`: passed.
- `openai==3.8.0` client E2E: upload, list/retrieve/content, vector-store attachment, filtered search, detach, and deletion passed against the rebuilt local gateway. The Rust CI job runs this script.
- Formatting and repository pre-commit hooks passed on signed-off commits. CI also runs PostgreSQL file-search persistence, sqlite-vec, and bounded PDF ingestion checks.
- OpenAI reference cassette parity for Files and vector-store routes remains unverified: this environment has no OpenAI API credential. The focused local gateway HTTP suite and OpenAI SDK client E2E cover the route lifecycle; no reference cassette was hand-authored.


Signed-off-by: Francisco Javier Arceo <4163062+franciscojavierarceo@users.noreply.github.com>
Signed-off-by: Francisco Javier Arceo <4163062+franciscojavierarceo@users.noreply.github.com>
Signed-off-by: Francisco Javier Arceo <4163062+franciscojavierarceo@users.noreply.github.com>
Signed-off-by: Francisco Javier Arceo <4163062+franciscojavierarceo@users.noreply.github.com>
@franciscojavierarceo
franciscojavierarceo force-pushed the codex/file-search-responses-protocol branch from b1825cc to 8957cc3 Compare November 7, 2026 00:36
@franciscojavierarceo
franciscojavierarceo changed the base branch from codex/file-search-http to main November 7, 2026 00:36
Signed-off-by: Francisco Javier Arceo <4163062+franciscojavierarceo@users.noreply.github.com>
@franciscojavierarceo
franciscojavierarceo force-pushed the codex/file-search-responses-protocol branch from 8957cc3 to 3d90f65 Compare November 7, 2026 00:40
@franciscojavierarceo

Copy link
Copy Markdown
Collaborator Author

@copilot review

Copilot AI commented Nov 7, 2026

Copy link
Copy Markdown

@copilot review

Review findings:

[P2] crates/agentic-server-core/src/types/io/output/file_search.rs:32 — File-search result fields are too strict. The official OpenAI Responses SDK models attributes, file_id, filename, score, and text as optional, but this struct requires all five. A valid result omitting any one of them fails typed deserialization and can reject a strict JSON/SSE response. Model these as optional/defaulted fields and add a partial-result regression.

[P2] crates/agentic-server-core/src/types/io/output/file_search.rs:56 — FileCitation is not wired into output annotations. OutputTextContent.annotations remains Option<Vec<serde_json::Value>> (output.rs:38), so response decoding and OpenAPI do not expose this type through the actual annotation field. Add an extensible typed annotation shape that includes file citations and test the generated schema.

[P2] crates/agentic-server-core/tests/relayed_stream_validation_test.rs:38 — The citation relay test hand-builds JSON/SSE; there is no recorded file-search response scenario. Because this PR adds response wire types and lifecycle events, add a recorder-generated file-search scenario and replay assertion for the observed JSON/SSE shape.

Coverage: reviewed the full PR diff against main and the cassette workflow. I attempted the focused Cargo tests, but they waited on the build-directory lock; I stopped that run, so I’m not claiming test results.

## Summary

Register and execute the Responses `file_search` built-in tool through the existing typed gateway tool loop. The handler searches the configured file-search service, keeps retrieved passages private for model continuation, exposes `file_search_call.results` only when requested, and restores typed file citations in completed and streamed output. This is the top layer of a smaller stack; the Files and vector-store APIs, provider contracts, SQLite-vec adapter, and protocol types are reviewed below it.

Review order: #483 (storage contracts and portable SQL/local providers) → #484 (SQLite-vec) → #485 (Files and vector-store HTTP APIs and provider configuration) → #486 (Responses protocol and stream lifecycle) → this PR. The existing PostgreSQL/pgvector follow-up is #278; later retrieval and lifecycle work remains stacked above it.

The storage service accepts injected `FileSearchRepository` and `FileBlobStore` implementations. The lower layers configure SQLite-vec for semantic retrieval; Milvus and remote blob stores remain adapter extension points.

## Test Plan

- `cargo test -p agentic-server-core --test file_search_tool_test`: 6 passed, covering blocking and streaming calls, citations, opt-in results, mixed client tools, failures, and selector validation.
- `cargo test -p agentic-server-core --lib`: 1,385 passed, 6 ignored on the corrected, restacked file tree. Full workspace `cargo test --offline` also passed on that tree.
- Changed-crate all-target Clippy with warnings denied, formatting, and all-file pre-commit hooks passed on the corrected, restacked file tree.
- The failing Rust CI check at `33b20ba` came from an output-item test fixture missing the typed `logprobs: []` field. The corrected citation projection test passes locally; CI is running on the cleanly restacked head.
- SQLite-vec review fixes from #484 are carried through the stack: atomic publication, safe orphan cleanup, empty/unpublished row handling, and exact fallback above sqlite-vec's candidate limit.

Signed-off-by: Francisco Javier Arceo <arceofrancisco@gmail.com>
@franciscojavierarceo franciscojavierarceo changed the title feat: type Responses file search protocol and stream lifecycle feat: add Responses file_search protocol and execution Nov 8, 2026
Signed-off-by: Francisco Javier Arceo <4163062+franciscojavierarceo@users.noreply.github.com>

Copy link
Copy Markdown
Collaborator Author

fixed the first two findings in 45c729f: all five optional result fields now round-trip when omitted, and OutputTextContent.annotations uses a typed, extensible OutputAnnotation with FileCitation. added partial-result, generated OpenAPI, and JSON/SSE typed-decoding regressions.

the recorder-generated file-search cassette is still outstanding. this environment has no live provider credential, and the cassette workflow requires recording a real response rather than writing YAML by hand. the existing gateway execution integration tests use a mocked upstream and cover blocking/streaming search, citations, opt-in results, and continuation; i’m not treating those as a recorded OpenAI wire capture.

@franciscojavierarceo
franciscojavierarceo merged commit 432b80d into main Nov 8, 2026
17 checks passed
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.

3 participants