1#![recursion_limit = "256"]
2#![allow(clippy::result_large_err, clippy::cloned_ref_to_slice_refs)]
3pub use harn_clock as clock;
23
24pub mod a2a;
25pub mod actor_chain;
26pub mod agent_events;
27pub mod agent_sessions;
28pub mod atomic_io;
29pub mod autonomy;
30pub(crate) mod aws_sigv4;
31pub mod bridge;
32mod builtin_id;
33pub mod bytecode_cache;
34pub mod call_budget;
35pub mod channel_guardrails;
36pub mod channels;
37pub mod checkpoint;
38mod chunk;
39mod compiler;
40pub mod composition;
41pub mod config;
42pub mod connectors;
43pub mod corrections;
44pub mod coverage;
45pub(crate) mod durable_rate_limit;
46pub(crate) mod duration_parse;
47pub mod egress;
48pub mod event_log;
49pub mod events;
50pub mod external_agent;
51pub mod flow;
52pub mod harness;
53pub mod harness_auth;
54pub(crate) mod harness_crypto;
55pub mod harness_net;
56pub mod harness_system;
57pub mod harness_tenant;
58mod http;
59pub mod jsonrpc;
60pub(crate) mod limits;
61pub mod llm;
62pub mod llm_config;
63pub mod mcp;
64pub mod mcp_allowlist;
65pub mod mcp_auth;
66pub mod mcp_bulk_auth;
67pub mod mcp_card;
68pub mod mcp_elicit;
69pub mod mcp_file_upload;
70pub mod mcp_host;
71pub mod mcp_identity;
72pub mod mcp_json_discovery;
73pub mod mcp_oauth;
74pub mod mcp_presets;
75pub mod mcp_progress;
76pub mod mcp_protocol;
77pub mod mcp_registry;
78pub mod mcp_sampling;
79pub mod mcp_server;
80pub mod metadata;
81pub mod module_artifact;
82pub mod observability;
83pub mod op_interrupt;
84pub mod orchestration;
85pub mod personas;
86pub mod process_sandbox;
87pub mod profile;
88pub mod provenance;
89pub mod provider_catalog;
90pub mod receipts;
91pub mod record_filter;
92pub mod redact;
93pub mod run_events;
94pub mod runtime_context;
95pub(crate) mod runtime_guards;
96pub mod runtime_limits;
97pub mod runtime_paths;
98pub(crate) mod runtime_sqlite;
99pub mod schema;
100pub(crate) mod secret_patterns;
101pub mod secrets;
102pub mod security;
103pub mod session_bundle;
104pub mod session_timeline;
105pub mod sessions;
106pub(crate) mod shared_state;
107pub mod shells;
108pub mod skills;
109pub mod stdlib;
110pub mod stdlib_modules;
111pub mod step_runtime;
112pub mod store;
113pub(crate) mod synchronization;
114pub mod tenant;
115pub(crate) mod term;
116pub mod testbench;
117pub mod tool_annotations;
118pub mod tool_call_cancellations;
119pub mod tool_surface;
120pub mod tracing;
121pub mod triggers;
122pub mod trust_graph;
123pub(crate) mod url_encoding;
124pub mod user_dirs;
125
126pub mod clock_mock {
131 pub use crate::triggers::test_util::clock::{
132 active_mock_clock, advance, clear_overrides, install_override, instant_now, is_mocked,
133 now_ms, now_utc, sleep, ClockInstant, ClockOverrideGuard, MockClock,
134 };
135
136 pub mod leak_audit {
140 #[cfg(test)]
141 pub use crate::triggers::test_util::clock_leak::TEST_LOCK;
142 pub use crate::triggers::test_util::clock_leak::{
143 drain, instant_now, reset, snapshot, wall_now, ClockLeak,
144 };
145 }
146}
147
148pub mod typecheck;
149pub mod value;
150pub mod verification;
151pub mod visible_text;
152mod vm;
153pub(crate) mod wait_for_graph;
154pub mod waitpoints;
155pub mod workspace_anchor;
156pub mod workspace_path;
157
158pub use actor_chain::{
159 ActorChain, ActorChainEntry, ActorChainError, Principal, ScopeAttenuationMode,
160 ScopeAttenuationPolicy, ScopeAttenuationViolation,
161};
162pub use builtin_id::BuiltinId;
163pub use call_budget::{
164 charge_mcp_call, charge_pg_query, install_mcp_call_budget, install_pg_query_budget,
165 McpCallBudgetGuard, PgQueryBudgetGuard,
166};
167pub use checkpoint::register_checkpoint_builtins;
168pub use chunk::*;
169pub use compiler::*;
170pub use connectors::{
171 active_connector_client, active_metrics_registry, clear_active_connector_clients,
172 clear_active_metrics_registry, connector_export_denied_builtin_reason,
173 connector_export_effect_class,
174 cron::{CatchupMode, CronConnector},
175 default_connector_export_policy,
176 harn_module::{
177 load_contract as load_harn_connector_contract, HarnConnector, HarnConnectorContract,
178 },
179 hmac::{verify_hmac_signed, SIGNATURE_VERIFY_AUDIT_TOPIC},
180 install_active_connector_clients, install_active_metrics_registry,
181 postprocess_normalized_event, ActivationHandle, ClientError, Connector, ConnectorClient,
182 ConnectorCtx, ConnectorError, ConnectorExportEffectClass, ConnectorHttpResponse,
183 ConnectorMetricsSnapshot, ConnectorNormalizeResult, ConnectorRegistry, GenericWebhookConnector,
184 HarnConnectorEffectPolicies, MetricsRegistry, PostNormalizeOutcome, ProviderPayloadSchema,
185 RateLimitConfig, RateLimiterFactory, RawInbound, StreamConnector, TriggerBinding, TriggerKind,
186 TriggerRegistry, WebhookSignatureVariant,
187};
188pub use corrections::{
189 append_correction_record, apply_corrections_to_policy, correction_query_filters_from_json,
190 correction_record_from_json, policy_with_corrections, query_correction_records,
191 CorrectionQueryFilters, CorrectionRecord, CorrectionScope, CORRECTIONS_TOPIC,
192 CORRECTION_EVENT_KIND, CORRECTION_SCHEMA_V0,
193};
194pub use harness::{
195 DenyEvent, Harness, HarnessCall, HarnessClock, HarnessCrypto, HarnessEnv, HarnessFs,
196 HarnessKind, HarnessLlm, HarnessNet, HarnessObs, HarnessProcess, HarnessRandom, HarnessSecrets,
197 HarnessStdio, HarnessSystem, HarnessTenant, HarnessTerm, MockAwareClock, MockHarnessBuilder,
198 VmHarness,
199};
200pub use harness_auth::{
201 current_auth_principal, enter_auth_principal, AuthPrincipal, AuthPrincipalScopeGuard,
202 MISSING_PRINCIPAL_MESSAGE,
203};
204pub use harness_net::{
205 bypass_enabled as net_policy_bypass_enabled, NetMatcher, NetPolicy, NetPolicyAudit,
206 NetPolicyDecision, NetPolicyDefault, NetPolicyRule, OnViolation, HARN_NET_POLICY_BYPASS_ENV,
207 NET_POLICY_AUDIT_TOPIC,
208};
209pub use harness_tenant::{
210 current_tenant_id, enter_tenant, TenantScopeGuard, MISSING_TENANT_MESSAGE,
211};
212pub use http::{register_http_builtins, reset_http_state};
213pub use llm::register_llm_builtins;
214pub use llm::trigger_predicate::TriggerPredicateBudget;
215pub use llm::{
216 current_agent_session_id, install_llm_cost_budget, install_llm_token_budget,
217 peek_llm_cost_budget, peek_llm_token_budget, register_session_end_hook, set_llm_cost_budget,
218 set_llm_token_budget, LlmBudgetGuard, LlmTokenBudgetGuard,
219};
220pub use mcp::{connect_mcp_server_from_json, connect_mcp_server_from_spec, register_mcp_builtins};
221pub use mcp_allowlist::{
222 build_catalog as build_mcp_catalog, catalog_for_request as mcp_catalog_for_request,
223 AdvertisedItem as McpAdvertisedItem, CatalogRequest as McpCatalogRequest, McpAllowlist,
224 McpAllowlistItem, McpCatalog, McpCatalogItem, McpCatalogServer, McpItemKind,
225 MCP_ALLOWLIST_SCHEMA_VERSION,
226};
227pub use mcp_card::{fetch_server_card, load_server_card_from_path, CardError};
228pub use mcp_host::{
229 cache_stats as mcp_host_cache_stats, set_allowlist as set_mcp_host_allowlist,
230 AllowlistDecision as McpHostAllowlistDecision, AllowlistGuard as McpHostAllowlistGuard,
231 BreakerState as McpHostBreakerState, CacheStats as McpHostCacheStats, McpHostStatus,
232 SpawnOptions as McpHostSpawnOptions, SupervisionPolicy as McpHostSupervisionPolicy,
233};
234pub use mcp_registry::{
235 active_handle as mcp_active_handle, ensure_active as mcp_ensure_active,
236 get_registration as mcp_get_registration, install_active as mcp_install_active,
237 is_registered as mcp_is_registered, register_servers as mcp_register_servers,
238 release as mcp_release, reset as mcp_reset_registry, snapshot_status as mcp_snapshot_status,
239 sweep_expired as mcp_sweep_expired, RegisteredMcpServer, RegistryStatus,
240};
241pub use mcp_server::{
242 take_mcp_serve_prompts, take_mcp_serve_registry, take_mcp_serve_resource_templates,
243 take_mcp_serve_resources, tool_registry_to_mcp_tools, McpServer,
244};
245pub use metadata::register_metadata_builtins;
246pub use observability::audit::{audit_events as audit_obs_events, AuditFinding, AuditFindingKind};
247pub use observability::request_id::{current_request_id, enter_request_id, RequestIdScopeGuard};
248pub use orchestration::{
249 benchmark_adapted_replay_pair, benchmark_replay_trace, build_replay_benchmark_report,
250 OpenCodeJsonlAdapter, ReplayBenchmarkCloudIngest, ReplayBenchmarkError,
251 ReplayBenchmarkFixtureReceipt, ReplayBenchmarkFixtureReport, ReplayBenchmarkMetrics,
252 ReplayBenchmarkReport, ReplayBenchmarkSuiteIdentity, ReplayBenchmarkSummary,
253 ReplayCategoryMetric, ReplayDebuggingProxyMetrics, ReplayRuntimeCostMetrics,
254 ReplayTraceAdapter, OPENCODE_JSONL_ADAPTER_ID, OPENCODE_JSONL_ADAPTER_SCHEMA_VERSION,
255 REPLAY_BENCHMARK_CLOUD_INGEST_KIND, REPLAY_BENCHMARK_REPORT_SCHEMA_VERSION,
256};
257pub use orchestration::{
258 canonicalize_run, first_divergence, run_replay_oracle_trace, ReplayAllowlistRule,
259 ReplayDivergence, ReplayExpectation, ReplayOracleError, ReplayOracleReport, ReplayOracleTrace,
260 ReplayTraceRun, ReplayTraceRunCounts, REPLAY_TRACE_SCHEMA_VERSION,
261};
262pub use orchestration::{
263 install_handoff_routes, snapshot_handoff_routes, HandoffRouteConfig,
264 HandoffRouteDecisionRecord, HandoffRouteTargetConfig,
265};
266pub use personas::{
267 disable_persona, fire_schedule as fire_persona_schedule, fire_trigger as fire_persona_trigger,
268 format_ms as format_persona_ms, now_ms as persona_now_ms, parse_rfc3339_ms as parse_persona_ms,
269 pause_persona, persona_status, record_persona_spend, register_persona_supervision_sink,
270 register_persona_value_sink, report_repair_worker_status, restore_persona_checkpoint,
271 resume_persona, PersonaAssignmentStatus, PersonaBudgetPolicy, PersonaBudgetStatus,
272 PersonaCheckpointAction, PersonaCheckpointRestoreOutcome, PersonaCheckpointRestoreRequest,
273 PersonaCheckpointResume, PersonaCheckpointUpdate, PersonaHandoffInboxItem, PersonaLease,
274 PersonaLifecycleState, PersonaQueuePositionUpdate, PersonaQueuedWork, PersonaReceiptUpdate,
275 PersonaRepairWorkerLifecycle, PersonaRepairWorkerStatusUpdate, PersonaRunCost,
276 PersonaRunReceipt, PersonaRuntimeBinding, PersonaStatus, PersonaSupervisionEvent,
277 PersonaSupervisionSink, PersonaSupervisionSinkRegistration, PersonaTriggerEnvelope,
278 PersonaValueEvent, PersonaValueEventKind, PersonaValueReceipt, PersonaValueSink,
279 PersonaValueSinkRegistration, StageDecl, StageExit, PERSONA_RUNTIME_TOPIC,
280};
281pub use provenance::{
282 build_signed_receipt, load_or_generate_agent_signing_key, verify_receipt, ProvenanceReceipt,
283 ReceiptBuildOptions, ReceiptVerificationReport,
284};
285pub use receipts::{
286 Receipt, ReceiptSink, ReceiptStatus, ReceiptValidationError, RedactingReceiptSink,
287 RedactionClass, RECEIPT_SCHEMA_ID, RECEIPT_SCHEMA_JSON, RECEIPT_SCHEMA_VERSION,
288};
289pub use record_filter::{normalize_record_filter_expression, CompiledRecordFilter};
290pub use runtime_limits::{
291 RuntimeLimitDescription, RuntimeLimitEntry, RuntimeLimits, RuntimeLimitsReport,
292 RUNTIME_LIMIT_DESCRIPTIONS,
293};
294pub use schema::json_to_vm_value;
295pub use sessions::{
296 CreateSession, ExpireSession, Session, SessionAttributes, SessionError, SessionStore,
297 TouchSession, SESSIONS_TOPIC,
298};
299pub use stdlib::hitl::{
300 append_hitl_response, ApprovalRequest, HitlHostResponse, HITL_APPROVALS_TOPIC,
301 HITL_DUAL_CONTROL_TOPIC, HITL_ESCALATIONS_TOPIC, HITL_QUESTIONS_TOPIC,
302};
303pub use stdlib::host::{clear_host_call_bridge, set_host_call_bridge, HostCallBridge};
304pub use stdlib::http_response::{
305 parse_envelope as parse_http_envelope, HttpEnvelope, HttpHeaderValue, WsUpgradeSpec,
306 HTTP_RESPONSE_TAG_KEY, HTTP_RESPONSE_TAG_VERSION,
307};
308#[cfg(feature = "postgres")]
309pub use stdlib::install_shared_pool_registry;
310pub use stdlib::io::{set_stdout_passthrough, take_stderr_buffer};
311pub use stdlib::long_running::cancel_handle as cancel_long_running_handle;
312pub use stdlib::observability::install_default_backend as install_obs_default_backend;
313pub use stdlib::secret_scan::{
314 append_secret_scan_audit, audit_secret_scan_active, scan_content as secret_scan_content,
315 SecretFinding, SECRET_SCAN_AUDIT_TOPIC,
316};
317pub use stdlib::template::{
318 lookup_prompt_consumers, lookup_prompt_span, prompt_render_indices, record_prompt_render_index,
319 PromptSourceSpan, PromptSpanKind,
320};
321pub use stdlib::waitpoint::{
322 process_waitpoint_resume_event, service_waitpoints_once, WAITPOINT_RESUME_TOPIC,
323};
324pub use stdlib::workflow_messages::{
325 workflow_pause_for_base, workflow_publish_query_for_base, workflow_query_for_base,
326 workflow_respond_update_for_base, workflow_resume_for_base, workflow_signal_for_base,
327 workflow_update_for_base, WorkflowMailboxState,
328};
329pub use stdlib::{
330 register_agent_stdlib, register_core_stdlib, register_io_stdlib, register_vm_stdlib,
331};
332pub use store::register_store_builtins;
333pub use tenant::{
334 tenant_event_topic_prefix, tenant_secret_namespace, tenant_topic, validate_tenant_id, ApiKeyId,
335 TenantApiKeyRecord, TenantBudget, TenantEventLog, TenantRecord, TenantRegistrySnapshot,
336 TenantResolutionError, TenantScope, TenantSecretProvider, TenantStatus, TenantStore,
337 TENANT_EVENT_TOPIC_PREFIX, TENANT_REGISTRY_DIR, TENANT_REGISTRY_FILE,
338 TENANT_SECRET_NAMESPACE_PREFIX,
339};
340pub use triggers::{
341 append_dispatch_cancel_request, begin_in_flight, binding_autonomy_budget_would_exceed,
342 binding_budget_would_exceed, binding_version_as_of, classify_trigger_dlq_error,
343 clear_dispatcher_state, clear_orchestrator_budget, clear_trigger_registry, drain,
344 dynamic_deregister, dynamic_register, expected_predicate_cost_usd_micros, finish_in_flight,
345 install_manifest_triggers, install_orchestrator_budget, install_provider_catalog,
346 micros_to_usd, note_autonomous_decision, note_orchestrator_budget_cost,
347 orchestrator_budget_would_exceed, parse_flow_control_duration, pause, pin_trigger_binding,
348 provider_metadata, record_predicate_cost_sample, redact_headers, register_provider_schema,
349 registered_provider_metadata, registered_provider_schema_names, reset_binding_budget_windows,
350 reset_provider_catalog, reset_provider_catalog_with, resolve_live_or_as_of,
351 resolve_live_trigger_binding, resolve_trigger_binding_as_of, resume,
352 run_trigger_harness_fixture, scheduler_in_flight_by_key, scheduler_ready_stats_by_key,
353 snapshot_dispatcher_stats, snapshot_orchestrator_budget, snapshot_trigger_bindings,
354 unpin_trigger_binding, usd_to_micros, worker_claims_topic_name, worker_job_topic_name,
355 worker_response_topic_name, ClaimedWorkerJob, DispatchCancelRequest, DispatchError,
356 DispatchOutcome, DispatchStatus, Dispatcher, DispatcherDrainReport, DispatcherStatsSnapshot,
357 FairnessKey, HeaderRedactionPolicy, InboxIndex, NotionPolledChangeEvent,
358 OrchestratorBudgetConfig, OrchestratorBudgetSnapshot, ProviderCatalog, ProviderCatalogError,
359 ProviderId, ProviderMetadata, ProviderOutboundMethod, ProviderPayload, ProviderRuntimeMetadata,
360 ProviderSchema, ProviderSecretRequirement, ReadyKeyStats, RecordedTriggerBinding, RetryPolicy,
361 SchedulableJob, SchedulerKeyStat, SchedulerPolicy, SchedulerSnapshot, SchedulerState,
362 SchedulerStrategy, SignatureStatus, SignatureVerificationMetadata, StreamEventPayload,
363 TenantId, TraceId, TriggerBatchConfig, TriggerBindingSnapshot, TriggerBindingSource,
364 TriggerBindingSpec, TriggerBudgetExhaustionStrategy, TriggerConcurrencyConfig,
365 TriggerDebounceConfig, TriggerDispatchOutcome, TriggerEvent, TriggerEventId,
366 TriggerExpressionSpec, TriggerFlowControlConfig, TriggerHandlerSpec, TriggerHarnessResult,
367 TriggerId, TriggerMetricsSnapshot, TriggerPredicateSpec, TriggerPriorityOrderConfig,
368 TriggerRateLimitConfig, TriggerRegistryError, TriggerRetryConfig, TriggerSingletonConfig,
369 TriggerState, TriggerThrottleConfig, WorkerQueue, WorkerQueueClaimHandle,
370 WorkerQueueEnqueueReceipt, WorkerQueueInspectSnapshot, WorkerQueueJob, WorkerQueueJobState,
371 WorkerQueuePriority, WorkerQueueResponseRecord, WorkerQueueState, WorkerQueueSummary,
372 DEFAULT_INBOX_RETENTION_DAYS, DEFAULT_STARVATION_AGE_MS, TRIGGERS_LIFECYCLE_TOPIC,
373 TRIGGER_ATTEMPTS_TOPIC, TRIGGER_CANCEL_REQUESTS_TOPIC, TRIGGER_DLQ_TOPIC,
374 TRIGGER_INBOX_CLAIMS_TOPIC, TRIGGER_INBOX_ENVELOPES_TOPIC, TRIGGER_INBOX_LEGACY_TOPIC,
375 TRIGGER_INBOX_OBSERVABILITY_TOPIC, TRIGGER_OPERATION_AUDIT_TOPIC, TRIGGER_OUTBOX_TOPIC,
376 TRIGGER_TEST_FIXTURES, WORKER_QUEUE_CATALOG_TOPIC,
377};
378pub use trust_graph::{
379 append_active_scope_attenuation_alert, append_active_trust_record,
380 append_scope_attenuation_alert, append_trust_record, export_trust_chain,
381 group_trust_records_by_trace, policy_for_agent, policy_for_autonomy_tier,
382 query_trust_graph_records, query_trust_records, resolve_agent_autonomy_tier,
383 summarize_trust_records, topic_for_agent, trust_score_for, verify_trust_chain, AutonomyTier,
384 TrustAgentSummary, TrustChainExport, TrustChainExportMetadata, TrustChainExportProducer,
385 TrustChainReport, TrustGraphRecord, TrustOutcome, TrustQueryFilters, TrustRecord,
386 TrustRecordActionKind, TrustScore, TrustTraceGroup, METADATA_KEY_ACTOR_CHAIN,
387 METADATA_KEY_ACTOR_CHAIN_ALERT, METADATA_KEY_EFFECTS_GRANT, METADATA_KEY_EFFECTS_USED,
388 METADATA_KEY_PARENT_RECORD_ID, OPENTRUSTGRAPH_ACCEPTED_SCHEMAS, OPENTRUSTGRAPH_CHAIN_SCHEMA_V0,
389 OPENTRUSTGRAPH_SCHEMA_V0, OPENTRUSTGRAPH_SCHEMA_V0_1, TRUST_ACTION_RELEASE,
390 TRUST_GRAPH_GLOBAL_TOPIC, TRUST_GRAPH_LEGACY_GLOBAL_TOPIC, TRUST_GRAPH_LEGACY_TOPIC_PREFIX,
391 TRUST_GRAPH_RECORDS_TOPIC, TRUST_GRAPH_TOPIC_PREFIX,
392};
393pub use value::*;
394pub use vm::*;
395
396#[cfg(feature = "vm-bench-internals")]
397#[doc(hidden)]
398pub mod bench_internals;
399
400pub fn compile_source(source: &str) -> Result<Chunk, String> {
405 let program = harn_parser::check_source_strict(source).map_err(|e| e.to_string())?;
406 Compiler::new().compile(&program).map_err(|e| e.to_string())
407}
408
409pub fn compile_source_named(source: &str, pipeline_name: &str) -> Result<Chunk, String> {
414 let program = harn_parser::check_source_strict(source).map_err(|e| e.to_string())?;
415 let has_pipeline = program.iter().any(|sn| {
416 let (_, inner) = harn_parser::peel_attributes(sn);
417 matches!(&inner.node, harn_parser::Node::Pipeline { name, .. } if name == pipeline_name)
418 });
419 if !has_pipeline {
420 return Err(format!("no pipeline named `{pipeline_name}` in source"));
421 }
422 Compiler::new()
423 .compile_named(&program, pipeline_name)
424 .map_err(|e| e.to_string())
425}
426
427pub fn json_schema_for_type_expr(type_expr: &harn_parser::TypeExpr) -> Option<serde_json::Value> {
428 let schema = compiler::Compiler::type_expr_to_schema_value(type_expr)?;
429 let json_schema = schema::schema_to_json_schema_value(&schema).ok()?;
430 Some(llm::vm_value_to_json(&json_schema))
431}
432
433pub fn json_schema_for_typed_params(params: &[harn_parser::TypedParam]) -> serde_json::Value {
434 let mut properties = serde_json::Map::new();
435 let mut required = Vec::new();
436
437 for param in params {
438 let param_schema = param
439 .type_expr
440 .as_ref()
441 .and_then(json_schema_for_type_expr)
442 .unwrap_or_else(|| serde_json::json!({}));
443 if param.default_value.is_none() {
444 required.push(serde_json::Value::String(param.name.clone()));
445 }
446 properties.insert(param.name.clone(), param_schema);
447 }
448
449 let mut schema = serde_json::Map::new();
450 schema.insert(
451 "type".to_string(),
452 serde_json::Value::String("object".to_string()),
453 );
454 schema.insert(
455 "properties".to_string(),
456 serde_json::Value::Object(properties),
457 );
458 if !required.is_empty() {
459 schema.insert("required".to_string(), serde_json::Value::Array(required));
460 }
461 serde_json::Value::Object(schema)
462}
463
464fn reset_llm_state_for_thread_reset() {
465 llm::reset_llm_state();
466 #[cfg(test)]
467 reset_thread_local_state_test_hooks::before_llm_global_reset();
468 llm::reset_rate_limit_registry();
471 llm_config::clear_user_overrides();
472}
473
474#[cfg(test)]
475mod reset_thread_local_state_test_hooks {
476 use std::sync::{Arc, Mutex, OnceLock};
477
478 type Hook = Arc<dyn Fn() + Send + Sync + 'static>;
479
480 static BEFORE_LLM_GLOBAL_RESET: OnceLock<Mutex<Option<Hook>>> = OnceLock::new();
481
482 fn before_llm_global_reset_hook() -> &'static Mutex<Option<Hook>> {
483 BEFORE_LLM_GLOBAL_RESET.get_or_init(|| Mutex::new(None))
484 }
485
486 pub(crate) struct HookGuard;
487
488 impl Drop for HookGuard {
489 fn drop(&mut self) {
490 let mut hook = before_llm_global_reset_hook()
491 .lock()
492 .unwrap_or_else(std::sync::PoisonError::into_inner);
493 *hook = None;
494 }
495 }
496
497 pub(crate) fn install_before_llm_global_reset(hook: Hook) -> HookGuard {
498 let mut slot = before_llm_global_reset_hook()
499 .lock()
500 .unwrap_or_else(std::sync::PoisonError::into_inner);
501 *slot = Some(hook);
502 HookGuard
503 }
504
505 pub(crate) fn before_llm_global_reset() {
506 let hook = before_llm_global_reset_hook()
507 .lock()
508 .unwrap_or_else(std::sync::PoisonError::into_inner)
509 .clone();
510 if let Some(hook) = hook {
511 hook();
512 }
513 }
514}
515
516pub fn reset_thread_local_state() {
518 #[cfg(test)]
519 {
520 let _guard = llm::env_guard();
525 reset_llm_state_for_thread_reset();
526 }
527 #[cfg(not(test))]
528 reset_llm_state_for_thread_reset();
529
530 http::reset_http_state();
531 channels::reset_channel_state();
532 event_log::reset_active_event_log();
533 egress::clear_explicit_egress_policy_requirement_for_host();
534 egress::clear_ssrf_guard_requirement_for_host();
535 stdlib::reset_stdlib_state();
536 connectors::clear_active_connector_clients();
537 orchestration::clear_runtime_hooks();
538 orchestration::clear_execution_policy_stacks();
539 orchestration::clear_command_policies();
540 orchestration::clear_pipeline_on_finish();
541 orchestration::reset_lifecycle_receipt_registry();
542 orchestration::agent_inbox::reset();
543 tool_call_cancellations::reset_registry();
544 redact::clear_policy_stack();
545 security::reset_thread_state();
546 triggers::clear_dispatcher_state();
547 triggers::clear_trigger_registry();
548 events::reset_event_sinks();
549 tracing::set_tracing_enabled(false);
550 tracing::reset_tracing();
551 agent_events::reset_all_sinks();
552 agent_sessions::reset_session_store();
553 mcp_registry::reset();
554 mcp_host::reset_for_tests();
555 call_budget::reset_call_budget_state();
556 clock_mock::leak_audit::reset();
557}
558
559#[cfg(test)]
560mod reset_leak_tests {
561 use super::*;
567 use crate::value::VmValue;
568
569 #[test]
570 fn reset_drains_agent_inbox() {
571 orchestration::agent_inbox::reset();
572 orchestration::agent_inbox::push("sess-2660", "note", "leak", "test");
573 assert!(orchestration::agent_inbox::session_count() > 0);
574 reset_thread_local_state();
575 assert_eq!(
576 orchestration::agent_inbox::session_count(),
577 0,
578 "agent_inbox must be empty after reset"
579 );
580 }
581
582 #[test]
583 fn reset_drains_tool_call_cancellation_registry() {
584 tool_call_cancellations::reset_registry();
585 let registered = tool_call_cancellations::register("sess-2660", "call-1", "tool");
588 if let Some((_handle, guard)) = registered {
589 std::mem::forget(guard);
590 }
591 assert!(tool_call_cancellations::registry_len() > 0);
592 reset_thread_local_state();
593 assert_eq!(
594 tool_call_cancellations::registry_len(),
595 0,
596 "tool-call cancellation registry must be empty after reset"
597 );
598 }
599
600 #[test]
601 fn reset_drains_routing_policy_registry() {
602 llm::routing::clear_policy_registry();
603 let mut config: crate::value::DictMap = crate::value::DictMap::new();
604 config.insert(
605 crate::value::intern_key("chain"),
606 VmValue::List(std::sync::Arc::new(vec![VmValue::String(
607 arcstr::ArcStr::from("mock:mock"),
608 )])),
609 );
610 llm::routing::build_routing_policy(&config).expect("intern a routing policy");
611 assert!(llm::routing::policy_registry_len() > 0);
612 reset_thread_local_state();
613 assert_eq!(
614 llm::routing::policy_registry_len(),
615 0,
616 "routing policy registry must be empty after reset"
617 );
618 }
619
620 #[test]
621 fn reset_holds_llm_env_guard_while_wiping_llm_globals() {
622 let observed = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
623 let observed_hook = std::sync::Arc::clone(&observed);
624 let _hook = reset_thread_local_state_test_hooks::install_before_llm_global_reset(
625 std::sync::Arc::new(move || {
626 assert!(
627 matches!(
628 llm::env_lock().try_lock(),
629 Err(std::sync::TryLockError::WouldBlock)
630 ),
631 "reset_thread_local_state must hold env_guard before wiping LLM globals"
632 );
633 observed_hook.store(true, std::sync::atomic::Ordering::SeqCst);
634 }),
635 );
636
637 reset_thread_local_state();
638 assert!(
639 observed.load(std::sync::atomic::Ordering::SeqCst),
640 "LLM global reset hook should have run"
641 );
642 }
643}