Skip to main content

Module projection

Module projection 

Source
Expand description

GET /v1/tasks/:id/runs/:run/steps* (the metadata + content debug plane over a Run’s step OUTPUT — McpQueryAdapter, a server-side mlua_swarm::core::projection::ProjectionAdapter impl reading through the Data-plane OutputStore with a persisted RunRecord.result_ref fallback). See the module doc for how this relates to operator_ws::session’s in-flight FileProjectionAdapter hook and worker’s Worker-axis context.steps pointer assembly. McpQueryAdapter — server-side [ProjectionAdapter], and the REST hierarchy that serves a Run’s step OUTPUT as metadata + content (projection-adapter ST5’s HTTP debug plane — replaces the ST2/ST4 GET /v1/tasks/:id/ctx single-value endpoint / ProjectionResponse).

§Two consumers, two roles (ST5)

  • Worker axis (crates/mlua-swarm-server/src/worker.rs’s GET /v1/worker/prompt handler) — the primary supply path. A worker’s fetch payload carries context.steps: Vec<StepPointer>, a ContextPolicy.steps-filtered pointer list assembled automatically at fetch time; no separate tool call needed.
  • HTTP debug plane (this module’s GET /v1/tasks/:id/runs/:run/steps* routes) — the content the above pointers’ content_url addresses, plus an unfiltered metadata/content view for operators / humans debugging a run.

§GH #23 subtask-3: table-driven addressing (replaces the runtime union

rule)

Both consumers share McpQueryAdapter::list_steps’s enumeration. Previously every distinct step_ref name in RunRecord.step_entries was resolved through the Data-plane OutputStore, unioned with RunRecord.result_ref’s top-level object keys (the finalized-Run fallback), Data-plane winning a name collision — the pre-GH-#23 runtime union rule. That rule is now statically replaced: every real Compiler::compile output carries a mlua_swarm::core::step_naming::StepNaming table (built once, at compile time — see that module’s doc), and this module’s enumeration / single-key resolution ([Self::enumerate_steps] / [Self::resolve_async]) look the table up via Engine::step_naming_for and report every step under its ONE canonical name, addressable by that name OR any alias (Step.ref / the out ctx-path’s top-level segment). The runtime union / collision-priority logic itself no longer runs per-request; it is baked into the table once, at register time (StepNaming::from_blueprint).

