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 search;
24pub mod session;
25pub mod session_graph;
26pub mod session_model;
27mod stable_hash;
28pub mod store;
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)]
44pub enum DurabilityTier {
45    Inline,
46    Durable,
47}
48
49// Re-exports
50pub use attachments::{
51    AttachmentStore, AttachmentStoreError, AttachmentStorePersistence, FileAttachmentStore,
52    InMemoryAttachmentStore, SessionScopedAttachmentStore, StoredAttachment,
53};
54pub use chronological::{
55    BorrowedChronologicalEntry, BorrowedChronologicalMessage, BorrowedChronologicalPayload,
56    ChronologicalEntry, ChronologicalPayload, ChronologicalProjection, visit_turn_view,
57};
58pub use direct::{
59    DirectJsonSchema, DirectLlmClient, DirectLlmError, DirectMessage, DirectOutputSpec, DirectPart,
60    DirectRequest, DirectRole,
61};
62pub use lash_sansio::llm::types::{
63    GenerationOptions, LlmOutputPart, LlmRequest, LlmResponse, LlmTerminalReason,
64};
65pub use lash_sansio::{
66    AcceptedInjectedTurnInput, AttachmentCreateMeta, AttachmentId, AttachmentMeta, AttachmentRef,
67    BaseRenderCache, CheckpointDelivery, CheckpointKind, CompactToolContract, EffectId,
68    ErrorEnvelope, ExecImage, ExecResponse, ImageMediaType, LashSchema, LlmCallError, MediaType,
69    Message, MessageOrigin, MessageRole, MessageSequence, ModelToolReturn, ModelToolReturnPart,
70    Part, PartKind, PluginMessage, PluginRuntimeEvent, PreparedPrompt, PromptBuildInput,
71    PromptBuiltin, PromptContext, PromptContribution, PromptContributionGate,
72    PromptContributionSet, PromptFingerprint, PromptLayer, PromptSlot, PromptSlotLayer,
73    PromptTemplate, PromptTemplateEntry, PromptTemplateSection, PruneState, RenderedPrompt,
74    ResolvedPromptLayer, Response, SchemaProjectionOverride, SessionEvent, TextProjectionMetadata,
75    TokenUsage, ToolActivation, ToolArgumentProjectionPolicy, ToolAvailability,
76    ToolAvailabilityConfig, ToolCallOutcome, ToolCallOutput, ToolCallRecord, ToolCallStatus,
77    ToolCancellation, ToolCatalog, ToolCatalogBuildInput, ToolCatalogEntry, ToolCatalogOverride,
78    ToolContract, ToolControl, ToolDefinition, ToolFailure, ToolFailureClass, ToolFailureSource,
79    ToolId, ToolManifest, ToolOutputContract, ToolRetryDisposition, ToolRetryPolicy,
80    ToolScheduling, ToolValue, TurnCause, TurnFinish, TurnLimitFinalMessage, TurnOutcome, TurnStop,
81    append_assistant_text_part, build_prompt, build_tool_catalog, build_turn,
82    default_prompt_template, head_tail_truncate, messages_are_prompt_resume_safe,
83    normalized_response_parts, prompt_template_fingerprint, prompt_text_fingerprint,
84    prompt_tool_names_fingerprint, reasoning_part, render_turn_causes_prompt,
85    resolve_prompt_layers, shared_parts, validate_tool_input,
86};
87pub use protocol_build::ProtocolBuildInput;
88pub use tool_registry::{
89    ReconfigureError, ToolRegistry, ToolRestoreReport, ToolSourceHandle, ToolState, ToolStateEntry,
90};
91pub use tool_result::{CancelHint, PendingCompletion, TimeoutBehavior, ToolResult};
92pub use triggers::{
93    InMemoryTriggerStore, TriggerDeliveryReservation, TriggerEmitReport, TriggerEvent,
94    TriggerEventCatalog, TriggerEventKey, TriggerEventType, TriggerInputBinding,
95    TriggerOccurrenceRecord, TriggerOccurrenceRequest, TriggerRegistration, TriggerRouter,
96    TriggerStore, TriggerSubscriptionDraft, TriggerSubscriptionFilter, TriggerSubscriptionRecord,
97    TriggerTargetSummary, default_trigger_source_key, deterministic_delivery_process_id,
98    deterministic_occurrence_id, empty_trigger_source_key, trigger_event_type,
99    trigger_occurrence_request_hash, validate_trigger_occurrence_request,
100};
101#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
102pub struct ProtocolTurnOptions {
103    #[serde(default = "empty_protocol_turn_payload")]
104    pub payload: serde_json::Value,
105}
106
107fn empty_protocol_turn_payload() -> serde_json::Value {
108    serde_json::Value::Object(serde_json::Map::new())
109}
110
111impl Default for ProtocolTurnOptions {
112    fn default() -> Self {
113        Self::empty()
114    }
115}
116
117impl ProtocolTurnOptions {
118    pub fn empty() -> Self {
119        Self {
120            payload: serde_json::Value::Object(serde_json::Map::new()),
121        }
122    }
123
124    pub fn is_empty(&self) -> bool {
125        match &self.payload {
126            serde_json::Value::Object(map) => map.is_empty(),
127            _ => false,
128        }
129    }
130
131    pub fn merged_with_override(&self, override_options: &Self) -> Self {
132        match (&self.payload, &override_options.payload) {
133            (serde_json::Value::Object(base), serde_json::Value::Object(overrides)) => {
134                let mut payload = base.clone();
135                payload.extend(overrides.clone());
136                Self {
137                    payload: serde_json::Value::Object(payload),
138                }
139            }
140            _ => override_options.clone(),
141        }
142    }
143
144    pub fn typed<T>(value: T) -> Result<Self, serde_json::Error>
145    where
146        T: serde::Serialize,
147    {
148        Ok(Self {
149            payload: serde_json::to_value(value)?,
150        })
151    }
152
153    pub fn decode<T>(&self) -> Result<T, serde_json::Error>
154    where
155        T: serde::de::DeserializeOwned,
156    {
157        serde_json::from_value(self.payload.clone())
158    }
159}
160
161#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
162pub struct ProtocolDriverState {
163    pub plugin_id: String,
164    pub payload: serde_json::Value,
165}
166
167impl ProtocolDriverState {
168    pub fn new(plugin_id: impl Into<String>, payload: serde_json::Value) -> Self {
169        Self {
170            plugin_id: plugin_id.into(),
171            payload,
172        }
173    }
174}
175
176#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
177pub struct HostTurnProtocol;
178
179impl lash_sansio::TurnProtocol for HostTurnProtocol {
180    type Event = crate::session_model::ProtocolEvent;
181    type Termination = ProtocolTurnOptions;
182    type DriverState = ProtocolDriverState;
183}
184
185pub type Effect = lash_sansio::Effect<HostTurnProtocol>;
186pub type DriverAction = lash_sansio::DriverAction<HostTurnProtocol>;
187pub type DriverContextView<'a> = lash_sansio::DriverContextView<'a, HostTurnProtocol>;
188pub type TurnDriverConfig = lash_sansio::TurnDriverConfig<HostTurnProtocol>;
189pub type TurnDriverPreamble = lash_sansio::TurnDriverPreamble<HostTurnProtocol>;
190pub type ProjectorContext<'a> = lash_sansio::ProjectorContext<'a, HostTurnProtocol>;
191pub type PreparedTurnMachine = lash_sansio::PreparedTurnMachine<HostTurnProtocol>;
192pub type SansIoTurnInput = lash_sansio::SansIoTurnInput<HostTurnProtocol>;
193pub type TurnMachine = lash_sansio::TurnMachine<HostTurnProtocol>;
194pub type TurnMachineConfig = lash_sansio::TurnMachineConfig<HostTurnProtocol>;
195#[cfg(feature = "otel-trace")]
196pub use lash_trace::otel::{OtelTraceOptions, OtelTraceSink};
197pub use lash_trace::{
198    JsonlTraceSink, TraceAttachment, TraceBranchSelection, TraceContentBlock, TraceContext,
199    TraceError, TraceEvent, TraceLabelMetadata, TraceLevel, TraceLlmMessage, TraceLlmRequest,
200    TraceLlmResponse, TracePromptComponent, TraceProviderStreamEvent, TraceRecord,
201    TraceRuntimeScope, TraceRuntimeStreamEvent, TraceRuntimeSubject, TraceSink, TraceSinkError,
202    TraceTokenUsage, TraceToolSpec,
203};
204pub use llm::transport::{LlmTransportError, ProviderFailure, ProviderFailureKind};
205pub use model::{ModelLimits, ModelSpec};
206pub use plugin::{
207    AgentFrameAssignment, AgentFrameId, AgentFrameReason, AgentFrameRecord, AgentFrameStatus,
208    AppendSessionNodesRequest, AppendSessionNodesResult, AssistantResponseHookContext,
209    AssistantResponseTransform, AssistantStreamHookContext, AssistantStreamTransform,
210    CheckpointHookContext, CompactionContext, ContextCompaction, ContextCompactor, ContextError,
211    ContextRegistrations, DirectCompletion, DirectLlmCompletion, OpenAgentFrameRequest,
212    OpenAgentFrameResult, PersistentRuntimeServices, PluginAction, PluginActionContext,
213    PluginActionDef, PluginActionFailure, PluginActionInvokeError, PluginActionKind,
214    PluginDirective, PluginError, PluginExtensionContribution, PluginExtensions, PluginFactory,
215    PluginHost, PluginLifecycleEvent, PluginLifecycleEventHook, PluginOptions, PluginOwned,
216    PluginRegistrar, PluginSession, PluginSessionContext, PluginSessionSnapshot,
217    PluginSnapshotArtifact, PluginSnapshotEntry, PluginSnapshotMeta, PluginSpec, PluginSpecFactory,
218    PromptHookContext, ProtocolBeforeLlmCallContext, ProtocolLlmCallAction, RuntimeServices,
219    SessionAppendNode, SessionConfigChangedContext, SessionContextOverlay, SessionCreateRequest,
220    SessionGraphService, SessionHandle, SessionLifecycleService, SessionParam, SessionPlugin,
221    SessionPluginSource, SessionReadView, SessionRelation, SessionSnapshot, SessionStartPoint,
222    SessionStateChangedContext, SessionStateService, SessionToolAccess, SessionTurnInput,
223    SessionTurnRequest, SnapshotReader, SnapshotWriter, SubagentSessionContext,
224    ToolCatalogContribution, ToolDiscoveryContext, ToolDiscoveryContribution,
225    ToolDiscoveryContributor, ToolDiscoveryToolContribution, ToolResultProjectionContext,
226    ToolResultProjector, TriggerEventRegistrations, TurnContextTransform, TurnHookContext,
227    TurnResultHookContext, TurnResultSummary, TurnTransformContext, plugin_action_def,
228};
229pub use plugin_stack::PluginStack;
230pub use provider::{
231    CacheRetention, EmptyProviderResolver, LlmTimeouts, MapProviderResolver, Provider,
232    ProviderBinding, ProviderComponents, ProviderFactory, ProviderHandle, ProviderModelPolicy,
233    ProviderOptions, ProviderResolutionError, ProviderSpec, RequestTimeout,
234    RuntimeProviderResolver, SingleProviderResolver, StaticModelPolicy,
235};
236#[cfg(any(test, feature = "testing"))]
237pub use runtime::TestLocalProcessRegistry;
238pub use runtime::{
239    AgentFrameRun, AssembledTurn, AssistantOutput, AwaitEventKey, AwaitEventWaitIdentity,
240    CausalRef, Clock, CodeOutputRecord, DefaultProcessCancelAbility, DeliveryPolicy,
241    DirectCompletionClient, DurableProcessWorker, DurableProcessWorkerConfig, DurableStoreFacet,
242    EffectHost, EmbeddedRuntimeBuilder, EmbeddedRuntimeHost, EventSink, ExecutionScope,
243    ExecutionSummary, ExternalCompletionError, InMemoryLiveReplayStore,
244    InMemoryLiveReplayStoreConfig, InMemoryProcessExecutionEnvStore, InMemorySessionStore,
245    InMemorySessionStoreFactory, InlineEffectHost, InlineProcessRunHandle,
246    InlineRuntimeEffectController, InputItem, LashRuntime, LiveReplayGap, LiveReplayGapReason,
247    LiveReplayResult, LiveReplayStore, LiveReplayStoreError, LiveReplaySubscribeResult,
248    LiveReplaySubscription, MergeKey, NoopEventSink, NoopTurnActivitySink, ObservedProcess,
249    ObservedProcessEvent, ObservedWorkItem, OutputState, PROCESS_LEASE_SCHEMA_VERSION,
250    ParkedSession, ProcessAwaitOutput, ProcessCancelAbility, ProcessCancelAllRequest,
251    ProcessCancelRequest, ProcessCancelSource, ProcessCancelSummary, ProcessEngine,
252    ProcessEngineRegistry, ProcessEngineRunContext, ProcessEngineRunGuard,
253    ProcessEngineRuntimeContext, ProcessEngineValidationContext, ProcessEvent,
254    ProcessEventAppendPlan, ProcessEventAppendRequest, ProcessEventAppendResult, ProcessEventType,
255    ProcessExecutionContext, ProcessExecutionEnvRef, ProcessExecutionEnvSpec,
256    ProcessExecutionEnvStore, ProcessExternalRef, ProcessHandleDescriptor, ProcessHandleGrant,
257    ProcessHandleSummary, ProcessId, ProcessIdentity, ProcessInput, ProcessLease,
258    ProcessLeaseCompletion, ProcessLifecycleStatus, ProcessListFilter, ProcessListMode,
259    ProcessOpScope, ProcessOriginator, ProcessProvenance, ProcessRecord, ProcessRegistration,
260    ProcessRegistry, ProcessRunHandle, ProcessRuntimeHost, ProcessService,
261    ProcessSessionDeleteReport, ProcessSpawnProvenance, ProcessStartGrant, ProcessStartOptions,
262    ProcessStartRequest, ProcessStatus, ProcessStatusFilter, ProcessTerminalSemantics,
263    ProcessTerminalSpec, ProcessTerminalState, ProcessValueSelector, ProcessWake,
264    ProcessWakeDedupeKey, ProcessWakeDelivery, ProcessWakeSpec, ProcessWorkDriver,
265    ProcessWorkObserver, ProcessWorkSnapshot, PromptUsage, ProtocolSessionExtension,
266    ProtocolSessionExtensionHandle, ProtocolTurnExtension, ProtocolTurnExtensionHandle,
267    QueuedWorkDriver, QueuedWorkRunHandle, QueuedWorkRunRequest, Residency, Resolution,
268    ResolveOutcome, RuntimeEnvironment, RuntimeEnvironmentBuilder, RuntimeError, RuntimeErrorCode,
269    RuntimeHandle, RuntimeHostConfig, RuntimeObservation, ScopedEffectController, SessionCommand,
270    SessionCommandReceipt, SessionCursor, SessionCursorError, SessionObservation,
271    SessionObservationEvent, SessionObservationEventPayload, SessionObservationSubscription,
272    SessionProcessEventKind, SessionQueueEventKind, SessionResume, SessionRevision, SessionScope,
273    SessionScopeId, SessionStoreCreateRequest, SessionStoreFactory, SessionUsageReport, SlotPolicy,
274    SystemClock, TerminationPolicy, TokenLedgerEntry, ToolCallLaunch, TurnActivity, TurnActivityId,
275    TurnActivitySink, TurnContext, TurnEvent, TurnInput, TurnIssue, TurnOptions,
276    UnavailableProcessService, UsageReportRow, UsageTotals, WaitKind, WaitState,
277    apply_process_status_projection, current_epoch_ms, diff_token_ledger, diff_usage_reports,
278    ensure_durable_effect_input, epoch_ms_from_system_time, process_signal_event_type,
279    process_signal_name_from_event_type, process_signal_wait_key, system_time_from_epoch_ms,
280    validate_process_signal_name,
281};
282#[allow(unused_imports)]
283pub(crate) use runtime::{
284    LlmAttachmentSpec, ProcessEventSemantics, QUEUED_WORK_CLAIM_TTL_MS, QueuedCheckpointWork,
285    QueuedTurnWork, QueuedWorkBatch, QueuedWorkBatchDraft, QueuedWorkClaim,
286    QueuedWorkClaimBoundary, QueuedWorkCompletion, QueuedWorkItem, QueuedWorkPayload,
287    RuntimeReplay, RuntimeScope, RuntimeSubject, load_process_execution_env,
288    materialize_process_event_semantics, persist_process_execution_env,
289    prepare_process_event_append, prepare_process_registration, process_event_invocation,
290    process_event_payload_hash, process_wake_batch_draft, process_wake_delivery,
291    process_wake_input_from_event_payload, process_wake_turn_cause, process_wake_turn_text,
292    require_event_replay,
293};
294// Effect / process-control types consumed by external effect hosts (e.g.
295// lash-restate's workflows) and their integration tests. Kept on the public
296// surface; the rest of the runtime block above stays crate-internal.
297pub use runtime::{
298    LlmRequestSpec, ProcessCommand, ProcessEffectOutcome, ProcessEventSemanticsSpec,
299    RuntimeEffectCommand, RuntimeEffectController, RuntimeEffectControllerError,
300    RuntimeEffectEnvelope, RuntimeEffectKind, RuntimeEffectLocalExecutor, RuntimeEffectOutcome,
301    RuntimeInvocation, RuntimeSessionState, ToolBatchEffectOutcome,
302};
303pub use schemars::JsonSchema;
304pub use session::{
305    ExecRequest, InjectedTurnInput, RuntimeExecutionContext, Session, SessionError, ToolInvocation,
306    ToolInvocationReply,
307};
308pub use session_graph::{
309    PersistedSessionConfig, PersistedTurnState, SessionGraph, SessionMessageTreeNode,
310    SessionNodePayload, SessionNodeRecord,
311};
312pub use session_model::context::PreparedContext;
313pub use session_model::{ConversationRecord, ProtocolEvent, SessionEventRecord};
314pub use session_model::{RuntimeSessionPolicy, SessionPolicy, SessionSpec};
315pub use store::{
316    AttachmentIntent, AttachmentManifest, AttachmentManifestEntry, BlobRef, GcReport,
317    RuntimePersistence, SessionMeta, SessionPickerInfo, SessionReadScope, StoreError, VacuumReport,
318};
319#[allow(unused_imports)]
320pub(crate) use store::{
321    GraphCommitDelta, PersistedSessionRead, RuntimeCommitResult, SessionCheckpoint,
322    SessionHeadMeta, ensure_supported_schema_version, load_persisted_session_state,
323    load_persisted_session_state_active_path,
324};
325pub use store::{
326    HydratedSessionCheckpoint, RuntimeCommit, RuntimeTurnCommitStamp, SessionHead,
327    refresh_persisted_session_state,
328};
329pub use tool_provider::{
330    PreparedToolBatch, PreparedToolBatchCall, PreparedToolCall, ProgressSender, SandboxMessage,
331    ToolCall, ToolChildExecutionTraceHook, ToolChildProcessStarted, ToolContext,
332    ToolDurableEffects, ToolPrepareCall, ToolPrepareContext, ToolProvider, ToolSessionAdmin,
333    ToolSessionModel, ToolTriggerClient,
334};
335
336#[cfg(test)]
337mod tests {
338    use super::*;
339
340    #[test]
341    fn protocol_turn_options_missing_payload_deserializes_to_empty_object() {
342        let options: ProtocolTurnOptions =
343            serde_json::from_value(serde_json::json!({})).expect("deserialize options");
344
345        assert!(options.is_empty());
346        assert_eq!(options.payload, serde_json::json!({}));
347    }
348
349    #[test]
350    fn protocol_turn_options_explicit_null_is_not_empty() {
351        let options: ProtocolTurnOptions =
352            serde_json::from_value(serde_json::json!({ "payload": null }))
353                .expect("deserialize options");
354
355        assert!(!options.is_empty());
356        assert_eq!(options.payload, serde_json::Value::Null);
357    }
358
359    #[test]
360    fn root_exports_do_not_reintroduce_removed_session_state_shapes() {
361        let source = include_str!("lib.rs");
362        let removed_envelope = ["SessionState", "Envelope"].concat();
363        let removed_persisted = ["PersistedSession", "Snapshot"].concat();
364        let removed_history_rewriter = ["History", "Rewriter"].concat();
365        let removed_rewrite_trigger = ["Rewrite", "Trigger"].concat();
366        let removed_rewrite_context = ["Rewrite", "Context"].concat();
367        let removed_history_state = ["History", "State"].concat();
368        let removed_history_metadata = ["History", "Rewrite", "Metadata"].concat();
369
370        assert!(!source.contains(&removed_envelope));
371        assert!(!source.contains(&removed_persisted));
372        assert!(!source.contains(&removed_history_rewriter));
373        assert!(!source.contains(&removed_rewrite_trigger));
374        assert!(!source.contains(&removed_rewrite_context));
375        assert!(!source.contains(&removed_history_state));
376        assert!(!source.contains(&removed_history_metadata));
377    }
378
379    fn public_reexport_block(source: &str, module: &str) -> String {
380        let start = format!("pub use {module}::{{");
381        let mut block = String::new();
382        let mut collecting = false;
383        for line in source.lines() {
384            if line.trim_start().starts_with(&start) {
385                collecting = true;
386            }
387            if collecting {
388                block.push_str(line);
389                block.push('\n');
390                if line.trim_end() == "};" {
391                    break;
392                }
393            }
394        }
395        assert!(!block.is_empty(), "missing public {module} re-export block");
396        block
397    }
398
399    #[test]
400    fn root_runtime_exports_exclude_internal_runtime_records() {
401        let runtime_exports = public_reexport_block(include_str!("lib.rs"), "runtime");
402        for removed in [
403            "RuntimeEffectCommand",
404            "RuntimeEffectEnvelope",
405            "RuntimeEffectKind",
406            "RuntimeEffectOutcome",
407            "RuntimeInvocation",
408            "RuntimeScope",
409            "RuntimeSessionState",
410            "QueuedWorkBatch",
411            "QueuedWorkBatchDraft",
412            "QueuedWorkPayload",
413            "prepare_process_registration",
414            "process_wake_batch_draft",
415            "require_event_replay",
416        ] {
417            assert!(
418                !runtime_exports.contains(removed),
419                "runtime root export leaked {removed}"
420            );
421        }
422    }
423
424    #[test]
425    fn root_store_exports_exclude_wire_records() {
426        let store_exports = public_reexport_block(include_str!("lib.rs"), "store");
427        for removed in [
428            "SessionHead",
429            "SessionCheckpoint",
430            "RuntimeCommit",
431            "HydratedSessionCheckpoint",
432            "PersistedSessionRead",
433            "GraphCommitDelta",
434        ] {
435            assert!(
436                !store_exports.contains(removed),
437                "store root export leaked {removed}"
438            );
439        }
440    }
441
442    #[test]
443    fn removed_manager_and_host_trait_names_stay_removed() {
444        let removed_manager = ["Runtime", "Session", "Manager"].concat();
445        let removed_host = ["Runtime", "Session", "Host"].concat();
446        let sources = [
447            include_str!("runtime/session_manager/mod.rs"),
448            include_str!("plugin/runtime_host.rs"),
449            include_str!("tool_dispatch/context.rs"),
450            include_str!("tool_provider.rs"),
451        ];
452
453        for source in sources {
454            assert!(!source.contains(&removed_manager));
455            assert!(!source.contains(&removed_host));
456        }
457    }
458}