halter-runtime
halter-runtime is the execution engine for halter sessions.
It is responsible for turning configuration, resources, provider models, tools, hooks, and persistence into an actual running agent session.
If halter is the assembly layer, halter-runtime is the component that does the work.
Who this crate is for
Primary: programmers embedding or extending halter
Use this crate when you need to:
- create or resume sessions programmatically
- submit turns and consume runtime events
- manage context compaction
- integrate custom prompt assembly or context strategies
- coordinate subagents
- hot-swap compiled resources
- build a service on top of halter without going through the CLI
Secondary: advanced CLI users
The CLI is a thin wrapper over this runtime. If you want to understand what actually happens when you run:
halter runhalter chathalter resources
this crate explains the core machinery.
Public API at a glance
Key exports include:
SessionRuntimeHalterSessionSessionInitRuntimeServicesResourceHandleEventBusContextSettingsContextManager,DefaultContextManagerPromptAssembler,DefaultPromptAssembler- hook runtime integration helpers
score_message(...)
These are the building blocks for long-lived, tool-using, event-emitting agent sessions.
Mental model
At a high level, halter-runtime owns the session lifecycle.
A typical flow looks like this:
- create
SessionRuntime - open a new session with
new_session(...) - receive a
HalterSessionhandle - submit turns with
submit_turn(...) - observe emitted
SessionEvents via the event bus and/or session store - optionally compact, replay, resume, notify, or shut down the session
SessionRuntime is the factory and coordinator.
HalterSession is the live handle for one session.
SessionRuntime
This is the main runtime object.
Important methods:
newsubagent_controlnew_sessionresumelist_sessionsreplace_resources
Responsibilities
A SessionRuntime bundles the services required to execute sessions:
- model registry / providers
- tool runtime
- hook engine integration
- session persistence
- prompt assembly
- context management
- resource handle
- event publication
In other words, it is the runtime environment in which sessions live.
SessionInit
SessionInit controls how a session starts.
Fields include:
session_idparent_session_idworking_dirsystem_prompt_seedmax_turnsdefault_modelsubagent_modelsubagent_event_forwardingsubagent_depth
Typical use
use ;
let init = SessionInit ;
A parent session spawning a subagent would set:
parent_session_idsubagent_depth- often
subagent_model
Creating a session
Typical sketch:
use ;
async
The top-level halter crate wraps this with simpler convenience APIs.
HalterSession / SessionHandle
SessionHandle is the live control surface for a single session.
HalterSession is a backwards-compatible alias for SessionHandle.
The handle is cheaply cloneable: each clone is an Arc bump on the
shared session state. Hooks registered for the session are evicted from
the runtime store only when the last clone of the handle is dropped,
not when an arbitrary clone goes out of scope (this is the AC2.1
guarantee — pre-Phase-3 a clone moved into the spawned turn loop would
evict hooks under the still-live caller handle).
Important methods:
session_idsubmit_turnreplayshutdownnotifycompact(trigger, custom_instructions)
submit_turn(...)
This is the main way to drive work.
Conceptually, submitting a turn causes the runtime to:
- persist the user input/event
- assemble prompt context
- invoke provider inference
- execute tool calls if requested
- emit session events
- persist the resulting timeline updates
replay()
Reconstructs the event timeline from persisted state.
This is useful for:
- transcript display
- testing
- UI hydration after reconnect
- postmortem debugging
notify(...)
Injects runtime notifications into the session stream.
compact(...)
Triggers session compaction according to the configured context manager and provider support. The runtime asks the provider for a safe compaction window, then runs the same preparation, provider request, event, and state-application path used by automatic compaction.
If the underlying provider does not support compaction, you can see an error like:
failed to compact session: provider '{}' does not support compaction
shutdown()
Gracefully stops the session.
Event flow
EventBus
The runtime exposes a publish/subscribe event bus.
Important methods:
EventBus::new(capacity)publish(...)subscribe()dropped_events()
Why it matters
This lets you separate execution from observation.
You can:
- stream session events to a terminal UI
- feed them into logs or metrics
- power a web frontend
- react to tool starts/stops
- inspect event drops under backpressure
Example sketch:
use EventBus;
let bus = new;
let mut rx = bus.subscribe;
// elsewhere: bus.publish(event)
// consumer: read events from rx
If consumers fall behind, dropped_events() provides visibility into loss.
Context management
Large sessions need pruning and compaction.
This crate exports:
ContextSettingsContextManagerDefaultContextManagerscore_message(...)
What the context manager does
It decides how much of the existing transcript to keep inline when constructing the next model request.
That includes:
- estimating message weight/importance
- honoring compaction thresholds
- pruning within the provider-selected compaction window
- coordinating with provider-backed compaction when available
score_message(...)
This helper supports ranking/prioritization decisions about which messages matter most for context retention.
Practical implication
If you are building long-lived coding sessions, context management is what keeps them alive without unbounded prompt growth.
Prompt assembly
Prompt assembly is exposed through:
PromptAssemblerDefaultPromptAssembler
The runtime includes an embedded default system prompt sourced from:
crates/halter-runtime/prompts/default-system.md
What prompt assembly combines
A realistic prompt assembly pass can include:
- built-in system prompt
system_prompt_seedfromSessionInit- configured prompt overrides
- compiled resource instructions
- active skills/plugins
- hook-injected system messages
- selected conversation history
This is important because the final model request is not just "the last user message".
Resource hot-swapping
SessionRuntime::replace_resources(...) lets you atomically swap the resource handle used by future prompt assembly and runtime lookups.
This is useful when:
- you recompiled repo-local skills/plugins
- a project changed instructions on disk
- you want to reload policy or prompt resources without rebuilding the whole runtime
Existing sessions keep running against the runtime, but new prompt assembly operations use the updated resource set.
Resuming and listing sessions
resume(session_id)
Resumes a previously persisted session.
Use this when you have a durable session store and want continuity across process restarts.
list_sessions()
Returns the session inventory known to the backing store.
This is useful for:
- building admin UIs
- selecting resumable sessions
- cleanup or maintenance workflows
Subagent support
Subagents are not a bolt-on feature. The runtime has first-class support for them.
Key components include:
subagent_control()onSessionRuntimeparent_session_idandsubagent_depthinSessionInit- dedicated subagent modules in the crate
Why this matters
A parent session can delegate work while preserving:
- lineage
- policy constraints
- event attribution
- model routing
- persistence
Subagent control is also coordinated with the tool layer, where tools like spawn_agent, wait_agent, send_input, and close_agent live.
Hooks integration
This runtime is where hooks become operational.
It is responsible for invoking hook dispatch around meaningful lifecycle boundaries like:
- session start/end
- user prompt submission
- tool execution
- permission requests
- compaction
- subagent lifecycle
If you're debugging why a tool call was blocked or why extra system guidance appeared in a prompt, the hook invocation path through halter-runtime is usually where to look.
Runtime services
RuntimeServices and ResourceHandle help package runtime dependencies and the currently active compiled resources.
You will care about these when building a custom embedding, custom runtime composition layer, or resource reload system.
Realistic embedding pattern
A service embedding halter-runtime directly often looks like this architecturally:
- load config with
halter-config - build providers and model registry with
halter-providers - build tool runtime with
halter-tools - load/compile resources with
halter - create a durable session store with
halter-session - construct
SessionRuntime - create/resume sessions on demand
- subscribe to event streams for UI/logging
If you don't want to assemble all of that manually, use halter::Halter.
Failure modes and operational concerns
Provider capability mismatch
If your session needs compaction but the provider does not support it, compaction fails. Providers that do support compaction own the safe window selection; runtime does not need to know whether that provider uses a dedicated endpoint or an inline request.
Event backpressure
If subscribers are slow, EventBus can drop events depending on capacity and downstream behavior.
The per-turn stream can also include subagent events when subagent_event_forwarding is enabled for the session. Forwarded events keep the child session_id; the configured forwarding cap emits a synthetic Lagged event and stops forwarding for that parent turn when exceeded.
Persistence conflicts
If the backing store rejects concurrent commits, the session layer may surface commit conflict errors from halter-session.
Misconfigured subagent depth or model routing
If tool policy and runtime session init disagree about subagent constraints, delegation may fail.
Guidance for advanced users
- Use the default assembler and context manager first; replace them only if you have a concrete reason.
- Subscribe to the event bus early in development. It makes runtime behavior dramatically easier to understand.
- Treat session replay as a first-class debugging tool.
- Use durable session backends if you want resumability across restarts.
- Be explicit about default vs subagent model roles.
Example: building a simple runner abstraction
use ;
pub async
This is not the full richness of the runtime, but it shows the core shape.
Related docs
../halter/README.md— high-level builder and convenience layer../halter-session/README.md— session persistence backing the runtime../halter-tools/README.md— tool execution surface used inside turns../halter-hooks/README.md— event interception and policy overlays../halter-providers/README.md— model backends invoked by the runtime../halter-cli/README.md— command-line wrapper over this engine