[Self::enumerate_steps_legacy_union] keeps the OLD runtime-union body verbatim as a defensive-only fallback for the rare case no table resolves (a spawn stack the dispatcher never wired EngineDispatcher::with_step_naming for — certain test harnesses that seed OutputStore/RunStore fixtures directly without driving a real dispatch); it is not a “declared Blueprints get the new path, undeclared ones keep the old one” branch — undeclared Blueprints get the SAME table-driven path (their canonical name is simply their own Step.ref, byte-identical to the pre-GH-#23 name).

§Architecture (subtask-4 rework, carried into ST5, table-driven since

subtask-3)

McpQueryAdapter reads through two backings, tried in order, for every step’s OWN dispatch (RunRecord.step_entries row → its own StepId):

  1. Data-plane, in-flight-safe AND Run-scoped (subtask-4’s original reason for being; Run-scoped since subtask-3 — see the former KNOWN LIMITATION below): McpQueryAdapter::resolve_async / [Self::enumerate_steps_via_table] look up OutputStore::get_latest_by_name_in_run(step_entry.step_id, 1, canonical_name) — the same store Engine::submit_output’s submit-time projection sink dual-writes into (see mlua_swarm::core::engine::Engine::submit_output’s doc), keyed Run-scoped by construction (a StepId is globally unique per dispatch, so two concurrent Runs sharing a producer name never cross-resolve — no narrowing-by-guard needed any more). A hit here can be a not-yet-finalized Run’s already-submitted step — the in-flight case subtask-4 exists for.
  2. Persisted RunRecord.result_ref fallback (unchanged in kind, now tried under the canonical name AND every alias): used whenever (1) comes back empty (no Data-plane record for that step’s own dispatch yet — e.g. a Run that predates the engine having an OutputStore wired).

Unlike crate::operator_ws::session‘s spawn-time mlua_swarm::core::projection::FileProjectionAdapter hook (which materializes the spawning agent’s own AgentContextView), this adapter’s Data-plane path serves prior steps’ submitted OUTPUT — the pull-supply counterpart to Engine’s submit-time file sink.

§Former KNOWN LIMITATION (closed by GH #23 subtask-3)

OutputStore::get_latest_by_name is producer-name-scoped, not Run-scoped (see mlua_swarm::store::output’s module doc) — it returns the single newest Final submitted anywhere under that producer name, across every Run / Task, so two concurrent Runs whose flow.ir happens to dispatch an agent of the identical name could race each other. This module no longer calls that method: every lookup here goes through OutputStore::get_latest_by_name_in_run, scoped to the dispatching step’s own (globally unique) StepId, closing the race by construction, independent of whether the Blueprint declared a projection_name (see mlua_swarm::store::output::OutputStore::get_latest_by_name_in_run’s doc). get_latest_by_name itself is untouched (still used by Engine::submit_output’s own fail-open cross-Run compatibility path) — only this module’s consumption of it changed.

[ProjectionAdapter::fetch] is a synchronous trait method, but this adapter’s backing stores are async. McpQueryAdapter::resolve_async is the real, native-async implementation; [step_content] (the content-plane HTTP handler) calls McpQueryAdapter::list_steps directly. [ProjectionAdapter::fetch] instead bridges to McpQueryAdapter::resolve_async via tokio::task::block_in_place + Handle::block_on purely for trait conformance (dependency inversion — this adapter implements the same core::projection::ProjectionAdapter trait mlua_swarm::core::projection::FileProjectionAdapter does, so a caller holding a dyn ProjectionAdapter can use either polymorphically); the hot HTTP path never takes that bridge.

Structs§

McpQueryAdapter
Server-side ProjectionAdapter backed by an OutputStore (in-flight-safe, subtask-4, Run-scoped since GH #23 subtask-3) with a RunStore-backed result_ref fallback (see the module doc for the full narrative). Holds an Engine handle (GH #23 subtask-3) so Self::step_naming_for_run can pull the Blueprint-wide StepNaming table Engine::step_naming_for snapshotted at dispatch time.
StepList
Response body for GET /v1/tasks/:id/runs/:run/steps.
StepPathQuery
Query params shared by the metadata and content routes: narrows a single step’s value via $.a.b dot-path form (the leading $. is optional) — same syntax mlua_swarm::core::projection::ProjectionKey already establishes.
StepSummary
One step’s metadata (operator / debug plane) — GET /v1/tasks/:id/runs/:run/steps/:step, and each entry of StepList::steps.

Enums§

ProjectionSource
Which backing produced a StepSummary / a Worker-axis StepPointer — Data-plane wins a name collision (module doc’s “Architecture” section).

Functions§

step_content
GET /v1/tasks/:id/runs/:run/steps/:step/content?path=$.a.b — the raw body: full bytes, no envelope, no Range support. Content-Type and ETag follow StepSummary::content_type / StepSummary::sha256’s same rules (module doc). GH #23 subtask-3: :step is canonicalized the same way step_get does.
step_get
GET /v1/tasks/:id/runs/:run/steps/:step?path=$.a.b — one step’s metadata, optionally narrowed. GH #23 subtask-3: :step is canonicalized (adapter.resolve_step_name) before the lookup, so either the canonical name or any alias 200s — the reported StepSummary::name is always the canonical form.
steps_list
GET /v1/tasks/:id/runs/:run/steps — every step visible for the addressed Run, unfiltered (see the module doc’s role split).