1#![recursion_limit = "256"]
2#![allow(clippy::result_large_err, clippy::cloned_ref_to_slice_refs)]
3pub use harn_clock as clock;
23
24mod runtime_stack;
25pub use runtime_stack::RUNTIME_STACK_SIZE;
26
27pub mod a2a;
28pub mod actor_chain;
29pub mod agent_events;
30pub(crate) mod agent_session_journal;
31pub mod agent_session_restore;
32pub mod agent_sessions;
33pub mod agent_transcript_budget;
34pub mod atomic_io;
35pub mod autonomy;
36#[cfg(feature = "cloud-aws")]
37pub(crate) mod aws_sigv4;
38#[cfg(not(feature = "cloud-aws"))]
39#[path = "aws_sigv4_disabled.rs"]
40pub(crate) mod aws_sigv4;
41pub mod boundary;
42pub mod bridge;
43pub use bridge::{
44 inject_leading_authorities, inject_leading_authority, leading_authority_param_count,
45};
46mod bounded_files;
47pub mod builtin_profile;
48pub mod bytecode_cache;
49pub mod call_budget;
50pub mod canonical_json;
51pub mod channel_guardrails;
52pub mod channels;
53pub mod checkpoint;
54mod chunk;
55mod compiler;
56pub mod composition;
57pub mod conditional_replace;
58pub mod config;
59pub mod connectors;
60pub mod context_manifest;
61pub mod corrections;
62pub mod coverage;
63pub(crate) mod durable_rate_limit;
64pub mod duration_parse;
65pub mod egress;
66pub mod environment_registry;
67pub mod event_log;
68pub mod events;
69pub mod external_agent;
70pub mod flight_recorder;
71pub mod flow;
72pub mod harness;
73pub mod harness_auth;
74pub(crate) mod harness_crypto;
75pub mod harness_net;
76pub mod harness_system;
77pub mod harness_tenant;
78pub mod host_attachments;
79
80pub mod subtask {
86 pub use crate::vm::subtask::{
87 placement, scope_placement, SubtaskPlacement, SubtaskPlacementParseError, PLACEMENT_ENV,
88 PLACEMENT_VALUES,
89 };
90}
91mod http;
92pub mod jsonrpc;
93pub(crate) mod limits;
94pub mod linked_program;
95pub mod llm;
96pub mod llm_config;
97pub mod local_selection;
98pub mod mcp;
99pub mod mcp_allowlist;
100pub mod mcp_auth;
101pub mod mcp_bulk_auth;
102pub mod mcp_card;
103pub mod mcp_client_roots;
104pub mod mcp_elicit;
105pub mod mcp_host;
106pub mod mcp_identity;
107pub mod mcp_input;
108pub mod mcp_json_discovery;
109pub mod mcp_oauth;
110pub mod mcp_presets;
111pub mod mcp_progress;
112pub mod mcp_protocol;
113pub mod mcp_registry;
114pub mod mcp_sampling;
115pub mod mcp_server;
116pub mod mcp_tasks;
117pub mod metadata;
118pub mod module_artifact;
119pub mod module_source;
120pub mod observability;
121pub mod op_interrupt;
122pub mod orchestration;
123pub mod runtime_content;
124pub use runtime_content::{
125 runtime_content_fingerprint, RuntimeBuildFeatures, RuntimeCompatibilityFingerprint,
126 RuntimeContentFingerprint,
127};
128mod persistent_state;
129pub mod personas;
130pub mod portable;
131mod prepared_module;
132pub mod prepared_run;
133pub mod process_sandbox;
134pub mod profile;
135pub mod provenance;
136pub mod provider_catalog;
137pub mod receipts;
138pub mod record_filter;
139pub mod redact;
140pub mod run_events;
141pub mod runtime_context;
142pub(crate) mod runtime_guards;
143pub mod runtime_limits;
144pub mod runtime_paths;
145pub(crate) mod runtime_sqlite;
146pub mod schema;
147pub(crate) mod secret_patterns;
148pub mod secrets;
149pub mod security;
150pub mod session_bundle;
151pub mod session_recap;
152pub mod session_timeline;
153pub mod sessions;
154pub(crate) mod shared_state;
155pub mod shells;
156pub mod skills;
157pub mod stdlib;
158pub use stdlib::session_change::{
160 subscribe as subscribe_session_changes, SessionChangeSubscription,
161};
162pub use stdlib::session_store::open_canonical_store;
163pub mod stdlib_modules;
164pub mod step_runtime;
165pub mod store;
166pub(crate) mod synchronization;
167pub mod tenant;
168pub(crate) mod term;
169pub(crate) mod test_env;
170pub mod testbench;
171pub mod text;
172pub mod text_diff;
173pub mod tool_annotations;
174pub mod tool_call_cancellations;
175pub mod tool_registry;
176pub mod tool_surface;
177pub mod tracing;
178pub mod triggers;
179pub mod trust_graph;
180pub(crate) mod url_encoding;
181pub mod user_dirs;
182
183pub fn initialize_runtime_assets() {
191 secret_patterns::initialize_default_secret_patterns();
192}
193
194#[derive(Debug)]
196pub enum RuntimeInitError {
197 Environment(environment_registry::EnvironmentValidationError),
199 CacheDir(bytecode_cache::CacheDirError),
201}
202
203impl std::fmt::Display for RuntimeInitError {
204 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205 match self {
206 Self::Environment(error) => error.fmt(formatter),
207 Self::CacheDir(error) => error.fmt(formatter),
208 }
209 }
210}
211
212impl std::error::Error for RuntimeInitError {}
213
214pub fn initialize_runtime() -> Result<Option<&'static str>, RuntimeInitError> {
223 environment_registry::validate_startup_environment().map_err(RuntimeInitError::Environment)?;
224 let warning = bytecode_cache::check_cache_config().map_err(RuntimeInitError::CacheDir)?;
225 initialize_runtime_assets();
226 Ok(warning)
227}
228
229pub mod clock_mock {
234 pub(crate) use crate::triggers::test_util::clock::scope_capability_clock;
235 pub use crate::triggers::test_util::clock::{
236 active_clock, active_mock_clock, advance, clear_overrides, install_override, instant_now,
237 is_mocked, now_ms, now_utc, sleep, ClockInstant, ClockOverrideGuard, MockClock,
238 };
239
240 pub mod leak_audit {
244 pub use crate::triggers::test_util::clock_leak::{
245 drain, enter_scope, install_scope, instant_now, reset, snapshot, wall_now, ClockLeak,
246 ClockLeakScope, ClockLeakScopeGuard,
247 };
248 }
249}
250
251pub(crate) mod text_index;
252pub mod typecheck;
253pub mod value;
254pub mod verification;
255pub mod visible_text;
256mod vm;
257pub(crate) mod wait_for_graph;
258pub mod waitpoints;
259pub mod windows_path;
260pub mod workspace_anchor;
261pub mod workspace_path;
262
263pub use persistent_state::{
264 register_persistent_state_builtins_at_root, scope_persistent_state_root, PersistentStateRoot,
265 ScopedPersistentStateRoot,
266};
267pub use prepared_module::{PreparedModuleCache, PreparedModuleCacheStats};
268
269pub use actor_chain::{
270 ActorChain, ActorChainEntry, ActorChainError, Principal, ScopeAttenuationMode,
271 ScopeAttenuationPolicy, ScopeAttenuationViolation,
272};
273pub use call_budget::{
274 charge_mcp_call, charge_pg_query, install_mcp_call_budget, install_pg_query_budget,
275 McpCallBudgetGuard, PgQueryBudgetGuard,
276};
277pub use checkpoint::register_checkpoint_builtins;
278pub use chunk::*;
279pub use compiler::*;
280pub use connectors::{
281 active_connector_client, active_metrics_registry, clear_active_connector_clients,
282 clear_active_metrics_registry, connector_export_denied_builtin_reason,
283 connector_export_denied_harness_method_reason, connector_export_effect_class,
284 cron::{CatchupMode, CronConnector},
285 declared_secret_ids, default_connector_export_policy,
286 harn_module::{
287 load_contract as load_harn_connector_contract, HarnConnector, HarnConnectorContract,
288 },
289 hmac::{verify_hmac_signed, SIGNATURE_VERIFY_AUDIT_TOPIC},
290 install_active_connector_clients, install_active_metrics_registry,
291 postprocess_normalized_event, scope_active_connector_clients, ActivationHandle,
292 ActiveConnectorClientsGuard, ClientError, Connector, ConnectorClient, ConnectorClientResolver,
293 ConnectorCtx, ConnectorError, ConnectorExportEffectClass, ConnectorHttpResponse,
294 ConnectorMetricsSnapshot, ConnectorNormalizeResult, ConnectorRegistry, GenericWebhookConnector,
295 HarnConnectorEffectPolicies, MetricsRegistry, PostNormalizeOutcome, ProviderPayloadSchema,
296 RateLimitConfig, RateLimiterFactory, RawInbound, StreamConnector, TriggerBinding, TriggerKind,
297 TriggerRegistry, VmConnectorClients, WebhookSignatureVariant,
298};
299pub use corrections::{
300 append_correction_record, apply_corrections_to_policy, correction_query_filters_from_json,
301 correction_record_from_json, policy_with_corrections, query_correction_records,
302 CorrectionQueryFilters, CorrectionRecord, CorrectionScope, CORRECTIONS_TOPIC,
303 CORRECTION_EVENT_KIND, CORRECTION_SCHEMA_V0,
304};
305pub use harn_kernel::BuiltinId;
306pub use harness::{
307 DenyEvent, Harness, HarnessAgent, HarnessCall, HarnessChannels, HarnessClock, HarnessEnv,
308 HarnessFs, HarnessKind, HarnessLlm, HarnessMemory, HarnessNet, HarnessObs, HarnessPostgres,
309 HarnessProcess, HarnessRandom, HarnessSecrets, HarnessSqlite, HarnessStdio, HarnessSystem,
310 HarnessTenant, HarnessTerm, HarnessTesting, MockHarnessBuilder, VmHarness,
311};
312pub use harness_auth::{
313 current_auth_principal, enter_auth_principal, AuthPrincipal, AuthPrincipalScopeGuard,
314 MISSING_PRINCIPAL_MESSAGE,
315};
316pub use harness_net::{
317 bypass_enabled as net_policy_bypass_enabled, NetMatcher, NetPolicy, NetPolicyAudit,
318 NetPolicyDecision, NetPolicyDefault, NetPolicyRule, OnViolation, HARN_NET_POLICY_BYPASS_ENV,
319 NET_POLICY_AUDIT_TOPIC,
320};
321pub use harness_tenant::{
322 current_tenant_id, enter_tenant, TenantScopeGuard, MISSING_TENANT_MESSAGE,
323};
324pub use http::{register_http_builtins, reset_http_state};
325pub use llm::register_llm_builtins;
326pub use llm::trigger_predicate::TriggerPredicateBudget;
327pub use llm::{
328 current_agent_session_id, install_llm_cost_budget, install_llm_token_budget,
329 peek_llm_cost_budget, peek_llm_token_budget, register_session_end_hook, set_llm_cost_budget,
330 set_llm_token_budget, LlmBudgetGuard, LlmTokenBudgetGuard, SessionEndHookRegistration,
331};
332pub use mcp::{connect_mcp_server_from_json, connect_mcp_server_from_spec, register_mcp_builtins};
333pub use mcp_allowlist::{
334 build_catalog as build_mcp_catalog, catalog_for_request as mcp_catalog_for_request,
335 AdvertisedItem as McpAdvertisedItem, CatalogRequest as McpCatalogRequest, McpAllowlist,
336 McpAllowlistItem, McpCatalog, McpCatalogItem, McpCatalogServer, McpItemKind,
337 MCP_ALLOWLIST_SCHEMA_VERSION,
338};
339pub use mcp_card::{fetch_server_card, load_server_card_from_path, CardError};
340pub use mcp_host::{
341 cache_stats as mcp_host_cache_stats, set_allowlist as set_mcp_host_allowlist,
342 AllowlistDecision as McpHostAllowlistDecision, AllowlistGuard as McpHostAllowlistGuard,
343 BreakerState as McpHostBreakerState, CacheStats as McpHostCacheStats, McpHostStatus,
344 SpawnOptions as McpHostSpawnOptions, SupervisionPolicy as McpHostSupervisionPolicy,
345};
346pub use mcp_registry::{
347 active_handle as mcp_active_handle, ensure_active as mcp_ensure_active,
348 get_registration as mcp_get_registration, install_active as mcp_install_active,
349 is_registered as mcp_is_registered, register_servers as mcp_register_servers,
350 release as mcp_release, reset as mcp_reset_registry, snapshot_status as mcp_snapshot_status,
351 sweep_expired as mcp_sweep_expired, RegisteredMcpServer, RegistryStatus,
352};
353pub use mcp_server::{
354 take_mcp_serve_metadata, take_mcp_serve_prompts, take_mcp_serve_registry,
355 take_mcp_serve_resource_templates, take_mcp_serve_resources, tool_registry_to_mcp_tools,
356 McpPromptDef, McpResourceDef, McpResourceTemplateDef, McpServer, McpServerMetadata,
357};
358pub use metadata::register_metadata_builtins;
359pub use observability::audit::{audit_events as audit_obs_events, AuditFinding, AuditFindingKind};
360pub use observability::execution_scope::{
361 current_execution_scope, enter_execution_scope, mint_execution_scope, ExecutionScopeGuard,
362};
363pub use observability::request_id::{current_request_id, enter_request_id, RequestIdScopeGuard};
364pub use orchestration::{
365 benchmark_adapted_replay_pair, benchmark_replay_trace, build_replay_benchmark_report,
366 OpenCodeJsonlAdapter, ReplayBenchmarkCloudIngest, ReplayBenchmarkError,
367 ReplayBenchmarkFixtureReceipt, ReplayBenchmarkFixtureReport, ReplayBenchmarkMetrics,
368 ReplayBenchmarkReport, ReplayBenchmarkSuiteIdentity, ReplayBenchmarkSummary,
369 ReplayCategoryMetric, ReplayDebuggingProxyMetrics, ReplayRuntimeCostMetrics,
370 ReplayTraceAdapter, OPENCODE_JSONL_ADAPTER_ID, OPENCODE_JSONL_ADAPTER_SCHEMA_VERSION,
371 REPLAY_BENCHMARK_CLOUD_INGEST_KIND, REPLAY_BENCHMARK_REPORT_SCHEMA_VERSION,
372};
373pub use orchestration::{
374 canonicalize_run, first_divergence, run_replay_oracle_trace, ReplayAllowlistRule,
375 ReplayDivergence, ReplayExpectation, ReplayOracleError, ReplayOracleReport, ReplayOracleTrace,
376 ReplayTraceRun, ReplayTraceRunCounts, REPLAY_TRACE_SCHEMA_VERSION,
377};
378pub use orchestration::{
379 install_handoff_routes, snapshot_handoff_routes, HandoffRouteConfig,
380 HandoffRouteDecisionRecord, HandoffRouteTargetConfig,
381};
382pub use personas::{
383 disable_persona, fire_schedule as fire_persona_schedule, fire_trigger as fire_persona_trigger,
384 format_ms as format_persona_ms, now_ms as persona_now_ms, parse_rfc3339_ms as parse_persona_ms,
385 pause_persona, persona_status, record_persona_spend, register_persona_supervision_sink,
386 register_persona_value_sink, report_repair_worker_status, restore_persona_checkpoint,
387 resume_persona, PersonaAssignmentStatus, PersonaBudgetPolicy, PersonaBudgetStatus,
388 PersonaCheckpointAction, PersonaCheckpointRestoreOutcome, PersonaCheckpointRestoreRequest,
389 PersonaCheckpointResume, PersonaCheckpointUpdate, PersonaHandoffInboxItem, PersonaLease,
390 PersonaLifecycleState, PersonaQueuePositionUpdate, PersonaQueuedWork, PersonaReceiptUpdate,
391 PersonaRepairWorkerLifecycle, PersonaRepairWorkerStatusUpdate, PersonaRunCost,
392 PersonaRunReceipt, PersonaRuntimeBinding, PersonaStatus, PersonaSupervisionEvent,
393 PersonaSupervisionSink, PersonaSupervisionSinkRegistration, PersonaTriggerEnvelope,
394 PersonaValueEvent, PersonaValueEventKind, PersonaValueReceipt, PersonaValueSink,
395 PersonaValueSinkRegistration, StageDecl, StageExit, PERSONA_RUNTIME_TOPIC,
396};
397pub use provenance::{
398 build_signed_receipt, load_or_generate_agent_signing_key, verify_receipt, ProvenanceReceipt,
399 ReceiptBuildOptions, ReceiptVerificationReport,
400};
401pub use receipts::{
402 Receipt, ReceiptSink, ReceiptStatus, ReceiptValidationError, RedactingReceiptSink,
403 RedactionClass, RECEIPT_SCHEMA_ID, RECEIPT_SCHEMA_JSON, RECEIPT_SCHEMA_VERSION,
404};
405pub use record_filter::{normalize_record_filter_expression, CompiledRecordFilter};
406pub use runtime_limits::{
407 RuntimeLimitDescription, RuntimeLimitEntry, RuntimeLimits, RuntimeLimitsReport,
408 RUNTIME_LIMIT_DESCRIPTIONS,
409};
410pub use schema::json_to_vm_value;
411pub use sessions::{
412 CreateSession, ExpireSession, Session, SessionAttributes, SessionError, SessionStore,
413 TouchSession, SESSIONS_TOPIC,
414};
415pub use stdlib::fs::ignore_policy;
421#[doc(hidden)]
422pub use stdlib::fs::invalidate_cached_file_text;
423pub use stdlib::hitl::{
424 append_hitl_response, ApprovalRequest, HitlHostResponse, HITL_APPROVALS_TOPIC,
425 HITL_DUAL_CONTROL_TOPIC, HITL_ESCALATIONS_TOPIC, HITL_QUESTIONS_TOPIC,
426};
427pub use stdlib::host::turn_cache as host_turn_cache;
429pub use stdlib::host::{
430 clear_host_call_bridge, dispatch_host_operation, host_call_ready, install_host_call_bridge,
431 set_host_call_bridge, HostCallBridge, HostCallBridgeGuard, HostCallDispatchFuture,
432};
433pub use stdlib::http_response::{
434 parse_envelope as parse_http_envelope, HttpEnvelope, HttpHeaderValue, WsUpgradeSpec,
435 HTTP_RESPONSE_TAG_KEY, HTTP_RESPONSE_TAG_VERSION,
436};
437#[cfg(feature = "postgres")]
438pub use stdlib::install_shared_pool_registry;
439pub use stdlib::io::{
440 reserve_stdio_for_current_thread, set_stdout_passthrough, take_stderr_buffer,
441 StdioReservationGuard,
442};
443pub use stdlib::long_running::cancel_handle as cancel_long_running_handle;
444pub use stdlib::observability::install_default_backend as install_obs_default_backend;
445pub use stdlib::secret_scan::{
446 append_secret_scan_audit, audit_secret_scan_active, scan_content as secret_scan_content,
447 SecretFinding, SECRET_SCAN_AUDIT_TOPIC,
448};
449pub use stdlib::template::{
450 lookup_prompt_consumers, lookup_prompt_span, prompt_render_indices, record_prompt_render_index,
451 PromptSourceSpan, PromptSpanKind,
452};
453pub use stdlib::waitpoint::{
454 process_waitpoint_resume_event, service_waitpoints_once, WAITPOINT_RESUME_TOPIC,
455};
456pub use stdlib::workflow_messages::{
457 workflow_pause_for_base, workflow_publish_query_for_base, workflow_query_for_base,
458 workflow_respond_update_for_base, workflow_resume_for_base, workflow_signal_for_base,
459 workflow_update_for_base, WorkflowMailboxState,
460};
461pub use stdlib::{
462 register_agent_stdlib, register_core_stdlib, register_io_stdlib, register_vm_stdlib,
463};
464pub use store::register_store_builtins;
465pub use tenant::{
466 tenant_event_topic_prefix, tenant_secret_namespace, tenant_topic, validate_tenant_id, ApiKeyId,
467 TenantApiKeyRecord, TenantBudget, TenantEventLog, TenantRecord, TenantRegistrySnapshot,
468 TenantResolutionError, TenantScope, TenantSecretProvider, TenantStatus, TenantStore,
469 TENANT_EVENT_TOPIC_PREFIX, TENANT_REGISTRY_DIR, TENANT_REGISTRY_FILE,
470 TENANT_SECRET_NAMESPACE_PREFIX,
471};
472pub use triggers::{
473 append_dispatch_cancel_request, begin_in_flight, binding_autonomy_budget_would_exceed,
474 binding_budget_would_exceed, binding_version_as_of, classify_trigger_dlq_error,
475 clear_dispatcher_state, clear_orchestrator_budget, clear_trigger_registry, drain,
476 dynamic_deregister, dynamic_register, expected_predicate_cost_usd_micros, finish_in_flight,
477 install_manifest_triggers, install_orchestrator_budget, micros_to_usd,
478 note_autonomous_decision, note_orchestrator_budget_cost, orchestrator_budget_would_exceed,
479 parse_flow_control_duration, pause, pin_trigger_binding, provider_metadata,
480 record_predicate_cost_sample, redact_headers, register_provider_schemas,
481 registered_provider_metadata, registered_provider_schema_names, reset_binding_budget_windows,
482 reset_provider_catalog, resolve_live_or_as_of, resolve_live_trigger_binding,
483 resolve_trigger_binding_as_of, resume, run_trigger_harness_fixture, scheduler_in_flight_by_key,
484 scheduler_ready_stats_by_key, snapshot_dispatcher_stats, snapshot_orchestrator_budget,
485 snapshot_trigger_bindings, unpin_trigger_binding, usd_to_micros, worker_claims_topic_name,
486 worker_job_topic_name, worker_response_topic_name, ClaimedWorkerJob, DispatchCancelRequest,
487 DispatchError, DispatchOutcome, DispatchStatus, Dispatcher, DispatcherDrainReport,
488 DispatcherStatsSnapshot, ExtensionProviderPayload, FairnessKey, HeaderRedactionPolicy,
489 InboxIndex, OrchestratorBudgetConfig, OrchestratorBudgetSnapshot, ProviderCatalog,
490 ProviderCatalogError, ProviderId, ProviderMetadata, ProviderOutboundMethod, ProviderPayload,
491 ProviderRuntimeMetadata, ProviderSchema, ProviderSecretRequirement, ReadyKeyStats,
492 RecordedTriggerBinding, RetryPolicy, SchedulableJob, SchedulerKeyStat, SchedulerPolicy,
493 SchedulerSnapshot, SchedulerState, SchedulerStrategy, SignatureStatus,
494 SignatureVerificationMetadata, StreamEventPayload, TenantId, TraceId, TriggerBatchConfig,
495 TriggerBindingSnapshot, TriggerBindingSource, TriggerBindingSpec,
496 TriggerBudgetExhaustionStrategy, TriggerConcurrencyConfig, TriggerDebounceConfig,
497 TriggerDispatchOutcome, TriggerEvent, TriggerEventId, TriggerExpressionSpec,
498 TriggerFlowControlConfig, TriggerHandlerSpec, TriggerHarnessResult, TriggerId,
499 TriggerMetricsSnapshot, TriggerPredicateSpec, TriggerPriorityOrderConfig,
500 TriggerRateLimitConfig, TriggerRegistryError, TriggerRetryConfig, TriggerSingletonConfig,
501 TriggerState, TriggerThrottleConfig, WorkerQueue, WorkerQueueClaimHandle,
502 WorkerQueueEnqueueReceipt, WorkerQueueInspectSnapshot, WorkerQueueJob, WorkerQueueJobState,
503 WorkerQueuePriority, WorkerQueueResponseRecord, WorkerQueueState, WorkerQueueSummary,
504 DEFAULT_INBOX_RETENTION_DAYS, DEFAULT_STARVATION_AGE_MS, TRIGGERS_LIFECYCLE_TOPIC,
505 TRIGGER_ATTEMPTS_TOPIC, TRIGGER_CANCEL_REQUESTS_TOPIC, TRIGGER_DLQ_TOPIC,
506 TRIGGER_INBOX_CLAIMS_TOPIC, TRIGGER_INBOX_ENVELOPES_TOPIC, TRIGGER_INBOX_LEGACY_TOPIC,
507 TRIGGER_INBOX_OBSERVABILITY_TOPIC, TRIGGER_OPERATION_AUDIT_TOPIC, TRIGGER_OUTBOX_TOPIC,
508 TRIGGER_TEST_FIXTURES, WORKER_QUEUE_CATALOG_TOPIC,
509};
510pub use trust_graph::{
511 append_active_scope_attenuation_alert, append_active_trust_record,
512 append_scope_attenuation_alert, append_trust_record, export_trust_chain,
513 group_trust_records_by_trace, policy_for_agent, policy_for_autonomy_tier,
514 query_trust_graph_records, query_trust_records, resolve_agent_autonomy_tier,
515 summarize_trust_records, topic_for_agent, trust_score_for, verify_trust_chain, AutonomyTier,
516 TrustAgentSummary, TrustChainExport, TrustChainExportMetadata, TrustChainExportProducer,
517 TrustChainReport, TrustGraphRecord, TrustOutcome, TrustQueryFilters, TrustRecord,
518 TrustRecordActionKind, TrustScore, TrustTraceGroup, METADATA_KEY_ACTOR_CHAIN,
519 METADATA_KEY_ACTOR_CHAIN_ALERT, METADATA_KEY_EFFECTS_GRANT, METADATA_KEY_EFFECTS_USED,
520 METADATA_KEY_PARENT_RECORD_ID, OPENTRUSTGRAPH_ACCEPTED_SCHEMAS, OPENTRUSTGRAPH_CHAIN_SCHEMA_V0,
521 OPENTRUSTGRAPH_SCHEMA_V0, OPENTRUSTGRAPH_SCHEMA_V0_1, TRUST_ACTION_RELEASE,
522 TRUST_GRAPH_GLOBAL_TOPIC, TRUST_GRAPH_LEGACY_GLOBAL_TOPIC, TRUST_GRAPH_LEGACY_TOPIC_PREFIX,
523 TRUST_GRAPH_RECORDS_TOPIC, TRUST_GRAPH_TOPIC_PREFIX,
524};
525pub use value::*;
526pub use vm::*;
527
528#[cfg(feature = "vm-bench-internals")]
529#[doc(hidden)]
530pub mod bench_internals;
531
532pub fn compile_source(source: &str) -> Result<Chunk, String> {
537 let program = harn_parser::check_source_strict(source).map_err(|e| e.to_string())?;
538 Compiler::new().compile(&program).map_err(|e| e.to_string())
539}
540
541pub fn compile_source_named(source: &str, pipeline_name: &str) -> Result<Chunk, String> {
546 let program = harn_parser::check_source_strict(source).map_err(|e| e.to_string())?;
547 let has_pipeline = program.iter().any(|sn| {
548 let (_, inner) = harn_parser::peel_attributes(sn);
549 matches!(&inner.node, harn_parser::Node::Pipeline { name, .. } if name == pipeline_name)
550 });
551 if !has_pipeline {
552 return Err(format!("no pipeline named `{pipeline_name}` in source"));
553 }
554 Compiler::new()
555 .compile_named(&program, pipeline_name)
556 .map_err(|e| e.to_string())
557}
558
559pub struct TypeSchemaResolver {
565 compiler: compiler::Compiler,
566 nominal_types: std::collections::BTreeMap<String, SchemaNominalType>,
567}
568
569#[derive(Clone)]
570enum SchemaNominalType {
571 Struct {
572 type_params: Vec<harn_parser::TypeParam>,
573 fields: Vec<harn_parser::StructField>,
574 },
575 Enum {
576 type_params: Vec<harn_parser::TypeParam>,
577 variants: Vec<harn_parser::EnumVariant>,
578 },
579}
580
581impl TypeSchemaResolver {
582 pub fn empty() -> Self {
584 Self {
585 compiler: compiler::Compiler::new(),
586 nominal_types: std::collections::BTreeMap::new(),
587 }
588 }
589
590 pub fn from_program(program: &[harn_parser::SNode]) -> Self {
592 let mut compiler = compiler::Compiler::new();
593 compiler.collect_type_aliases(program);
594 let mut nominal_types = std::collections::BTreeMap::new();
595 for node in program {
596 let (_, declaration) = harn_parser::peel_attributes(node);
597 match &declaration.node {
598 harn_parser::Node::StructDecl {
599 name,
600 type_params,
601 fields,
602 ..
603 } => {
604 nominal_types.insert(
605 name.clone(),
606 SchemaNominalType::Struct {
607 type_params: type_params.clone(),
608 fields: fields.clone(),
609 },
610 );
611 }
612 harn_parser::Node::EnumDecl {
613 name,
614 type_params,
615 variants,
616 ..
617 } => {
618 nominal_types.insert(
619 name.clone(),
620 SchemaNominalType::Enum {
621 type_params: type_params.clone(),
622 variants: variants.clone(),
623 },
624 );
625 }
626 _ => {}
627 }
628 }
629 Self {
630 compiler,
631 nominal_types,
632 }
633 }
634
635 pub fn json_schema_for_type_expr(
638 &self,
639 type_expr: &harn_parser::TypeExpr,
640 ) -> Option<serde_json::Value> {
641 self.json_schema_for_type_expr_inner(type_expr, &mut Vec::new())
642 }
643
644 pub fn json_schema_for_input_type_expr(
649 &self,
650 type_expr: &harn_parser::TypeExpr,
651 ) -> Option<serde_json::Value> {
652 let expanded = self.compiler.expand_alias(type_expr);
653 let schema = compiler::Compiler::type_expr_to_schema_value(&expanded)?;
654 let json_schema = schema::schema_to_json_schema_value(&schema).ok()?;
655 Some(llm::vm_value_to_json(&json_schema))
656 }
657
658 fn json_schema_for_type_expr_inner(
659 &self,
660 type_expr: &harn_parser::TypeExpr,
661 resolving: &mut Vec<harn_parser::TypeExpr>,
662 ) -> Option<serde_json::Value> {
663 const MAX_SCHEMA_TYPE_NEST: usize = 128;
664 let expanded = self.compiler.expand_alias(type_expr);
665 if resolving.len() >= MAX_SCHEMA_TYPE_NEST || resolving.contains(&expanded) {
666 return Some(serde_json::json!({}));
667 }
668
669 if let Some((name, args)) = nominal_reference(&expanded) {
670 if let Some(declaration) = self.nominal_types.get(name).cloned() {
671 resolving.push(expanded.clone());
672 let schema = self.json_schema_for_nominal(name, &declaration, args, resolving);
673 resolving.pop();
674 return schema;
675 }
676 }
677
678 if !contains_nominal_reference(&expanded, &self.nominal_types) {
679 let schema = compiler::Compiler::type_expr_to_schema_value(&expanded)?;
680 let json_schema = schema::schema_to_json_schema_value(&schema).ok()?;
681 return Some(llm::vm_value_to_json(&json_schema));
682 }
683
684 use harn_parser::TypeExpr;
685 match expanded {
686 TypeExpr::Shape(fields) => {
687 let mut properties = serde_json::Map::new();
688 let mut required = Vec::new();
689 for field in fields {
690 let mut field_schema =
691 self.json_schema_for_type_expr_inner(&field.type_expr, resolving)?;
692 if field.optional {
693 field_schema = serde_json::json!({
694 "anyOf": [field_schema, {"type": "null"}],
695 });
696 } else {
697 required.push(serde_json::Value::String(field.name.clone()));
698 }
699 properties.insert(field.name, field_schema);
700 }
701 Some(serde_json::json!({
702 "type": "object",
703 "properties": properties,
704 "required": required,
705 }))
706 }
707 TypeExpr::List(inner) => Some(serde_json::json!({
708 "type": "array",
709 "items": self.json_schema_for_type_expr_inner(&inner, resolving)?,
710 })),
711 TypeExpr::Tuple(elements) => {
712 let prefix_items = elements
713 .iter()
714 .map(|element| self.json_schema_for_type_expr_inner(element, resolving))
715 .collect::<Option<Vec<_>>>()?;
716 Some(serde_json::json!({
717 "type": "array",
718 "prefixItems": prefix_items,
719 "items": false,
720 "minItems": elements.len(),
721 "maxItems": elements.len(),
722 }))
723 }
724 TypeExpr::DictType(key, value) if matches!(key.as_ref(), TypeExpr::Named(name) if name == "string") => {
725 Some(serde_json::json!({
726 "type": "object",
727 "additionalProperties": self
728 .json_schema_for_type_expr_inner(&value, resolving)?,
729 }))
730 }
731 TypeExpr::Union(members) => Some(serde_json::json!({
732 "anyOf": members
733 .iter()
734 .map(|member| self.json_schema_for_type_expr_inner(member, resolving))
735 .collect::<Option<Vec<_>>>()?,
736 })),
737 TypeExpr::Intersection(members) => Some(serde_json::json!({
738 "allOf": members
739 .iter()
740 .map(|member| self.json_schema_for_type_expr_inner(member, resolving))
741 .collect::<Option<Vec<_>>>()?,
742 })),
743 TypeExpr::Owned(inner) => self.json_schema_for_type_expr_inner(&inner, resolving),
744 _ => None,
745 }
746 }
747
748 fn json_schema_for_nominal(
749 &self,
750 name: &str,
751 declaration: &SchemaNominalType,
752 args: &[harn_parser::TypeExpr],
753 resolving: &mut Vec<harn_parser::TypeExpr>,
754 ) -> Option<serde_json::Value> {
755 let type_params = match declaration {
756 SchemaNominalType::Struct { type_params, .. }
757 | SchemaNominalType::Enum { type_params, .. } => type_params,
758 };
759 if type_params.len() != args.len() {
760 return None;
761 }
762 let bindings = type_params
763 .iter()
764 .zip(args.iter().cloned())
765 .map(|(param, arg)| (param.name.clone(), arg))
766 .collect::<std::collections::BTreeMap<_, _>>();
767
768 match declaration {
769 SchemaNominalType::Struct { fields, .. } => {
770 let fields = fields
771 .iter()
772 .map(|field| harn_parser::ShapeField {
773 name: field.name.clone(),
774 type_expr: field
775 .type_expr
776 .as_ref()
777 .map(|ty| harn_parser::substitute_type_expr(ty, &bindings))
778 .unwrap_or_else(|| harn_parser::TypeExpr::Named("unknown".into())),
779 optional: field.optional,
780 span: field.span,
781 })
782 .collect();
783 self.json_schema_for_type_expr_inner(
784 &harn_parser::TypeExpr::Shape(fields),
785 resolving,
786 )
787 }
788 SchemaNominalType::Enum { variants, .. } => {
789 let branches = variants
790 .iter()
791 .map(|variant| {
792 let prefix_items = variant
793 .fields
794 .iter()
795 .map(|field| {
796 let type_expr = field.type_expr.as_ref()?;
797 let instantiated =
798 harn_parser::substitute_type_expr(type_expr, &bindings);
799 let mut schema =
800 self.json_schema_for_type_expr_inner(&instantiated, resolving)?;
801 if let serde_json::Value::Object(object) = &mut schema {
802 object.insert(
803 "title".to_string(),
804 serde_json::Value::String(field.name.clone()),
805 );
806 }
807 Some(schema)
808 })
809 .collect::<Option<Vec<_>>>()?;
810 Some(serde_json::json!({
811 "type": "object",
812 "properties": {
813 "enum": {"const": name},
814 "variant": {"const": variant.name},
815 "fields": {
816 "type": "array",
817 "prefixItems": prefix_items,
818 "items": false,
819 "minItems": variant.fields.len(),
820 "maxItems": variant.fields.len(),
821 },
822 },
823 "required": ["enum", "variant", "fields"],
824 "additionalProperties": false,
825 }))
826 })
827 .collect::<Option<Vec<_>>>()?;
828 Some(serde_json::json!({"oneOf": branches}))
829 }
830 }
831 }
832
833 pub fn json_schema_for_typed_params(
836 &self,
837 params: &[harn_parser::TypedParam],
838 ) -> serde_json::Value {
839 let mut properties = serde_json::Map::new();
840 let mut required = Vec::new();
841
842 for param in params {
843 let param_schema = param
844 .type_expr
845 .as_ref()
846 .and_then(|type_expr| self.json_schema_for_input_type_expr(type_expr))
847 .unwrap_or_else(|| serde_json::json!({}));
848 if param.default_value.is_none() {
849 required.push(serde_json::Value::String(param.name.clone()));
850 }
851 properties.insert(param.name.clone(), param_schema);
852 }
853
854 let mut schema = serde_json::Map::new();
855 schema.insert(
856 "type".to_string(),
857 serde_json::Value::String("object".to_string()),
858 );
859 schema.insert(
860 "properties".to_string(),
861 serde_json::Value::Object(properties),
862 );
863 if !required.is_empty() {
864 schema.insert("required".to_string(), serde_json::Value::Array(required));
865 }
866 serde_json::Value::Object(schema)
867 }
868}
869
870fn nominal_reference(
871 type_expr: &harn_parser::TypeExpr,
872) -> Option<(&str, &[harn_parser::TypeExpr])> {
873 match type_expr {
874 harn_parser::TypeExpr::Named(name) => Some((name, &[])),
875 harn_parser::TypeExpr::Applied { name, args } => Some((name, args)),
876 _ => None,
877 }
878}
879
880fn contains_nominal_reference(
881 type_expr: &harn_parser::TypeExpr,
882 nominal_types: &std::collections::BTreeMap<String, SchemaNominalType>,
883) -> bool {
884 use harn_parser::TypeExpr;
885 match type_expr {
886 TypeExpr::Named(name) => nominal_types.contains_key(name),
887 TypeExpr::Applied { name, args } => {
888 nominal_types.contains_key(name)
889 || args
890 .iter()
891 .any(|arg| contains_nominal_reference(arg, nominal_types))
892 }
893 TypeExpr::Union(types) | TypeExpr::Intersection(types) | TypeExpr::Tuple(types) => types
894 .iter()
895 .any(|ty| contains_nominal_reference(ty, nominal_types)),
896 TypeExpr::Shape(fields) => fields
897 .iter()
898 .any(|field| contains_nominal_reference(&field.type_expr, nominal_types)),
899 TypeExpr::OpenShape { fields, rests } => {
900 fields
901 .iter()
902 .any(|field| contains_nominal_reference(&field.type_expr, nominal_types))
903 || rests
904 .iter()
905 .any(|rest| contains_nominal_reference(rest, nominal_types))
906 }
907 TypeExpr::List(inner)
908 | TypeExpr::Iter(inner)
909 | TypeExpr::Generator(inner)
910 | TypeExpr::Stream(inner)
911 | TypeExpr::Owned(inner) => contains_nominal_reference(inner, nominal_types),
912 TypeExpr::DictType(key, value) => {
913 contains_nominal_reference(key, nominal_types)
914 || contains_nominal_reference(value, nominal_types)
915 }
916 TypeExpr::FnType {
917 params,
918 return_type,
919 } => {
920 params
921 .iter()
922 .any(|param| contains_nominal_reference(param, nominal_types))
923 || contains_nominal_reference(return_type, nominal_types)
924 }
925 TypeExpr::Never | TypeExpr::LitString(_) | TypeExpr::LitInt(_) => false,
926 }
927}
928
929pub fn json_schema_for_type_expr(type_expr: &harn_parser::TypeExpr) -> Option<serde_json::Value> {
933 TypeSchemaResolver::empty().json_schema_for_type_expr(type_expr)
934}
935
936pub fn json_schema_for_typed_params(params: &[harn_parser::TypedParam]) -> serde_json::Value {
937 TypeSchemaResolver::empty().json_schema_for_typed_params(params)
938}
939
940#[cfg(test)]
941mod schema_alias_resolver_tests {
942 use super::*;
943
944 fn fn_params_schema(src: &str) -> serde_json::Value {
945 let program = harn_parser::parse_source(src).expect("parse test source");
946 let resolver = TypeSchemaResolver::from_program(&program);
947 for node in &program {
948 let (_, inner) = harn_parser::peel_attributes(node);
949 if let harn_parser::Node::FnDecl { params, .. } = &inner.node {
950 return resolver.json_schema_for_typed_params(params);
951 }
952 }
953 panic!("no fn decl in test source");
954 }
955
956 #[test]
957 fn named_shape_alias_projects_like_inline_shape() {
958 let inline = fn_params_schema("pub fn f(p: {kind: string, path: string}) {}");
959 let aliased =
960 fn_params_schema("type Src = {kind: string, path: string}\npub fn f(p: Src) {}");
961 assert_eq!(
962 aliased, inline,
963 "a named shape alias must project the same inputSchema as its inline shape",
964 );
965 assert_ne!(
966 aliased["properties"]["p"],
967 serde_json::json!({}),
968 "the alias parameter must not erase to an empty schema",
969 );
970 }
971
972 #[test]
973 fn literal_union_alias_projects_json_enum() {
974 let schema = fn_params_schema("type Kind = \"local\" | \"ssh\"\npub fn f(p: Kind) {}");
975 let p = &schema["properties"]["p"];
976 assert_eq!(p["type"], "string");
977 assert_eq!(p["enum"], serde_json::json!(["local", "ssh"]));
978 }
979
980 #[test]
981 fn unknown_named_type_still_erases_to_empty() {
982 let schema = fn_params_schema("pub fn f(p: Unknown) {}");
984 assert_eq!(schema["properties"]["p"], serde_json::json!({}));
985 }
986}
987
988fn reset_llm_state_for_thread_reset() {
989 llm::reset_llm_state();
990 #[cfg(test)]
991 reset_thread_local_state_test_hooks::before_llm_global_reset();
992 llm::reset_rate_limit_registry();
995 llm_config::clear_user_overrides();
996 llm_config::clear_runtime_provider_endpoint_overrides();
997}
998
999#[cfg(test)]
1000mod reset_thread_local_state_test_hooks {
1001 use std::sync::{Arc, Mutex, OnceLock};
1002
1003 type Hook = Arc<dyn Fn() + Send + Sync + 'static>;
1004
1005 static BEFORE_LLM_GLOBAL_RESET: OnceLock<Mutex<Option<Hook>>> = OnceLock::new();
1006
1007 fn before_llm_global_reset_hook() -> &'static Mutex<Option<Hook>> {
1008 BEFORE_LLM_GLOBAL_RESET.get_or_init(|| Mutex::new(None))
1009 }
1010
1011 pub(crate) struct HookGuard;
1012
1013 impl Drop for HookGuard {
1014 fn drop(&mut self) {
1015 let mut hook = before_llm_global_reset_hook()
1016 .lock()
1017 .unwrap_or_else(std::sync::PoisonError::into_inner);
1018 *hook = None;
1019 }
1020 }
1021
1022 pub(crate) fn install_before_llm_global_reset(hook: Hook) -> HookGuard {
1023 let mut slot = before_llm_global_reset_hook()
1024 .lock()
1025 .unwrap_or_else(std::sync::PoisonError::into_inner);
1026 *slot = Some(hook);
1027 HookGuard
1028 }
1029
1030 pub(crate) fn before_llm_global_reset() {
1031 let hook = before_llm_global_reset_hook()
1032 .lock()
1033 .unwrap_or_else(std::sync::PoisonError::into_inner)
1034 .clone();
1035 if let Some(hook) = hook {
1036 hook();
1037 }
1038 }
1039}
1040
1041pub fn reset_thread_local_state() {
1043 #[cfg(test)]
1044 {
1045 let _guard = llm::env_guard();
1050 reset_llm_state_for_thread_reset();
1051 }
1052 #[cfg(not(test))]
1053 reset_llm_state_for_thread_reset();
1054
1055 http::reset_http_state();
1056 channels::reset_channel_state();
1057 event_log::reset_active_event_log();
1058 egress::clear_explicit_egress_policy_requirement_for_host();
1059 egress::clear_ssrf_guard_requirement_for_host();
1060 stdlib::reset_stdlib_state();
1061 connectors::clear_active_connector_clients();
1062 orchestration::clear_runtime_hooks();
1063 orchestration::clear_file_edit_queue();
1064 orchestration::clear_execution_policy_stacks();
1065 orchestration::clear_command_policies();
1066 orchestration::clear_pipeline_on_finish();
1067 orchestration::reset_lifecycle_receipt_registry();
1068 orchestration::agent_inbox::reset();
1069 tool_call_cancellations::reset_registry();
1070 redact::clear_policy_stack();
1071 security::reset_thread_state();
1072 triggers::clear_dispatcher_state();
1073 triggers::clear_trigger_registry();
1074 events::reset_event_sinks();
1075 tracing::set_tracing_enabled(false);
1076 tracing::reset_tracing();
1077 agent_events::reset_all_sinks();
1089 agent_sessions::reset_session_store();
1090 mcp_registry::reset();
1091 mcp_host::reset_for_tests();
1092 call_budget::reset_call_budget_state();
1093 clock_mock::leak_audit::reset();
1094}
1095
1096#[cfg(test)]
1097mod reset_leak_tests {
1098 use super::*;
1104 use crate::value::VmValue;
1105
1106 #[test]
1107 fn reset_drains_pending_file_edit_notifications() {
1108 orchestration::queue_file_edited("stale.harn", serde_json::json!({"operation": "write"}));
1109
1110 reset_thread_local_state();
1111
1112 assert!(
1113 orchestration::drain_file_edits().is_empty(),
1114 "a later VM run must not receive file edits queued by the previous run"
1115 );
1116 }
1117
1118 #[test]
1127 fn builtin_profile_recording_ends_with_its_run_not_with_a_global_reset() {
1128 let _lock = builtin_profile::test_lock()
1129 .lock()
1130 .unwrap_or_else(std::sync::PoisonError::into_inner);
1131 let recording = builtin_profile::enable();
1132 builtin_profile::record("run_shell", std::time::Duration::from_millis(5));
1133 assert!(builtin_profile::is_enabled());
1134 assert!(!builtin_profile::snapshot().is_empty());
1135
1136 reset_thread_local_state();
1137
1138 assert!(
1139 builtin_profile::is_enabled(),
1140 "an unrelated global reset must not disarm an in-flight profiled run"
1141 );
1142 assert!(
1143 !builtin_profile::snapshot().is_empty(),
1144 "an unrelated global reset must not drop totals the run still owns"
1145 );
1146
1147 drop(recording);
1148
1149 assert!(
1150 !builtin_profile::is_enabled(),
1151 "a profiled run must not leave the recorder on for the next one"
1152 );
1153 assert!(
1154 builtin_profile::snapshot().is_empty(),
1155 "builtin totals must be empty once the run's guard drops"
1156 );
1157 }
1158
1159 #[test]
1164 fn reset_drains_session_changed_paths() {
1165 let session = "sess-leak";
1166 agent_sessions::open_or_create(Some(session.to_string()));
1167 agent_sessions::record_session_changed_path(session, "/tmp/written-by-a-dead-run.txt");
1168 assert!(!agent_sessions::session_changed_paths(session).is_empty());
1169 reset_thread_local_state();
1170 assert!(
1171 agent_sessions::session_changed_paths(session).is_empty(),
1172 "a receipt must not inherit an abandoned session's writes"
1173 );
1174 }
1175
1176 #[test]
1177 fn reset_drains_agent_inbox() {
1178 orchestration::agent_inbox::reset();
1179 orchestration::agent_inbox::push("sess-2660", "note", "leak", "test");
1180 assert!(orchestration::agent_inbox::session_count() > 0);
1181 reset_thread_local_state();
1182 assert_eq!(
1183 orchestration::agent_inbox::session_count(),
1184 0,
1185 "agent_inbox must be empty after reset"
1186 );
1187 }
1188
1189 #[test]
1190 fn reset_drains_tool_call_cancellation_registry() {
1191 tool_call_cancellations::reset_registry();
1192 let registered = tool_call_cancellations::register("sess-2660", "call-1", "tool");
1195 if let Some((_handle, guard)) = registered {
1196 std::mem::forget(guard);
1197 }
1198 assert!(tool_call_cancellations::registry_len() > 0);
1199 reset_thread_local_state();
1200 assert_eq!(
1201 tool_call_cancellations::registry_len(),
1202 0,
1203 "tool-call cancellation registry must be empty after reset"
1204 );
1205 }
1206
1207 #[test]
1208 fn reset_drains_routing_policy_registry() {
1209 llm::routing::clear_policy_registry();
1210 let mut config: crate::value::DictMap = crate::value::DictMap::new();
1211 config.insert(
1212 crate::value::intern_key("chain"),
1213 VmValue::List(std::sync::Arc::new(vec![VmValue::String(
1214 arcstr::ArcStr::from("mock:mock"),
1215 )])),
1216 );
1217 llm::routing::build_routing_policy(&config).expect("intern a routing policy");
1218 assert!(llm::routing::policy_registry_len() > 0);
1219 reset_thread_local_state();
1220 assert_eq!(
1221 llm::routing::policy_registry_len(),
1222 0,
1223 "routing policy registry must be empty after reset"
1224 );
1225 }
1226
1227 #[test]
1228 fn reset_holds_llm_env_guard_while_wiping_llm_globals() {
1229 let observed = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
1230 let observed_hook = std::sync::Arc::clone(&observed);
1231 let _hook = reset_thread_local_state_test_hooks::install_before_llm_global_reset(
1232 std::sync::Arc::new(move || {
1233 assert!(
1234 matches!(
1235 llm::env_lock().try_lock(),
1236 Err(std::sync::TryLockError::WouldBlock)
1237 ),
1238 "reset_thread_local_state must hold env_guard before wiping LLM globals"
1239 );
1240 observed_hook.store(true, std::sync::atomic::Ordering::SeqCst);
1241 }),
1242 );
1243
1244 reset_thread_local_state();
1245 assert!(
1246 observed.load(std::sync::atomic::Ordering::SeqCst),
1247 "LLM global reset hook should have run"
1248 );
1249 }
1250}