Skip to main content

lash_core/
lib.rs

1//! Runtime kernel for Lash.
2//!
3//! The process kernel intentionally understands `ToolCall`, `SessionTurn`, and
4//! `External` because those inputs carry runtime mechanisms core must enforce:
5//! tool orchestration, child-session turns, and externally completed work. New
6//! process runtimes should use `ProcessInput::Engine { kind, payload }` unless
7//! core must understand their semantics to enforce a kernel mechanism.
8//!
9//! Protocols follow the same boundary: core owns the `HostTurnProtocol` state
10//! shape and the `ProtocolDriverPlugin` slot, while external protocol crates
11//! provide the driver implementation.
12
13pub mod attachments;
14pub mod chronological;
15pub mod direct;
16pub mod llm;
17mod model;
18pub mod plugin;
19mod plugin_stack;
20mod protocol_build;
21pub mod provider;
22pub mod runtime;
23pub mod session;
24pub mod session_graph;
25pub mod session_model;
26mod stable_hash;
27pub mod store;
28#[cfg(any(test, feature = "testing"))]
29pub mod testing;
30pub mod tool_dispatch;
31mod tool_provider;
32pub mod tool_registry;
33mod tool_result;
34mod trace;
35pub mod triggers;
36
37pub use lash_sansio::sansio;
38
39pub const VERSION: &str = env!("CARGO_PKG_VERSION");
40pub const SANSIO_VERSION: &str = lash_sansio::VERSION;
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
43#[serde(rename_all = "snake_case")]
44pub enum DurabilityTier {
45    Inline,
46    Durable,
47}
48
49// Re-exports
50pub use attachments::{
51    AttachmentReclamationReport, AttachmentRootSet, AttachmentStore, AttachmentStoreError,
52    AttachmentStorePersistence, FileAttachmentStore, InMemoryAttachmentStore,
53    NoopAttachmentManifest, SessionAttachmentStore, StoredAttachment, StoredBlobRef,
54    reclaim_unreferenced_attachments,
55};
56pub use chronological::{
57    BorrowedChronologicalEntry, BorrowedChronologicalMessage, BorrowedChronologicalPayload,
58    ChronologicalEntry, ChronologicalPayload, ChronologicalProjection, visit_turn_view,
59};
60pub use direct::{
61    DirectJsonSchema, DirectLlmClient, DirectLlmError, DirectLlmResult, DirectMessage,
62    DirectOutputSpec, DirectPart, DirectRequest, DirectRole,
63};
64pub use lash_sansio::llm::types::{
65    AttemptOutcome, AttemptRecord, ExecutionEvidence, GenerationOptions, LlmCallId, LlmCallRecord,
66    LlmOutputPart, LlmRequest, LlmRequestScope, LlmResponse, LlmTerminalReason, NormalizedError,
67    ProtocolPosition, RetryDecision,
68};
69pub use lash_sansio::{
70    AcceptedInjectedTurnInput, AttachmentCreateMeta, AttachmentId, AttachmentMeta, AttachmentRef,
71    BaseRenderCache, CheckpointDelivery, CheckpointKind, CompactToolContract, EffectId,
72    ErrorEnvelope, ExecImage, ExecResponse, ImageMediaType, LashSchema, LlmCallError, MediaType,
73    Message, MessageOrigin, MessageRole, MessageSequence, ModelToolReturn, ModelToolReturnPart,
74    Part, PartKind, PluginMessage, PluginRuntimeEvent, PreparedPrompt, ProjectionMode,
75    PromptBuildInput, PromptBuiltin, PromptContext, PromptContribution, PromptContributionGate,
76    PromptContributionSet, PromptFingerprint, PromptLayer, PromptSlot, PromptSlotLayer,
77    PromptTemplate, PromptTemplateEntry, PromptTemplateSection, ProviderSchemaCapabilities,
78    PruneState, RenderedPrompt, ResolvedPromptLayer, ResolvedSchema, Response, SchemaContract,
79    SchemaDialect, SchemaProjectionOverride, SchemaProjectionPolicy, SchemaPurpose,
80    SchemaResolutionError, SchemaResolutionRequest, SessionAppendNode, SessionStreamEvent,
81    TextProjectionMetadata, TokenUsage, ToolActivation, ToolArgumentProjectionPolicy,
82    ToolCallOutcome, ToolCallOutput, ToolCallRecord, ToolCallStatus, ToolCancellation, ToolCatalog,
83    ToolCatalogBuildInput, ToolCatalogEntry, ToolContract, ToolControl, ToolDefinition,
84    ToolFailure, ToolFailureClass, ToolFailureSource, ToolId, ToolManifest, ToolOutputContract,
85    ToolRetryDisposition, ToolRetryPolicy, ToolScheduling, ToolValue, TurnCause, TurnFinish,
86    TurnLimitFinalMessage, TurnOutcome, TurnStop, append_assistant_text_part, build_prompt,
87    build_tool_catalog, build_turn, default_prompt_template, head_tail_truncate,
88    messages_are_prompt_resume_safe, normalized_response_parts, project_anthropic_bedrock_schema,
89    project_for_dialect, prompt_template_fingerprint, prompt_text_fingerprint,
90    prompt_tool_names_fingerprint, reasoning_part, render_turn_causes_prompt,
91    resolve_prompt_layers, resolve_schema, shared_parts, validate_tool_input,
92    visible_response_parts, visible_response_text_from_parts,
93};
94
95/// Project a successful tool control into its terminal turn outcome.
96///
97/// Agent-frame seeds are typed at their serde boundary, so a terminal outcome
98/// can never advertise nodes that the commit materializer would have to drop.
99pub fn turn_outcome_from_tool_control(
100    tool_name: &str,
101    control: &ToolControl,
102) -> Option<TurnOutcome> {
103    match control {
104        ToolControl::SwitchAgentFrame {
105            frame_id,
106            initial_nodes,
107            task: Some(task),
108        } if !frame_id.trim().is_empty() && !task.trim().is_empty() => {
109            Some(TurnOutcome::AgentFrameSwitch {
110                frame_id: frame_id.clone(),
111                task: task.clone(),
112                initial_nodes: initial_nodes.clone(),
113            })
114        }
115        ToolControl::Finish { value } => Some(TurnOutcome::Finished(TurnFinish::ToolValue {
116            tool_name: tool_name.to_string(),
117            value: value.to_json_value(),
118        })),
119        ToolControl::Fail { failure } => Some(TurnOutcome::Stopped(TurnStop::ToolError {
120            tool_name: tool_name.to_string(),
121            value: failure.to_json_value(),
122        })),
123        ToolControl::SwitchAgentFrame { .. } => None,
124    }
125}
126pub use protocol_build::ProtocolBuildInput;
127pub use tool_registry::{
128    PLUGIN_TOOL_SOURCE_ID, ReconfigureError, ToolRegistry, ToolRestoreReport, ToolSourceHandle,
129    ToolState, ToolStateEntry,
130};
131pub use tool_result::{CancelHint, PendingCompletion, TimeoutBehavior, ToolResult};
132pub use triggers::{
133    InMemoryTriggerStore, TriggerDeliveryEmitOutcome, TriggerDeliveryEmitReport,
134    TriggerDeliveryReservation, TriggerDeliveryReservationStatus, TriggerEmitReport, TriggerEvent,
135    TriggerEventCatalog, TriggerEventKey, TriggerEventType, TriggerInputBinding,
136    TriggerOccurrenceFilter, TriggerOccurrenceRecord, TriggerOccurrenceRequest,
137    TriggerRegistration, TriggerRouter, TriggerStore, TriggerSubscriptionDraft,
138    TriggerSubscriptionFilter, TriggerSubscriptionRecord, TriggerTargetSummary,
139    default_trigger_source_key, deterministic_delivery_process_id, deterministic_occurrence_id,
140    empty_trigger_source_key, trigger_event_type, trigger_occurrence_request_hash,
141    validate_trigger_occurrence_request,
142};
143pub const PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION: u32 = 1;
144
145#[derive(Clone, Debug, serde::Serialize)]
146pub struct ProtocolTurnOptions {
147    pub schema_version: u32,
148    pub payload: serde_json::Value,
149}
150
151fn empty_protocol_turn_payload() -> serde_json::Value {
152    serde_json::Value::Object(serde_json::Map::new())
153}
154
155#[derive(Debug, thiserror::Error)]
156pub enum ProtocolTurnOptionsError {
157    #[error(
158        "protocol turn options are missing schema_version and were written by unsupported pre-versioned state (expected {expected})"
159    )]
160    MissingSchemaVersion { expected: u32 },
161    #[error(
162        "protocol turn options schema_version {actual} is not supported by this binary (expected {expected})"
163    )]
164    UnsupportedSchemaVersion { actual: u32, expected: u32 },
165    #[error(
166        "protocol turn options schema_version {actual} is invalid (expected integer {expected})"
167    )]
168    InvalidSchemaVersion { actual: String, expected: u32 },
169    #[error("failed to decode protocol turn options payload: {0}")]
170    Decode(#[source] serde_json::Error),
171}
172
173fn parse_protocol_turn_options_schema_version(
174    value: Option<serde_json::Value>,
175) -> Result<u32, ProtocolTurnOptionsError> {
176    let expected = PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION;
177    let Some(value) = value else {
178        return Err(ProtocolTurnOptionsError::MissingSchemaVersion { expected });
179    };
180    let Some(actual) = value
181        .as_u64()
182        .and_then(|version| u32::try_from(version).ok())
183    else {
184        return Err(ProtocolTurnOptionsError::InvalidSchemaVersion {
185            actual: value.to_string(),
186            expected,
187        });
188    };
189    ensure_protocol_turn_options_schema_version(actual)?;
190    Ok(actual)
191}
192
193fn ensure_protocol_turn_options_schema_version(
194    actual: u32,
195) -> Result<(), ProtocolTurnOptionsError> {
196    let expected = PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION;
197    if actual == expected {
198        Ok(())
199    } else {
200        Err(ProtocolTurnOptionsError::UnsupportedSchemaVersion { actual, expected })
201    }
202}
203
204impl<'de> serde::Deserialize<'de> for ProtocolTurnOptions {
205    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
206    where
207        D: serde::Deserializer<'de>,
208    {
209        #[derive(serde::Deserialize)]
210        struct ProtocolTurnOptionsWire {
211            schema_version: Option<serde_json::Value>,
212            #[serde(default = "empty_protocol_turn_payload")]
213            payload: serde_json::Value,
214        }
215
216        let wire = ProtocolTurnOptionsWire::deserialize(deserializer)?;
217        let schema_version = parse_protocol_turn_options_schema_version(wire.schema_version)
218            .map_err(serde::de::Error::custom)?;
219        Ok(Self {
220            schema_version,
221            payload: wire.payload,
222        })
223    }
224}
225
226impl Default for ProtocolTurnOptions {
227    fn default() -> Self {
228        Self::empty()
229    }
230}
231
232impl ProtocolTurnOptions {
233    pub fn empty() -> Self {
234        Self {
235            schema_version: PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION,
236            payload: serde_json::Value::Object(serde_json::Map::new()),
237        }
238    }
239
240    pub fn from_payload(payload: serde_json::Value) -> Self {
241        Self {
242            schema_version: PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION,
243            payload,
244        }
245    }
246
247    pub fn is_empty(&self) -> bool {
248        match &self.payload {
249            serde_json::Value::Object(map) => map.is_empty(),
250            _ => false,
251        }
252    }
253
254    pub fn merged_with_override(&self, override_options: &Self) -> Self {
255        match (&self.payload, &override_options.payload) {
256            (serde_json::Value::Object(base), serde_json::Value::Object(overrides)) => {
257                let mut payload = base.clone();
258                payload.extend(overrides.clone());
259                Self {
260                    schema_version: PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION,
261                    payload: serde_json::Value::Object(payload),
262                }
263            }
264            _ => override_options.clone(),
265        }
266    }
267
268    pub fn typed<T>(value: T) -> Result<Self, serde_json::Error>
269    where
270        T: serde::Serialize,
271    {
272        Ok(Self {
273            schema_version: PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION,
274            payload: serde_json::to_value(value)?,
275        })
276    }
277
278    pub fn decode<T>(&self) -> Result<T, ProtocolTurnOptionsError>
279    where
280        T: serde::de::DeserializeOwned,
281    {
282        ensure_protocol_turn_options_schema_version(self.schema_version)?;
283        serde_json::from_value(self.payload.clone()).map_err(ProtocolTurnOptionsError::Decode)
284    }
285}
286
287#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
288pub struct ProtocolDriverState {
289    pub plugin_id: String,
290    pub payload: serde_json::Value,
291}
292
293impl ProtocolDriverState {
294    pub fn new(plugin_id: impl Into<String>, payload: serde_json::Value) -> Self {
295        Self {
296            plugin_id: plugin_id.into(),
297            payload,
298        }
299    }
300}
301
302#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
303pub struct HostTurnProtocol;
304
305impl lash_sansio::TurnProtocol for HostTurnProtocol {
306    type Event = crate::session_model::ProtocolEvent;
307    type Termination = ProtocolTurnOptions;
308    type DriverState = ProtocolDriverState;
309}
310
311pub type Effect = lash_sansio::Effect<HostTurnProtocol>;
312pub type DriverAction = lash_sansio::DriverAction<HostTurnProtocol>;
313pub type DriverContextView<'a> = lash_sansio::DriverContextView<'a, HostTurnProtocol>;
314pub type TurnDriverConfig = lash_sansio::TurnDriverConfig<HostTurnProtocol>;
315pub type TurnDriverPreamble = lash_sansio::TurnDriverPreamble<HostTurnProtocol>;
316pub type ProjectorContext<'a> = lash_sansio::ProjectorContext<'a, HostTurnProtocol>;
317pub type PreparedTurnMachine = lash_sansio::PreparedTurnMachine<HostTurnProtocol>;
318pub type SansIoTurnInput = lash_sansio::SansIoTurnInput<HostTurnProtocol>;
319pub type TurnMachine = lash_sansio::TurnMachine<HostTurnProtocol>;
320pub type TurnMachineConfig = lash_sansio::TurnMachineConfig<HostTurnProtocol>;
321#[cfg(feature = "otel-trace")]
322pub use lash_trace::otel::{OtelTraceOptions, OtelTraceSink};
323pub use lash_trace::{
324    JsonlTraceSink, TraceAttachment, TraceBranchSelection, TraceContentBlock, TraceContext,
325    TraceError, TraceEvent, TraceLabelMetadata, TraceLevel, TraceLlmMessage, TraceLlmRequest,
326    TraceLlmResponse, TracePromptComponent, TraceProviderStreamEvent, TraceRecord,
327    TraceRuntimeScope, TraceRuntimeStreamEvent, TraceRuntimeSubject, TraceSink, TraceSinkError,
328    TraceTokenUsage, TraceToolSpec,
329};
330pub use llm::transport::{LlmTransportError, ProviderFailure, ProviderFailureKind};
331pub use model::{ModelLimits, ModelSpec};
332pub use plugin::{
333    AgentFrameAssignment, AgentFrameId, AgentFrameReason, AgentFrameRecord, AgentFrameStatus,
334    AppendSessionNodesRequest, AppendSessionNodesResult, AssistantResponseHookContext,
335    AssistantResponseTransform, AssistantStreamHookContext, AssistantStreamTransform,
336    CheckpointHookContext, CompactionContext, ContextCompaction, ContextCompactor, ContextError,
337    ContextRegistrations, DirectCompletion, DirectLlmCompletion, OpenAgentFrameRequest,
338    OpenAgentFrameResult, PersistentRuntimeServices, PluginCommand, PluginCommandContext,
339    PluginCommandOutcome, PluginCommandReceipt, PluginDirective, PluginError,
340    PluginExtensionContribution, PluginExtensions, PluginFactory, PluginHost, PluginLifecycleEvent,
341    PluginLifecycleEventHook, PluginOperation, PluginOperationDef, PluginOperationFailure,
342    PluginOperationInvokeError, PluginOperationKind, PluginOptions, PluginOwned, PluginQuery,
343    PluginQueryContext, PluginRegistrar, PluginRuntimeDirective, PluginSession,
344    PluginSessionContext, PluginSessionSnapshot, PluginSnapshotArtifact, PluginSnapshotEntry,
345    PluginSnapshotMeta, PluginSpec, PluginSpecFactory, PluginTask, PluginTaskContext,
346    PluginTaskOutcome, PluginTaskReceipt, ProcessEngineContributionContext, PromptHookContext,
347    ProtocolBeforeLlmCallContext, ProtocolLlmCallAction, RuntimeServices,
348    SessionConfigChangedContext, SessionContextOverlay, SessionCreateRequest, SessionGraphService,
349    SessionHandle, SessionLifecycleService, SessionParam, SessionPlugin, SessionPluginSource,
350    SessionReadView, SessionRelation, SessionSnapshot, SessionStartPoint,
351    SessionStateChangedContext, SessionStateService, SessionToolAccess, SessionTurnInput,
352    SessionTurnRequest, SnapshotReader, SnapshotWriter, SubagentSessionContext,
353    ToolCatalogContribution, ToolResultProjectionContext, ToolResultProjector,
354    TriggerEventRegistrations, TurnContextTransform, TurnHookContext, TurnResultHookContext,
355    TurnResultSummary, TurnTransformContext, plugin_operation_def,
356};
357pub use plugin_stack::PluginStack;
358pub use provider::{
359    CacheControlDialect, CacheRetention, EmptyProviderResolver, LlmTimeouts, MapProviderResolver,
360    ModelCapability, ModelEffortValidationCategory, ModelEffortValidationError, Provider,
361    ProviderBinding, ProviderCompletion, ProviderCompletionError, ProviderComponents,
362    ProviderFactory, ProviderHandle, ProviderOptions, ProviderResolutionError, ProviderSpec,
363    ReasoningCapability, ReasoningDisableEncoding, ReasoningEncoding, ReasoningSelection,
364    RequestTimeout, RuntimeProviderResolver, SingleProviderResolver, StreamTermination,
365};
366#[cfg(any(test, feature = "testing"))]
367pub use runtime::TestLocalProcessRegistry;
368pub use runtime::{
369    AbandonEvidence, AbandonRequest, AbandonWriter, AgentFrameRun, AssembledTurn, AssistantOutput,
370    AwaitEventKey, AwaitEventResolver, AwaitEventWaitIdentity, BoundaryReason, CausalRef, Clock,
371    CodeOutputRecord, DefaultProcessCancelAbility, DeliveryPolicy, DirectCompletionClient,
372    DurableProcessWorker, DurableProcessWorkerConfig, DurableStoreFacet, EffectHost,
373    EmbeddedRuntimeBuilder, EmbeddedRuntimeHost, EventSink, ExecutionScope, ExecutionSummary,
374    ExternalCompletionError, InMemoryLiveReplayStore, InMemoryLiveReplayStoreConfig,
375    InMemoryProcessExecutionEnvStore, InMemorySessionStore, InMemorySessionStoreFactory,
376    InlineEffectHost, InlineProcessRunHandle, InlineRuntimeEffectController, InputItem,
377    LashRuntime, LiveReplayGap, LiveReplayGapReason, LiveReplayResult, LiveReplayStore,
378    LiveReplayStoreError, LiveReplaySubscribeResult, LiveReplaySubscription, MergeKey,
379    NoopEventSink, NoopTurnActivitySink, ObservedProcess, ObservedProcessEvent, ObservedWorkItem,
380    OutputState, PROCESS_LEASE_SCHEMA_VERSION, ParkedSession, PendingTurnInput,
381    PendingTurnInputCancelOutcome, PendingTurnInputCancelResult, PendingTurnInputCancelTarget,
382    PendingTurnInputClaimDiagnostics, PendingTurnInputDraft, PendingTurnInputSuffixCancelOutcome,
383    PersistedSegmentHandover, ProcessAttach, ProcessAwaitOutput, ProcessAwaiter,
384    ProcessCancelAbility, ProcessCancelAllRequest, ProcessCancelRequest, ProcessCancelSource,
385    ProcessCancelSummary, ProcessChangeCursor, ProcessChangeHub, ProcessCompletionAuthority,
386    ProcessDrainReport, ProcessEngine, ProcessEngineRegistry, ProcessEngineRunContext,
387    ProcessEngineRunGuard, ProcessEngineRuntimeContext, ProcessEngineValidationContext,
388    ProcessEvent, ProcessEventAppendPlan, ProcessEventAppendRequest, ProcessEventAppendResult,
389    ProcessEventSink, ProcessEventType, ProcessExecutionContext, ProcessExecutionEnvRef,
390    ProcessExecutionEnvSpec, ProcessExecutionEnvStore, ProcessExternalRef, ProcessHandleDescriptor,
391    ProcessHandleGrant, ProcessHandleSummary, ProcessId, ProcessIdentity, ProcessInput,
392    ProcessLease, ProcessLeaseClaimOutcome, ProcessLeaseCompletion, ProcessLifecycleStatus,
393    ProcessListFilter, ProcessListMode, ProcessLiveReferenceSummary, ProcessOpScope,
394    ProcessOriginator, ProcessProvenance, ProcessPruneReport, ProcessRecord, ProcessRegistration,
395    ProcessRegistry, ProcessRunHandle, ProcessRunOutcome, ProcessRuntimeHost, ProcessService,
396    ProcessSessionDeleteReport, ProcessSpawnProvenance, ProcessStartGrant, ProcessStartOptions,
397    ProcessStartRequest, ProcessStarted, ProcessStatus, ProcessStatusFilter,
398    ProcessTerminalSemantics, ProcessTerminalSpec, ProcessTerminalState, ProcessValueSelector,
399    ProcessWake, ProcessWakeDedupeKey, ProcessWakeDelivery, ProcessWakeDeliveryRequest,
400    ProcessWakeSpec, ProcessWorkDriver, ProcessWorkObserver, ProcessWorkSnapshot, PromptUsage,
401    ProtocolSessionExtension, ProtocolSessionExtensionHandle, ProtocolTurnExtension,
402    ProtocolTurnExtensionHandle, QueuedWorkDriver, QueuedWorkRunHandle, QueuedWorkRunRequest,
403    RecoveryDisposition, Residency, Resolution, ResolveOutcome, RuntimeEnvironment,
404    RuntimeEnvironmentBuilder, RuntimeError, RuntimeErrorCode, RuntimeHandle, RuntimeHostConfig,
405    RuntimeObservation, ScopedEffectController, SegmentHandover, SegmentProgress, SessionCommand,
406    SessionCommandReceipt, SessionCursor, SessionCursorError, SessionObservation,
407    SessionObservationEvent, SessionObservationEventPayload, SessionObservationSubscription,
408    SessionProcessEventKind, SessionQueueEventKind, SessionResume, SessionRevision, SessionScope,
409    SessionScopeId, SessionStoreCreateRequest, SessionStoreFactory, SessionUsageReport, SlotPolicy,
410    SystemClock, TerminationPolicy, TokenLedgerEntry, ToolCallLaunch, TurnActivity, TurnActivityId,
411    TurnActivitySink, TurnAddress, TurnAttach, TurnCancelOriginHint, TurnCancelOutcome,
412    TurnCancelReceipt, TurnCancelRequest, TurnCancellationEvidence, TurnContext, TurnEvent,
413    TurnInput, TurnInputCheckpointBoundary, TurnInputClaim, TurnInputClaimMode,
414    TurnInputCompletion, TurnInputIngress, TurnInputState, TurnIssue, TurnOptions, TurnTerminal,
415    TurnWorkDriver, UnavailableProcessService, UsageReportRow, UsageTotals, WaitKind, WaitState,
416    apply_process_status_projection, current_epoch_ms, diff_token_ledger, diff_usage_reports,
417    ensure_durable_effect_input, epoch_ms_from_system_time, process_signal_event_type,
418    process_signal_name_from_event_type, process_signal_wait_key, process_wake_delivery,
419    system_time_from_epoch_ms, terminal_append_request, terminal_event_type_name,
420    validate_process_signal_name, watch_process_registry, watch_process_registry_with_sink,
421};
422#[allow(unused_imports)]
423pub(crate) use runtime::{
424    LlmAttachmentSpec, ProcessEventSemantics, QueuedCheckpointTurnInput, QueuedCheckpointWork,
425    QueuedTurnWork, QueuedWorkBatch, QueuedWorkBatchDraft, QueuedWorkClaim,
426    QueuedWorkClaimBoundary, QueuedWorkCompletion, QueuedWorkItem, QueuedWorkPayload,
427    RuntimeReplay, RuntimeScope, RuntimeSubject, load_process_execution_env,
428    materialize_process_event_semantics, persist_process_execution_env,
429    prepare_process_event_append, prepare_process_registration, process_event_invocation,
430    process_event_payload_hash, process_wake_batch_draft, process_wake_input_from_event_payload,
431    process_wake_turn_cause, process_wake_turn_text, require_event_replay,
432};
433pub use session_model::{
434    PLUGIN_RUNTIME_PROTOCOL_PLUGIN_ID, PersistedPluginRuntimeEvent,
435    plugin_runtime_event_from_protocol, plugin_runtime_protocol_event,
436};
437// Effect / process-control types consumed by external effect hosts (e.g.
438// lash-restate's workflows) and their integration tests. Kept on the public
439// surface; the rest of the runtime block above stays crate-internal.
440pub use runtime::{
441    LlmRequestSpec, ProcessCommand, ProcessEffectOutcome, ProcessEventSemanticsSpec,
442    RuntimeAwaitEventOptions, RuntimeEffectCommand, RuntimeEffectController,
443    RuntimeEffectControllerError, RuntimeEffectEnvelope, RuntimeEffectKind,
444    RuntimeEffectLocalExecutor, RuntimeEffectOutcome, RuntimeInvocation, RuntimeSessionState,
445    ToolAttemptEffectOutcome, ToolAttemptLaunch, ToolBatchEffectOutcome,
446};
447pub use schemars::JsonSchema;
448pub(crate) use session::RuntimeExecutionTracing;
449pub use session::{
450    ExecRequest, InjectedTurnInput, RuntimeExecutionContext, Session, SessionError, ToolInvocation,
451    ToolInvocationReply,
452};
453pub use session_graph::{
454    PersistedSessionConfig, PersistedTurnState, SessionGraph, SessionMessageTreeNode,
455    SessionNodePayload, SessionNodeRecord,
456};
457pub use session_model::context::PreparedContext;
458pub use session_model::{ConversationRecord, ProtocolEvent, SessionHistoryRecord};
459pub use session_model::{RuntimeSessionPolicy, SessionPolicy, SessionSpec};
460pub use store::{
461    AttachmentIntent, AttachmentManifest, AttachmentManifestEntry, BlobRef, GcReport,
462    LeaseOwnerIdentity, LeaseOwnerLiveness, LeaseTimings, LeaseTimingsError, QueuedWorkStore,
463    RuntimePersistence, SessionCommitStore, SessionExecutionLease,
464    SessionExecutionLeaseClaimOutcome, SessionExecutionLeaseCompletion, SessionExecutionLeaseFence,
465    SessionExecutionLeaseStore, SessionMeta, SessionPickerInfo, SessionReadScope, StoreError,
466    StoreMaintenance, TurnInputStore, VacuumReport,
467};
468#[allow(unused_imports)]
469pub(crate) use store::{
470    GraphCommitDelta, PersistedSessionRead, RuntimeCommitResult, SessionCheckpoint,
471    SessionHeadMeta, ensure_supported_schema_version, load_persisted_session_state,
472    load_persisted_session_state_active_path,
473};
474pub use store::{
475    HydratedSessionCheckpoint, RuntimeCommit, RuntimeTurnCommitStamp, SessionHead,
476    refresh_persisted_session_state,
477};
478pub use tool_provider::{
479    PreparedToolBatch, PreparedToolBatchCall, PreparedToolCall, ProgressSender, SandboxMessage,
480    ToolCall, ToolChildExecutionTraceHook, ToolChildProcessStarted, ToolContext,
481    ToolDurableEffects, ToolExecutionGrant, ToolPrepareCall, ToolPrepareContext, ToolProvider,
482    ToolSessionAdmin, ToolSessionModel, ToolSessionProcessAdmin, ToolTriggerClient,
483};
484
485#[cfg(test)]
486mod tests {
487    use super::*;
488
489    #[test]
490    fn invalid_agent_frame_seed_is_rejected_at_the_serde_boundary() {
491        let err = serde_json::from_value::<ToolControl>(serde_json::json!({
492            "type": "switch_agent_frame",
493            "frame_id": "delegate",
494            "initial_nodes": [{ "not": "a session append node" }],
495            "task": "continue the work"
496        }))
497        .expect_err("invalid seed cannot construct a tool control");
498
499        assert!(err.to_string().contains("kind"), "unexpected error: {err}");
500    }
501
502    #[test]
503    fn protocol_turn_options_missing_payload_deserializes_to_empty_object() {
504        let options: ProtocolTurnOptions = serde_json::from_value(serde_json::json!({
505            "schema_version": PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION
506        }))
507        .expect("deserialize options");
508
509        assert!(options.is_empty());
510        assert_eq!(options.schema_version, PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION);
511        assert_eq!(options.payload, serde_json::json!({}));
512    }
513
514    #[test]
515    fn protocol_turn_options_explicit_null_is_not_empty() {
516        let options: ProtocolTurnOptions = serde_json::from_value(serde_json::json!({
517            "schema_version": PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION,
518            "payload": null
519        }))
520        .expect("deserialize options");
521
522        assert!(!options.is_empty());
523        assert_eq!(options.payload, serde_json::Value::Null);
524    }
525
526    #[test]
527    fn protocol_turn_options_missing_schema_version_rejects_preversioned_state() {
528        let err =
529            serde_json::from_value::<ProtocolTurnOptions>(serde_json::json!({ "payload": {} }))
530                .expect_err("pre-versioned options should fail");
531
532        assert!(
533            err.to_string().contains(
534                "missing schema_version and were written by unsupported pre-versioned state"
535            ),
536            "{err}"
537        );
538    }
539
540    #[test]
541    fn protocol_turn_options_unsupported_schema_version_rejects_state() {
542        let err = serde_json::from_value::<ProtocolTurnOptions>(serde_json::json!({
543            "schema_version": PROTOCOL_TURN_OPTIONS_SCHEMA_VERSION + 1,
544            "payload": {}
545        }))
546        .expect_err("unsupported options version should fail");
547
548        assert!(
549            err.to_string().contains("is not supported by this binary"),
550            "{err}"
551        );
552    }
553
554    #[test]
555    fn root_exports_do_not_reintroduce_removed_session_state_shapes() {
556        let source = include_str!("lib.rs");
557        let removed_envelope = ["SessionState", "Envelope"].concat();
558        let removed_persisted = ["PersistedSession", "Snapshot"].concat();
559        let removed_history_rewriter = ["History", "Rewriter"].concat();
560        let removed_rewrite_trigger = ["Rewrite", "Trigger"].concat();
561        let removed_rewrite_context = ["Rewrite", "Context"].concat();
562        let removed_history_state = ["History", "State"].concat();
563        let removed_history_metadata = ["History", "Rewrite", "Metadata"].concat();
564
565        assert!(!source.contains(&removed_envelope));
566        assert!(!source.contains(&removed_persisted));
567        assert!(!source.contains(&removed_history_rewriter));
568        assert!(!source.contains(&removed_rewrite_trigger));
569        assert!(!source.contains(&removed_rewrite_context));
570        assert!(!source.contains(&removed_history_state));
571        assert!(!source.contains(&removed_history_metadata));
572    }
573
574    fn public_reexport_block(source: &str, module: &str) -> String {
575        let start = format!("pub use {module}::{{");
576        let mut block = String::new();
577        let mut collecting = false;
578        for line in source.lines() {
579            if line.trim_start().starts_with(&start) {
580                collecting = true;
581            }
582            if collecting {
583                block.push_str(line);
584                block.push('\n');
585                if line.trim_end() == "};" {
586                    break;
587                }
588            }
589        }
590        assert!(!block.is_empty(), "missing public {module} re-export block");
591        block
592    }
593
594    #[test]
595    fn root_runtime_exports_exclude_internal_runtime_records() {
596        let runtime_exports = public_reexport_block(include_str!("lib.rs"), "runtime");
597        for removed in [
598            "RuntimeEffectCommand",
599            "RuntimeEffectEnvelope",
600            "RuntimeEffectKind",
601            "RuntimeEffectOutcome",
602            "RuntimeInvocation",
603            "RuntimeScope",
604            "RuntimeSessionState",
605            "QueuedWorkBatch",
606            "QueuedWorkBatchDraft",
607            "QueuedWorkPayload",
608            "prepare_process_registration",
609            "process_wake_batch_draft",
610            "require_event_replay",
611        ] {
612            assert!(
613                !runtime_exports.contains(removed),
614                "runtime root export leaked {removed}"
615            );
616        }
617    }
618
619    #[test]
620    fn root_store_exports_exclude_wire_records() {
621        let store_exports = public_reexport_block(include_str!("lib.rs"), "store");
622        for removed in [
623            "SessionHead",
624            "SessionCheckpoint",
625            "RuntimeCommit",
626            "HydratedSessionCheckpoint",
627            "PersistedSessionRead",
628            "GraphCommitDelta",
629        ] {
630            assert!(
631                !store_exports.contains(removed),
632                "store root export leaked {removed}"
633            );
634        }
635    }
636
637    #[test]
638    fn removed_manager_and_host_trait_names_stay_removed() {
639        let removed_manager = ["Runtime", "Session", "Manager"].concat();
640        let removed_host = ["Runtime", "Session", "Host"].concat();
641        let sources = [
642            include_str!("runtime/session_manager/mod.rs"),
643            include_str!("plugin/runtime_host.rs"),
644            include_str!("tool_dispatch/context.rs"),
645            include_str!("tool_provider.rs"),
646        ];
647
648        for source in sources {
649            assert!(!source.contains(&removed_manager));
650            assert!(!source.contains(&removed_host));
651        }
652    }
653}