use std::fmt;
use serde::de::{self, Deserializer, Visitor};
use serde::{Deserialize, Serialize, Serializer};
use crate::context::measurement::PromptMeasurement;
use crate::types::durable_content::DurableContent;
use super::root::{LogicalAgentSpec, MessageRole};
use super::scalar::{
AttemptId, BoundedJson, CallId, EffectId, FiniteF64, HandleId, InputId, MAX_ID_BYTES,
MemoryBindingId, NodeId, SCALAR_ERROR_MARKER, TaskId, WireScalarError, WireU64,
};
use super::syscall::{MemoryKind, SyscallCausation};
#[doc(hidden)]
pub(crate) fn validate_opaque(label: &'static str, value: &str) -> Result<(), WireScalarError> {
if value.is_empty() {
return Err(WireScalarError::new(format!("{label} must not be empty")));
}
if value.len() > MAX_ID_BYTES {
return Err(WireScalarError::new(format!(
"{label} is {} bytes; the bound is {MAX_ID_BYTES}",
value.len()
)));
}
if value.chars().any(char::is_control) {
return Err(WireScalarError::new(format!(
"{label} must not contain control characters"
)));
}
Ok(())
}
macro_rules! wire_opaque_ref {
($(#[$doc:meta])* $name:ident, $label:literal) => {
$(#[$doc])*
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct $name(String);
impl $name {
pub fn new(value: impl Into<String>) -> Result<Self, WireScalarError> {
let value = value.into();
$crate::runtime::kernel::wire::effect::validate_opaque($label, &value)?;
Ok(Self(value))
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn into_string(self) -> String {
self.0
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl Serialize for $name {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.0)
}
}
impl<'de> Deserialize<'de> for $name {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct RefVisitor;
impl Visitor<'_> for RefVisitor {
type Value = $name;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(concat!("a non-empty ", $label, " string"))
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
$name::new(value).map_err(|err| {
E::custom(format!("{SCALAR_ERROR_MARKER}: {}", err.message))
})
}
fn visit_u64<E: de::Error>(self, value: u64) -> Result<Self::Value, E> {
Err(E::custom(format!(
"{SCALAR_ERROR_MARKER}: {} must be a branded string, got {value}",
$label
)))
}
fn visit_i64<E: de::Error>(self, value: i64) -> Result<Self::Value, E> {
Err(E::custom(format!(
"{SCALAR_ERROR_MARKER}: {} must be a branded string, got {value}",
$label
)))
}
fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
Err(E::custom(format!(
"{SCALAR_ERROR_MARKER}: {} must be a branded string, got null",
$label
)))
}
}
deserializer.deserialize_any(RefVisitor)
}
}
};
}
pub(crate) use wire_opaque_ref;
wire_opaque_ref!(
PayloadRef,
"payload ref"
);
wire_opaque_ref!(
Digest,
"digest"
);
wire_opaque_ref!(
LaunchToken,
"launch token"
);
wire_opaque_ref!(
MemoryRecordRef,
"memory record ref"
);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct KernelEffect {
pub effect_id: EffectId,
pub causation_input_id: InputId,
pub effect: EffectKind,
}
impl KernelEffect {
pub fn tag(&self) -> EffectKindTag {
self.effect.tag()
}
pub fn accept_outcome(&self, outcome: &EffectOutcome) -> Result<(), EffectResolutionMismatch> {
match outcome {
EffectOutcome::Failed(_) => Ok(()),
EffectOutcome::Succeeded(success) => {
let expected = self.effect.tag().expected_success();
let received = success.result.tag();
if expected == received {
Ok(())
} else {
Err(EffectResolutionMismatch {
effect_id: self.effect_id.clone(),
expected,
received,
})
}
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EffectResolutionMismatch {
pub effect_id: EffectId,
pub expected: EffectSuccessTag,
pub received: EffectSuccessTag,
}
impl fmt::Display for EffectResolutionMismatch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"effect {} expects a {} resolution, got {}",
self.effect_id,
self.expected.as_str(),
self.received.as_str()
)
}
}
impl std::error::Error for EffectResolutionMismatch {}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum EffectKind {
CallProvider(CallProviderEffect),
ExecuteTools(ExecuteToolsEffect),
RequestApproval(RequestApprovalEffect),
SpawnTasks(SpawnTasksEffect),
PreemptTasks(PreemptTasksEffect),
PersistMemory(PersistMemoryEffect),
QueryMemory(QueryMemoryEffect),
ArchivePageOut(ArchivePageOutEffect),
LoadPayload(LoadPayloadEffect),
EvaluateMilestone(EvaluateMilestoneEffect),
MeasurePrompt(MeasurePromptEffect),
}
impl EffectKind {
pub fn tag(&self) -> EffectKindTag {
match self {
Self::CallProvider(_) => EffectKindTag::CallProvider,
Self::ExecuteTools(_) => EffectKindTag::ExecuteTools,
Self::RequestApproval(_) => EffectKindTag::RequestApproval,
Self::SpawnTasks(_) => EffectKindTag::SpawnTasks,
Self::PreemptTasks(_) => EffectKindTag::PreemptTasks,
Self::PersistMemory(_) => EffectKindTag::PersistMemory,
Self::QueryMemory(_) => EffectKindTag::QueryMemory,
Self::ArchivePageOut(_) => EffectKindTag::ArchivePageOut,
Self::LoadPayload(_) => EffectKindTag::LoadPayload,
Self::EvaluateMilestone(_) => EffectKindTag::EvaluateMilestone,
Self::MeasurePrompt(_) => EffectKindTag::MeasurePrompt,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EffectKindTag {
CallProvider,
ExecuteTools,
RequestApproval,
SpawnTasks,
PreemptTasks,
PersistMemory,
QueryMemory,
ArchivePageOut,
LoadPayload,
EvaluateMilestone,
MeasurePrompt,
}
impl EffectKindTag {
pub const ALL: [Self; 11] = [
Self::CallProvider,
Self::ExecuteTools,
Self::RequestApproval,
Self::SpawnTasks,
Self::PreemptTasks,
Self::PersistMemory,
Self::QueryMemory,
Self::ArchivePageOut,
Self::LoadPayload,
Self::EvaluateMilestone,
Self::MeasurePrompt,
];
pub fn as_str(self) -> &'static str {
match self {
Self::CallProvider => "call_provider",
Self::ExecuteTools => "execute_tools",
Self::RequestApproval => "request_approval",
Self::SpawnTasks => "spawn_tasks",
Self::PreemptTasks => "preempt_tasks",
Self::PersistMemory => "persist_memory",
Self::QueryMemory => "query_memory",
Self::ArchivePageOut => "archive_page_out",
Self::LoadPayload => "load_payload",
Self::EvaluateMilestone => "evaluate_milestone",
Self::MeasurePrompt => "measure_prompt",
}
}
pub fn expected_success(self) -> EffectSuccessTag {
match self {
Self::CallProvider => EffectSuccessTag::Provider,
Self::ExecuteTools => EffectSuccessTag::Tools,
Self::RequestApproval => EffectSuccessTag::Approval,
Self::SpawnTasks => EffectSuccessTag::TasksSpawned,
Self::PreemptTasks => EffectSuccessTag::TasksPreempted,
Self::PersistMemory => EffectSuccessTag::MemoryPersisted,
Self::QueryMemory => EffectSuccessTag::MemoryQueried,
Self::ArchivePageOut => EffectSuccessTag::PageOutArchived,
Self::LoadPayload => EffectSuccessTag::PayloadLoaded,
Self::EvaluateMilestone => EffectSuccessTag::MilestoneEvaluated,
Self::MeasurePrompt => EffectSuccessTag::PromptMeasured,
}
}
}
impl fmt::Display for EffectKindTag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CallProviderEffect {
pub context: RenderedContext,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tools: Vec<ToolSchema>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MeasurePromptEffect {
pub context: RenderedContext,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tools: Vec<ToolSchema>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ExecuteToolsEffect {
pub calls: Vec<ToolCall>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RequestApprovalEffect {
pub requests: Vec<ApprovalRequest>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SpawnTasksEffect {
pub tasks: Vec<TaskLaunch>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub budget: Option<WorkflowBudget>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PreemptTasksEffect {
pub attempts: Vec<TaskAttemptRef>,
pub reason: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PersistMemoryEffect {
pub binding: MemoryAccessBinding,
pub memory: CanonicalMemoryWrite,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct QueryMemoryEffect {
pub binding: MemoryAccessBinding,
pub query: CanonicalMemoryQuery,
pub requested_k: u32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ArchivePageOutEffect {
pub handle_id: HandleId,
pub payload: PageOutPayload,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LoadPayloadEffect {
pub handle_id: HandleId,
pub payload_ref: PayloadRef,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct EvaluateMilestoneEffect {
pub request: MilestoneRequest,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RenderedContext {
#[serde(default, skip_serializing_if = "String::is_empty")]
pub system_stable: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub system_knowledge: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub turns: Vec<ProviderMessage>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub state_turn: Option<ProviderMessage>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub frozen_prefix_len: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ProviderMessage {
pub role: MessageRole,
pub content: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tool_calls: Vec<ToolCall>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool_call_id: Option<CallId>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tokens: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ToolSchema {
pub name: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub description: String,
#[serde(default, skip_serializing_if = "BoundedJson::is_null")]
pub parameters: BoundedJson,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ToolCall {
pub call_id: CallId,
pub name: String,
#[serde(default, skip_serializing_if = "BoundedJson::is_null")]
pub arguments: BoundedJson,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ApprovalRequest {
pub call_id: CallId,
pub tool_name: String,
#[serde(default, skip_serializing_if = "BoundedJson::is_null")]
pub arguments: BoundedJson,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TaskLaunch {
pub task_id: TaskId,
pub attempt_id: AttemptId,
pub launch_token: LaunchToken,
pub node_id: NodeId,
pub spec: LogicalAgentSpec,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WorkflowBudget {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_total_tokens: Option<WireU64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_turns: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_concurrency: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TaskAttemptRef {
pub task_id: TaskId,
pub attempt_id: AttemptId,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MemoryAccessBinding {
pub binding_id: MemoryBindingId,
pub capabilities: MemoryCapabilities,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MemoryCapabilities {
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub read: bool,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub write: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CanonicalMemoryWrite {
pub name: String,
pub kind: MemoryKind,
pub content: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub description: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub evidence_refs: Vec<String>,
pub accepted_at_ms: WireU64,
pub causation: SyscallCausation,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CanonicalMemoryQuery {
#[serde(default, skip_serializing_if = "String::is_empty")]
pub text: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub kinds: Vec<MemoryKind>,
pub accepted_at_ms: WireU64,
pub causation: SyscallCausation,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PageOutPayload {
pub content: String,
pub digest: Digest,
pub original_size: WireU64,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub preview: String,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MilestoneRequest {
pub contract_id: String,
pub phase_id: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "status", rename_all = "snake_case")]
pub enum EffectOutcome {
Succeeded(EffectSucceeded),
Failed(EffectFailed),
}
impl EffectOutcome {
pub fn success_tag(&self) -> Option<EffectSuccessTag> {
match self {
Self::Succeeded(success) => Some(success.result.tag()),
Self::Failed(_) => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct EffectSucceeded {
pub result: EffectSuccess,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct EffectFailed {
pub failure: HostEffectFailure,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct HostEffectFailure {
pub kind: HostEffectFailureKind,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub message: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub retryable: Option<bool>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum HostEffectFailureKind {
TransportExhausted,
ProtocolError,
StorageUnavailable,
PermissionDenied,
ResourceExhausted,
Unknown,
}
impl HostEffectFailureKind {
pub const ALL: [Self; 6] = [
Self::TransportExhausted,
Self::ProtocolError,
Self::StorageUnavailable,
Self::PermissionDenied,
Self::ResourceExhausted,
Self::Unknown,
];
pub fn as_str(self) -> &'static str {
match self {
Self::TransportExhausted => "transport_exhausted",
Self::ProtocolError => "protocol_error",
Self::StorageUnavailable => "storage_unavailable",
Self::PermissionDenied => "permission_denied",
Self::ResourceExhausted => "resource_exhausted",
Self::Unknown => "unknown",
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum EffectSuccess {
Provider(ProviderSuccess),
Tools(ToolsSuccess),
Approval(ApprovalSuccess),
TasksSpawned(TasksSpawnedSuccess),
TasksPreempted(TasksPreemptedSuccess),
MemoryPersisted(MemoryPersistedSuccess),
MemoryQueried(MemoryQueriedSuccess),
PageOutArchived(PageOutArchivedSuccess),
PayloadLoaded(PayloadLoadedSuccess),
MilestoneEvaluated(MilestoneEvaluatedSuccess),
PromptMeasured(PromptMeasuredSuccess),
}
impl EffectSuccess {
pub fn tag(&self) -> EffectSuccessTag {
match self {
Self::Provider(_) => EffectSuccessTag::Provider,
Self::Tools(_) => EffectSuccessTag::Tools,
Self::Approval(_) => EffectSuccessTag::Approval,
Self::TasksSpawned(_) => EffectSuccessTag::TasksSpawned,
Self::TasksPreempted(_) => EffectSuccessTag::TasksPreempted,
Self::MemoryPersisted(_) => EffectSuccessTag::MemoryPersisted,
Self::MemoryQueried(_) => EffectSuccessTag::MemoryQueried,
Self::PageOutArchived(_) => EffectSuccessTag::PageOutArchived,
Self::PayloadLoaded(_) => EffectSuccessTag::PayloadLoaded,
Self::MilestoneEvaluated(_) => EffectSuccessTag::MilestoneEvaluated,
Self::PromptMeasured(_) => EffectSuccessTag::PromptMeasured,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EffectSuccessTag {
Provider,
Tools,
Approval,
TasksSpawned,
TasksPreempted,
MemoryPersisted,
MemoryQueried,
PageOutArchived,
PayloadLoaded,
MilestoneEvaluated,
PromptMeasured,
}
impl EffectSuccessTag {
pub const ALL: [Self; 11] = [
Self::Provider,
Self::Tools,
Self::Approval,
Self::TasksSpawned,
Self::TasksPreempted,
Self::MemoryPersisted,
Self::MemoryQueried,
Self::PageOutArchived,
Self::PayloadLoaded,
Self::MilestoneEvaluated,
Self::PromptMeasured,
];
pub fn as_str(self) -> &'static str {
match self {
Self::Provider => "provider",
Self::Tools => "tools",
Self::Approval => "approval",
Self::TasksSpawned => "tasks_spawned",
Self::TasksPreempted => "tasks_preempted",
Self::MemoryPersisted => "memory_persisted",
Self::MemoryQueried => "memory_queried",
Self::PageOutArchived => "page_out_archived",
Self::PayloadLoaded => "payload_loaded",
Self::MilestoneEvaluated => "milestone_evaluated",
Self::PromptMeasured => "prompt_measured",
}
}
pub fn resolves(self) -> EffectKindTag {
match self {
Self::Provider => EffectKindTag::CallProvider,
Self::Tools => EffectKindTag::ExecuteTools,
Self::Approval => EffectKindTag::RequestApproval,
Self::TasksSpawned => EffectKindTag::SpawnTasks,
Self::TasksPreempted => EffectKindTag::PreemptTasks,
Self::MemoryPersisted => EffectKindTag::PersistMemory,
Self::MemoryQueried => EffectKindTag::QueryMemory,
Self::PageOutArchived => EffectKindTag::ArchivePageOut,
Self::PayloadLoaded => EffectKindTag::LoadPayload,
Self::MilestoneEvaluated => EffectKindTag::EvaluateMilestone,
Self::PromptMeasured => EffectKindTag::MeasurePrompt,
}
}
}
impl fmt::Display for EffectSuccessTag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ProviderSuccess {
pub outcome: ProviderOutcome,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ProviderOutcome {
Completed(ProviderCompleted),
ContextOverflow(ProviderContextOverflow),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ProviderCompleted {
pub message: ProviderMessage,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub observed_input_tokens: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub observed_output_tokens: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stop_reason: Option<ProviderStopReason>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ProviderContextOverflow {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub observed_input_tokens: Option<u32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ProviderStopReason {
EndTurn,
ToolUse,
MaxTokens,
StopSequence,
ContentFilter,
Other,
}
impl ProviderStopReason {
pub const ALL: [Self; 6] = [
Self::EndTurn,
Self::ToolUse,
Self::MaxTokens,
Self::StopSequence,
Self::ContentFilter,
Self::Other,
];
pub fn as_str(self) -> &'static str {
match self {
Self::EndTurn => "end_turn",
Self::ToolUse => "tool_use",
Self::MaxTokens => "max_tokens",
Self::StopSequence => "stop_sequence",
Self::ContentFilter => "content_filter",
Self::Other => "other",
}
}
}
impl fmt::Display for ProviderStopReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ToolsSuccess {
pub results: Vec<ToolResultPayload>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ApprovalSuccess {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub approved_call_ids: Vec<CallId>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub denied_call_ids: Vec<CallId>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TasksSpawnedSuccess {
pub attempts: Vec<TaskLaunchOutcome>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TaskLaunchOutcome {
pub task_id: TaskId,
pub attempt_id: AttemptId,
pub outcome: TaskLaunchStatus,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "status", rename_all = "snake_case")]
pub enum TaskLaunchStatus {
Started(TaskLaunchStarted),
Failed(TaskLaunchFailed),
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TaskLaunchStarted {}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TaskLaunchFailed {
pub failure: TaskLaunchFailure,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TaskLaunchFailure {
pub kind: HostEffectFailureKind,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub message: String,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TasksPreemptedSuccess {
pub attempts: Vec<TaskPreemptOutcome>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TaskPreemptOutcome {
pub task_id: TaskId,
pub attempt_id: AttemptId,
pub outcome: TaskPreemptStatus,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "status", rename_all = "snake_case")]
pub enum TaskPreemptStatus {
Preempted(TaskPreempted),
AlreadyFinished(TaskAlreadyFinished),
Failed(TaskPreemptFailed),
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TaskPreempted {}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TaskAlreadyFinished {}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TaskPreemptFailed {
pub failure: TaskLaunchFailure,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MemoryPersistedSuccess {
pub receipt: MemoryPersistReceipt,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MemoryPersistReceipt {
pub binding_id: MemoryBindingId,
pub record_ref: MemoryRecordRef,
pub digest: Digest,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MemoryQueriedSuccess {
pub recalls: Vec<MemoryRecall>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MemoryRecall {
pub record_ref: MemoryRecordRef,
pub name: String,
pub kind: MemoryKind,
pub content: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub score: Option<FiniteF64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PageOutArchivedSuccess {
pub receipt: ArchiveReceipt,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ArchiveReceipt {
pub handle_id: HandleId,
pub payload_ref: PayloadRef,
pub digest: Digest,
pub original_size: WireU64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PayloadLoadedSuccess {
pub handle_id: HandleId,
pub payload: InlinePayload,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct InlinePayload {
pub content: String,
pub digest: Digest,
pub original_size: WireU64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MilestoneEvaluatedSuccess {
pub result: MilestoneCheckResult,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MilestoneCheckResult {
pub phase_id: String,
pub passed: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub failed_criteria: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub score: Option<FiniteF64>,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub notes: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PromptMeasuredSuccess {
pub measurement: PromptMeasurement,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ToolResultPayload {
Inline(InlineToolResult),
External(ExternalToolResult),
}
impl ToolResultPayload {
pub fn call_id(&self) -> &CallId {
match self {
Self::Inline(inline) => &inline.call_id,
Self::External(external) => &external.call_id,
}
}
pub fn disposition(&self) -> ToolResultDisposition {
match self {
Self::Inline(inline) => inline.result.disposition,
Self::External(external) => external.disposition,
}
}
pub fn is_error(&self) -> bool {
match self {
Self::Inline(inline) => inline.result.is_error,
Self::External(external) => external.is_error,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct InlineToolResult {
pub call_id: CallId,
pub result: ToolResult,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ExternalToolResult {
pub call_id: CallId,
pub payload_ref: PayloadRef,
pub digest: Digest,
pub original_size: WireU64,
pub preview: String,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub is_error: bool,
pub disposition: ToolResultDisposition,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ToolResult {
pub output: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub durable_content: Option<DurableContent>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub is_error: bool,
pub disposition: ToolResultDisposition,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tokens: Option<u32>,
}
#[derive(
Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
)]
#[serde(rename_all = "snake_case")]
pub enum ToolResultDisposition {
#[default]
Recoverable,
Fatal,
}
impl ToolResultDisposition {
pub const ALL: [Self; 2] = [Self::Recoverable, Self::Fatal];
pub fn as_str(self) -> &'static str {
match self {
Self::Recoverable => "recoverable",
Self::Fatal => "fatal",
}
}
pub fn is_fatal(self) -> bool {
matches!(self, Self::Fatal)
}
}
impl fmt::Display for ToolResultDisposition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum PayloadResidency {
Resident(ResidentPayload),
External(ExternalResidency),
PagedOut(PagedOutResidency),
Collapsed(CollapsedPayload),
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ResidentPayload {}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ExternalResidency {
pub payload_ref: PayloadRef,
pub digest: Digest,
pub original_size: WireU64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PagedOutResidency {
pub payload_ref: PayloadRef,
pub digest: Digest,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CollapsedPayload {}
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use std::fs;
use std::path::PathBuf;
use serde_json::{Value, json};
use crate::context::measurement::{
MeasurementConfidence, MeasurementSource, PromptMeasurement,
};
use super::super::*;
fn fixture_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../tests/fixtures/kernel-wire")
}
fn fixtures_with_prefix(prefix: &str) -> Vec<(String, Value)> {
let dir = fixture_dir();
let mut names: Vec<String> = fs::read_dir(&dir)
.unwrap_or_else(|e| panic!("failed to read {}: {e}", dir.display()))
.map(|entry| {
entry
.expect("dir entry")
.file_name()
.to_string_lossy()
.to_string()
})
.filter(|name| name.ends_with(".json") && name.starts_with(prefix))
.collect();
names.sort();
assert!(!names.is_empty(), "no {prefix}*.json fixtures");
names
.into_iter()
.map(|name| {
let raw = fs::read_to_string(dir.join(&name)).unwrap();
let value: Value = serde_json::from_str(&raw).unwrap();
(name, value)
})
.collect()
}
fn keys(value: &Value, out: &mut BTreeSet<String>) {
match value {
Value::Object(map) => {
for (key, child) in map {
out.insert(key.clone());
keys(child, out);
}
}
Value::Array(items) => items.iter().for_each(|item| keys(item, out)),
_ => {}
}
}
fn effect_json(outcome: Value) -> String {
serde_json::to_string(&json!({
"operation_id": "op-1",
"input_id": "in-1",
"observed_at_ms": "1700000000000",
"input": { "kind": "resolve_effect", "effect_id": "op-1:step:1:effect:0", "outcome": outcome },
}))
.unwrap()
}
fn decode_effect(outcome: Value) -> Result<WireEnvelope, WireRejection> {
decode_envelope_json(&effect_json(outcome), &KernelBootstrapLimits::default())
}
fn call_id(id: &str) -> CallId {
CallId::new(id).unwrap()
}
fn digest() -> Digest {
Digest::new("sha256:3b1f4a7c9e2d05186a4c7f0b9d3e8c25714f6a0b8c5d2e9f1a3b6c8d0e2f4a61")
.unwrap()
}
fn payload_ref() -> PayloadRef {
PayloadRef::new("payload:01J8Y2QK7C4N0V").unwrap()
}
fn binding() -> MemoryAccessBinding {
MemoryAccessBinding {
binding_id: MemoryBindingId::new("binding-a").unwrap(),
capabilities: MemoryCapabilities {
read: true,
write: true,
},
}
}
fn causation() -> SyscallCausation {
SyscallCausation::ProviderTool(ProviderToolCausation {
provider_effect_id: EffectId::new("op-1:step:1:effect:0").unwrap(),
call_id: call_id("call-1"),
task_id: TaskId::new("task-1").unwrap(),
})
}
fn effect_samples() -> Vec<EffectKind> {
vec![
EffectKind::CallProvider(CallProviderEffect {
context: RenderedContext::default(),
tools: vec![ToolSchema {
name: "read_file".to_string(),
description: "read a file".to_string(),
parameters: BoundedJson::null(),
}],
}),
EffectKind::ExecuteTools(ExecuteToolsEffect {
calls: vec![ToolCall {
call_id: call_id("call-1"),
name: "read_file".to_string(),
arguments: BoundedJson::null(),
}],
}),
EffectKind::RequestApproval(RequestApprovalEffect {
requests: vec![ApprovalRequest {
call_id: call_id("call-1"),
tool_name: "rm".to_string(),
arguments: BoundedJson::null(),
reason: Some("destructive".to_string()),
}],
}),
EffectKind::SpawnTasks(SpawnTasksEffect {
tasks: vec![TaskLaunch {
task_id: TaskId::new("task-1").unwrap(),
attempt_id: AttemptId::new("task-1:attempt:1").unwrap(),
launch_token: LaunchToken::new("launch-1").unwrap(),
node_id: NodeId::new("node-a").unwrap(),
spec: LogicalAgentSpec::new("research"),
}],
budget: None,
}),
EffectKind::PreemptTasks(PreemptTasksEffect {
attempts: vec![TaskAttemptRef {
task_id: TaskId::new("task-1").unwrap(),
attempt_id: AttemptId::new("task-1:attempt:1").unwrap(),
}],
reason: "budget exhausted".to_string(),
}),
EffectKind::PersistMemory(PersistMemoryEffect {
binding: binding(),
memory: CanonicalMemoryWrite {
name: "release pipeline".to_string(),
kind: MemoryKind::Project,
content: "tag v* publishes".to_string(),
description: String::new(),
evidence_refs: Vec::new(),
accepted_at_ms: WireU64::new(1_700_000_000_000),
causation: causation(),
},
}),
EffectKind::QueryMemory(QueryMemoryEffect {
binding: binding(),
query: CanonicalMemoryQuery {
text: "release".to_string(),
kinds: vec![MemoryKind::Project],
accepted_at_ms: WireU64::new(1_700_000_000_000),
causation: causation(),
},
requested_k: 5,
}),
EffectKind::ArchivePageOut(ArchivePageOutEffect {
handle_id: HandleId::new("handle-9").unwrap(),
payload: PageOutPayload {
content: "the full tool output".to_string(),
digest: digest(),
original_size: WireU64::new(262_144),
preview: "the full…".to_string(),
},
}),
EffectKind::LoadPayload(LoadPayloadEffect {
handle_id: HandleId::new("handle-9").unwrap(),
payload_ref: payload_ref(),
}),
EffectKind::EvaluateMilestone(EvaluateMilestoneEffect {
request: MilestoneRequest {
contract_id: "brief-quality-primary".to_string(),
phase_id: "phase-2".to_string(),
},
}),
EffectKind::MeasurePrompt(MeasurePromptEffect {
context: RenderedContext::default(),
tools: Vec::new(),
}),
]
}
fn success_samples() -> Vec<EffectSuccess> {
vec![
EffectSuccess::Provider(ProviderSuccess {
outcome: ProviderOutcome::Completed(ProviderCompleted {
message: ProviderMessage {
role: MessageRole::Assistant,
content: "done".to_string(),
tool_calls: Vec::new(),
tool_call_id: None,
tokens: None,
},
observed_input_tokens: Some(120),
observed_output_tokens: Some(8),
stop_reason: Some(ProviderStopReason::EndTurn),
}),
}),
EffectSuccess::Tools(ToolsSuccess {
results: vec![
ToolResultPayload::Inline(InlineToolResult {
call_id: call_id("call-1"),
result: ToolResult {
output: "ok".to_string(),
durable_content: None,
is_error: false,
disposition: ToolResultDisposition::Recoverable,
tokens: Some(2),
},
}),
ToolResultPayload::External(ExternalToolResult {
call_id: call_id("call-2"),
payload_ref: payload_ref(),
digest: digest(),
original_size: WireU64::new(1_048_576),
preview: "total 42".to_string(),
is_error: false,
disposition: ToolResultDisposition::Recoverable,
}),
],
}),
EffectSuccess::Approval(ApprovalSuccess {
approved_call_ids: vec![call_id("call-1")],
denied_call_ids: vec![call_id("call-2")],
}),
EffectSuccess::TasksSpawned(TasksSpawnedSuccess {
attempts: vec![TaskLaunchOutcome {
task_id: TaskId::new("task-1").unwrap(),
attempt_id: AttemptId::new("task-1:attempt:1").unwrap(),
outcome: TaskLaunchStatus::Started(TaskLaunchStarted {}),
}],
}),
EffectSuccess::TasksPreempted(TasksPreemptedSuccess {
attempts: vec![TaskPreemptOutcome {
task_id: TaskId::new("task-1").unwrap(),
attempt_id: AttemptId::new("task-1:attempt:1").unwrap(),
outcome: TaskPreemptStatus::Preempted(TaskPreempted {}),
}],
}),
EffectSuccess::MemoryPersisted(MemoryPersistedSuccess {
receipt: MemoryPersistReceipt {
binding_id: MemoryBindingId::new("binding-a").unwrap(),
record_ref: MemoryRecordRef::new("memory:01J8Y2QK7C4N0W").unwrap(),
digest: digest(),
},
}),
EffectSuccess::MemoryQueried(MemoryQueriedSuccess {
recalls: vec![MemoryRecall {
record_ref: MemoryRecordRef::new("memory:01J8Y2QK7C4N0X").unwrap(),
name: "release pipeline".to_string(),
kind: MemoryKind::Project,
content: "tag v* publishes".to_string(),
score: Some(FiniteF64::new(0.82).unwrap()),
}],
}),
EffectSuccess::PageOutArchived(PageOutArchivedSuccess {
receipt: ArchiveReceipt {
handle_id: HandleId::new("handle-9").unwrap(),
payload_ref: payload_ref(),
digest: digest(),
original_size: WireU64::new(262_144),
},
}),
EffectSuccess::PayloadLoaded(PayloadLoadedSuccess {
handle_id: HandleId::new("handle-9").unwrap(),
payload: InlinePayload {
content: "the full tool output".to_string(),
digest: digest(),
original_size: WireU64::new(262_144),
},
}),
EffectSuccess::MilestoneEvaluated(MilestoneEvaluatedSuccess {
result: MilestoneCheckResult {
phase_id: "phase-2".to_string(),
passed: true,
failed_criteria: Vec::new(),
score: None,
notes: String::new(),
},
}),
EffectSuccess::PromptMeasured(PromptMeasuredSuccess {
measurement: PromptMeasurement {
input_tokens: 4200,
source: MeasurementSource::Native {
provider: "anthropic".to_string(),
},
confidence: MeasurementConfidence::Exact,
},
}),
]
}
fn kernel_effect(effect: EffectKind) -> KernelEffect {
KernelEffect {
effect_id: EffectId::new("op-1:step:1:effect:0").unwrap(),
causation_input_id: InputId::new("in-1").unwrap(),
effect,
}
}
#[test]
fn the_effect_union_is_exactly_the_eleven_host_executable_actions() {
let tags: BTreeSet<&str> = EffectKindTag::ALL.iter().map(|tag| tag.as_str()).collect();
assert_eq!(
tags,
BTreeSet::from([
"call_provider",
"execute_tools",
"request_approval",
"spawn_tasks",
"preempt_tasks",
"persist_memory",
"query_memory",
"archive_page_out",
"load_payload",
"evaluate_milestone",
"measure_prompt",
])
);
assert_eq!(EffectKindTag::ALL.len(), 11);
let sampled: Vec<EffectKindTag> = effect_samples().iter().map(EffectKind::tag).collect();
assert_eq!(
sampled,
EffectKindTag::ALL.to_vec(),
"one sample per variant"
);
}
#[test]
fn terminal_and_observation_shapes_are_not_effects() {
for gone in [
"done",
"terminal",
"compact",
"sync_compact",
"knowledge_sweep",
"workflow_completed",
"control_rejection",
"signal_disposition",
"budget_usage",
"spool_large_result",
] {
assert!(
!EffectKindTag::ALL.iter().any(|tag| tag.as_str() == gone),
"{gone} is a terminal or an observation, not an effect"
);
let raw = json!({ "kind": gone });
assert!(serde_json::from_value::<EffectKind>(raw).is_err());
}
}
#[test]
fn every_effect_carries_its_kernel_minted_id_and_causation() {
for effect in effect_samples() {
let value = serde_json::to_value(kernel_effect(effect)).unwrap();
assert_eq!(value["effect_id"], json!("op-1:step:1:effect:0"));
assert_eq!(value["causation_input_id"], json!("in-1"));
}
}
#[test]
fn each_effect_kind_has_exactly_one_matching_success_kind() {
let expected: Vec<EffectSuccessTag> = EffectKindTag::ALL
.iter()
.map(|tag| tag.expected_success())
.collect();
let distinct: BTreeSet<&str> = expected.iter().map(|tag| tag.as_str()).collect();
assert_eq!(
distinct.len(),
EffectKindTag::ALL.len(),
"the effect→success map must be a bijection"
);
assert_eq!(
distinct,
EffectSuccessTag::ALL
.iter()
.map(|tag| tag.as_str())
.collect::<BTreeSet<&str>>()
);
let sampled: Vec<EffectSuccessTag> =
success_samples().iter().map(EffectSuccess::tag).collect();
assert_eq!(
sampled, expected,
"samples must line up 1:1 with the effects"
);
}
#[test]
fn a_resolution_of_the_wrong_kind_is_rejected_for_every_wrong_pair() {
let effects = effect_samples();
let successes = success_samples();
for (i, effect) in effects.iter().enumerate() {
let pending = kernel_effect(effect.clone());
for (j, success) in successes.iter().enumerate() {
let outcome = EffectOutcome::Succeeded(EffectSucceeded {
result: success.clone(),
});
let verdict = pending.accept_outcome(&outcome);
if i == j {
assert!(
verdict.is_ok(),
"{:?} must accept its own success payload",
effect.tag()
);
} else {
let mismatch = verdict.expect_err("kind mismatch must be refused");
assert_eq!(mismatch.effect_id, pending.effect_id);
assert_eq!(mismatch.expected, effect.tag().expected_success());
assert_eq!(mismatch.received, success.tag());
}
}
}
}
#[test]
fn every_effect_accepts_the_same_host_failure_including_milestone() {
let kinds = [
HostEffectFailureKind::TransportExhausted,
HostEffectFailureKind::ProtocolError,
HostEffectFailureKind::StorageUnavailable,
HostEffectFailureKind::PermissionDenied,
HostEffectFailureKind::ResourceExhausted,
HostEffectFailureKind::Unknown,
];
assert_eq!(kinds.len(), 6, "§7.9 fixes six executor failure classes");
for effect in effect_samples() {
let pending = kernel_effect(effect);
for kind in kinds {
let outcome = EffectOutcome::Failed(EffectFailed {
failure: HostEffectFailure {
kind,
message: "boom".to_string(),
retryable: None,
},
});
assert!(
pending.accept_outcome(&outcome).is_ok(),
"{:?} must have the same failure path as every other effect",
pending.effect.tag()
);
}
}
}
#[test]
fn the_outcome_union_has_exactly_two_arms() {
let arms: BTreeSet<String> = [
EffectOutcome::Succeeded(EffectSucceeded {
result: success_samples().remove(0),
}),
EffectOutcome::Failed(EffectFailed {
failure: HostEffectFailure {
kind: HostEffectFailureKind::Unknown,
message: String::new(),
retryable: None,
},
}),
]
.iter()
.map(|outcome| {
serde_json::to_value(outcome).unwrap()["status"]
.as_str()
.unwrap()
.to_string()
})
.collect();
assert_eq!(
arms,
BTreeSet::from(["succeeded".to_string(), "failed".to_string()])
);
for third in ["partial", "pending", "deferred", "succeeded_with_warnings"] {
let raw = json!({ "status": third });
assert!(
serde_json::from_value::<EffectOutcome>(raw).is_err(),
"{third} is not an outcome"
);
}
}
#[test]
fn the_kernel_never_retries_so_retryable_is_host_advice_only() {
let with = json!({
"status": "failed",
"failure": { "kind": "transport_exhausted", "message": "429", "retryable": true },
});
let outcome: EffectOutcome = serde_json::from_value(with).unwrap();
match outcome {
EffectOutcome::Failed(failed) => {
assert_eq!(failed.failure.retryable, Some(true));
assert_eq!(
failed.failure.kind,
HostEffectFailureKind::TransportExhausted
);
}
EffectOutcome::Succeeded(_) => panic!("failed outcome decoded as succeeded"),
}
}
#[test]
fn every_provider_family_maps_onto_the_canonical_stop_reason_vocabulary() {
let canonical: BTreeSet<&str> = ProviderStopReason::ALL
.iter()
.map(|reason| reason.as_str())
.collect();
assert_eq!(
canonical,
BTreeSet::from([
"end_turn",
"tool_use",
"max_tokens",
"stop_sequence",
"content_filter",
"other",
])
);
for reason in ProviderStopReason::ALL {
let decoded: ProviderStopReason =
serde_json::from_value(json!(reason.as_str())).unwrap();
assert_eq!(decoded, reason);
}
for vendor_word in [
"stop", "length", "tool_calls", "function_call", "content_filter", "STOP", "MAX_TOKENS", "SAFETY", "FINISH_REASON_STOP", "eos", "eos_token", "sensitive", "insufficient_system_resource",
] {
let decoded = serde_json::from_value::<ProviderStopReason>(json!(vendor_word));
if vendor_word == "content_filter" {
assert!(decoded.is_ok(), "content_filter *is* canonical");
continue;
}
assert!(
decoded.is_err(),
"{vendor_word:?} is a vendor spelling; the host maps it, core never learns it"
);
}
let value = serde_json::to_value(ProviderStopReason::Other).unwrap();
assert_eq!(value, json!("other"));
assert!(
serde_json::from_value::<ProviderStopReason>(
json!({ "kind": "other", "raw": "insufficient_system_resource" })
)
.is_err(),
"`other` is not a pass-through for vendor text"
);
}
#[test]
fn no_vendor_failure_vocabulary_is_expressible_on_the_canonical_face() {
assert_eq!(HostEffectFailureKind::ALL.len(), 6);
let canonical: BTreeSet<&str> = HostEffectFailureKind::ALL
.iter()
.map(|kind| kind.as_str())
.collect();
assert_eq!(
canonical,
BTreeSet::from([
"transport_exhausted",
"protocol_error",
"storage_unavailable",
"permission_denied",
"resource_exhausted",
"unknown",
])
);
for absent in [
"rate_limited",
"rate_limit_exceeded",
"too_many_requests",
"overloaded",
"service_unavailable",
"server_error",
"timeout",
"context_length_exceeded",
"context_overflow",
"cancelled",
"canceled",
"aborted",
"user_interrupt",
"interrupted",
] {
assert!(
serde_json::from_value::<HostEffectFailureKind>(json!(absent)).is_err(),
"{absent:?} must not be an executor failure class"
);
assert!(
!canonical.contains(absent),
"{absent:?} leaked into the canonical vocabulary"
);
}
let spent: EffectOutcome = serde_json::from_value(json!({
"status": "failed",
"failure": {
"kind": "transport_exhausted",
"message": "5 attempts over 41s",
"retryable": false,
},
}))
.unwrap();
let EffectOutcome::Failed(failed) = spent else {
panic!("a spent ladder is a failure");
};
assert_eq!(
failed.failure.kind,
HostEffectFailureKind::TransportExhausted
);
}
#[test]
fn retryable_is_uniformly_optional_advice_across_all_six_failure_kinds() {
for kind in HostEffectFailureKind::ALL {
for retryable in [None, Some(true), Some(false)] {
let mut failure = json!({ "kind": kind.as_str(), "message": "boom" });
if let Some(flag) = retryable {
failure
.as_object_mut()
.unwrap()
.insert("retryable".to_string(), json!(flag));
}
let outcome: EffectOutcome =
serde_json::from_value(json!({ "status": "failed", "failure": failure }))
.unwrap();
let EffectOutcome::Failed(failed) = outcome else {
panic!("failure decoded as success");
};
assert_eq!(failed.failure.kind, kind);
assert_eq!(failed.failure.retryable, retryable);
}
}
}
#[test]
fn a_tool_result_must_state_whether_the_batch_can_continue() {
assert_eq!(
decode_effect(json!({
"status": "succeeded",
"result": { "kind": "tools", "results": [
{ "kind": "inline", "call_id": "c-1", "result": { "output": "ok" } }] },
}))
.unwrap_err()
.kind,
WireRejectionKind::MissingField
);
assert_eq!(
ToolResultDisposition::ALL
.iter()
.map(|d| d.as_str())
.collect::<BTreeSet<&str>>(),
BTreeSet::from(["recoverable", "fatal"])
);
for value in ["recoverable", "fatal"] {
let decoded: ToolResultDisposition = serde_json::from_value(json!(value)).expect(value);
assert_eq!(decoded.as_str(), value);
assert_eq!(decoded.is_fatal(), value == "fatal");
}
for gone in [
"user_interrupt",
"governance_denied",
"provider_failure",
"timeout",
"cancelled",
] {
assert!(
serde_json::from_value::<ToolResultDisposition>(json!(gone)).is_err(),
"{gone:?} is not a tool-result disposition"
);
}
}
#[test]
fn both_residency_arms_state_the_same_two_failure_facts() {
let inline = ToolResultPayload::Inline(InlineToolResult {
call_id: call_id("call-1"),
result: ToolResult {
output: "boom".to_string(),
durable_content: None,
is_error: true,
disposition: ToolResultDisposition::Fatal,
tokens: None,
},
});
let external = ToolResultPayload::External(ExternalToolResult {
call_id: call_id("call-2"),
payload_ref: payload_ref(),
digest: digest(),
original_size: WireU64::new(1_048_576),
preview: "Traceback (most recent call last):".to_string(),
is_error: true,
disposition: ToolResultDisposition::Fatal,
});
for payload in [&inline, &external] {
assert!(payload.is_error(), "{payload:?}");
assert_eq!(payload.disposition(), ToolResultDisposition::Fatal);
assert!(payload.disposition().is_fatal());
}
assert_eq!(
decode_effect(json!({
"status": "succeeded",
"result": { "kind": "tools", "results": [
{ "kind": "external", "call_id": "c-1", "payload_ref": "p-1",
"digest": "sha256:ab", "original_size": "1", "preview": "x" }] },
}))
.unwrap_err()
.kind,
WireRejectionKind::MissingField
);
let ok: EffectOutcome = serde_json::from_value(json!({
"status": "succeeded",
"result": { "kind": "tools", "results": [
{ "kind": "external", "call_id": "c-1", "payload_ref": "p-1",
"digest": "sha256:ab", "original_size": "1", "preview": "x",
"disposition": "recoverable" }] },
}))
.unwrap();
let EffectOutcome::Succeeded(success) = &ok else {
panic!("not a success");
};
let EffectSuccess::Tools(tools) = &success.result else {
panic!("not a tools success");
};
assert!(!tools.results[0].is_error());
let value = serde_json::to_value(&tools.results[0]).unwrap();
assert!(value.get("is_error").is_none(), "false stays off the wire");
assert_eq!(value["disposition"], json!("recoverable"));
}
#[test]
fn a_milestone_request_is_exactly_the_contract_and_phase_pair() {
let request = MilestoneRequest {
contract_id: "brief-quality-primary".to_string(),
phase_id: "collect".to_string(),
};
let value = serde_json::to_value(&request).unwrap();
assert_eq!(
value.as_object().unwrap().keys().collect::<Vec<_>>(),
vec!["contract_id", "phase_id"],
"a phase id is unique only inside its contract, so the pair is the whole key"
);
for host_owned in ["criteria", "required_evidence", "verifier", "evidence"] {
let mut with_extra = value.clone();
with_extra
.as_object_mut()
.unwrap()
.insert(host_owned.to_string(), json!([]));
assert!(
serde_json::from_value::<MilestoneRequest>(with_extra).is_err(),
"{host_owned} must not decode"
);
}
for missing in ["contract_id", "phase_id"] {
let mut without = value.clone();
without.as_object_mut().unwrap().remove(missing);
assert!(
serde_json::from_value::<MilestoneRequest>(without).is_err(),
"{missing} is half the key and cannot be omitted"
);
}
}
#[test]
fn no_effect_outcome_payload_carries_a_host_wall_clock() {
const BANNED: [&str; 8] = [
"now_ms",
"observed_at_ms",
"timestamp",
"timestamp_ms",
"started_at_ms",
"completed_at_ms",
"wall_clock_ms",
"received_at_ms",
];
let mut outcomes: Vec<EffectOutcome> = success_samples()
.into_iter()
.map(|result| EffectOutcome::Succeeded(EffectSucceeded { result }))
.collect();
outcomes.push(EffectOutcome::Failed(EffectFailed {
failure: HostEffectFailure {
kind: HostEffectFailureKind::TransportExhausted,
message: "socket hang up".to_string(),
retryable: Some(false),
},
}));
for outcome in outcomes {
let value = serde_json::to_value(&outcome).unwrap();
let mut all = BTreeSet::new();
keys(&value, &mut all);
for banned in BANNED {
assert!(
!all.contains(banned),
"outcome payload must not carry {banned:?}: {value}"
);
}
}
}
#[test]
fn an_external_tool_result_is_a_handle_with_a_digest_size_and_preview() {
let external = ToolResultPayload::External(ExternalToolResult {
call_id: call_id("call-2"),
payload_ref: payload_ref(),
digest: digest(),
original_size: WireU64::new(1_048_576),
preview: "total 42".to_string(),
is_error: false,
disposition: ToolResultDisposition::Recoverable,
});
let value = serde_json::to_value(&external).unwrap();
assert_eq!(value["kind"], json!("external"));
for required in ["payload_ref", "digest", "original_size", "preview"] {
assert!(value.get(required).is_some(), "external needs {required}");
}
assert_eq!(
value["original_size"],
json!("1048576"),
"sizes are decimal-string u64, not JS numbers"
);
let mut all = BTreeSet::new();
keys(&value, &mut all);
for banned in ["path", "file_path", "spool_ref", "spool_dir", "archive_ref"] {
assert!(!all.contains(banned), "payload ref must stay opaque");
}
}
#[test]
fn payload_residency_distinguishes_generated_over_limit_from_pressure_archival() {
let residencies = [
PayloadResidency::Resident(ResidentPayload {}),
PayloadResidency::External(ExternalResidency {
payload_ref: payload_ref(),
digest: digest(),
original_size: WireU64::new(1_048_576),
}),
PayloadResidency::PagedOut(PagedOutResidency {
payload_ref: payload_ref(),
digest: digest(),
}),
PayloadResidency::Collapsed(CollapsedPayload {}),
];
let tags: BTreeSet<String> = residencies
.iter()
.map(|residency| {
serde_json::to_value(residency).unwrap()["kind"]
.as_str()
.unwrap()
.to_string()
})
.collect();
assert_eq!(
tags,
BTreeSet::from([
"resident".to_string(),
"external".to_string(),
"paged_out".to_string(),
"collapsed".to_string(),
]),
"§7.10 keeps `external` (generated over limit) apart from `paged_out` (pressure)"
);
let paged = serde_json::to_value(&residencies[2]).unwrap();
assert!(paged.get("original_size").is_none());
}
#[test]
fn unknown_success_kinds_fields_and_variants_are_rejected() {
assert_eq!(
decode_effect(json!({ "status": "succeeded", "result": { "kind": "spooled" } }))
.unwrap_err()
.kind,
WireRejectionKind::UnknownVariant
);
assert_eq!(
decode_effect(json!({ "status": "failed", "failure": { "kind": "rate_limited" } }))
.unwrap_err()
.kind,
WireRejectionKind::UnknownVariant
);
assert_eq!(
decode_effect(json!({
"status": "succeeded",
"result": { "kind": "approval" },
"now_ms": 1,
}))
.unwrap_err()
.kind,
WireRejectionKind::UnknownField
);
assert_eq!(
decode_effect(json!({
"status": "succeeded",
"result": { "kind": "payload_loaded", "handle_id": "h-1",
"payload": { "content": "x", "digest": "sha256:ab",
"original_size": "1", "path": "/tmp/x" } },
}))
.unwrap_err()
.kind,
WireRejectionKind::UnknownField
);
assert_eq!(
decode_effect(json!({
"status": "succeeded",
"result": { "kind": "tools", "results": [
{ "kind": "external", "call_id": "c-1", "payload_ref": "p-1",
"original_size": "1", "preview": "x" }] },
}))
.unwrap_err()
.kind,
WireRejectionKind::MissingField
);
assert_eq!(
decode_effect(json!({
"status": "succeeded",
"result": { "kind": "payload_loaded", "handle_id": "h-1",
"payload": { "content": "x", "digest": "sha256:ab",
"original_size": 1 } },
}))
.unwrap_err()
.kind,
WireRejectionKind::InvalidScalar
);
}
#[test]
fn every_effect_and_success_payload_denies_unknown_fields() {
for effect in effect_samples() {
let mut value = serde_json::to_value(&effect).unwrap();
value
.as_object_mut()
.unwrap()
.insert("host_hint".to_string(), json!("x"));
assert!(
serde_json::from_value::<EffectKind>(value).is_err(),
"{:?} must deny unknown fields",
effect.tag()
);
}
for success in success_samples() {
let mut value = serde_json::to_value(&success).unwrap();
value
.as_object_mut()
.unwrap()
.insert("host_hint".to_string(), json!("x"));
assert!(
serde_json::from_value::<EffectSuccess>(value).is_err(),
"{:?} must deny unknown fields",
success.tag()
);
}
}
#[test]
fn resolve_effect_goldens_cover_every_success_kind_and_the_failure_path() {
let mut success_kinds: BTreeSet<String> = BTreeSet::new();
let mut failure_kinds: BTreeSet<String> = BTreeSet::new();
for (name, fixture) in fixtures_with_prefix("input_resolve_") {
let text = serde_json::to_string(&fixture).unwrap();
let envelope = decode_envelope_json(&text, &KernelBootstrapLimits::default())
.unwrap_or_else(|e| panic!("{name}: {e}"));
assert_eq!(
serde_json::to_value(&envelope).unwrap(),
fixture,
"{name}: round-trip changed the document"
);
let KernelInput::ResolveEffect(resolve) = &envelope.input else {
panic!("{name} is not a resolve_effect golden");
};
match &resolve.outcome {
EffectOutcome::Succeeded(ok) => {
success_kinds.insert(ok.result.tag().as_str().to_string());
}
EffectOutcome::Failed(failed) => {
failure_kinds.insert(failed.failure.kind.as_str().to_string());
}
}
}
assert_eq!(
success_kinds,
EffectSuccessTag::ALL
.iter()
.map(|tag| tag.as_str().to_string())
.collect::<BTreeSet<String>>(),
"every effect kind needs at least one resolve golden"
);
assert!(
!failure_kinds.is_empty(),
"the unified failure path needs a golden too"
);
}
#[test]
fn the_milestone_effect_has_a_failure_channel_like_every_other_effect() {
let golden = fixtures_with_prefix("input_resolve_effect_milestone_failed");
assert_eq!(golden.len(), 1);
let text = serde_json::to_string(&golden[0].1).unwrap();
let envelope = decode_envelope_json(&text, &KernelBootstrapLimits::default()).unwrap();
let KernelInput::ResolveEffect(resolve) = &envelope.input else {
panic!("not a resolve_effect golden");
};
let EffectOutcome::Failed(failed) = &resolve.outcome else {
panic!("milestone failure golden must be a Failed outcome");
};
assert_eq!(
failed.failure.kind,
HostEffectFailureKind::StorageUnavailable
);
let milestone = kernel_effect(EffectKind::EvaluateMilestone(EvaluateMilestoneEffect {
request: MilestoneRequest {
contract_id: "brief-quality-primary".to_string(),
phase_id: "phase-2".to_string(),
},
}));
assert!(milestone.accept_outcome(&resolve.outcome).is_ok());
}
#[test]
fn effect_rejection_goldens_cover_the_payload_failure_modes() {
let mut expected: BTreeSet<String> = BTreeSet::new();
for (name, fixture) in fixtures_with_prefix("reject_effect_") {
let kind = fixture["expect"]
.as_str()
.unwrap_or_else(|| panic!("{name}: missing `expect`"));
let text = serde_json::to_string(&fixture["envelope"]).unwrap();
let rejection = decode_envelope_json(&text, &KernelBootstrapLimits::default())
.map(|ok| panic!("{name}: expected rejection, decoded {ok:?}"))
.unwrap_err();
assert_eq!(
rejection.kind.as_str(),
kind,
"{name}: {}",
rejection.message
);
expected.insert(kind.to_string());
}
for required in [
"unknown_field",
"unknown_variant",
"missing_field",
"invalid_scalar",
] {
assert!(
expected.contains(required),
"effect rejection goldens must cover {required}"
);
}
}
}