leonard_agent_io library

I/O-only harness entrypoint for hosts that create VM-service connections.

This re-exports the web-safe leonard_agent.dart API and adds the owning connection functions used by command-line and native hosts. Web consumers should continue to import leonard_agent.dart and supply a borrowed VmService connection.

Classes

ActionSchema
JSON Schema (draft-07) constraining a single model decision.
ActionValidator
Five-pass validator for candidate actions.
AnthropicBackend
Anthropic (Claude) via stock dartantic AnthropicChatModel.
AssistantTurn
An assistant-role turn: the thinking trace (empty when absent) and the tool call the model chose.
ChangedValue
One key-level change inside a structured extension diff.
ConversationBuilder
ConversationSnapshot
Immutable snapshot of a chat-shape conversation at a point in time.
ConversationTurn
Sealed turn hierarchy for the append-only chat conversation (chat-shape rebuild).
CoreDiff
Diff over the core fragment.
CoreFragment
Core fragment: route stack + semantics nodes (id-keyed) + recent errors.
DartanticModelProvider
A ModelProvider that drives ANY dartantic ChatModel built from a ModelBackendSpec (ADR 0003, lenny-4dhv.3).
DefaultLoopHost
ExtensionAutoDisabled
Emitted when LeonardSession.disableExtension auto-disables a extension.
ExtensionBusy
Subset of the binding's per-extension "busy at termination" descriptor.
ExtensionDiff
Sealed extension diff; one of ExtensionDiffStructured, ExtensionDiffOpaque, ExtensionDiffAdded, ExtensionDiffRemoved.
ExtensionDiffAdded
Extension namespace appears in curr but not in prev.
ExtensionDiffOpaque
Opaque pair (previous, current). Used when either side is not deltaFriendly.
ExtensionDiffRemoved
Extension namespace appears in prev but not in curr (e.g. extension auto-disabled).
ExtensionDiffStructured
Key-level structured diff. Selected when both prev and curr declared deltaFriendly: true.
ExtensionDisabledEvent
ExtensionFailureTracker
ExtensionFragment
One extension's contribution to the observation bundle.
ExtensionManifestEntry
One entry in the handshake extension manifest.
ExtensionManifestRecord
HandshakeResult
Result of the ext.leonard.core.handshake exchange.
HostAttachment
One host to attach: its diagnostic label + the ws:// uri to reach it. Exported so CLI callers can name endpoints (e.g. flutter/native).
JsonObservationRenderer
Renders Observation as compact JSON. Top-level keys are stable (core, extensions, stability) so a jsonDecode of the output is structurally identical to the wire format the binding produced (modulo screenshot, which the renderer omits — providers add the image as a separate content part).
LeonardConfig
Per-session configuration consumed by later harness stories (turn budgets, session budgets, max turns).
LeonardSession
Owns the run lifecycle: connect, start, observe, act, end.
LoopDriver
LoopHost
ModelBackendSpec
Declarative description of a model backend the agent can drive, independent of how it is wired (ADR 0003, lenny-4dhv.2).
ModelCapabilities
Capabilities advertised by a ModelProvider.
ModelDecision
One decision returned by ModelProvider.decide.
ModelProvider
Provider contract for model backends used by the Leonard agent.
MultiHostSession
N-host generalization of LeonardSession. Implements SessionSurface so DefaultLoopHost/bringUpSession drive it identically to a single-host session.
NodeChange
One semantics node whose content differs between prev and curr.
Observation
Top-level typed observation bundle.
ObservationDiff
Top-level diff: per-turn delta over Observation.
ObservationDiffer
Stateless differ. All routines are pure functions of the two inputs.
ObservationRenderer
Interface used by ConversationBuilder to flatten an Observation into the text body of a UserTurn for any provider.
OpenAIBackend
OpenAI (or any OpenAI-compatible endpoint) via stock dartantic OpenAIProvider / OpenAIChatModel.
OpenAiModel
One OpenAI model entry — id + capabilities.
RouteChange
One route-stack change: previous full stack -> current full stack.
RuntimeError
One runtime error captured in the binding's error ring buffer.
SemanticsNode
One semantics node from the captured tree.
SessionEnded
Emitted exactly once when LeonardSession.end runs.
SessionFooter
SessionHeader
SessionProgressEvent
Sealed base type for events emitted on LeonardSession.progress.
SessionStarted
Emitted exactly once when LeonardSession.start succeeds.
SessionSurface
The contract the loop driver's host adapter relies on.
SessionTermination
Structured return value from LoopDriver.runSession.
StabilityMetadata
Wire-typed mirror of the binding's stability block.
SwiftInferBackend
swift-infer (local MLX, Anthropic-compatible wire) via the custom SwiftInferChatModel — the backend that needed lenient SSE parsing and the Qwen sampling knobs (lenny-4dhv.1).
SwiftInferChatModel
A dartantic ChatModel that speaks swift-infer's Anthropic-compatible /v1/messages wire (ADR 0003, lenny-4dhv.1).
SwiftInferChatOptions
Generation options for SwiftInferChatModel.
SwiftInferConfig
Configuration values for the dartantic SwiftInferChatModel seam.
ThinkingDelta
One delta emitted on the ModelProvider.thinking stream.
ToolDescriptor
Description of a single tool available to the model on a given turn.
TrajectoryReader
Decodes JSONL trajectories into TrajectoryRecords.
TrajectoryRecord
Wire format for the trajectory JSONL stream (PRD §14).
TrajectorySink
Append-only line sink for trajectory writers. Implementations live outside leonard_agent (e.g. FileTrajectorySink using dart:io in the CLI, DtdTrajectorySink using package:dtd in DevTools).
TrajectoryWriter
Serializes typed trajectory records to JSONL via a TrajectorySink.
TurnActionDecided
The action the loop chose for this turn (post-validation).
TurnBegan
Emitted at the start of each turn.
TurnComplete
End-of-turn marker.
TurnEvent
One event in the per-turn stream.
TurnRecord
TurnThinking
One reasoning delta from the model provider's thinking() stream.
TurnUsage
Per-turn token-usage snapshot. Emitted just before TurnComplete at each turn boundary. Carries the whitespace-split estimate and the trim-budget ceiling so DevTools consumers can display used/ceiling without reading internal ConversationBuilder state.
TurnValidation
Validation outcome for the turn's chosen action.
UnknownTrajectoryRecord
Fallback when the type discriminator is absent or unrecognized.
UserTurn
A user-role turn: one observation + diff from the loop driver, plus an optional tool-result map for error feedback (schema/validation retry or failed action). trimmed is set by ConversationBuilder.trimIfOverBudget.
ValidationLoopResult
Result of a successful decideAndValidate call.
ValidationOk
The candidate action passed all three validation passes.
ValidationReject
The candidate action was rejected by one of the three passes.
ValidationResult
Outcome of validating a candidate action against the merged tool list and the current observation.
VmServiceClient
Typed VM-service client used by LeonardSession.

