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