Runtime
src/runtimeis the L2 Agent layer — it owns the model conversation loop. The host-facingcreateAgentreturns anAgentthat drives one prompt to settlement. Imported asindusagi/runtime, or as theruntimenamespace fromindusagi.
The runtime is split into a pure transition machine (the cadence reducer, which
returns Effects and never side-effects) and a single stateful conductor (which
performs those effects: reach the model, run tools, persist, condense, publish). A
host only needs createAgent; advanced hosts can drive the FSM directly.
Table of Contents
Public exports
From src/runtime/index.ts:
| Export | Kind | Source | Purpose |
|---|---|---|---|
createAgent |
function | conductor/agent.ts |
The host-facing factory; returns an Agent |
Agent |
type | conductor/agent.ts |
The driveable handle (submit/subscribe/abort/snapshot/resume) |
AgentDeps |
type | conductor/agent.ts |
Injectable collaborators (invokeModel/store/ledger) |
AgentEventHandler |
type | conductor/agent.ts |
The subscribe callback shape |
runError |
function | contract |
Construct a RunError |
step |
function | cadence |
Alias of cadence — the bound step(state, signal) factory |
cadence |
function | cadence |
The pure transition machine factory |
initialSnapshot |
function | cadence |
Seed a fresh RunSnapshot |
Public types branched on by callers: RunSnapshot, RunPhase, PendingTool,
RunEvent, AgentConfig, CompactionPolicy, ToolBox, ToolRunner, ToolCall,
ToolOutcome, RunError, RunErrorKind, ModelInvoker (all from contract), plus
StepFn and Transition (from cadence).
Note: the gateway's types (Conversation, Turn, Block, ToolDescriptor,
Reply, Emission, Channel, StreamOptions, …) are intentionally not
re-exported here — import those from indusagi/llmgateway or .../llmgateway/contract.
Sub-directories
| Directory | Holds |
|---|---|
conductor/ |
agent.ts — the only stateful, side-effecting layer; createAgent lives here |
cadence/ |
reducer.ts, fold.ts — the pure FSM; the cadence(config) factory yields step |
contract/ |
The type vocabulary: config.ts, run-state.ts, events.ts, signal.ts, effect.ts, session.ts, tools.ts, errors.ts |
turn/ |
driver.ts — adapts a gateway Channel into runtime Signals (driveTurn) |
dispatch/ |
scheduler.ts — bounded-concurrency tool dispatch (createScheduler) |
memory/ |
compactor.ts, estimate.ts — context condensation (compact/findCutPoint/shouldCompact) |
ledger/ |
bus.ts, accumulator.ts — the RunLedger event hub and its subscribers |
store/ |
dag.ts, hash.ts, persist.ts — the content-addressed SessionGraph / SessionStore |
wire/ |
projectors.ts — projections of run state onto the wire protocol |
Driving an agent
import { runtime, capabilities } from "indusagi";
const agent = runtime.createAgent({
model: "claude-sonnet-4",
tools: capabilities.toolBox("coding"),
// optional: system, maxOutputTokens, thinking, compaction, maxTurns
});
const unsubscribe = agent.subscribe((event) => {
if (event.kind === "text_delta") process.stdout.write(event.delta);
});
const snapshot = await agent.submit("explain this repo");
console.log(snapshot.phase); // "settled" or "faulted"
unsubscribe();
The Agent handle exposes five methods: submit(input) drives one prompt
(Turn[] or a bare string) to settlement and resolves the terminal RunSnapshot —
a submit landing while a run is in flight is folded in as steering rather than
starting a competing run; subscribe(handler) taps the live RunEvent stream and
returns a disposer; abort() cancels the model stream and tool dispatch and faults
the run; snapshot() reads the latest state; and resume(sessionId) rehydrates an
earlier session from the store.
AgentConfig is the immutable setup: model (required), optional system,
tools (a ToolBox), maxOutputTokens, thinking (a ThinkingLevel),
compaction (a CompactionPolicy with triggerRatio and keepRecent), and
maxTurns — the per-run model-turn ceiling that force-faults a looping model,
defaulting to 64.
The model entrypoint is injectable through AgentDeps: by default the conductor
binds the gateway's stream to config.model, but a test passes a scripted
ModelInvoker so a run is deterministic with no network. A store (a
SessionStore) enables persistence and resume; a ledger (a RunLedger)
receives published events.
The pure FSM seam
The conductor threads a serial stream of Signals through the pure reducer, which
returns a Transition ({ state, effects }). The conductor performs each Effect
(publish, invoke_model, run_tool, compact, persist) and folds the
resulting signals back in, turning over until a terminal RunPhase (settled or
faulted). Advanced hosts can drive that machine themselves:
import { cadence, initialSnapshot } from "indusagi/runtime";
const step = cadence(config); // StepFn bound to the config
let snapshot = initialSnapshot(sessionId, config.model);
const transition = step(snapshot, { kind: "submit", input: turns });
snapshot = transition.state; // apply, then perform transition.effects
Before each invoke_model, the conductor checks accumulated history against the
model's context window (shouldCompact) and condenses the older prefix when it
crosses triggerRatio, preserving the most recent keepRecent turns — but only
when the cut genuinely shrinks history, so a model is never trapped in a compaction
loop.
Relationship to neighbors
The runtime reaches the LLM Gateway for every model
turn and the Capabilities layer for tool execution
(its ToolBox / ToolRunner / ToolCall / ToolOutcome contracts are the
boundary tools satisfy). Tracing consumes the runtime's
RunEvent stream through traceAgentRun; Swarm and
Smithy both build their agents with createAgent; and
the Shell App runners wrap an Agent for each mode.
Back to the Architecture overview.