Enums

HarnessError
Sub-classification of a session that ended with SessionOutcome.harnessError. Mirrors the wire-level harness_error field carried in the trajectory footer.
SessionOutcome
StabilityPolicy
Wire-name mapping for the request's policy parameter. Mirrors kStabilityPolicyWireNames on the binding side (packages/leonard_flutter/.../observation_request.dart).
SwiftInferReasoningEffort
How much reasoning swift-infer's chat template budgets for a turn (reasoning_effort). Non-Anthropic-standard.
SwiftInferToolChoice
How swift-infer should be told to pick a tool each turn.

Extensions

HarnessErrorWire on HarnessError
Wire name for HarnessError values, as written to the trajectory footer's harness_error string.

Constants

kAnthropicVisionModels → const Set<String>
Set of Claude model ids that accept image inputs (PRD §22). Public so a host can advertise vision support without instantiating a provider. (Relocated here from the deleted hand-rolled Anthropic provider — dartantic cutover, ADR 0003 / lenny-4dhv.4.)
kDefaultAgentsMd → const String
The bundled operating guide. See the library doc for provenance.
kIoTransportExceptionNames → const Set<String>
Runtime-type names of the dart:io transport exceptions treated as provider transport failures.
kMaxDetailLength → const int
Maximum length of a footer detail message, so one long exception cannot bloat the trajectory footer.
kQwen38IdPrefix → const String
Model-id prefix whose chat template runs xhigh when reasoning_effort is absent. Mirrors the startsWith('qwen3') test in capabilitiesFor.
kSwiftInferDriverMaxTokens → const int
Driver default for max_tokens on the swift-infer tier.
openAiModels → const Map<String, OpenAiModel>
Known GPT-5-class models. Keyed by wire id.

Properties

kDefaultAgentsMdHash String
Stable provenance hash of kDefaultAgentsMd for trajectory headers.
final

Functions

