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