Expand description
The ECS pipeline (Phase 2): components + systems that drive every agent through check-input → infer → tools → apply → repeat, entirely as data.
Agents are entities; their execution phase is a marker component
(ReadyToInfer, AwaitingInference, …) so systems can query by phase. A
system never blocks on I/O: the dispatch systems hand work to the async
bridges (inference_bridge, crate::tool_bridge) and the collect
systems apply the results on a later tick. This module is built alongside the
existing imperative engine; the two are unified in a later phase.
Structs§
- Agent
Blueprint - The agent’s blueprint (its stage graph), as a component.
- Awaiting
Compaction - A compaction job (LLM summarization) is in flight; the agent is held out of inference until its summaries land.
- Awaiting
Inference - Inference has been dispatched to the pool; the agent is waiting for its result (which the inference-collect system will apply).
- Awaiting
Tools - The agent’s tool batch has been handed to the tool lane; it is waiting for the results (which the tool-collect system will apply).
- Awaiting
Transition Choice - The stage completed with multiple candidate edges (or a single edge the stage may decline); an LLM must choose. Holds the choosable edges for the async transition-choice system.
- Awaiting
Transition Response - A transition-choice inference is in flight (an LLM is picking the next stage);
holds the choosable edges so the collect system can match the response back to
one. (Ported from the async portion of
graph::prompt_llm_transition.) - Compaction
Results - The receiving end of the compaction-outcomes channel, as a world resource.
(The sending end lives in
InferenceStage::compaction_outcomes.) - Compaction
Settings - Per-agent compaction configuration; its presence opts the agent into
automatic eviction + LLM compaction before each inference (mirrors the
imperative loop’s
Option<&CompactionConfig>). - Context
Tool Results - Context-tool results computed inline by
dispatch_tools(thecontext_*tools mutate the ECS window, so they can’t run on the async lane), held untilcollect_toolsmerges them with the lane results. Absent when a batch had no context tools. - Dynamic
Tools - Marker: this agent opted into
dynamic_tools. Only such agents are polled bypoll_dynamic_tool_refreshfor a pending tool re-scan, so the default (static) agent pays nothing. - Gate
Script Rules - The scripted gate rules (
~/.config/leviath/rules/*.rhai), as a world resource. The daemon builds the checker (it owns the Rhai engine); the gate consults it after the static allowlist. Absent ⇒ no scripted rules. - InFlight
Work - The cancellation handles for an agent’s currently in-flight async work (its inference request, its tool batch). Attached when the work is dispatched, removed when it lands - so the presence of this component means “there is something running for this agent that a cancel needs to stop”.
- Inference
Results - The receiving end of the inference-outcomes channel, as a world resource for
the collect system. (The sending end lives in
InferenceStage.) - Inference
Stage - The plumbing the inference-dispatch system needs: the per-model pools, the channel to report outcomes on, the tick wake handle, and a runtime handle to spawn the (bounded, per-request) worker tasks onto.
- Message
Intake - The receiving end of the world’s inbound-message channel. Clients (the
control API) send
AgentMessages here; the delivery system routes and delivers them. - Pending
Edge Compact - Regions an edge transform asked to LLM-compact after a transition, awaiting
the compaction lane (drained by
dispatch_edge_compact). - Persist
Watermark - Debounce watermark: the (iteration, stage index, status) last persisted for an
agent. A snapshot is written only when one of these changes, so the world
writes on meaningful progress rather than every tick.
Noneuntil the first snapshot, so a freshly-spawned agent is always written once. - Persistence
Stage - The sending end of the persistence I/O lane (the receiving end is drained by
persistence_bridge::persistence_worker). - Policy
Gate - The tool allowlist policy (
policy.toml), as a world resource. The daemon inserts it; a taint-gated agent’s outbound calls are checked against it. When absent, the gate falls back to an empty policy (deny-by-clearance only). - Process
Response - The response has been applied and is ready to be examined for tool calls (or completion) by the process-response system.
- Providers
- The registered providers, as a world resource.
- Ready
ForTools - The response had tool calls; the agent is ready for the tool-dispatch system
to run them (the calls live on its
InferenceResult). - Ready
ForTransition - The response had no tool calls; the agent is ready for the empty-response handler to decide finish vs. a “use your tools” nudge.
- Ready
ToInfer - The agent is active and ready to build a request and (permits allowing) dispatch inference.
- Required
Reentries - Counts how many times the current stage has been re-run to satisfy required context regions. Absent ⇒ 0; reset when a new stage is entered.
- Resolve
Transition - The agent’s current stage is complete; the transition system will resolve the next stage (or completion).
- Resolved
Stage - A blueprint stage resolved to a concrete provider, model, and effective tool
set - the per-stage input to
spawn_agent. The caller (CLI / daemon) owns the model-selection policy (overrides, availability, user defaults) and tool filtering; the runtime just turns the result into agent data. - Stage
Cursor - The index of the agent’s current stage within its blueprint.
- Stage
Inference - Resolved inference parameters for the agent’s current stage, set when it enters that stage. Pure data - the dispatch system reads it to build the request.
- Stage
Inferences - Pre-resolved
StageInferencefor every stage of the agent’s blueprint, built once when the agent is spawned (the CLI resolves each stage’s provider, model, and tool definitions). The transition system swaps the agent’sStageInferenceto the entry for its new stage by index. - Stage
IoBuffer - Buffered per-stage output/log lines awaiting the persistence lane. Emitters
(
collect_inference,collect_tools) push;dispatch_persistencedrains and clears, forwarding the lines tostages/<idx>/output.log(readable assistant output) andstages/<idx>/logs.log(tool + token + error events). - Stage
Just Entered - Transient tag: the agent just entered a stage (index + name). The
sync_tool_stagessystem reads it to notify theToolServiceof the stage change, then removes it. Carries the data so the tool service need not query the world. - Stage
Ledger - One
StageRecordper blueprint stage, seeded at spawn (names +Pending) and reconciled bydispatch_persistence(status + timestamps), with per-stage tokens accrued bycollect_inference. Serialized tostages.jsonso the dashboard / serve API can show every stage’s real name and status - not just the active one (whose name is the only one carried inmeta.json). - Stage
Progress - Per-stage progress counters, reset when an agent enters a stage.
- Stage
Setup - Pre-resolved per-stage setup, applied by
enter_stagewhen an agent enters a stage: inference parameters, tool-result routing, whether the stage accepts live user input, an optional stage-specific context layout, and an optional system prompt. Built once per stage when the agent is spawned (mirrorsStageInferences) so stage entry stays synchronous and query-friendly. (Ported from the imperative loop’s per-stage setup in the CLI executor.) - Stage
Setups - Pre-resolved
StageSetupfor every stage of the agent’s blueprint. - Tool
Results - The receiving end of the tool-outcomes channel, as a world resource.
- Tool
Sensitivities - Per-tool output sensitivity for an agent, populated by the taint-gate system when taint tracking is enabled. Absent ⇒ taint off ⇒ results tagged Public.
- Tool
Service Res - The tool service, as a world resource.
- Tool
Stage - The job sender feeding the tool lane, as a world resource.
- Tools
Need Refresh - Marker: this agent’s advertised tools should be re-resolved before its next
turn - mid-run dynamic tool discovery. Consumed by
refresh_advertised_tools, which asks theToolServicefor the stage’s fresh tool defs and writes them into the liveStageInference. - Transition
Results - The receiving end of the transition-choice outcomes channel, as a world
resource for the collect system. (The sending end lives in
InferenceStage::transition_outcomes.) - Visit
Counts - How many times the agent has entered each stage (for
max_revisits). - Waiting
ForChildren - Marks a parent agent held at a
requires_childrenstage boundary until all its spawned sub-agents are terminal. Distinct fromFanOutWaiting(which is the fan-out split/merge wait).
Enums§
- Stage
Outcome - How a stage ended, when that governs the transition. Absent ⇒ the stage
completed normally. Read by
resolve_transitionto follow anerror/max_iterations/stuck-conditioned edge (e.g. → error_recovery) when the stage errored, hit its iteration cap, or stopped making progress.
Constants§
- WORKSPACE_
CHECK_ INTERVAL - How often (in per-stage iterations)
check_workspace_healthstats the agent’s working directory. Onemetadatacall every few iterations is far cheaper than the tool failures it replaces.
Traits§
- Tool
Service - Provides a per-agent tool-execution closure. The concrete implementation
(in the CLI) holds each agent’s tool registry, workdir, and permission
policy; the pipeline stays agnostic to how tools run.
exec_forreturns a boxed closure the tool worker runs off the tick.
Functions§
- abort_
terminal_ work - Stop the in-flight work of every agent that has reached a terminal state, and drop the handles. Runs before the dispatch systems each tick, so a cancel takes effect on the very next tick rather than whenever the provider or tool happens to answer.
- check_
workspace_ health - Workspace health guard: fail a run whose working directory has disappeared.
- collect_
compaction - Compaction-collect system: drain finished compaction jobs and apply each
summary into its paired
CompactHistoryregion, clearing the summarized source region. A provider error leaves the context untouched (best-effort). Either way the agent returns toReadyToInfer. (Ported from the storage tail ofAgentEngine::compact_region.) - collect_
inference - Inference-collect system: drain completed inferences and apply them. A
success is stored on the agent (bumping its iteration) and the agent advances
to
ProcessResponse; an error marks the agentError. An outcome for an agent that is no longerAwaitingInference(cancelled or despawned between dispatch and now) is dropped. - collect_
tools - Tool-collect system: drain finished tool batches and apply them. Results are
written into the agent’s context window (routing/truncation/taint honored)
and the agent loops back to
ReadyToInfer. Outcomes for agents no longerAwaitingTools(cancelled/despawned) are dropped. - collect_
transition_ choice - Transition-choice collect: drain completed routing inferences, match each to a
target stage (or completion), record the decision in context, and either enter
the chosen stage (loop to
ReadyToInfer) or mark the agentComplete. A provider error marks the agentError. - deliver_
messages - Message-delivery system: route inbound messages to their target agents’
inboxes (by agent id), then deliver each inbox into the agent’s context
window - but only for agents whose current stage accepts messages; otherwise
the messages wait in the inbox for a stage that does. Ported from
AgentEngine::process_messages/deliver_inbox_messages. - detect_
stuck_ stage - Stuck-detection guard: for each
ReadyToInferagent whose current stage declares astuck-conditioned edge, evaluate that edge’s thresholds against the stage’s progress. When one trips, write the diagnosis into context and route the agent down the stuck edge (ResolveTransition+StageOutcome::Stuck) instead of running another inference. - dispatch_
compaction - Compaction-dispatch system: for each
ReadyToInferagent withCompactionSettingswhose window is over the eviction threshold, do the synchronous eviction inline; if that surfaces regions needing LLM summarization (and content to summarize), build one request per region, acquire a permit for the compaction model, spawn the job, and hold the agent asAwaitingCompaction. Anything that can’t proceed (under threshold, nothing to summarize, provider missing, pool full) simply leaves the agentReadyToInferso inference proceeds - compaction is best-effort. (Ported fromAgentEngine::evict_and_compact.) - dispatch_
edge_ compact - Edge-compaction dispatch: for each
ReadyToInferagent with aPendingEdgeCompact(an edge transform requested LLM summarization), spawn a compaction job for the named regions (reusing the compaction lane) and hold the agentAwaitingCompaction. If the agent has no compaction config, nothing to summarize, or no provider/permit, the request is dropped and the agent proceeds to inference un-compacted (memory-pressure compaction still applies later). - dispatch_
inference - Inference-dispatch system: for every
ReadyToInferagent, resolve its provider and, if a per-model permit is free, build the request, spawn the inference job, and move it toAwaitingInference. If its provider is missing or no slot is free, it staysReadyToInferand is retried on a later tick - no blocking, no wasted task. - dispatch_
persistence - dispatch_
tools - Tool-dispatch system: for each
ReadyForToolsagent, apply itscontext_*tool calls inline (they mutate the ECS window) and hand the rest to the sequential tool lane, moving it toAwaitingTools. If a batch is all context tools there is nothing for the lane, so the results are applied immediately and the agent loops straight back toReadyToInfer. The lane serializes execution, so there is no permit gate - every ready agent is enqueued in turn. - dispatch_
transition_ choice - Transition-choice dispatch: for each
AwaitingTransitionChoiceagent, inject the “which stage next?” prompt into its context, build a short deterministic request, acquire a per-model permit, spawn the inference onto the transition lane, and move it toAwaitingTransitionResponse. Provider-missing / pool-full leaves it choosing and retries next tick (same backpressure asdispatch_inference). - enforce_
max_ iterations - Max-iterations guard: for each
ReadyToInferagent whose per-stage inference count has reached the stage’smax_iterations, end the stage (routing to amax_iterationsedge if one exists, else a normal transition) instead of running another inference. Ported from the imperativerun_autonomouscap. - force_
transition - Force an agent into the stage at
target_idxvia direct world access - the same effect asresolve_transition’s linear-Nextarm, but callable from an exclusive system (e.g. the fan-out collector jumping to itsmerge_stage) or the daemon (spawning a fan-out worker directly at its worker stage) where noCommandsqueue is available. On a system-prompt overflow the agent is markedError, mirroring the transition systems. - gate_
requires_ children requires_childrengate (exclusive, mirrors the fan-out wait): a stage markedrequires_childrenmay not transition while any of the agent’s spawned sub-agents (SubAgentChildren) are still running - the parent is heldWaiting(WaitingForChildren) and resumes (re-insertingResolveTransition, back toActive) once every child is terminal.- handle_
empty_ response - Empty-response system: for each
ReadyForTransitionagent decide whether the stage is done. If the agent has already made tool calls, or we’ve nudged the max number of times, the text response is accepted and the agent advances toResolveTransition. Otherwise (text only, no work yet) the response + a “use your tools” nudge are added to context and the agent loops back toReadyToInfer. Ported fromAgentEngine::loop_handle_empty_tool_calls. - is_
terminal_ status - Whether an agent status is terminal (the run/child has finished).
- poll_
dynamic_ tool_ refresh - Poll each
dynamic_toolsagent for a pending tool re-scan and, when the tool service reports one, tag itToolsNeedRefreshsorefresh_advertised_toolsre-advertises before its next turn. Only agents carryingDynamicToolsare queried, so static agents (the default) cost nothing. - process_
response - Process-response system: route each
ProcessResponseagent by whether its last inference asked for tools. Tool calls present ⇒ReadyForTools(and the stage’s running tool-call count is bumped); none ⇒ReadyForTransition. Pure routing - no I/O. - reflect_
interaction_ status - Persistence-dispatch system: for each agent carrying run metadata whose
(iteration, stage, status) has changed since its last snapshot, build the
meta.json+context.jsonvalue snapshot and hand it to the persistence lane. Fire-and-forget - no result to collect; the single-worker lane keeps a given agent’s writes ordered. Agents withoutRunMetadataaren’t persisted. Interaction-status reflection system: mirror the sharedInteractionHub’s open requests into agent status so a blocked agent shows asWaiting(and the dashboard /lev pssurface its prompt) instead of a silentActive. - refresh_
advertised_ tools - Re-advertise an agent’s tools mid-run: when tagged
ToolsNeedRefresh, ask the tool service for this stage’s freshly-resolved tool defs and, if it returns a set, write it into the liveStageInference(what the next inference request advertises, read fresh bybuild_request) and the matchingStageInferencescatalog entry (so a later revisit of this stage keeps the updated set). Always consumes the marker. This is the mechanism behind mid-run dynamic tool discovery and lazily-listed MCP tools. - require_
context_ regions - Required-region gate: before a normally-completed stage transitions, if it can
write context and a
requiredregion is still empty, inject a nudge and re-run the stage (loop back toReadyToInfer) instead of transitioning - bounded by the stage’smax_revisits(or a default cap), after which it proceeds with a warning. Skipped when the stage ended on an error / max-iter outcome (those transitions take precedence). Ported from the imperative gate. - resolve_
transition - Transition-resolution system: for each
ResolveTransitionagent, resolve the next stage. Terminal ⇒ mark the agentComplete. A single/linear target ⇒ enter the new stage (swap itsStageInference, reset stage progress, bump the visit count) and loop toReadyToInfer. Multiple candidate edges ⇒ hand off to the async transition-choice system viaAwaitingTransitionChoice. - spawn_
agent - Spawn a fully-formed agent into
worldfrom its blueprint, task, and per-stage resolution, and return its entity. Builds every stage’sStageInference/StageSetupup front (so transitions are pure component swaps), seeds the context window, applies the first stage’s setup (its layout and system prompt), pre-counts the first stage’s visit, and marks the agentReadyToInfer. ReturnsErrif the first stage’s system prompt doesn’t fit its region (the same hard failure the imperative loop raises at stage 0). - spawn_
agent_ seeded - Like
spawn_agent, but seeds the context window from a name→content map (caller-input regions filled by the CLI/ACP/API, plus blueprint-resolved seeds) rather than a single task string.spawn_agentis the thin wrapper that seeds only thetaskkey. - sync_
tool_ stages - Notify the
ToolServiceof every agent that just entered a stage (tagged withStageJustEnteredby the transition systems), so it can re-sync that agent’s per-stage tool permissions, then clear the tag. Runs after the transition systems each tick.