Skip to main content

Module middleware

Module middleware 

Source
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:

  1. IN is immutable, canonical form is JSON. Blueprint / mlua_flow_ir::Node are plain serde data. No compile pass, no schema field that the engine expands, no Rust helper that builds Exprs. 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.
  2. 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.
  3. Runtime extension lives HERE, as a SpawnerLayer. A middleware (or any future extension mechanism) may interpret the results of a Flow.ir run — Ctx, the output_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. SeniorEscalationMiddleware pushes an override Final rather 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_verdicts anti-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 — the SpawnerLayer for the Data plane’s multi-in prompt injection.
lua_layer
LuaSpawnerLayer — inserts middleware written in Lua into the SpawnerStack.
project_name_alias
ProjectNameAliasMiddleware — a SpawnerLayer that propagates a task-level project alias.
resolver
Routing resolver — dynamic agent resolution at spawn time. Slotted into the SpawnerStack as a SpawnerLayer. Treats Ctx.agent as a hint, rewrites it to the actual dispatch target, and hands the updated Ctx down to the inner spawner.
sink
SinkMiddleware — the SpawnerLayer for the Data plane (Big Response handling).

Structs§

AuditMiddleware
Mandatory base layer that emits Event::TaskAttemptStarted on every spawn, before delegating. This is the audit trail’s entry point into the EventLog broadcast channel.
LayerRegistry
Registry of LayerFactorys, split into base (always applied) and hints (applied only when a Blueprint declares the matching key in spawner_hints.layers). See the module-level # Factory pattern notes above for why factories rather than pre-built layers.
LongHoldMiddleware
Base layer that emits Event::TaskAttemptCompleted with a long_hold_warn marker when a spawn’s completion takes longer than default_hold. Purely observational — it never alters the signal or blocks completion.
MainAIMiddleware
Hint layer that fires ctx.operator.spawn_hook.before/after around a spawn, but only for MainAi / Composite sessions. No-op for other kinds (still delegates, just skips the hook calls).
OperatorDelegateMiddleware
When ctx.operator.operator.is_some() (the session has an Operator backend), bypass inner.spawn, call operator.execute(ctx, prompt), and box the result up as a WorkerHandle. In other words: the path that hands “this spawn” to whatever external Operator backend the engine has registered.
SeniorEscalationMiddleware
Hint layer: on ok=false completion with ctx.operator.senior_bridge set, asks the bridge for guidance and pushes an override Final (ok=true) carrying senior_answer. See the module comment above this type for the full contract.
SpawnerStack
Stack builder that layers SpawnerLayers on top of a base adapter.

Traits§

SpawnerLayer
Layer trait — one middleware stage wrapping a SpawnerAdapter.

Type Aliases§

LayerFactory
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 like event_tx should do |engine| Arc::new(MyLayer::new(engine.event_tx())).