Expand description
Middleware overlay — cross-cutting concerns (Audit / MainAI / Senior / LongHold).
Ships four SpawnerLayer implementations plus the SpawnerStack builder.
Some layers key off Ctx.operator.kind and only fire for
MainAi / Composite sessions; others (Audit / LongHold) apply
uniformly across every kind.
§Extension discipline — this layer is THE extension point (canonical)
Background: an earlier iteration grew a verdict-specialised machinery
(judgment.rs canonical type + 3-form parser + state.agent_verdicts
map + dedicated accessor) that re-interpreted agent output inside the
engine core and banned string-literal conds in favour of a Blueprint
compile-layer translation. That whole complex was dismantled: the value
it added over plain data was zero, while it created an IN-side dialect
that every consumer had to learn. The design conclusion is a
three-principle layering:
- IN is immutable, canonical form is JSON.
Blueprint/mlua_flow_ir::Nodeare plain serde data. No compile pass, no schema field that the engine expands, no Rust helper that buildsExprs. Flow control is written literally in Flow.ir:Eq(Path("$.<step>.verdict"), Lit("blocked"))— domain verdicts are plain strings inside step output, consumed by plain conds. - Generation (authoring sugar) lives OUT, on the consumer side (e.g. a vendored pure-Lua builder that prints Blueprint JSON). It never leaks into engine / schema crates, whatever language it is written in — the ban is on the placement, not the language.
- Runtime extension lives HERE, as a
SpawnerLayer. A middleware (or any future extension mechanism) may interpret the results of a Flow.ir run —Ctx, theoutput_tail,Final { ok }— in its own way and transform them. What it must NOT do:- introduce a new dialect on the IN side (schema fields / node rewriting / cond translation) — extensions read and transform, the wire format stays plain Flow.ir + JSON;
- hide its effect: overrides are appended to the output tail
(e.g.
SeniorEscalationMiddlewarepushes an overrideFinalrather than mutating the recorded one), so the trace stays replayable and the flow stays observable; - accumulate private engine state keyed by its own semantics (the
agent_verdictsanti-pattern) — state lives in ctx / output store as plain data.
AgentResolver, ProjectNameAliasMiddleware, SinkMiddleware,
InputInjectMiddleware, LuaMiddleware, SeniorEscalationMiddleware
all follow this shape: edit ctx / wrap the worker, call the inner
spawner, append observable output. Note LuaMiddleware’s scripts are
host-constructed — embedding Lua source in a Blueprint is the IN-side
dialect this discipline forbids, and would require its own guard
design if ever revisited).
Modules§
- input_
inject InputInjectMiddleware— theSpawnerLayerfor the Data plane’s multi-in prompt injection.- lua_
layer - LuaSpawnerLayer — inserts middleware written in Lua into the
SpawnerStack. - project_
name_ alias ProjectNameAliasMiddleware— aSpawnerLayerthat propagates a task-level project alias.- resolver
- Routing resolver — dynamic agent resolution at spawn time. Slotted into
the
SpawnerStackas aSpawnerLayer. TreatsCtx.agentas a hint, rewrites it to the actual dispatch target, and hands the updatedCtxdown to the inner spawner. - sink
SinkMiddleware— theSpawnerLayerfor the Data plane (Big Response handling).
Structs§
- Audit
Middleware - Mandatory base layer that emits
Event::TaskAttemptStartedon every spawn, before delegating. This is the audit trail’s entry point into the EventLog broadcast channel. - Layer
Registry - Registry of
LayerFactorys, split intobase(always applied) andhints(applied only when a Blueprint declares the matching key inspawner_hints.layers). See the module-level# Factory patternnotes above for why factories rather than pre-built layers. - Long
Hold Middleware - Base layer that emits
Event::TaskAttemptCompletedwith along_hold_warnmarker when a spawn’s completion takes longer thandefault_hold. Purely observational — it never alters the signal or blocks completion. - MainAI
Middleware - Hint layer that fires
ctx.operator.spawn_hook.before/afteraround a spawn, but only forMainAi/Compositesessions. No-op for other kinds (still delegates, just skips the hook calls). - Operator
Delegate Middleware - When
ctx.operator.operator.is_some()(the session has an Operator backend), bypassinner.spawn, calloperator.execute(ctx, prompt), and box the result up as aWorkerHandle. In other words: the path that hands “this spawn” to whatever external Operator backend the engine has registered. - Senior
Escalation Middleware - Hint layer: on
ok=falsecompletion withctx.operator.senior_bridgeset, asks the bridge for guidance and pushes an overrideFinal(ok=true) carryingsenior_answer. See the module comment above this type for the full contract. - Spawner
Stack - Stack builder that layers
SpawnerLayers on top of a base adapter.
Traits§
- Spawner
Layer - Layer trait — one middleware stage wrapping a
SpawnerAdapter.
Type Aliases§
- Layer
Factory - Factory closure for a
SpawnerLayer. The caller registers these at startup, and they are called with the engine context at bind time. Stateless layers can use|_engine| Arc::new(MyLayer); layers that need something likeevent_txshould do|engine| Arc::new(MyLayer::new(engine.event_tx())).