{"openapi":"3.1.0","info":{"title":"agentic-rag-research","description":"Agentic RAG research agent: a bounded **plan → retrieve → critique** loop that answers\nwith citations resolving to retrieved passages, or refuses and says why.\n\n`POST /v1/research` runs the loop. Its default retriever is a deterministic in-process\nfixture over a small committed corpus: it makes the loop's control flow, budget\naccounting, trace and refusal path exercisable with no credential and no network, and it\nsupports **no claim whatsoever about retrieval or answer quality**. Selecting\n`retriever: \"http\"` requires a configured production-rag instance and fails with\n`capability_missing` when there is none — the fixture is never substituted for a backend\nthat was asked for explicitly.\n\nFinished runs may be fetched from `GET /v1/runs/{id}` while this process still holds\nthem. Download `GET /v1/runs/{id}/run.json` to keep a full artifact after a recycle.\n`POST /v1/runs/compare` diffs two **payloads** (not server ids) — files are the source of\ntruth when the in-memory store is empty.\n\nEvery response carries `X-Request-ID`, echoed from the caller when it is safe to echo and\nminted otherwise. Every failure is `{\"error\", \"error_type\", \"request_id\"}`.","license":{"name":"MIT","identifier":"MIT"},"version":"1.0.0"},"paths":{"/health":{"get":{"tags":["ops"],"summary":"Liveness probe","description":"Report that the process is alive.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}},"/metrics":{"get":{"tags":["ops"],"summary":"Prometheus exposition for this process","description":"Return what this process has counted since it started.\n\nCounters are process-local and carry no question text, no correlation id and no\nconfiguration value — a scrape target that leaks the request it served is a log with\na dashboard on it.\n\nArgs:\n    request: The incoming request, read for the application's registry.\n\nReturns:\n    The exposition, in the format a Prometheus scrape expects.","operationId":"metrics_metrics_get","responses":{"200":{"description":"`process_up`, `requests_total` by method/route/status, `research_total` by terminal status, and `research_steps_used_total`.","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/v1/research":{"post":{"tags":["research"],"summary":"Run one bounded research loop","description":"Research a question under a step budget and report how the run ended.\n\nA run that refuses or exhausts its budget is a **200**: it is a completed run with an\nhonest terminal status, not a failed request. Only a request this service cannot\nserve at all is a non-2xx.\n\nArgs:\n    payload: The validated request.\n    request: The incoming request, read for its correlation id.\n    response: The outgoing response, whose correlation header is set here.\n    service: The application's research service.\n\nReturns:\n    The finished run, correlated to this request.","operationId":"research_v1_research_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResearchRequest"}}},"required":true},"responses":{"200":{"description":"The finished run: how it ended, the report, the citations its markers resolve to, the steps it spent, the full trace, and the correlation id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResearchResponse"}}}},"422":{"description":"The request was rejected: a blank or oversized question, a bound outside its range, an unknown field, or an unknown retriever.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"`capability_missing` when the requested retriever is not configured in this deployment, or `backend_unavailable` when a configured backend failed the call. The free retriever is never substituted for one that was asked for explicitly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"A defect in this service. Carries no traceback and no request data.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/runs/{run_id}":{"get":{"tags":["research"],"summary":"Fetch one finished run by its correlation id","description":"Return one finished run.\n\nThe artifact is what makes a run reviewable after the response that produced it is\ngone: an id can be pasted into an issue, and whoever opens it reads the same report,\nthe same citations and the same trace — not a re-run that is merely expected to\nmatch.\n\n``stop_reason`` is a field here rather than something to dig out of the terminal\n``stop`` event. Everything else is exactly what the run recorded.\n\nArgs:\n    run_id: Correlation id of the run.\n    request: The incoming request, read for its correlation id.\n    response: The outgoing response, whose correlation header is set here.\n    service: The application's research service.\n\nReturns:\n    The stored run.","operationId":"get_run_v1_runs__run_id__get","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"responses":{"200":{"description":"The stored run: the question, the backend, how it ended, why it stopped, the report, the citations, the notes it relied on, the steps it spent against its budget, and the full trace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunArtifact"}}}},"404":{"description":"`not_found`: this process does not hold that run. The store is bounded, in-memory and not shared between instances, so an id can be absent because it never existed, because it was evicted, or because another instance served it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"A defect in this service. Carries no traceback and no request data.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/runs/{run_id}/trace.json":{"get":{"tags":["research"],"summary":"Download the trace of one finished run","description":"Serve one stored run's trace as a file.\n\nThis is the contract the result page's download button uses. The file is the run\nthat was performed, read from the store — not a second run of the same question that\nis assumed to be identical. The distinction only shows up on the day it stops being\ntrue, which is the day it matters.\n\nArgs:\n    run_id: Correlation id of the run.\n    request: The incoming request, read for its correlation id.\n    response: The outgoing response, whose correlation and download headers are set.\n    service: The application's research service.\n\nReturns:\n    Every event of the run, oldest first.","operationId":"get_run_trace_v1_runs__run_id__trace_json_get","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"responses":{"200":{"description":"That run's trace, oldest event first and ending in 'stop', served as a JSON attachment.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TraceEvent"},"title":"Response Get Run Trace V1 Runs  Run Id  Trace Json Get"}}}},"404":{"description":"`not_found`: this process does not hold that run. The store is bounded, in-memory and not shared between instances, so an id can be absent because it never existed, because it was evicted, or because another instance served it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"A defect in this service. Carries no traceback and no request data.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/runs/{run_id}/run.json":{"get":{"tags":["research"],"summary":"Download one finished run as a JSON file","description":"Serve one stored run as a downloadable file.\n\nThe file is the durable hand-off for compare: after a serverless recycle the id is\ngone, but the JSON still carries every field ``POST /v1/runs/compare`` needs.\n\nArgs:\n    run_id: Correlation id of the run.\n    request: The incoming request, read for its correlation id.\n    response: The outgoing response, whose correlation and download headers are set.\n    service: The application's research service.\n\nReturns:\n    The stored artifact.","operationId":"get_run_artifact_file_v1_runs__run_id__run_json_get","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"responses":{"200":{"description":"The full stored artifact — question, stop reason, steps, notes, citations, report, and trace — served as a JSON attachment a reviewer can keep after the in-memory id is gone.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunArtifact"}}}},"404":{"description":"`not_found`: this process does not hold that run. The store is bounded, in-memory and not shared between instances, so an id can be absent because it never existed, because it was evicted, or because another instance served it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"A defect in this service. Carries no traceback and no request data.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/runs/compare":{"post":{"tags":["research"],"summary":"Compare two finished-run payloads (not server ids)","description":"Diff two complete run artifacts supplied in the body.\n\nIds are deliberately not accepted. The store is bounded, in-memory, and not shared\nacross serverless instances; a compare keyed on ids would 404 the day a reviewer\nneeded it most. The payloads — usually two downloaded ``run-*.json`` files — are the\nsource of truth.\n\nArgs:\n    payload: Left and right finished-run bodies.\n    request: The incoming request, read for its correlation id.\n    response: The outgoing response, whose correlation header is set here.\n\nReturns:\n    The typed diff, byte-stable for a given pair of inputs.","operationId":"compare_run_payloads_v1_runs_compare_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompareRequest"}}},"required":true},"responses":{"200":{"description":"A typed field-level diff of stop reason, steps, notes, citations, and the other audited fields. Empty when the payloads match. Never looks up a run by id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompareResponse"}}}},"422":{"description":"The request was rejected: a blank or oversized question, a bound outside its range, an unknown field, or an unknown retriever.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"A defect in this service. Carries no traceback and no request data.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/experiments/pack":{"post":{"tags":["research"],"summary":"Build an experiment pack from two run payloads + policy","description":"Assemble a pack from two finished-run payloads.\n\nIf ``compare`` is supplied it must match a recomputed payload diff; otherwise the\nserver computes the diff. The returned ``manifest.pack_hash`` is stable for the\nsame policy, runs, compare, and experiment records.","operationId":"build_experiment_pack_v1_experiments_pack_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PackLoadRequest"}}},"required":true},"responses":{"200":{"description":"Pack manifest, policy, both runs, compare diff, and experiment records. Never resolves server ids; payloads are the source of truth.","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Build Experiment Pack V1 Experiments Pack Post"}}}},"422":{"description":"The request was rejected: a blank or oversized question, a bound outside its range, an unknown field, or an unknown retriever.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"A defect in this service. Carries no traceback and no request data.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/research/trace":{"post":{"tags":["research"],"summary":"Run one bounded research loop and download only its trace","description":"Export the trace of one run as a file.\n\nTakes the same request as ``POST /v1/research`` and answers with the same events\nthat run's ``trace`` carries — this is a projection of the existing response, not a\nsecond contract, and no event means anything here that it does not mean there.\n\nThere is deliberately no stored \"last trace\" to fetch. A server-side slot holding the\nmost recent run would be shared mutable state between requests: two callers exporting\nat once would race for it, and the second would download the first one's evidence.\nThe run is performed for the export instead, which on the free path is deterministic\n— the same question under the same budget produces the same trace, so the file\nmatches the page it was downloaded from.\n\nArgs:\n    payload: The validated request.\n    request: The incoming request, read for its correlation id.\n    response: The outgoing response, whose correlation and download headers are set.\n    service: The application's research service.\n\nReturns:\n    Every event of the run, oldest first.","operationId":"research_trace_v1_research_trace_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResearchRequest"}}},"required":true},"responses":{"200":{"description":"The run's trace, oldest event first and ending in 'stop' — the same list `POST /v1/research` returns under `trace`, served as a JSON attachment.","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/TraceEvent"},"type":"array","title":"Response Research Trace V1 Research Trace Post"}}}},"422":{"description":"The request was rejected: a blank or oversized question, a bound outside its range, an unknown field, or an unknown retriever.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"`capability_missing` when the requested retriever is not configured in this deployment, or `backend_unavailable` when a configured backend failed the call. The free retriever is never substituted for one that was asked for explicitly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"A defect in this service. Carries no traceback and no request data.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/research/stream":{"get":{"tags":["research"],"summary":"Run one bounded research loop and watch it as it happens","description":"Stream one run's events, then its outcome.\n\nThe parameters are the fields of ``ResearchRequest`` and are validated by that model\nrather than restated here, so the stream cannot accept a request the JSON route\nwould reject.\n\nArgs:\n    request: The incoming request, read for its correlation id and registry.\n    service: The application's research service.\n    question: The research question.\n    max_steps: Hard cap on retrieval steps for this run.\n    top_k: Upper bound on passages one retrieval step returns.\n    retriever: Backend to serve the run.\n\nReturns:\n    The event stream.\n\nRaises:\n    RequestInvalid: The parameters do not describe a runnable request. This is the\n        one failure reported as a status code rather than an event, because it is\n        decided before any byte of the stream is written.","operationId":"research_stream_v1_research_stream_get","parameters":[{"name":"question","in":"query","required":true,"schema":{"type":"string","description":"The research question.","title":"Question"},"description":"The research question."},{"name":"max_steps","in":"query","required":false,"schema":{"type":"integer","description":"Hard cap on retrieval steps.","default":4,"title":"Max Steps"},"description":"Hard cap on retrieval steps."},{"name":"top_k","in":"query","required":false,"schema":{"type":"integer","description":"Passages one step may return.","default":5,"title":"Top K"},"description":"Passages one step may return."},{"name":"retriever","in":"query","required":false,"schema":{"$ref":"#/components/schemas/RetrieverChoice","description":"Retrieval backend to serve the run.","default":"fake"},"description":"Retrieval backend to serve the run."}],"responses":{"200":{"description":"A `text/event-stream`: one `trace` event per recorded step, in order, then exactly one `done` carrying the terminal status, the stop reason and the id the run is stored under — or one `error` carrying the standard envelope."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"Citation":{"properties":{"marker":{"type":"integer","minimum":1.0,"title":"Marker","description":"The number printed as [n] in the report."},"source_path":{"type":"string","title":"Source Path","description":"Corpus-relative path of the cited document."},"chunk_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chunk Id","description":"Stable id of the cited chunk."},"snippet":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Snippet","description":"The cited text as it was printed."},"start_line":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Start Line","description":"Unused for prose corpora."},"end_line":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"End Line","description":"Unused for prose corpora."}},"type":"object","required":["marker","source_path"],"title":"Citation","description":"One marker in a report, resolved to the passage it points at.\n\nThe shape is the portfolio-wide citation object: ``start_line`` and\n``end_line`` stay ``None`` here because a prose corpus has no line spans to\nreport, and a field filled with a plausible-looking number is worse than one\nleft empty. The code-intelligence project in the series fills them."},"CompareRequest":{"properties":{"left":{"$ref":"#/components/schemas/RunArtifact","description":"First finished run, as a complete payload."},"right":{"$ref":"#/components/schemas/RunArtifact","description":"Second finished run, as a complete payload."}},"additionalProperties":false,"type":"object","required":["left","right"],"title":"CompareRequest","description":"Two finished-run payloads to place next to each other.\n\nEither side may come from a download, a prior ``POST /v1/research`` body\npromoted to a :class:`RunArtifact`, or a hand-edited fixture. Unknown fields\nare rejected so a misspelled key cannot silently drop half the comparison."},"CompareResponse":{"properties":{"identical":{"type":"boolean","title":"Identical","description":"True when every compared field matches."},"diffs":{"items":{"$ref":"#/components/schemas/FieldDiff"},"type":"array","title":"Diffs","description":"Ordered field differences; empty when identical.","default":[]},"left_request_id":{"type":"string","title":"Left Request Id","description":"Correlation id carried by the left payload."},"right_request_id":{"type":"string","title":"Right Request Id","description":"Correlation id carried by the right payload."}},"type":"object","required":["identical","left_request_id","right_request_id"],"title":"CompareResponse","description":"Typed field-level diff of two finished runs.\n\n``identical`` is true only when every compared field matches. ``diffs`` is\nempty in that case and ordered by :data:`_COMPARED_FIELDS` otherwise, so the\nresponse is byte-stable for a given pair of inputs."},"ErrorResponse":{"properties":{"error":{"type":"string","title":"Error","description":"What went wrong, in one sentence written by this service."},"error_type":{"$ref":"#/components/schemas/ErrorType","description":"Stable slug a caller may branch on."},"request_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Request Id","description":"Correlation id of the request that failed, when one was established."}},"type":"object","required":["error","error_type"],"title":"ErrorResponse","description":"The one shape every failure is reported in.\n\nFrozen, and identical across HTTP and the CLI: the CLI prints this object verbatim\nwhen a run cannot be produced, so a script that reads one reads the other."},"ErrorType":{"type":"string","enum":["validation_error","capability_missing","backend_unavailable","internal_error","not_found","method_not_allowed","http_error"],"title":"ErrorType","description":"The stable slugs this service is allowed to answer with.\n\nClosed on purpose: a caller branches on these, and a slug invented at a call site is\na slug nobody can branch on until they have seen it in production."},"ExperimentRecord":{"properties":{"id":{"type":"string","minLength":1,"title":"Id","description":"Stable experiment or request id."},"seed":{"type":"integer","minimum":0.0,"title":"Seed","default":0},"question":{"type":"string","minLength":1,"title":"Question"},"budget":{"$ref":"#/components/schemas/ToolBudget"},"note_ids":{"items":{"type":"string"},"type":"array","title":"Note Ids","default":[]},"status":{"type":"string","minLength":1,"title":"Status"},"stop_reason":{"type":"string","enum":["evidence_sufficient","no_evidence","insufficient_evidence","budget_spent","tool_budget_spent"],"title":"Stop Reason"},"tool_calls":{"additionalProperties":{"type":"integer"},"type":"object","title":"Tool Calls"},"pack_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pack Hash","description":"SHA-256 of the experiment pack when this run is pack-bound."},"retriever":{"type":"string","title":"Retriever","default":"fake"},"trace_event_count":{"type":"integer","minimum":0.0,"title":"Trace Event Count","default":0}},"additionalProperties":false,"type":"object","required":["id","question","budget","status","stop_reason"],"title":"ExperimentRecord","description":"One lab experiment: the durable summary of a finished research run.\n\nFields match SEASON Month 1: ``id``, ``seed``, ``budget``, note ids,\n``stop_reason``, ``pack_hash``. Status and tool-call counts are included so a\nscorecard can read the record without re-opening the full artifact."},"FieldDiff":{"properties":{"field":{"type":"string","title":"Field","description":"Canonical field name on the run artifact."},"left":{"title":"Left","description":"Value from the left payload."},"right":{"title":"Right","description":"Value from the right payload."}},"type":"object","required":["field","left","right"],"title":"FieldDiff","description":"One field that differs between the two payloads."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HealthResponse":{"properties":{"status":{"type":"string","const":"ok","title":"Status","description":"Constant marker; the HTTP status code carries the real signal.","default":"ok"},"service":{"type":"string","title":"Service","description":"Logical service name."},"version":{"type":"string","title":"Version","description":"Installed package version."}},"type":"object","required":["service","version"],"title":"HealthResponse","description":"Liveness payload: the process is up and can serve requests.\n\nDeliberately free of dependency state. A liveness probe that fails because a\ndownstream is unavailable makes the orchestrator restart a healthy process, which\ndoes not fix the downstream. Dependency state belongs in a readiness route, and that\nroute arrives with the first dependency this service cannot run without — the\nretrieval backend is not one, because the free path needs none."},"Note":{"properties":{"id":{"type":"string","minLength":1,"title":"Id","description":"Positional id, stable within one run."},"claim":{"type":"string","minLength":1,"title":"Claim","description":"What is relied on, lifted verbatim."},"source":{"type":"string","title":"Source","description":"Corpus-relative path the claim came from. Never resolved."},"context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Context","description":"Heading ancestry the claim sits under, when the backend supplied one."},"citation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Citation","description":"Chunk id the claim was lifted from. None when no passage backs it."}},"type":"object","required":["id","claim","source"],"title":"Note","description":"One claim the run is relying on, and what backs it.\n\nFrozen for the same reason a passage is: a note that can be edited after the\nfact is a note whose citation cannot be checked against anything."},"PackLoadRequest":{"properties":{"policy":{"$ref":"#/components/schemas/PackPolicy"},"left":{"$ref":"#/components/schemas/RunArtifact"},"right":{"$ref":"#/components/schemas/RunArtifact"},"compare":{"anyOf":[{"$ref":"#/components/schemas/CompareResponse"},{"type":"null"}]},"experiments":{"items":{"$ref":"#/components/schemas/ExperimentRecord"},"type":"array","title":"Experiments"}},"additionalProperties":false,"type":"object","required":["policy","left","right"],"title":"PackLoadRequest","description":"API body: either inline JSON parts or a base64 zip (UI uses inline parts)."},"PackPolicy":{"properties":{"retriever":{"type":"string","title":"Retriever","default":"fake"},"max_steps":{"type":"integer","maximum":20.0,"minimum":1.0,"title":"Max Steps"},"max_calls":{"additionalProperties":{"type":"integer"},"type":"object","title":"Max Calls"},"season_tag":{"type":"string","title":"Season Tag","default":"v1.0"},"billed":{"type":"boolean","title":"Billed","default":false}},"additionalProperties":false,"type":"object","required":["max_steps"],"title":"PackPolicy","description":"Budgets and tool caps that governed the packed runs."},"ResearchRequest":{"properties":{"question":{"type":"string","maxLength":8000,"minLength":1,"title":"Question","description":"The research question. Trimmed; may not be empty."},"max_steps":{"type":"integer","maximum":20.0,"minimum":1.0,"title":"Max Steps","description":"Hard cap on retrieval steps for this run.","default":4},"top_k":{"type":"integer","maximum":50.0,"minimum":1.0,"title":"Top K","description":"Upper bound on the passages one retrieval step returns.","default":5},"retriever":{"$ref":"#/components/schemas/RetrieverChoice","description":"Retrieval backend to serve the run. 'fake' is deterministic, in-process and contacts nothing; 'http' requires a configured production-rag instance and fails with capability_missing when there is none.","default":"fake"},"max_tool_calls":{"anyOf":[{"additionalProperties":{"type":"integer"},"type":"object"},{"type":"null"}],"title":"Max Tool Calls","description":"Optional per-tool call caps (retrieve, search_notes, lexicon). When a tool is exhausted the run stops with stop_reason tool_budget_spent."}},"additionalProperties":false,"type":"object","required":["question"],"title":"ResearchRequest","description":"One research run, as a caller asks for it.\n\nWhitespace is stripped before validation, so a question of three spaces is empty and\nis rejected rather than becoming a run with nothing to research.","examples":[{"max_steps":3,"question":"Why use citations in RAG?","retriever":"fake","top_k":5}]},"ResearchResponse":{"properties":{"status":{"$ref":"#/components/schemas/ResearchStatus","description":"How the run ended. Always terminal."},"report":{"type":"string","title":"Report","description":"The composed report, or the refusal and its gaps."},"citations":{"items":{"$ref":"#/components/schemas/Citation"},"type":"array","title":"Citations","description":"One entry per marker in the report, in marker order."},"steps_used":{"type":"integer","minimum":0.0,"title":"Steps Used","description":"Retrieval steps the run actually spent."},"trace":{"items":{"$ref":"#/components/schemas/TraceEvent"},"type":"array","title":"Trace","description":"Every event the run recorded, oldest first, ending in 'stop'."},"request_id":{"type":"string","title":"Request Id","description":"Correlation id of this run, echoed in the header."}},"type":"object","required":["status","report","steps_used","request_id"],"title":"ResearchResponse","description":"A finished run, as the API and the CLI both report it.\n\nExactly the fields the canonical contract names. ``stop_reason`` is not among them\nand is not added here: it is already carried by the terminal ``stop`` event of the\ntrace, together with the status, the steps used and the budget, and a seventh\ntop-level field would be a contract change this milestone was not asked to make."},"ResearchStatus":{"type":"string","enum":["running","done","refused","budget_exhausted","degraded"],"title":"ResearchStatus","description":"Where a run is, and if it is over, how it ended.\n\n``DEGRADED`` is declared but not yet produced: it is reserved for a run that\ncompleted with a tool failure it worked around, and no tool on the free path\ncan fail that way. Naming it here rather than adding it later keeps the\nstatus set stable for anything that already switches on it."},"RetrieverChoice":{"type":"string","enum":["fake","http"],"title":"RetrieverChoice","description":"Which retrieval backend a run is to be served by.\n\nA closed set rather than a free string: the two values are the two implementations\nof the retrieval seam, and a caller asking for a third has made a mistake worth\nreporting at the edge."},"RunArtifact":{"properties":{"request_id":{"type":"string","title":"Request Id","description":"Correlation id of the run, and its id in the store."},"question":{"type":"string","title":"Question","description":"The question as it was asked."},"retriever":{"type":"string","title":"Retriever","description":"Backend the run's steps were spent on."},"status":{"$ref":"#/components/schemas/ResearchStatus","description":"How the run ended. Always terminal."},"stop_reason":{"type":"string","enum":["evidence_sufficient","no_evidence","insufficient_evidence","budget_spent","tool_budget_spent"],"title":"Stop Reason","description":"Why the loop stopped, from the closed set."},"report":{"type":"string","title":"Report","description":"The composed report, or the refusal and its gaps."},"citations":{"items":{"$ref":"#/components/schemas/Citation"},"type":"array","title":"Citations","description":"One entry per marker in the report, in marker order.","default":[]},"notes":{"items":{"$ref":"#/components/schemas/Note"},"type":"array","title":"Notes","description":"What the run relied on, in the order the claims were written.","default":[]},"steps_used":{"type":"integer","minimum":0.0,"title":"Steps Used","description":"Retrieval steps the run actually spent."},"max_steps":{"type":"integer","minimum":1.0,"title":"Max Steps","description":"The budget the run was given."},"trace":{"items":{"$ref":"#/components/schemas/TraceEvent"},"type":"array","title":"Trace","description":"Every event the run recorded, oldest first, ending in 'stop'.","default":[]}},"type":"object","required":["request_id","question","retriever","status","stop_reason","report","steps_used","max_steps"],"title":"RunArtifact","description":"One finished run, as it is stored and served.\n\nFrozen: an artifact that can be edited after the run finished is an artifact whose\ntrace and whose report can disagree."},"ToolBudget":{"properties":{"max_steps":{"type":"integer","maximum":20.0,"minimum":1.0,"title":"Max Steps"},"max_calls":{"additionalProperties":{"type":"integer"},"type":"object","title":"Max Calls"}},"additionalProperties":false,"type":"object","required":["max_steps"],"title":"ToolBudget","description":"Per-run global step budget plus optional per-tool call caps."},"TraceEvent":{"properties":{"offset":{"type":"integer","minimum":0.0,"title":"Offset","description":"Position of this event in the run.","default":0},"event":{"type":"string","enum":["plan_created","tool_call","tool_result","note_added","critique","synthesize","stop"],"title":"Event","description":"What happened."},"payload":{"additionalProperties":true,"type":"object","title":"Payload","description":"Details of the event."}},"type":"object","required":["event"],"title":"TraceEvent","description":"One thing that happened, in the order it happened.\n\n``payload`` is a plain mapping rather than a per-event model because the\ntrace is read as JSON by people and by tests, and a union of seven models\nbuys nothing at the point where it is serialised. What each event carries is\ndocumented on the ``record_*`` method that emits it.\n\n``offset`` is the event's position in the run, counted from zero. It is what\na streaming client resumes from and what a reader cites when pointing at one\nevent out of thirty. It is deliberately an ordinal and **not** a timestamp: a\nfree-path run is deterministic, so two runs of the same question under the\nsame budget serialise byte for byte, and a wall clock would be the one field\nthat made every trace differ from every other trace of the same run. Timings\nbelong to the observability layer, which has a request id to bind them to."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}}}