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, 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    ProcessEventAppendRequest, ProcessEventAppendResult, ProcessEventType, ProcessExecutionContext,
255    ProcessExecutionEnvRef, ProcessExecutionEnvSpec, ProcessExecutionEnvStore, ProcessExternalRef,
256    ProcessHandleDescriptor, ProcessHandleGrant, ProcessHandleSummary, ProcessId, ProcessIdentity,
257    ProcessInput, ProcessLease, ProcessLeaseCompletion, ProcessLifecycleStatus, ProcessListFilter,
258    ProcessListMode, ProcessOpScope, ProcessOriginator, ProcessProvenance, ProcessRecord,
259    ProcessRegistration, ProcessRegistry, ProcessRunHandle, ProcessRuntimeHost, ProcessService,
260    ProcessSessionDeleteReport, ProcessSpawnProvenance, ProcessStartGrant, ProcessStartOptions,
261    ProcessStartRequest, ProcessStatus, ProcessStatusFilter, ProcessTerminalSemantics,
262    ProcessTerminalSpec, ProcessTerminalState, ProcessValueSelector, ProcessWake,
263    ProcessWakeDedupeKey, ProcessWakeDelivery, ProcessWakeSpec, ProcessWorkDriver,
264    ProcessWorkObserver, ProcessWorkPoke, ProcessWorkRunner, ProcessWorkSnapshot, PromptUsage,
265    ProtocolSessionExtension, ProtocolSessionExtensionHandle, ProtocolTurnExtension,
266    ProtocolTurnExtensionHandle, QueuedWorkPoke, QueuedWorkRunHandle, QueuedWorkRunOutcome,
267    QueuedWorkRunRequest, QueuedWorkRunner, Residency, Resolution, ResolveOutcome,
268    RuntimeEnvironment, RuntimeEnvironmentBuilder, RuntimeError, RuntimeErrorCode, RuntimeHandle,
269    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    TerminationPolicy, TokenLedgerEntry, ToolCallLaunch, TurnActivity, TurnActivityId,
275    TurnActivitySink, TurnContext, TurnEvent, TurnInput, TurnIssue, TurnOptions,
276    UnavailableProcessService, UsageReportRow, UsageTotals, WaitKind, WaitState, current_epoch_ms,
277    diff_token_ledger, diff_usage_reports, ensure_durable_effect_input, epoch_ms_from_system_time,
278    process_signal_event_type, process_signal_name_from_event_type, process_signal_wait_key,
279    system_time_from_epoch_ms, validate_process_signal_name,
280};
281#[allow(unused_imports)]
282pub(crate) use runtime::{
283    LlmAttachmentSpec, PreparedProcessEventAppend, ProcessEventSemantics, QUEUED_WORK_CLAIM_TTL_MS,
284    QueuedCheckpointWork, QueuedTurnWork, QueuedWorkBatch, QueuedWorkBatchDraft, QueuedWorkClaim,
285    QueuedWorkClaimBoundary, QueuedWorkCompletion, QueuedWorkItem, QueuedWorkPayload,
286    RuntimeReplay, RuntimeScope, RuntimeSubject, load_process_execution_env,
287    materialize_process_event_semantics, persist_process_execution_env,
288    prepare_process_event_append, prepare_process_registration, process_event_invocation,
289    process_event_payload_hash, process_wake_batch_draft, process_wake_delivery,
290    process_wake_input_from_event_payload, process_wake_turn_cause, process_wake_turn_text,
291    require_event_replay,
292};
293// Effect / process-control types consumed by external effect hosts (e.g.
294// lash-restate's workflows) and their integration tests. Kept on the public
295// surface; the rest of the runtime block above stays crate-internal.
296pub use runtime::{
297    LlmRequestSpec, ProcessCommand, ProcessEffectOutcome, ProcessEventSemanticsSpec,
298    RuntimeEffectCommand, RuntimeEffectController, RuntimeEffectControllerError,
299    RuntimeEffectEnvelope, RuntimeEffectKind, RuntimeEffectLocalExecutor, RuntimeEffectOutcome,
300    RuntimeInvocation, RuntimeSessionState,
301};
302pub use schemars::JsonSchema;
303pub use session::{
304    ExecRequest, InjectedTurnInput, RuntimeExecutionContext, Session, SessionError, ToolInvocation,
305    ToolInvocationReply,
306};
307pub use session_graph::{
308    PersistedSessionConfig, PersistedTurnState, SessionGraph, SessionMessageTreeNode,
309    SessionNodePayload, SessionNodeRecord,
310};
311pub use session_model::context::PreparedContext;
312pub use session_model::{ConversationRecord, ProtocolEvent, SessionEventRecord};
313pub use session_model::{RuntimeSessionPolicy, SessionPolicy, SessionSpec};
314pub use store::{
315    AttachmentIntent, AttachmentManifest, AttachmentManifestEntry, BlobRef, GcReport,
316    RuntimePersistence, SessionMeta, SessionPickerInfo, SessionReadScope, StoreError, VacuumReport,
317};
318#[allow(unused_imports)]
319pub(crate) use store::{
320    GraphCommitDelta, PersistedSessionRead, RuntimeCommitResult, SessionCheckpoint,
321    SessionHeadMeta, ensure_supported_schema_version, load_persisted_session_state,
322    load_persisted_session_state_active_path,
323};
324pub use store::{
325    HydratedSessionCheckpoint, RuntimeCommit, RuntimeTurnCommitStamp, SessionHead,
326    refresh_persisted_session_state,
327};
328pub use tool_provider::{
329    PreparedToolCall, ProgressSender, SandboxMessage, ToolCall, ToolChildExecutionTraceHook,
330    ToolChildProcessStarted, ToolContext, ToolDurableEffects, ToolPrepareCall, ToolPrepareContext,
331    ToolProvider, ToolSessionAdmin, ToolSessionModel, ToolTriggerClient,
332};
333
334#[cfg(test)]
335mod tests {
336    use super::*;
337
338    #[test]
339    fn protocol_turn_options_missing_payload_deserializes_to_empty_object() {
340        let options: ProtocolTurnOptions =
341            serde_json::from_value(serde_json::json!({})).expect("deserialize options");
342
343        assert!(options.is_empty());
344        assert_eq!(options.payload, serde_json::json!({}));
345    }
346
347    #[test]
348    fn protocol_turn_options_explicit_null_is_not_empty() {
349        let options: ProtocolTurnOptions =
350            serde_json::from_value(serde_json::json!({ "payload": null }))
351                .expect("deserialize options");
352
353        assert!(!options.is_empty());
354        assert_eq!(options.payload, serde_json::Value::Null);
355    }
356
357    #[test]
358    fn root_exports_do_not_reintroduce_removed_session_state_shapes() {
359        let source = include_str!("lib.rs");
360        let removed_envelope = ["SessionState", "Envelope"].concat();
361        let removed_persisted = ["PersistedSession", "Snapshot"].concat();
362        let removed_history_rewriter = ["History", "Rewriter"].concat();
363        let removed_rewrite_trigger = ["Rewrite", "Trigger"].concat();
364        let removed_rewrite_context = ["Rewrite", "Context"].concat();
365        let removed_history_state = ["History", "State"].concat();
366        let removed_history_metadata = ["History", "Rewrite", "Metadata"].concat();
367
368        assert!(!source.contains(&removed_envelope));
369        assert!(!source.contains(&removed_persisted));
370        assert!(!source.contains(&removed_history_rewriter));
371        assert!(!source.contains(&removed_rewrite_trigger));
372        assert!(!source.contains(&removed_rewrite_context));
373        assert!(!source.contains(&removed_history_state));
374        assert!(!source.contains(&removed_history_metadata));
375    }
376
377    fn public_reexport_block(source: &str, module: &str) -> String {
378        let start = format!("pub use {module}::{{");
379        let mut block = String::new();
380        let mut collecting = false;
381        for line in source.lines() {
382            if line.trim_start().starts_with(&start) {
383                collecting = true;
384            }
385            if collecting {
386                block.push_str(line);
387                block.push('\n');
388                if line.trim_end() == "};" {
389                    break;
390                }
391            }
392        }
393        assert!(!block.is_empty(), "missing public {module} re-export block");
394        block
395    }
396
397    #[test]
398    fn root_runtime_exports_exclude_internal_runtime_records() {
399        let runtime_exports = public_reexport_block(include_str!("lib.rs"), "runtime");
400        for removed in [
401            "RuntimeEffectCommand",
402            "RuntimeEffectEnvelope",
403            "RuntimeEffectKind",
404            "RuntimeEffectOutcome",
405            "RuntimeInvocation",
406            "RuntimeScope",
407            "RuntimeSessionState",
408            "QueuedWorkBatch",
409            "QueuedWorkBatchDraft",
410            "QueuedWorkPayload",
411            "prepare_process_registration",
412            "process_wake_batch_draft",
413            "require_event_replay",
414        ] {
415            assert!(
416                !runtime_exports.contains(removed),
417                "runtime root export leaked {removed}"
418            );
419        }
420    }
421
422    #[test]
423    fn root_store_exports_exclude_wire_records() {
424        let store_exports = public_reexport_block(include_str!("lib.rs"), "store");
425        for removed in [
426            "SessionHead",
427            "SessionCheckpoint",
428            "RuntimeCommit",
429            "HydratedSessionCheckpoint",
430            "PersistedSessionRead",
431            "GraphCommitDelta",
432        ] {
433            assert!(
434                !store_exports.contains(removed),
435                "store root export leaked {removed}"
436            );
437        }
438    }
439
440    #[test]
441    fn removed_manager_and_host_trait_names_stay_removed() {
442        let removed_manager = ["Runtime", "Session", "Manager"].concat();
443        let removed_host = ["Runtime", "Session", "Host"].concat();
444        let sources = [
445            include_str!("runtime/session_manager/mod.rs"),
446            include_str!("plugin/runtime_host.rs"),
447            include_str!("tool_dispatch/context.rs"),
448            include_str!("tool_provider.rs"),
449        ];
450
451        for source in sources {
452            assert!(!source.contains(&removed_manager));
453            assert!(!source.contains(&removed_host));
454        }
455    }
456}