use crate::value::VmDictExt;
use std::{cell::RefCell, collections::BTreeMap, sync::Arc};
use crate::agent_events::AgentEvent;
use crate::stdlib::macros::{harn_builtin, register_builtin_defs, VmBuiltinDef};
use crate::value::{VmError, VmValue};
use crate::vm::Vm;
use super::agent_terminal_class::{
agent_loop_made_no_llm_call, agent_terminal_class, session_status_indicates_error,
};
use super::tools::build_assistant_response_message;
use super::{emit_live_agent_event_sync as emit_event, permissions};
const HOST_SESSION_FINALIZE: &str = "__host_agent_session_finalize";
const HOST_SESSION_RECORD_ASSISTANT: &str = "__host_agent_session_record_assistant";
const HOST_SESSION_RECORD_TOOL_RESULTS: &str = "__host_agent_session_record_tool_results";
const HOST_SESSION_RECORD_USAGE: &str = "__host_agent_session_record_usage";
const HOST_SESSION_DRAIN_FEEDBACK: &str = "__host_agent_session_drain_feedback";
const HOST_SESSION_DRAIN_COMMAND_UPDATES: &str = "__host_agent_session_drain_command_updates";
const HOST_SESSION_AWAIT_INBOX: &str = "__host_agent_session_await_inbox";
const HOST_SESSION_DRAIN_HOST_INJECTIONS: &str = "__host_agent_session_drain_host_injections";
const HOST_SESSION_DRAIN_BRIDGE_INJECTIONS: &str = "__host_agent_session_drain_bridge_injections";
const HOST_SESSION_PUSH_BRIDGE_INJECTION: &str = "__host_agent_session_push_bridge_injection";
const HOST_SESSION_PUSH_USER_MESSAGE: &str = "__host_agent_session_push_user_message";
const HOST_SESSION_PENDING_INJECTIONS: &str = "__host_agent_session_pending_injections";
const HOST_SESSION_REVOKE_REMINDER: &str = "__host_agent_session_revoke_reminder";
const HOST_SESSION_INJECT_REMINDER: &str = "__host_agent_session_inject_reminder";
const HOST_SESSION_TOTALS: &str = "__host_agent_session_totals";
const HOST_SESSION_POST_EVENT: &str = "__host_agent_session_post_event";
const HOST_SESSION_APPLY_REMINDER_POST_TURN: &str = "__host_agent_session_apply_reminder_post_turn";
const HOST_SESSION_SET_ACTIVE_SKILLS: &str = "__host_agent_session_set_active_skills";
const HOST_SESSION_ACTIVE_SKILLS: &str = "__host_agent_session_active_skills";
const HOST_SESSION_REPLACE_MESSAGES: &str = "__host_agent_session_replace_messages";
const HOST_SESSION_PROJECT_TURN: &str = "__host_agent_session_project_turn";
const HOST_SESSION_CLAIM_TOOL_FORMAT: &str = "__host_agent_session_claim_tool_format";
const HOST_DAEMON_SNAPSHOT: &str = "__host_agent_daemon_snapshot";
const HOST_DAEMON_WAIT: &str = "__host_agent_daemon_wait";
const HOST_AGENT_RECORD_NATIVE_TOOL_FALLBACK: &str = "__host_agent_record_native_tool_fallback";
const HOST_AGENT_RECORD_COMPACTION: &str = "__host_agent_record_compaction";
mod assistant_messages;
pub(crate) mod cancellation;
mod daemon_bridge;
mod inbox;
mod lifecycle;
mod tool_result_messages;
use assistant_messages::durable_anthropic_blocks;
use cancellation::CancelSafeNestedExecutionGuard;
mod live_transcript_journal;
mod message_history;
mod plan_document;
mod run_identity;
mod session_policies;
mod skill_state;
mod turn_projection;
mod usage_accounting;
use session_policies::{
build_nested_budget_denial, install_session_nested_budget, release_session_policies,
};
pub(crate) use session_policies::{install_session_policy_guard, options_request_session_policies};
mod visible_messages;
#[cfg(test)]
use inbox::{
host_agent_session_drain_command_updates_builtin, host_agent_session_drain_feedback_builtin,
host_agent_session_totals_builtin,
};
#[cfg(test)]
use lifecycle::text_has_tool_call_prefix;
pub(crate) use lifecycle::{
canonical_acp_stop_reason, canonical_provider_stop_reason, is_length_truncation,
truncated_tool_call_should_continue,
};
#[cfg(test)]
use message_history::{
assistant_message_from_llm_result, host_agent_session_record_assistant_builtin,
pair_orphaned_tool_use,
};
use plan_document::{next_plan_document_events, plan_artifact_from_result};
pub(crate) use run_identity::active_run_id;
use run_identity::{agent_init_control, agent_init_control_done};
use usage_accounting::{resolve_call_accounting, SessionUsageTotals};
#[cfg(test)]
pub(crate) use tool_result_messages::record_tool_results_for_test;
use tool_result_messages::{
assistant_tool_use_blocks, paired_tool_result_ids, screenshots_from_tool_result,
synthesize_orphan_tool_results, tool_result_message, ToolResultMessageInput,
};
struct AgentHostSession {
session_id: String,
run_id: String,
task: String,
tokens_used: i64,
cost_used: f64,
unpriced_calls: i64,
usage_unknown_calls: i64,
input_tokens: i64,
output_tokens: i64,
cache_read_tokens: i64,
cache_write_tokens: i64,
active_skills: Vec<String>,
tool_calls: Vec<serde_json::Value>,
successful_tools: Vec<String>,
rejected_tools: Vec<String>,
tool_mode: String,
last_provider: Option<String>,
last_model: Option<String>,
last_tool_format: Option<String>,
pushed_transcript_dir: bool,
started_at: String,
max_iterations: i64,
daemon_state: Option<String>,
daemon_snapshot_path: Option<String>,
resumed_iterations: usize,
daemon_watch_state: BTreeMap<String, u64>,
daemon_idle_backoff_ms: u64,
host_bridge: Option<Arc<crate::bridge::HostBridge>>,
last_llm_stop_reason: Option<String>,
file_provenance: crate::security::FileProvenanceLedger,
nested_policy_guard: Option<CancelSafeNestedExecutionGuard>,
}
#[derive(Default)]
struct InstalledPolicies {
pushed_execution: bool,
pushed_approval: bool,
pushed_command: bool,
pushed_permissions: bool,
pushed_precheck: bool,
}
pub(crate) struct SessionPolicyGuard {
installed: InstalledPolicies,
}
impl Drop for SessionPolicyGuard {
fn drop(&mut self) {
release_session_policies(&self.installed);
}
}
thread_local! {
static AGENT_HOST_SESSIONS: RefCell<BTreeMap<String, AgentHostSession>> =
const { RefCell::new(std::collections::BTreeMap::new()) };
}
pub(crate) fn reset_agent_session_host_state() {
AGENT_HOST_SESSIONS.with(|sessions| sessions.borrow_mut().clear());
}
#[cfg(test)]
pub(crate) fn seed_host_session_provider_model(session_id: &str, provider: &str, model: &str) {
let session = AgentHostSession {
session_id: session_id.to_string(),
run_id: format!("agent_run_{}", uuid::Uuid::now_v7()),
task: String::new(),
tokens_used: 0,
cost_used: 0.0,
unpriced_calls: 0,
usage_unknown_calls: 0,
input_tokens: 0,
output_tokens: 0,
cache_read_tokens: 0,
cache_write_tokens: 0,
active_skills: Vec::new(),
tool_calls: Vec::new(),
successful_tools: Vec::new(),
rejected_tools: Vec::new(),
tool_mode: String::new(),
last_provider: Some(provider.to_string()),
last_model: Some(model.to_string()),
last_tool_format: None,
pushed_transcript_dir: false,
started_at: now_id(),
max_iterations: 0,
daemon_state: None,
daemon_snapshot_path: None,
resumed_iterations: 0,
daemon_watch_state: std::collections::BTreeMap::new(),
daemon_idle_backoff_ms: 100,
host_bridge: None,
last_llm_stop_reason: None,
file_provenance: crate::security::FileProvenanceLedger::default(),
nested_policy_guard: None,
};
AGENT_HOST_SESSIONS.with(|sessions| {
sessions
.borrow_mut()
.insert(session_id.to_string(), session);
});
}
fn with_session<R>(
session_id: &str,
label: &str,
f: impl FnOnce(&mut AgentHostSession) -> Result<R, VmError>,
) -> Result<R, VmError> {
AGENT_HOST_SESSIONS.with(|sessions| {
let mut sessions = sessions.borrow_mut();
let session = sessions.get_mut(session_id).ok_or_else(|| {
VmError::Runtime(format!("{label}: unknown agent session `{session_id}`"))
})?;
f(session)
})
}
pub(crate) fn push_session_taint(session_id: &str, record: crate::security::TaintRecord) {
crate::agent_sessions::push_session_taint(session_id, record);
}
pub(crate) fn session_taint_snapshot(session_id: &str) -> Vec<crate::security::TaintRecord> {
crate::agent_sessions::session_taint_snapshot(session_id)
}
pub(crate) fn record_file_provenance(session_id: &str, path: &str, origin: &str) {
let _ = with_session(session_id, "record_file_provenance", |session| {
session.file_provenance.record(path, origin);
Ok(())
});
}
pub(crate) fn classify_file_read(
session_id: &str,
path: &str,
) -> Option<(crate::security::TrustLevel, String)> {
with_session(session_id, "classify_file_read", |session| {
Ok(session.file_provenance.classify(path))
})
.ok()
.flatten()
}
fn opts_dict(value: Option<&VmValue>) -> crate::value::DictMap {
match value {
Some(VmValue::Dict(d)) => (**d).clone(),
_ => crate::value::DictMap::new(),
}
}
fn json_to_vm(value: &serde_json::Value) -> VmValue {
crate::stdlib::json_to_vm_value(value)
}
pub(crate) fn vm_to_json(value: &VmValue) -> serde_json::Value {
crate::llm::vm_value_to_json(value)
}
pub(crate) fn list_items(value: &VmValue) -> Vec<VmValue> {
match value {
VmValue::List(items) => (**items).clone(),
_ => Vec::new(),
}
}
pub(crate) fn dict_get<'a>(value: &'a VmValue, key: &str) -> Option<&'a VmValue> {
match value {
VmValue::Dict(d) => d.get(key),
VmValue::StructInstance(_) => value.struct_field(key),
_ => None,
}
}
fn value_as_i64(value: &VmValue) -> Option<i64> {
match value {
VmValue::Int(i) => Some(*i),
VmValue::Float(f) => Some(*f as i64),
_ => None,
}
}
fn first_dict_i64(sources: &[&VmValue], keys: &[&str]) -> i64 {
for source in sources {
for key in keys {
if let Some(value) = dict_get(source, key).and_then(value_as_i64) {
return value;
}
}
}
0
}
fn first_provider_cache_usage_i64(
sources: &[&VmValue],
extractor: fn(&serde_json::Value) -> i64,
) -> i64 {
for source in sources {
let tokens = extractor(&vm_to_json(source));
if tokens != 0 {
return tokens;
}
}
0
}
fn opt_str(map: &crate::value::DictMap, key: &str) -> Option<String> {
map.get(key).and_then(|v| match v {
VmValue::String(s) => Some(s.to_string()),
_ => None,
})
}
fn opt_int(map: &crate::value::DictMap, key: &str) -> Option<i64> {
map.get(key).and_then(|v| match v {
VmValue::Int(i) => Some(*i),
VmValue::Float(f) => Some(*f as i64),
_ => None,
})
}
fn opt_json(map: &crate::value::DictMap, key: &str) -> Option<serde_json::Value> {
map.get(key)
.filter(|value| !matches!(value, VmValue::Nil))
.map(vm_to_json)
}
fn initial_user_content(
opts_map: &crate::value::DictMap,
fallback_message: &str,
) -> serde_json::Value {
opt_json(opts_map, "initial_user_content")
.or_else(|| opt_json(opts_map, "initial_message_content"))
.unwrap_or_else(|| serde_json::Value::String(fallback_message.to_string()))
}
fn seed_history_messages(opts_map: &crate::value::DictMap) -> Result<Vec<VmValue>, VmError> {
let Some(value) = opts_map.get("history") else {
return Ok(Vec::new());
};
if matches!(value, VmValue::Nil) {
return Ok(Vec::new());
}
let VmValue::List(list) = value else {
return Err(VmError::Runtime(format!(
"agent_loop: `history` must be a list of message dicts; got {}",
value.type_name()
)));
};
let mut out = Vec::with_capacity(list.len());
for (index, entry) in list.iter().enumerate() {
let Some(dict) = entry.as_dict() else {
return Err(VmError::Runtime(format!(
"agent_loop: `history[{index}]` must be a message dict; got {}",
entry.type_name()
)));
};
if !matches!(dict.get("role"), Some(VmValue::String(_))) {
return Err(VmError::Runtime(format!(
"agent_loop: `history[{index}]` must carry a string `role` \
(user|assistant|tool_result|system)"
)));
}
out.push(entry.clone());
}
Ok(out)
}
fn now_id() -> String {
uuid::Uuid::now_v7().to_string()
}
fn file_read_provenance(
session_id: &str,
tool_name: &str,
result: &VmValue,
) -> Option<(crate::security::TrustLevel, String)> {
let annotations = crate::orchestration::current_tool_annotations(tool_name);
if annotations.map(|a| a.kind) != Some(crate::tool_annotations::ToolKind::Read) {
return None;
}
let arguments = dict_get(result, "arguments").map(vm_to_json)?;
crate::security::path_arguments(&arguments)
.into_iter()
.find_map(|path| classify_file_read(session_id, &path))
}
fn command_read_provenance(
session_id: &str,
tool_name: &str,
result: &VmValue,
) -> Option<(crate::security::TrustLevel, String)> {
let annotations = crate::orchestration::current_tool_annotations(tool_name);
if annotations.map(|a| a.kind) != Some(crate::tool_annotations::ToolKind::Execute) {
return None;
}
let arguments = dict_get(result, "arguments").map(vm_to_json)?;
let command = crate::security::command_string(&arguments)?;
with_session(session_id, "command_read_provenance", |session| {
Ok(session.file_provenance.references_tainted_path(&command))
})
.ok()
.flatten()
}
fn record_write_provenance(
session_id: &str,
tool_name: &str,
result: &VmValue,
result_origin: Option<&str>,
context_tainted: bool,
) {
let annotations = crate::orchestration::current_tool_annotations(tool_name);
if !crate::security::mutates_workspace(annotations.as_ref()) {
return;
}
let origin = match result_origin {
Some(origin) => origin.to_string(),
None if context_tainted => {
crate::security::file_provenance::TAINTED_CONTEXT_ORIGIN.to_string()
}
None => return,
};
let Some(arguments) = dict_get(result, "arguments").map(vm_to_json) else {
return;
};
for path in crate::security::path_arguments(&arguments) {
record_file_provenance(session_id, &path, &origin);
}
}
#[harn_builtin(
exposure = "runtime_internal",
effects = [],
sig = "__host_agent_session_record_tool_results(session_id: string, dispatch: list) -> nil",
category = "agent.host",
runtime_only = true
)]
fn host_agent_session_record_tool_results_builtin(
args: &[VmValue],
_out: &mut String,
) -> Result<VmValue, VmError> {
let session_id = args.first().map(|v| v.display()).unwrap_or_default();
let dispatch = args.get(1).cloned().unwrap_or(VmValue::Nil);
let (provider, model, last_tool_format) =
assistant_messages::last_dispatch_receipt(&session_id);
let session_tool_format = crate::agent_sessions::tool_format(&session_id).unwrap_or_default();
let tool_format = if message_history::trailing_assistant_has_native_tool_use(&session_id) {
assistant_messages::effective_session_tool_format(&provider, &model, "native")
} else if let Some(format) = last_tool_format {
format
} else {
assistant_messages::effective_session_tool_format(&provider, &model, &session_tool_format)
};
let default_tool_format = tool_format
.trim()
.is_empty()
.then(|| crate::llm_config::default_tool_format(&model, &provider));
let resolved_tool_format = default_tool_format.as_deref().unwrap_or(&tool_format);
let tool_channel =
crate::llm_config::tool_format_channel(resolved_tool_format).ok_or_else(|| {
VmError::Runtime(format!(
"unknown effective tool format `{resolved_tool_format}`"
))
})?;
let results_value = dispatch;
let security_policy = crate::security::current_policy();
let mut successful = Vec::new();
let mut rejected = Vec::new();
let mut context_tainted = !session_taint_snapshot(&session_id).is_empty();
for result in list_items(&results_value).iter() {
let name = dict_get(result, "tool_name")
.or_else(|| dict_get(result, "name"))
.map(|v| v.display())
.unwrap_or_default();
let raw_observation = dict_get(result, "observation")
.or_else(|| dict_get(result, "rendered_result"))
.or_else(|| dict_get(result, "output"))
.or_else(|| dict_get(result, "content"))
.map(|v| v.display())
.unwrap_or_default();
let provenance = if security_policy.is_off() {
None
} else {
crate::security::classify_result_trust(
dict_get(result, "executor"),
crate::orchestration::current_tool_annotations(&name).as_ref(),
&name,
&security_policy,
)
.or_else(|| {
if security_policy.authenticate_directives {
crate::security::classify_directive_trust(&raw_observation)
} else {
None
}
})
.or_else(|| {
if security_policy.taint_file_provenance {
file_read_provenance(&session_id, &name, result)
} else {
None
}
})
.or_else(|| {
if security_policy.taint_command_reads {
command_read_provenance(&session_id, &name, result)
} else {
None
}
})
};
let ingress = provenance.as_ref().map(|(trust, origin)| {
crate::security::sanitize_ingress(&raw_observation, origin, *trust)
});
let observation = ingress
.as_ref()
.map(|ingress| ingress.delivered.clone())
.unwrap_or_else(|| raw_observation.clone());
let tool_call_id = dict_get(result, "tool_call_id")
.or_else(|| dict_get(result, "tool_use_id"))
.map(|v| v.display())
.unwrap_or_default();
let ok = match dict_get(result, "ok") {
Some(VmValue::Bool(value)) => *value,
_ => match dict_get(result, "success") {
Some(VmValue::Bool(value)) => *value,
_ => match dict_get(result, "status") {
Some(VmValue::String(s)) => s.as_str() == "ok",
_ => true,
},
},
};
if ok {
successful.push(name.clone());
} else {
rejected.push(name.clone());
}
if let Some((trust, origin)) = &provenance {
if trust.is_untrusted() && !raw_observation.is_empty() {
push_session_taint(
&session_id,
crate::security::TaintRecord {
origin: origin.clone(),
trust: *trust,
introduced_by: if tool_call_id.is_empty() {
name.clone()
} else {
tool_call_id.clone()
},
detector: ingress.as_ref().and_then(|value| value.detector.clone()),
labels: ingress
.as_ref()
.map(|value| value.labels.clone())
.unwrap_or_default(),
endpoints: ingress
.as_ref()
.map(|value| value.endpoints.clone())
.unwrap_or_default(),
},
);
context_tainted = true;
}
}
if ok && security_policy.taint_file_provenance {
let result_origin = provenance
.as_ref()
.filter(|(trust, _)| trust.is_untrusted())
.map(|(_, origin)| origin.as_str());
record_write_provenance(&session_id, &name, result, result_origin, context_tainted);
}
if ok && super::plan::is_plan_tool(&name) {
if let Some(plan_value) = plan_artifact_from_result(result) {
let created_at = crate::orchestration::now_unix_seconds_text();
let event_id = crate::orchestration::new_id("plan_event");
let document_events = next_plan_document_events(
&session_id,
&name,
result,
plan_value,
created_at,
event_id,
)
.map_err(|error| VmError::Runtime(error.to_string()))?;
for document_event in document_events {
super::plan::persist_plan_document_event(&session_id, &document_event)
.map_err(|error| VmError::Runtime(error.to_string()))?;
super::agent_runtime::emit_agent_event_sync(&AgentEvent::PlanDocumentUpdated {
session_id: session_id.clone(),
event: Box::new(document_event),
});
}
}
}
let screenshots = screenshots_from_tool_result(result);
let transcript_data = tool_result_messages::transcript_tool_result_data(result);
let message_index = crate::agent_sessions::inject_message(
&session_id,
tool_result_message(ToolResultMessageInput {
channel: tool_channel,
name: &name,
tool_call_id: &tool_call_id,
observation: &observation,
ok,
screenshots: &screenshots,
data: transcript_data.as_ref(),
}),
)
.map_err(VmError::Runtime)?;
crate::llm::pairing_receipts::emit_tool_result_receipt(
&session_id,
message_index,
&tool_call_id,
&name,
ok,
&tool_format,
);
}
let _ = with_session(&session_id, HOST_SESSION_RECORD_TOOL_RESULTS, |session| {
session.successful_tools.extend(successful);
session.rejected_tools.extend(rejected);
Ok(())
});
Ok(VmValue::Nil)
}
#[harn_builtin(
exposure = "runtime_internal",
effects = [],
sig = "__host_agent_session_record_usage(session_id: string, llm_result: dict) -> dict",
category = "agent.host",
runtime_only = true
)]
fn host_agent_session_record_usage_builtin(
args: &[VmValue],
_out: &mut String,
) -> Result<VmValue, VmError> {
let session_id = args.first().map(|v| v.display()).unwrap_or_default();
let llm_result = args.get(1).cloned().unwrap_or(VmValue::Nil);
let llm_block = dict_get(&llm_result, "llm")
.cloned()
.unwrap_or(VmValue::Nil);
let usage_block = dict_get(&llm_result, "usage")
.cloned()
.unwrap_or(VmValue::Nil);
let input_tokens = first_dict_i64(&[&llm_block, &usage_block, &llm_result], &["input_tokens"]);
let output_tokens =
first_dict_i64(&[&llm_block, &usage_block, &llm_result], &["output_tokens"]);
let usage_sources = [&llm_result, &usage_block, &llm_block];
let cache_read_tokens =
first_provider_cache_usage_i64(&usage_sources, super::api::extract_cache_read_tokens);
let cache_write_tokens =
first_provider_cache_usage_i64(&usage_sources, super::api::extract_cache_write_tokens);
let provider = dict_get(&llm_result, "provider")
.map(|v| v.display())
.unwrap_or_default();
let model = dict_get(&llm_result, "model")
.map(|v| v.display())
.unwrap_or_default();
let accounting = resolve_call_accounting(
&usage_block,
&provider,
&model,
input_tokens,
output_tokens,
cache_read_tokens,
cache_write_tokens,
);
let provider_attempts = dict_get(&usage_block, "provider_attempts").cloned();
let stop_reason = match dict_get(&llm_result, "stop_reason") {
Some(VmValue::String(s)) if !s.is_empty() => Some(s.to_string()),
_ => None,
};
let totals = with_session(&session_id, HOST_SESSION_RECORD_USAGE, |session| {
session.tokens_used = session
.tokens_used
.saturating_add(input_tokens)
.saturating_add(output_tokens);
session.input_tokens = session.input_tokens.saturating_add(input_tokens);
session.output_tokens = session.output_tokens.saturating_add(output_tokens);
session.cache_read_tokens = session.cache_read_tokens.saturating_add(cache_read_tokens);
session.cache_write_tokens = session
.cache_write_tokens
.saturating_add(cache_write_tokens);
if let Some(cost) = accounting.cost_usd {
session.cost_used += cost;
} else {
session.unpriced_calls = session.unpriced_calls.saturating_add(1);
}
if accounting.usage_unknown {
session.usage_unknown_calls = session.usage_unknown_calls.saturating_add(1);
}
if stop_reason.is_some() {
session.last_llm_stop_reason = stop_reason.clone();
}
Ok(SessionUsageTotals::from(&*session))
})?;
let mut llm_call_metadata = serde_json::json!({
"input_tokens": input_tokens,
"output_tokens": output_tokens,
"cache_read_tokens": cache_read_tokens,
"cache_write_tokens": cache_write_tokens,
"provider": provider,
"model": model,
"cost_usd": accounting.cost_usd,
"accounting_status": if accounting.usage_unknown { "unknown" } else { "reported" },
"provider_stop_reason": stop_reason,
"canonical_stop_reason": canonical_provider_stop_reason(stop_reason.as_deref()),
});
if let (Some(object), Some(attempts)) = (
llm_call_metadata.as_object_mut(),
provider_attempts.map(|attempts| crate::stdlib::observability::vm_value_to_json(&attempts)),
) {
object.insert("provider_attempts".to_string(), attempts);
}
crate::agent_sessions::append_event(
&session_id,
crate::llm::helpers::transcript_event(
"llm_call",
"assistant",
"internal",
"LLM call completed",
Some(llm_call_metadata),
),
)
.map_err(VmError::Runtime)?;
Ok(totals.to_vm(false))
}
const HOST_SESSION_BUILTINS: &[&VmBuiltinDef] = &[
&HOST_AGENT_SESSION_RECORD_TOOL_RESULTS_BUILTIN_DEF,
&HOST_AGENT_SESSION_RECORD_USAGE_BUILTIN_DEF,
];
pub fn register_agent_session_host_primitives(vm: &mut Vm) {
register_builtin_defs(vm, HOST_SESSION_BUILTINS);
daemon_bridge::register_daemon_bridge_primitives(vm);
inbox::register_inbox_primitives(vm);
lifecycle::register_lifecycle_primitives(vm);
live_transcript_journal::register_live_transcript_journal_primitives(vm);
message_history::register_message_history_primitives(vm);
skill_state::register_skill_state_primitives(vm);
turn_projection::register_turn_projection_primitives(vm);
visible_messages::register_visible_message_primitives(vm);
}
#[cfg(test)]
#[path = "agent_session_host_tests.rs"]
mod tests;