bringUpSession({required SessionSurface session, required String goal, required StabilityPolicy policy, required String modelIdentifier, required String buildIdentifier, required String harnessVersion, required List<ToolDescriptor> coreTools, required Map<String, List<ToolDescriptor>> extensionTools, required String agentsMd, String agentsMdHash = '', Map<String, dynamic>? extraConfig}) Future<BringUpResult>
Build the extension manifest, assemble a SessionHeader, compose a DefaultLoopHost, and return both.
buildBackendChatModel(ModelBackendSpec spec, {required String model, List<Tool<Object>>? tools, Client? client}) → ChatModel<ChatModelOptions>
Builds a configured dartantic ChatModel for spec.
buildExtensionTools({required Iterable<String> requested, required List<ExtensionManifestEntry> handshake}) Map<String, List<ToolDescriptor>>
Build the extensionTools map from a caller-supplied namespace whitelist and the binding's handshake manifest. See library doc for semantics.
capabilitiesFor(String providerId, String modelId) ModelCapabilities?
Resolve capabilities for the given (provider, model) pair.
connectLeonardSession(Uri vmServiceUri) Future<LeonardSession>
Opens an owned VM-service connection and wraps it in a LeonardSession.
connectMultiHostSession(List<HostAttachment> hosts) Future<MultiHostSession>
Opens one owned VM-service connection for each host, in attach order.
connectVmServiceClient(Uri wsUri) Future<VmServiceClient>
Opens an owned VM-service connection to wsUri and pins its first isolate.
decideAndValidate({required ModelProvider provider, required ConversationSnapshot baseSnapshot, required ActionSchema schema, required ActionValidator validator, required Observation observation, required List<ToolDescriptor> mergedTools, int maxValidationRetries = _kDefaultMaxValidationRetries}) Future<ValidationLoopResult>
Run one decide-and-validate cycle for a single turn against a chat-shape ConversationSnapshot.
defaultReasoningEffortFor(String modelId) SwiftInferReasoningEffort?
SwiftInferReasoningEffort.medium for a qwen3.8 node, else null — an unset effort is omitted from the body, leaving the node's card default.
defaultSwiftInferOptions(String modelId, {int? maxTokens, SwiftInferReasoningEffort? reasoningEffort, double? temperature, double? presencePenalty}) SwiftInferChatOptions
The options lenny's drivers send for modelId; each supplied override wins over the default.
describeThrowable(Object? error) String
Footer-safe rendering of error: its runtime type, then its credential-scrubbed message clipped to kMaxDetailLength.
encodeToolName(String dotted) String
Encode a dotted ToolDescriptor name (core.tap) into the wire form accepted by Anthropic's tool API (core_tap).
fnv1a32Hex(String s) String
Web-safe 32-bit FNV-1a hash (hex) — masks to 32 bits each step so it is deterministic on both the VM and dart2js/dart2wasm (unlike the CLI's native 64-bit variant, which relies on two's-complement overflow). Used only as a stable provenance stamp for SessionHeader.agentsMdHash; cross-harness equality is not required.
isProviderTransportError(Object error) bool
True when error is a provider TRANSPORT failure — the request or its response stream died before the model produced output.
lookupTool(List<ToolDescriptor> tools, String wireName) ToolDescriptor?
Look up a ToolDescriptor by its wire-encoded name (dotted name with . replaced by _). Returns null when no match exists.
manifestToolDescriptors(ExtensionManifestEntry entry) List<ToolDescriptor>
Project one handshake entry into qualified model tool descriptors.
mergeObservations(List<Observation> perHost) Observation
Merge perHost observations (in attach order, primary/Flutter first) into one Observation:
scrubCredentials(String text) String
Replaces credential-shaped substrings of text with <redacted>.
unknownExtensionNamespaces({required Iterable<String> requested, required List<ExtensionManifestEntry> handshake}) List<String>
Names the caller listed in requested that are not present in the handshake manifest (i.e. the binding does not have an extension with that namespace loaded). Returned in iteration order of requested so warnings match caller input.
unknownToolRejection(String wireName, List<ToolDescriptor> tools, {required Map<String, Object?> rawPayload}) SchemaRejection
Build the canonical SchemaRejection for "model emitted a tool name the prompt did not offer". All frontier providers share this shape so the loop driver and any panel-side observer see the same surface.
validateToolArgs(ToolDescriptor tool, Map<String, dynamic> args) → void
Validate args against tool.inputSchema using the same package:json_schema draft-07 path as ActionSchema.

Typedefs

BringUpResult = ({SessionHeader header, DefaultLoopHost host})
Result returned by bringUpSession.
ValidatorAction = ({Map<String, dynamic> args, String tool})
Shape of the action handed to ActionValidator.validate.

Exceptions / Errors

BindingNotInitializedError
Thrown when the target app's LeonardBinding is not initialized, i.e. the ext.leonard.core.handshake service extension is absent.
InvalidActionExhausted
Thrown by decideAndValidate when the validator rejected the model output 3 times (PRD §17). Carries the structured rejection messages the driver writes into the failed-turn record.
MultiHostNamespaceCollision
Thrown by MultiHostSession.start when two attached hosts each report the SAME manifest namespace in their handshake.
MultiHostUnknownNamespace
Thrown synchronously by MultiHostSession.executeAction (before any wire call) when an action's namespace is owned by no attached host.
ObservationEnvelopeError
Thrown when get_stable_observation does not return an observation envelope this harness can decode.
ProviderTransportFailure
Raised by decideAndValidate when ModelProvider.decide failed with a transport fault rather than a model-output fault. LoopDriver.runTurn converts it into a provider_transport TurnFailure.
SchemaExhausted
Thrown by decideAndValidate when the provider threw SchemaRejection twice in a row (PRD §17 schema budget = 1).
SchemaRejection
Thrown when a model response fails JSON-Schema validation.
SwiftInferHttpException
Thrown when swift-infer returns a non-2xx HTTP status. The seam maps this to a failed turn; it is distinct from a schema/validation rejection.
TurnFailure
Thrown by LoopDriver.runTurn when a turn fails for a reason that counts toward a failure budget (PRD §17).
TurnTimeoutError
Thrown by the per-turn budget when a single turn exceeds the wall- clock budget (PRD §10, default 30s).
VmServiceConnectionLost
Raised by LoopDriver.runSession when the underlying VM-service transport emits an unrecoverable error mid-session (e.g. the websocket dropped). The driver translates this into a HarnessError.connectionLost termination.