Expand description
AgentContextView — Contract C: the task-level context that must reach
the LLM/Agent boundary, materialized once and consumed by every axis.
§The gap this closes (GH #20)
Task-level context lives in Ctx.meta.runtime (project_root /
work_dir / task_metadata / run_id / project_name_alias, …) and
has to reach the executing Agent through two independent renderers:
- Spawner axis — the WS thin-path
(
crates/mlua-swarm-server/src/operator_ws/session.rs) splices individualCtx.meta.runtimekeys into theSpawn.directivetext a MainAI reads. - Worker axis —
Engine::fetch_worker_payload{,_trusted}(crate::core::engine) builds acrate::types::WorkerPayloada SubAgent pulls overGET /v1/worker/prompt; it never carried task-level meta at all (onlytask_id/attempt/agent/system/prompt).
Before this module, each axis field-by-field-pulled the keys it needed
straight out of ctx.meta.runtime, so a new field (e.g.
task_metadata) had to be wired into every consumer by hand — and
task_metadata never made it into the directive text at all (the F2
gap tracked in the operator-execution-model guide).
§The fix: materialize once, consume everywhere
AgentContextView formalizes Contract C as one view struct. A new
innermost SpawnerLayer (crate::middleware::agent_context) builds it
from Ctx exactly once per spawn — after the outer
TaskInputMiddleware / ProjectNameAliasMiddleware /
WorkerBindingMiddleware layers have inserted their runtime keys, and
before the base spawner stack (WS session / in-process AgentBlock) runs
— and fans the materialized view out on two independent rails:
TaskInput/Alias/Binding middlewares (outer, existing) — insert runtime keys
│ ctx (keys present)
▼
AgentContextMiddleware (innermost layer)
view = AgentContextView::from_ctx(ctx).apply_policy(&policy)
(a) EngineState.agent_ctx[(task_id, attempt)].view = view ← Worker axis source
(b) ctx.meta.runtime[AGENT_CONTEXT_KEY] = json(view) ← Spawner axis source
│ inner.spawn(new_ctx)
▼
base stack (OperatorDelegate → WS session.rs | in-proc AgentBlock runtime.rs)(a) is read back by Engine::fetch_worker_payload{,_trusted} (keyed by
(task_id, attempt), mirroring EngineState.prompts / .systems —
Ctx itself is not stored, so the view has to be snapshotted at
dispatch time to still be servable when the Worker axis fetches it
later). (b) is read back via AgentContextView::materialized_or_from_ctx
by the Spawner axis — falling back to AgentContextView::from_ctx
when the middleware was never layered (backward compat).
§Delivery points — where the view enters each backend
The materialization above is backend-agnostic and shared. What differs per backend is only the carrier, and there are exactly two:
| Carrier | Filled by | Serves |
|---|---|---|
WorkerPayload.context | Engine::fetch_worker_payload{,_trusted} from rail (a) | Out-of-process workers, over GET /v1/worker/prompt |
crate::worker::adapter::WorkerInvocation::context | InProcSpawner::spawn from rail (b) | Every in-process worker (RustFn / Lua / AgentBlock) |
Adding a backend means choosing one of those two carriers and deciding how to render the view into whatever the worker actually reads — NOT re-deriving the view. The renderings that exist today:
| Backend | Rendering |
|---|---|
WS Operator (operator_ws::session) | AgentContextView::to_directive_header spliced into the Spawn directive, plus the full view on WorkerPayload.context |
Subprocess (crate::worker::process_spawner) | work_dir / project_root → the {work_dir} template placeholder |
AgentBlock (crate::worker::agent_block::runtime) | work_dir / project_root → the SDK’s project_root; task_metadata → _TASK_METADATA; extra → _AGENT_CTX (both via the SDK’s extra_globals) |
Lua in-process (compiler::run_lua_worker) | The same two globals, rendered from the same shared agent_block::runtime::context_globals mapping, so a Lua gate is portable between the two in-process backends |
| RustFn in-process | Carried on inv.context; no rendering of its own (a Rust closure reads the typed view directly if it wants it) |
§Why steps is out-of-process only
AgentContextView::steps is populated on exactly one path — the
WorkerPayload clone that GET /v1/worker/prompt returns — and stays
empty on the in-process carrier. That is by construction, not an
oversight on the in-process side:
- A
StepPointeris a fetch instruction (content_url,file_path,sha256). It exists because a worker in another process cannot read the flow ctx, so it needs somewhere to go get a prior step’s OUTPUT. Assembling one requires server-side state (projection root, OutputStore, the HTTP base acontent_urlis relative to) that lives above this crate. - An in-process worker has a strictly better route to the same data:
the step’s own
inexpression.Step.inis anExprover ctx, so a Blueprint author writesin: $.<prior_step>(or any projection of it) and the value arrives directly — no pointer, no fetch, no policy-filtered pointer list to walk.
So the two carriers are not asymmetric in capability, only in
mechanism. A gate that needs prior-step output declares it in in.
A field added to AgentContextView (either a named field, or an
extra entry) reaches both carriers automatically — only a backend
that wants to render the new field needs an edit. ContextPolicy filters the materialized view before it is
snapshotted / stashed; GH #21 Phase 1 wires it to Blueprint schema
fields (Blueprint.default_agent_ctx / default_context_policy,
AgentMeta.ctx / context_policy, resolved by
crate::service::task_launch::derive_agent_ctx /
derive_context_policies and consumed by
crate::middleware::agent_context::AgentContextMiddleware).
Structs§
- Agent
Context View - Contract C’s view struct — the task-level context that must reach the LLM/Agent boundary, materialized once per spawn (see the module doc).
- Context
Policy - Filter over
AgentContextViewfields (GH #20/#21). Relocated to the schema crate in GH #21 Phase 1 so a Blueprint author can declare one viaBlueprint.default_context_policy/AgentMeta.context_policy— this re-export keepsmlua_swarm::core::agent_context::ContextPolicyresolving unchanged for every existing caller (path-compat; see thecontext_policy_path_compat_reexporttest below). The filter application logic lives on the view side, atAgentContextView::apply_policy. Receptacle for a Blueprint-driven filter over the materializedAgentContextView(GH #20/#21). Declared BP-side viaBlueprint::default_context_policy(BP-global) orAgentMeta::context_policy(per-agent, outranks the BP-global tier) — resolved and applied byAgentContextMiddlewarein themlua-swarmcore crate (this crate stays execution-free; see the crate doc). Default (include: None, exclude: vec![]) is pass-all —Self::allowsreturnstruefor every field name. - Step
Pointer - A pointer to one preceding step’s OUTPUT, embedded into
AgentContextView::stepsbycrates/mlua-swarm-server/src/worker.rs’sGET /v1/worker/prompthandler (theprojection-adapterST5 Worker axis) — never the OUTPUT content itself (pointer-only invariant: no preview, no content bytes, matching the “worker payload never inline-embeds a projected value” contractcrate::core::projection’s module doc establishes for the directive header).
Constants§
- AGENT_
CONTEXT_ KEY ctx.meta.runtimekey under which the materializedAgentContextView(JSON-serialized) is stashed bycrate::middleware::agent_context::AgentContextMiddleware— the Spawner axis’s read-back source (see the module doc).- PROJECTION_
PLACEMENT_ KEY ctx.meta.runtimekey under which the Blueprint-widecrate::core::projection_placement::ProjectionPlacementresolver (JSON-serialized, GH #27 follow-up to #23) is stashed bycrate::middleware::agent_context::AgentContextMiddleware— the read-back source for the spawn-time in-flight projection pointer (crates/mlua-swarm-server/src/operator_ws/session.rs’sappend_projection_pointer), which has no directEnginehandle to callEngine::projection_placement_forwith. Absent (or undeserializable) falls back toProjectionPlacement::default()(byte-compat).- PROJECT_
NAME_ ALIAS_ KEY ctx.meta.runtimekey that carriesBlueprint.metadata.project_name_alias. Canonical home as of GH #20; re-exported fromcrate::middleware::project_name_aliasfor API compatibility (same string value the existingctx.meta.runtime.get("project_name_alias")consumers keyed off).- RUN_
ID_ KEY ctx.meta.runtimekey that carries the issue #13 run-id propagation value. Canonical home as of GH #20 (previously a bare literal atEngine::dispatch_attempt_with).- STEP_
CTX_ KEY ctx.meta.runtimekey that carries the GH #21 Phase 2 Step tier’s resolved context bundle (TaskSpec.step_ctx, threaded through byEngine::dispatch_attempt_withon every attempt — same insertion site asRUN_ID_KEY). Consumed bycrate::middleware::agent_context::AgentContextMiddleware, which unpacks the bundle’s keys and applies them with the same only-if-absent mechanics as the Agent / BP-global tiers, ordered FIRST (Step outranks Agent and BP-global — see that middleware’s module doc for the full precedence narrative). The bundle itself (this key’s raw value) stays in the runtime bag verbatim — only its individual keys are folded intoAgentContextView::extra.- TASK_
METADATA_ KEY ctx.meta.runtimekey that carries the Task-level free-form metadata object. Canonical home as of GH #20; re-exported fromcrate::middleware::task_inputfor API compatibility.- TASK_
PROJECT_ ROOT_ KEY ctx.meta.runtimekey that carries the Task-level project root path. Canonical home as of GH #20; re-exported fromcrate::middleware::task_inputfor API compatibility.- TASK_
WORK_ DIR_ KEY ctx.meta.runtimekey that carries the Task-level work dir path. Canonical home as of GH #20; re-exported fromcrate::middleware::task_inputfor API compatibility.