use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use crate::agent_definition::AgentDefinition;
use crate::capability_types::AgentCapabilityConfig;
use crate::config_layer::AgentConfigOverlay;
use crate::error::{AgentLoopError, Result};
use crate::harness_definition::HarnessDefinition;
use crate::mcp_server::{
McpProtocolMode, McpServerAuthMode, McpServerTransportType, ScopedMcpServer,
};
use crate::network_access::NetworkAccessList;
use crate::session::ExecutionSession;
use crate::session_file::InitialFile;
use crate::tool_types::ToolDefinition;
use crate::typed_id::{AgentId, HarnessId, ModelId, SessionId, WorkspaceId};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SnapshotMcpServer {
pub transport_type: McpServerTransportType,
pub header_names: Vec<String>,
pub env_names: Vec<String>,
pub auth_mode: McpServerAuthMode,
pub protocol_mode: McpProtocolMode,
pub oauth_provider_id: Option<String>,
pub tool_discovery: bool,
}
impl From<&ScopedMcpServer> for SnapshotMcpServer {
fn from(server: &ScopedMcpServer) -> Self {
let mut header_names: Vec<String> = server.headers.keys().cloned().collect();
header_names.sort();
let mut env_names: Vec<String> = server.env.keys().cloned().collect();
env_names.sort();
Self {
transport_type: server.transport_type.clone(),
header_names,
env_names,
auth_mode: server.auth_mode.clone(),
protocol_mode: server.protocol_mode,
oauth_provider_id: server.oauth_provider_id.clone(),
tool_discovery: server.tool_discovery,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResolvedExecutionSnapshot {
pub session_id: SessionId,
pub workspace_id: WorkspaceId,
pub harness_id: HarnessId,
pub agent_id: Option<AgentId>,
pub organization_id: String,
pub instructions: Option<String>,
pub default_model_id: Option<ModelId>,
pub capabilities: Vec<AgentCapabilityConfig>,
pub tools: Vec<ToolDefinition>,
pub initial_files: Vec<InitialFile>,
pub mcp_servers: BTreeMap<String, SnapshotMcpServer>,
pub network_access: Option<NetworkAccessList>,
pub max_iterations: Option<usize>,
pub parallel_tool_calls: Option<bool>,
pub locale: Option<String>,
pub tags: Vec<String>,
pub blueprint_id: Option<String>,
pub blueprint_config: Option<serde_json::Value>,
pub cumulative_usage: Option<crate::events::TokenUsage>,
pub embedder_metadata: BTreeMap<String, String>,
}
impl ResolvedExecutionSnapshot {
pub fn project(
harness: &HarnessDefinition,
agent: Option<&AgentDefinition>,
session: &ExecutionSession,
) -> Result<Self> {
let agent = match (session.agent_id, agent) {
(Some(agent_id), Some(agent)) => {
if agent.id != agent_id {
return Err(AgentLoopError::config(format!(
"session {} references agent {} but agent {} was provided",
session.id, agent_id, agent.id
)));
}
Some(agent)
}
(Some(agent_id), None) => {
return Err(AgentLoopError::agent_not_found(agent_id));
}
(None, _) => None,
};
let agent_layers = agent.into_iter().map(AgentConfigOverlay::from);
let effective = AgentConfigOverlay::fold(
[AgentConfigOverlay::from(harness)]
.into_iter()
.chain(agent_layers)
.chain([AgentConfigOverlay::from(session)]),
);
let embedder_metadata = harness
.embedder_metadata
.iter()
.map(|(key, value)| (key.clone(), value.clone()))
.collect();
Ok(Self {
session_id: session.id,
workspace_id: session.workspace_id,
harness_id: session.harness_id,
agent_id: session.agent_id,
organization_id: session.organization_id.clone(),
instructions: effective.system_prompt,
default_model_id: effective.default_model_id,
capabilities: effective.capabilities,
tools: effective.tools,
initial_files: effective.initial_files,
mcp_servers: effective
.mcp_servers
.iter()
.map(|(name, server)| (name.clone(), SnapshotMcpServer::from(server)))
.collect(),
network_access: effective.network_access,
max_iterations: effective.max_iterations,
parallel_tool_calls: effective.parallel_tool_calls,
locale: session.locale.clone(),
tags: session.tags.clone(),
blueprint_id: session.blueprint_id.clone(),
blueprint_config: session.blueprint_config.clone(),
cumulative_usage: session.usage.clone(),
embedder_metadata,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::network_access::NetworkAccessList;
use std::collections::HashMap;
fn harness() -> HarnessDefinition {
HarnessDefinition {
capabilities: vec![AgentCapabilityConfig::new("session_file_system")],
..HarnessDefinition::new("base", "Harness prompt.")
}
}
fn agent(agent_id: AgentId, _harness_id: HarnessId) -> AgentDefinition {
AgentDefinition {
max_iterations: Some(8),
display_name: Some("Agent".into()),
..AgentDefinition::new(agent_id, "agent", "Agent prompt.")
}
}
fn session(
session_id: SessionId,
harness_id: HarnessId,
agent_id: Option<AgentId>,
) -> ExecutionSession {
ExecutionSession {
agent_id,
title: Some("UI-TITLE-MARKER".into()),
locale: Some("en-US".into()),
tags: vec!["tag-a".into()],
..ExecutionSession::with_own_workspace(session_id, harness_id)
}
}
fn ids() -> (HarnessId, AgentId, SessionId) {
(
HarnessId::from_seed(11),
AgentId::from_seed(11),
SessionId::from_seed(11),
)
}
fn file(path: &str, content: &str) -> InitialFile {
InitialFile {
path: path.into(),
content: content.into(),
encoding: "text".into(),
is_readonly: false,
}
}
#[test]
fn precedence_instructions_concatenate_root_to_leaf() {
let (harness_id, agent_id, session_id) = ids();
let harness = harness();
let agent = agent(agent_id, harness_id);
let mut session = session(session_id, harness_id, Some(agent_id));
session.system_prompt = Some("Session prompt.".into());
let snapshot =
ResolvedExecutionSnapshot::project(&harness, Some(&agent), &session).unwrap();
assert_eq!(
snapshot.instructions.as_deref(),
Some("Harness prompt.\n\nAgent prompt.\n\nSession prompt.")
);
}
#[test]
fn precedence_model_leaf_layer_wins() {
let (harness_id, agent_id, session_id) = ids();
let mut harness = harness();
harness.default_model_id = Some(ModelId::from_seed(1));
let mut agent = agent(agent_id, harness_id);
agent.default_model_id = Some(ModelId::from_seed(2));
let mut session = session(session_id, harness_id, Some(agent_id));
let snapshot =
ResolvedExecutionSnapshot::project(&harness, Some(&agent), &session).unwrap();
assert_eq!(snapshot.default_model_id, Some(ModelId::from_seed(2)));
session.model_id = Some(ModelId::from_seed(3));
let snapshot =
ResolvedExecutionSnapshot::project(&harness, Some(&agent), &session).unwrap();
assert_eq!(snapshot.default_model_id, Some(ModelId::from_seed(3)));
session.model_id = None;
session.agent_id = None;
let snapshot = ResolvedExecutionSnapshot::project(&harness, None, &session).unwrap();
assert_eq!(snapshot.default_model_id, Some(ModelId::from_seed(1)));
harness.default_model_id = None;
assert_eq!(
ResolvedExecutionSnapshot::project(&harness, None, &session)
.unwrap()
.default_model_id,
None
);
}
#[test]
fn precedence_capabilities_override_by_id() {
let (harness_id, agent_id, session_id) = ids();
let mut harness = harness();
harness.capabilities = vec![AgentCapabilityConfig::with_config(
"web_fetch",
serde_json::json!({"enable_file_download": true}),
)];
let mut agent = agent(agent_id, harness_id);
agent.capabilities = vec![AgentCapabilityConfig::with_config(
"current_time",
serde_json::json!({"zone":"UTC"}),
)];
let mut session = session(session_id, harness_id, Some(agent_id));
session.capabilities = vec![AgentCapabilityConfig::with_config(
"web_fetch",
serde_json::json!({"enable_file_download": false}),
)];
let snapshot =
ResolvedExecutionSnapshot::project(&harness, Some(&agent), &session).unwrap();
assert_eq!(snapshot.capabilities.len(), 2);
assert_eq!(
snapshot.capabilities[0],
AgentCapabilityConfig::with_config(
"web_fetch",
serde_json::json!({"enable_file_download": false})
)
);
assert_eq!(
snapshot.capabilities[1],
AgentCapabilityConfig::with_config("current_time", serde_json::json!({"zone":"UTC"}))
);
}
#[test]
fn capability_ref_round_trips_framework_persistence_and_worker_resolution() {
let framework_ref = everruns_capability::CapabilityRef::new("web_fetch")
.config(serde_json::json!({"enable_file_download": true}));
let persisted = serde_json::to_value(&framework_ref).unwrap();
assert_eq!(
persisted,
serde_json::json!({
"ref": "web_fetch",
"config": {"enable_file_download": true}
}),
"wire shape is the persisted attachment row shape"
);
let attachment: AgentCapabilityConfig = serde_json::from_value(persisted).unwrap();
assert_eq!(attachment, framework_ref);
let (harness_id, agent_id, session_id) = ids();
let mut harness = harness();
harness.capabilities = vec![attachment.clone()];
let agent = agent(agent_id, harness_id);
let session = session(session_id, harness_id, Some(agent_id));
let snapshot =
ResolvedExecutionSnapshot::project(&harness, Some(&agent), &session).unwrap();
assert_eq!(snapshot.capabilities, vec![framework_ref.clone()]);
assert_eq!(
serde_json::to_value(&snapshot.capabilities[0]).unwrap(),
serde_json::to_value(&framework_ref).unwrap()
);
}
#[test]
fn snapshot_debug_redacts_capability_config_values() {
let attachment = AgentCapabilityConfig::with_config(
"vendor.search",
serde_json::json!({"api_key": "sk-super-secret"}),
);
let (harness_id, _, session_id) = ids();
let mut harness = harness();
harness.capabilities = vec![attachment];
let snapshot = ResolvedExecutionSnapshot::project(
&harness,
None,
&session(session_id, harness_id, None),
)
.unwrap();
let debug = format!("{snapshot:?}");
assert!(debug.contains("vendor.search"));
assert!(!debug.contains("sk-super-secret"));
assert!(!debug.contains("api_key"));
}
#[test]
fn precedence_initial_files_override_by_path() {
let (harness_id, agent_id, session_id) = ids();
let mut harness = harness();
harness.initial_files = vec![file("/config.txt", "harness"), file("/keep.txt", "keep")];
let mut agent = agent(agent_id, harness_id);
agent.initial_files = vec![file("config.txt", "agent")];
agent.initial_files[0].is_readonly = true;
agent.initial_files[0].encoding = "base64".into();
agent.initial_files[0].content = "YWdlbnQ=".into();
let session = session(session_id, harness_id, Some(agent_id));
let snapshot =
ResolvedExecutionSnapshot::project(&harness, Some(&agent), &session).unwrap();
assert_eq!(
snapshot.initial_files,
vec![
agent.initial_files[0].clone(),
harness.initial_files[1].clone()
]
);
}
#[test]
fn precedence_mcp_servers_override_by_name() {
let (harness_id, agent_id, session_id) = ids();
let mut harness = harness();
harness.mcp_servers.insert(
"docs".into(),
ScopedMcpServer {
url: "https://harness.example.com/mcp".into(),
..Default::default()
},
);
harness
.mcp_servers
.insert("retained".into(), ScopedMcpServer::default());
let agent = agent(agent_id, harness_id);
let mut session = session(session_id, harness_id, Some(agent_id));
session.mcp_servers.insert(
"docs".into(),
ScopedMcpServer {
url: "https://session.example.com/mcp".into(),
transport_type: McpServerTransportType::Stdio,
headers: [
("Z-Header".into(), "z-value".into()),
("A-Header".into(), "a-value".into()),
]
.into(),
env: [("Z_ENV".into(), "z".into()), ("A_ENV".into(), "a".into())].into(),
auth_mode: McpServerAuthMode::OAuth,
protocol_mode: McpProtocolMode::V2025June,
oauth_provider_id: Some("session-provider".into()),
tool_discovery: false,
..Default::default()
},
);
let snapshot =
ResolvedExecutionSnapshot::project(&harness, Some(&agent), &session).unwrap();
assert_eq!(
snapshot.mcp_servers,
BTreeMap::from([
(
"docs".into(),
SnapshotMcpServer {
transport_type: McpServerTransportType::Stdio,
header_names: vec!["A-Header".into(), "Z-Header".into()],
env_names: vec!["A_ENV".into(), "Z_ENV".into()],
auth_mode: McpServerAuthMode::OAuth,
protocol_mode: McpProtocolMode::V2025June,
oauth_provider_id: Some("session-provider".into()),
tool_discovery: false,
}
),
(
"retained".into(),
SnapshotMcpServer {
transport_type: McpServerTransportType::Http,
header_names: vec![],
env_names: vec![],
auth_mode: McpServerAuthMode::None,
protocol_mode: McpProtocolMode::Auto,
oauth_provider_id: None,
tool_discovery: true,
}
),
])
);
}
#[test]
fn precedence_network_access_narrows() {
let (harness_id, agent_id, session_id) = ids();
let mut harness = harness();
harness.network_access = Some(NetworkAccessList::allow_only([
"*.example.com",
"api.github.com",
]));
harness
.network_access
.as_mut()
.unwrap()
.blocked
.push("private.example.com".into());
let agent = agent(agent_id, harness_id);
let mut session = session(session_id, harness_id, Some(agent_id));
session.network_access = Some(NetworkAccessList {
allowed: vec!["api.example.com".into(), "outside.net".into()],
blocked: vec!["blocked.example.com".into()],
});
let snapshot =
ResolvedExecutionSnapshot::project(&harness, Some(&agent), &session).unwrap();
let acl = snapshot.network_access.unwrap();
assert_eq!(
acl,
NetworkAccessList {
allowed: vec!["api.example.com".into()],
blocked: vec!["private.example.com".into(), "blocked.example.com".into()]
}
);
assert!(acl.is_url_allowed("https://api.example.com"));
assert!(!acl.is_url_allowed("https://outside.net"));
}
#[test]
fn precedence_iteration_controls_leaf_wins() {
let (harness_id, agent_id, session_id) = ids();
let harness = harness();
let mut agent = agent(agent_id, harness_id);
agent.max_iterations = Some(40);
agent.parallel_tool_calls = Some(true);
let mut session = session(session_id, harness_id, Some(agent_id));
session.max_iterations = Some(0);
session.parallel_tool_calls = Some(false);
let snapshot =
ResolvedExecutionSnapshot::project(&harness, Some(&agent), &session).unwrap();
assert_eq!(snapshot.max_iterations, Some(0));
assert_eq!(snapshot.parallel_tool_calls, Some(false));
}
#[test]
fn correlation_values_are_copied() {
let (harness_id, agent_id, session_id) = ids();
let mut harness = harness();
harness.embedder_metadata = [("embedder".into(), "fixture-host".into())].into();
let agent = agent(agent_id, harness_id);
let mut session = session(session_id, harness_id, Some(agent_id));
session.workspace_id = WorkspaceId::from_seed(71);
session.organization_id = "org_00000000000000000000000000000042".into();
session.blueprint_id = Some("review-blueprint".into());
session.blueprint_config = Some(serde_json::json!({"region":"eu","enabled":false}));
session.usage = Some(crate::events::TokenUsage {
input_tokens: 13,
output_tokens: 29,
cache_read_tokens: Some(7),
cache_creation_tokens: Some(3),
actual_cost_usd: Some(0.25),
estimated_cost_usd: Some(0.5),
effective_cost_usd: Some(0.75),
});
let snapshot =
ResolvedExecutionSnapshot::project(&harness, Some(&agent), &session).unwrap();
assert_eq!(snapshot.session_id, session_id);
assert_eq!(snapshot.workspace_id, session.workspace_id);
assert_eq!(snapshot.harness_id, harness_id);
assert_eq!(snapshot.agent_id, Some(agent_id));
assert_eq!(snapshot.organization_id, session.organization_id);
assert_eq!(snapshot.locale.as_deref(), Some("en-US"));
assert_eq!(snapshot.tags, vec!["tag-a".to_string()]);
assert_eq!(snapshot.blueprint_id, session.blueprint_id);
assert_eq!(snapshot.blueprint_config, session.blueprint_config);
assert_eq!(
serde_json::to_value(snapshot.cumulative_usage).unwrap(),
serde_json::to_value(session.usage).unwrap()
);
assert_eq!(
snapshot.embedder_metadata,
BTreeMap::from([("embedder".into(), "fixture-host".into())])
);
}
#[test]
fn projection_fails_on_missing_agent() {
let (harness_id, agent_id, session_id) = ids();
let harness = harness();
let session = session(session_id, harness_id, Some(agent_id));
assert!(
matches!(ResolvedExecutionSnapshot::project(&harness, None, &session), Err(AgentLoopError::AgentNotFound(id)) if id == agent_id)
);
}
#[test]
fn projection_fails_on_mismatched_agent() {
let (harness_id, agent_id, session_id) = ids();
let harness = harness();
let other_agent = agent(AgentId::from_seed(99), harness_id);
let session = session(session_id, harness_id, Some(agent_id));
match ResolvedExecutionSnapshot::project(&harness, Some(&other_agent), &session)
.unwrap_err()
{
AgentLoopError::Configuration(message) => assert_eq!(
message,
format!(
"session {session_id} references agent {agent_id} but agent {} was provided",
other_agent.id
)
),
other => panic!("wrong projection error: {other:?}"),
}
}
#[test]
fn snapshot_excludes_platform_metadata_and_credential_values() {
let (harness_id, agent_id, session_id) = ids();
let harness = harness();
let mut agent = agent(agent_id, harness_id);
agent.description = Some("UI-PREVIEW-MARKER".into());
let mut session = session(session_id, harness_id, Some(agent_id));
session.mcp_servers.insert(
"docs".into(),
ScopedMcpServer {
url: "https://user:SECRET-URL-MARKER@mcp.example.com".into(),
headers: [(
"Authorization".to_string(),
"Bearer SECRET-HEADER-MARKER".to_string(),
)]
.into_iter()
.collect(),
env: [("API_KEY".to_string(), "SECRET-ENV-MARKER".to_string())]
.into_iter()
.collect(),
command: Some("SECRET-COMMAND-MARKER".into()),
args: vec!["--api-key=SECRET-ARG-MARKER".into()],
..Default::default()
},
);
let snapshot =
ResolvedExecutionSnapshot::project(&harness, Some(&agent), &session).unwrap();
let serialized = serde_json::to_string(&snapshot).unwrap();
let debugged = format!("{snapshot:?}");
for surface in [serialized.as_str(), debugged.as_str()] {
assert!(!surface.contains("SECRET-HEADER-MARKER"), "{surface}");
assert!(!surface.contains("SECRET-ENV-MARKER"), "{surface}");
assert!(!surface.contains("SECRET-URL-MARKER"), "{surface}");
assert!(!surface.contains("SECRET-COMMAND-MARKER"), "{surface}");
assert!(!surface.contains("SECRET-ARG-MARKER"), "{surface}");
assert!(!surface.contains("UI-TITLE-MARKER"), "{surface}");
assert!(!surface.contains("UI-PREVIEW-MARKER"), "{surface}");
}
let docs = snapshot.mcp_servers.get("docs").unwrap();
assert_eq!(docs.header_names, vec!["Authorization".to_string()]);
assert_eq!(docs.env_names, vec!["API_KEY".to_string()]);
}
#[test]
fn snapshot_serialization_is_deterministic_and_round_trips() {
let (harness_id, agent_id, session_id) = ids();
let mut harness = harness();
harness.embedder_metadata = HashMap::from([
("zeta".to_string(), "z".to_string()),
("alpha".to_string(), "a".to_string()),
]);
let agent = agent(agent_id, harness_id);
let session = session(session_id, harness_id, Some(agent_id));
let first = ResolvedExecutionSnapshot::project(&harness, Some(&agent), &session).unwrap();
let mut reversed = harness.clone();
reversed.embedder_metadata =
HashMap::from([("alpha".into(), "a".into()), ("zeta".into(), "z".into())]);
let second = ResolvedExecutionSnapshot::project(&reversed, Some(&agent), &session).unwrap();
let first_json = serde_json::to_string(&first).unwrap();
let second_json = serde_json::to_string(&second).unwrap();
assert_eq!(first_json, second_json);
let round_tripped: ResolvedExecutionSnapshot = serde_json::from_str(&first_json).unwrap();
assert_eq!(serde_json::to_string(&round_tripped).unwrap(), first_json);
let alpha = first_json.find("alpha").unwrap();
let zeta = first_json.find("zeta").unwrap();
assert!(alpha < zeta);
}
#[test]
fn unreferenced_agent_cannot_contribute_configuration() {
let (harness_id, agent_id, session_id) = ids();
let harness = harness();
let agent = agent(agent_id, harness_id);
let session = session(session_id, harness_id, None);
let without = ResolvedExecutionSnapshot::project(&harness, None, &session).unwrap();
let with_unreferenced =
ResolvedExecutionSnapshot::project(&harness, Some(&agent), &session).unwrap();
assert_eq!(
serde_json::to_value(with_unreferenced).unwrap(),
serde_json::to_value(without).unwrap()
);
}
}