use super::*;
pub const KERNEL_ABI_VERSION: u32 = 2;
pub const KERNEL_SNAPSHOT_VERSION: u32 = 2;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum KernelLifecycle {
Created,
Configured,
Running,
Suspended,
Completed,
Cancelled,
Failed,
}
impl KernelLifecycle {
pub fn is_terminal(self) -> bool {
matches!(self, Self::Completed | Self::Cancelled | Self::Failed)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PolicyAction {
Allow,
Deny,
AskUser,
}
impl From<PolicyAction> for crate::governance::permission::PermissionAction {
fn from(action: PolicyAction) -> Self {
match action {
PolicyAction::Allow => Self::Allow,
PolicyAction::Deny => Self::Deny,
PolicyAction::AskUser => Self::AskUser,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PolicyRule {
pub tool_pattern: String,
pub action: PolicyAction,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RateLimitSpec {
pub tool: String,
pub max_calls: u32,
pub window_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ConstraintSpec {
Required { tool: String, path: String },
Enum {
tool: String,
path: String,
values: Vec<String>,
},
Range {
tool: String,
path: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
min: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
max: Option<f64>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KernelInput {
pub version: u32,
pub operation_id: String,
pub event_id: String,
pub observed_at_ms: u64,
pub event: KernelInputEvent,
}
impl KernelInput {
pub fn new(event: KernelInputEvent) -> Self {
use std::sync::atomic::{AtomicU64, Ordering};
static LOCAL_EVENT_SEQUENCE: AtomicU64 = AtomicU64::new(1);
let event_seq = LOCAL_EVENT_SEQUENCE.fetch_add(1, Ordering::Relaxed);
Self::correlated(
"local-operation",
format!("local-event-{event_seq}"),
0,
event,
)
}
pub fn correlated(
operation_id: impl Into<String>,
event_id: impl Into<String>,
observed_at_ms: u64,
event: KernelInputEvent,
) -> Self {
Self {
version: KERNEL_ABI_VERSION,
operation_id: operation_id.into(),
event_id: event_id.into(),
observed_at_ms,
event,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum KernelPreparationStatus {
Prepared,
Replayed,
Rejected,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KernelPreparedStep {
pub status: KernelPreparationStatus,
pub base_generation: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub prepare_token: Option<String>,
pub input: KernelInput,
pub step: KernelStep,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct GovernanceConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub default_action: Option<PolicyAction>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub rules: Vec<PolicyRule>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub vetoed_tools: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub rate_limits: Vec<RateLimitSpec>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub constraints: Vec<ConstraintSpec>,
}
pub const SIGNAL_POLICY_VERSION: u32 = 1;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SignalPolicyConfig {
pub version: u32,
pub queue_max: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ttl_ms: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub deadline_escalation: Option<bool>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct KernelReliabilityConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub event_replay_capacity: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub completed_effect_replay_capacity: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider_recovery_attempts: Option<u8>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub output_recovery_attempts: Option<u8>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub host_effect_retry_attempts: Option<u8>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub spool_threshold_bytes: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub spool_preview_bytes: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub snapshot_input_limit: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_input_bytes: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub snapshot_journal_bytes_limit: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct KernelDiagnostics {
pub lifecycle: KernelLifecycle,
pub next_step_seq: u64,
pub accepted_input_count: usize,
pub accepted_input_bytes: usize,
pub snapshot_input_limit: usize,
pub snapshot_journal_bytes_limit: usize,
pub max_input_bytes: usize,
pub snapshot_overflowed: bool,
pub recorded_event_count: usize,
pub completed_effect_count: usize,
pub pending_effect_count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KernelSnapshot {
pub snapshot_version: u32,
pub abi_version: u32,
pub initial_policy: KernelSnapshotPolicy,
pub lifecycle: KernelLifecycle,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub operation_id: Option<String>,
pub next_step_seq: u64,
pub snapshot_input_limit: usize,
pub max_input_bytes: usize,
pub snapshot_journal_bytes_limit: usize,
pub accepted_input_bytes: usize,
#[serde(default)]
pub accepted_inputs: Vec<KernelInput>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_step: Option<KernelStep>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct KernelSnapshotPolicy {
pub max_tokens: u32,
pub max_turns: u32,
pub max_total_tokens: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_wall_ms: Option<String>,
}
impl From<&SchedulerBudget> for KernelSnapshotPolicy {
fn from(policy: &SchedulerBudget) -> Self {
Self {
max_tokens: policy.max_tokens,
max_turns: policy.max_turns,
max_total_tokens: policy.max_total_tokens.to_string(),
max_wall_ms: policy.max_wall_ms.map(|value| value.to_string()),
}
}
}
impl TryFrom<&KernelSnapshotPolicy> for SchedulerBudget {
type Error = String;
fn try_from(policy: &KernelSnapshotPolicy) -> Result<Self, Self::Error> {
Ok(Self {
max_tokens: policy.max_tokens,
max_turns: policy.max_turns,
max_total_tokens: policy.max_total_tokens.parse().map_err(|_| {
"snapshot max_total_tokens must be a u64 decimal string".to_string()
})?,
max_wall_ms: policy
.max_wall_ms
.as_deref()
.map(str::parse)
.transpose()
.map_err(|_| "snapshot max_wall_ms must be a u64 decimal string".to_string())?,
})
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RunConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reliability: Option<KernelReliabilityConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<ToolSchema>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub available_skills: Option<Vec<SkillMetadata>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stable_core_tools: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub memory_enabled: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub knowledge_enabled: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub plan_tool_enabled: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tokenizer: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub governance: Option<GovernanceConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub signal_policy: Option<SignalPolicyConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub prompt_budget: Option<crate::context::config::PromptBudgetConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub context_policy: Option<crate::context::policy::ContextPolicyV1>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub scheduler_policy: Option<crate::scheduler::policy::SchedulerPolicyConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub resource_quota: Option<crate::governance::quota::ResourceQuota>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub budget_grant: Option<BudgetGrant>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub repeat_fuse: Option<crate::governance::repeat_fuse::RepeatFuseConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub criteria_gate: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub knowledge_budget_ratio: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub entropy_watch: Option<crate::scheduler::entropy::EntropyWatchConfig>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BudgetGrant {
pub reservation_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tokens: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subagents: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rounds: Option<u32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CancellationReason {
User,
Deadline,
LeaseLost,
HostShutdown,
}
pub(crate) fn build_governance_pipeline(
default_action: Option<PolicyAction>,
rules: Vec<PolicyRule>,
vetoed_tools: Vec<String>,
rate_limits: Vec<RateLimitSpec>,
constraints: Vec<ConstraintSpec>,
) -> crate::governance::pipeline::GovernancePipeline {
use crate::governance::constraint::{ConstraintRule, ParamConstraint};
use crate::governance::permission::PermissionRule;
use crate::governance::rate_limit::RateLimit;
let default = default_action.unwrap_or(PolicyAction::Allow).into();
let mut pipeline = crate::governance::pipeline::GovernancePipeline::new(default);
for rule in rules {
pipeline.permission.add_rule(PermissionRule {
tool_pattern: rule.tool_pattern.into(),
action: rule.action.into(),
});
}
for tool in vetoed_tools {
pipeline.veto.block_tool(tool);
}
for rl in rate_limits {
pipeline.rate_limiter.set_limit(
rl.tool,
RateLimit {
max_calls: rl.max_calls,
window_ms: rl.window_ms,
},
);
}
for c in constraints {
let (tool_name, param_path, rule) = match c {
ConstraintSpec::Required { tool, path } => (tool, path, ConstraintRule::Required),
ConstraintSpec::Enum { tool, path, values } => {
(tool, path, ConstraintRule::Enum(values))
}
ConstraintSpec::Range {
tool,
path,
min,
max,
} => (tool, path, ConstraintRule::Range { min, max }),
};
pipeline.constraints.add(ParamConstraint {
tool_name,
param_path,
rule,
});
}
pipeline
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum KernelInputEvent {
SetTools {
tools: Vec<ToolSchema>,
},
SetAvailableSkills {
skills: Vec<SkillMetadata>,
},
SkillActivated {
name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
lease_turns: Option<u32>,
},
SkillDeactivated {
name: String,
},
SetStableCoreTools {
tool_ids: Vec<String>,
},
SetMemoryEnabled {
enabled: bool,
},
SetKnowledgeEnabled {
enabled: bool,
},
SetPlanToolEnabled {
enabled: bool,
},
SetTokenizer {
name: String,
},
AddSystemMessage {
content: String,
tokens: u32,
},
AddKnowledgeMessage {
content: String,
tokens: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
key: Option<String>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pinned: bool,
},
RemoveKnowledge {
key: String,
},
AddHistoryMessage {
message: Message,
tokens: Option<u32>,
},
PreloadHistory {
messages: Vec<Message>,
},
MountCapability {
capability: CapabilityDescriptor,
},
UnmountCapability {
capability_kind: CapabilityKind,
id: String,
},
LoadMilestoneContract {
contract: MilestoneContract,
},
LoadGovernancePolicy {
#[serde(default)]
default_action: Option<PolicyAction>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
rules: Vec<PolicyRule>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
vetoed_tools: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
rate_limits: Vec<RateLimitSpec>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
constraints: Vec<ConstraintSpec>,
},
SetSignalPolicy {
policy: SignalPolicyConfig,
},
ForceCompact,
UpdateTask {
update: TaskUpdate,
},
StartRun {
task: RuntimeTask,
#[serde(default, skip_serializing_if = "Option::is_none")]
run_spec: Option<AgentRunSpec>,
},
ConfigureRun {
config: RunConfig,
},
CapabilityCommand {
command: CapabilityCommand,
},
Resume,
ApprovalResult {
effect_id: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
approved_calls: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
denied_calls: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
error: Option<String>,
},
WorkflowSpawnResult {
effect_id: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
started_agent_ids: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
failures: Vec<WorkflowSpawnFailure>,
#[serde(default, skip_serializing_if = "Option::is_none")]
error: Option<String>,
},
PreemptResult {
effect_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
error: Option<String>,
},
MemoryPersistResult {
effect_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
error: Option<String>,
},
MemoryQueryResult {
effect_id: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
hits: Vec<crate::mm::memory::MemoryRecall>,
#[serde(default, skip_serializing_if = "Option::is_none")]
error: Option<String>,
},
LargeResultSpoolResult {
effect_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
spool_ref: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
error: Option<String>,
},
PageOutArchiveResult {
effect_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
archive_ref: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
error: Option<String>,
},
SetKnowledgeBudget {
ratio: f64,
},
SetCriteriaGate {
enabled: bool,
},
SetRepeatFuse {
#[serde(default, skip_serializing_if = "Option::is_none")]
enabled: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
deny_after: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
terminate_after: Option<u32>,
},
SetEntropyWatch {
#[serde(default, skip_serializing_if = "Option::is_none")]
enabled: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
threshold: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
hysteresis: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
cooldown_turns: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
notify_model: Option<bool>,
},
SetSchedulerBudget {
#[serde(default, skip_serializing_if = "Option::is_none")]
max_wall_ms: Option<u64>,
},
SetResourceQuota {
quota: crate::governance::quota::ResourceQuota,
},
ProviderResult {
effect_id: String,
message: Message,
#[serde(default, skip_serializing_if = "Option::is_none")]
observed_input_tokens: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
observed_output_tokens: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
now_ms: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
stop_reason: Option<String>,
},
ToolResults {
effect_id: String,
results: Vec<ToolResult>,
},
ProviderError {
effect_id: String,
message: String,
},
DeliverSignal {
delivery_id: String,
attempt: u32,
signal: RuntimeSignal,
},
MilestoneResult {
effect_id: String,
result: MilestoneCheckResult,
},
SpawnSubAgent {
spec: AgentRunSpec,
parent_session_id: String,
},
LoadWorkflow {
spec: crate::orchestration::workflow::WorkflowSpec,
parent_session_id: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
resumed_submissions: Vec<Vec<crate::orchestration::workflow::WorkflowNode>>,
#[serde(default)]
resumed_submission_bases: Vec<u32>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
resumed_outcomes: Vec<crate::orchestration::workflow::ResumedNodeOutcome>,
},
SubAgentCompleted {
result: SubAgentResult,
},
SubmitWorkflowNodes {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
nodes: Vec<crate::orchestration::workflow::WorkflowNode>,
#[serde(default, skip_serializing_if = "Option::is_none")]
submitter_agent_id: Option<String>,
},
SubmitWorkflow {
spec: crate::orchestration::workflow::WorkflowSpec,
#[serde(default)]
parent_session_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
submitter_agent_id: Option<String>,
},
PageIn {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
entries: Vec<crate::mm::PageInEntry>,
},
SetMemoryPolicy {
#[serde(default)]
memory_path: String,
#[serde(default = "default_stale_days")]
stale_warning_days: u32,
#[serde(default = "default_top_k")]
retrieval_top_k: usize,
#[serde(default = "default_validation_enabled")]
validation_enabled: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
max_content_bytes: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
max_name_length: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
promotion_recall_threshold: Option<u64>,
},
WriteMemory {
memory: crate::mm::memory::MemoryRecord,
},
QueryMemory {
query: crate::mm::memory::MemoryQuery,
},
CompleteRun,
CancelOperation {
operation_id: String,
reason: CancellationReason,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pending_call_ids: Vec<String>,
},
}
fn default_stale_days() -> u32 {
2
}
fn default_top_k() -> usize {
5
}
fn default_validation_enabled() -> bool {
true
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KernelStep {
pub version: u32,
pub operation_id: String,
pub input_event_id: String,
pub step_seq: u64,
pub actions: Vec<KernelAction>,
pub observations: Vec<KernelObservation>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub faults: Vec<KernelFault>,
}
impl KernelStep {
pub(super) fn empty(
operation_id: String,
input_event_id: String,
step_seq: u64,
observations: Vec<KernelObservation>,
) -> Self {
Self {
version: KERNEL_ABI_VERSION,
operation_id,
input_event_id,
step_seq,
actions: Vec::new(),
observations,
faults: Vec::new(),
}
}
pub(super) fn single(
operation_id: String,
input_event_id: String,
step_seq: u64,
action: LoopAction,
observations: Vec<KernelObservation>,
) -> Self {
let effect_id = format!("{operation_id}:step:{step_seq}:effect:0");
Self {
version: KERNEL_ABI_VERSION,
operation_id,
input_event_id: input_event_id.clone(),
step_seq,
actions: vec![KernelAction::from_loop(effect_id, input_event_id, action)],
observations,
faults: Vec::new(),
}
}
pub(super) fn fault(
operation_id: String,
input_event_id: String,
step_seq: u64,
fault: KernelFault,
) -> Self {
Self {
version: KERNEL_ABI_VERSION,
operation_id,
input_event_id,
step_seq,
actions: Vec::new(),
observations: Vec::new(),
faults: vec![fault],
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum KernelFaultCode {
VersionMismatch,
OperationMismatch,
InvalidLifecycle,
InvalidConfig,
ResourceLimitExceeded,
DuplicateEventConflict,
UnexpectedEffectResult,
TransactionConflict,
SnapshotIncompatible,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct KernelFault {
pub code: KernelFaultCode,
pub message: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub operation_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub event_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub effect_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KernelAction {
pub effect_id: String,
pub causation_id: String,
#[serde(flatten)]
pub effect: KernelEffect,
}
impl KernelAction {
fn from_loop(effect_id: String, causation_id: String, action: LoopAction) -> Self {
Self {
effect_id,
causation_id,
effect: action.into(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct WorkflowSpawnFailure {
pub agent_id: String,
pub error: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum KernelEffect {
CallProvider {
context: RenderedContext,
tools: Vec<ToolSchema>,
},
ExecuteTool {
calls: Vec<ToolCall>,
},
RequestApproval {
requests: Vec<crate::scheduler::state_machine::ApprovalRequest>,
},
SpawnWorkflow {
nodes: Vec<crate::orchestration::workflow::WorkflowSpawnInfo>,
#[serde(default, skip_serializing_if = "Option::is_none")]
budget: Option<crate::orchestration::workflow::WorkflowBudget>,
},
PreemptSubAgents {
agent_ids: Vec<String>,
reason: String,
},
PersistMemory {
memory: crate::mm::memory::MemoryRecord,
},
QueryMemory {
query: crate::mm::memory::MemoryQuery,
requested_k: usize,
},
SpoolLargeResult {
call_id: String,
tool: String,
output: String,
original_size: u32,
preview_size: u32,
},
ArchivePageOut {
turn: u32,
action: KernelPressureAction,
summary: Option<String>,
archived: Vec<Message>,
tier: String,
},
EvaluateMilestone {
phase_id: String,
criteria: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
verifier: Option<crate::types::milestone::MilestoneVerifier>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
required_evidence: Vec<String>,
},
Done {
result: LoopResult,
},
}
impl From<LoopAction> for KernelEffect {
fn from(action: LoopAction) -> Self {
match action {
LoopAction::AwaitingResume => {
panic!("AwaitingResume must not be converted to KernelEffect")
}
LoopAction::CallLLM { context, tools } => Self::CallProvider { context, tools },
LoopAction::ExecuteTools { calls } => Self::ExecuteTool { calls },
LoopAction::RequestApproval { requests } => Self::RequestApproval { requests },
LoopAction::SpawnWorkflow { nodes, budget } => Self::SpawnWorkflow { nodes, budget },
LoopAction::PreemptSubAgents { agent_ids, reason } => {
Self::PreemptSubAgents { agent_ids, reason }
}
LoopAction::PersistMemory { memory } => Self::PersistMemory { memory },
LoopAction::QueryMemory { query, requested_k } => {
Self::QueryMemory { query, requested_k }
}
LoopAction::SpoolLargeResult {
call_id,
tool,
output,
original_size,
preview_size,
} => Self::SpoolLargeResult {
call_id,
tool,
output,
original_size,
preview_size,
},
LoopAction::ArchivePageOut {
turn,
action,
summary,
archived,
tier,
} => Self::ArchivePageOut {
turn,
action,
summary,
archived,
tier,
},
LoopAction::EvaluateMilestone {
phase_id,
criteria,
verifier,
required_evidence,
} => Self::EvaluateMilestone {
phase_id,
criteria,
verifier,
required_evidence,
},
LoopAction::Done { result } => Self::Done { result },
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum KernelObservation {
Compressed {
#[serde(default)]
turn: u32,
action: KernelPressureAction,
rho_after: f64,
summary: Option<String>,
archived_count: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
invalidates_prefix_at: Option<usize>,
},
Renewed {
sprint: u32,
},
ContextBudgetExceeded {
turn: u32,
overflow_kind: crate::context::renderer::ContextBudgetOverflowKind,
required_tokens: u32,
max_tokens: u32,
},
KnowledgeSwept {
turn: u32,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
removed_keys: Vec<String>,
tokens_freed: u32,
},
KnowledgeBudgetExceeded {
turn: u32,
used: u32,
budget: u32,
},
Rollbacked {
turn: u32,
checkpoint_history_len: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
reason: Option<RollbackReason>,
},
ControlRequestRejected {
turn: u32,
operation: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
subject: Option<String>,
reason: String,
},
CapabilityChanged {
turn: u32,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
added: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
removed: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
change_kind: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
capability_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
version: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
mounted_by: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
mount_reason: Option<String>,
},
MilestoneAdvanced {
turn: u32,
phase_id: String,
capabilities_unlocked: Vec<String>,
},
MilestoneBlocked {
turn: u32,
phase_id: String,
reason: String,
},
CheckpointTaken {
turn: u32,
history_len: u32,
},
RepeatFuseTripped {
turn: u32,
signature: String,
count: u32,
action: String,
},
CriteriaGateFired {
turn: u32,
criteria: Vec<String>,
},
EntropySample {
turn: u32,
score: f64,
score_version: u32,
rho: f64,
repeat_pressure: f64,
failure_rate: f64,
rollbacks_in_window: u32,
window_turns: u32,
},
EntropyAlert {
turn: u32,
score: f64,
threshold: f64,
},
AgentProcessChanged {
turn: u32,
agent_id: String,
parent_session_id: String,
role: String,
isolation: String,
context_inheritance: String,
state: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
permitted_capability_ids: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
result_termination: Option<String>,
},
WorkflowBatchSpawned {
turn: u32,
nodes: Vec<crate::orchestration::workflow::WorkflowSpawnInfo>,
#[serde(default, skip_serializing_if = "Option::is_none")]
budget: Option<crate::orchestration::workflow::WorkflowBudget>,
},
WorkflowSpawnFailed {
turn: u32,
error: String,
},
WorkflowCompleted {
turn: u32,
node_outcomes: Vec<crate::orchestration::workflow::run::WorkflowNodeOutcome>,
},
AgentPreempted {
turn: u32,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
agent_ids: Vec<String>,
reason: String,
},
AgentPreemptFailed {
turn: u32,
agent_ids: Vec<String>,
reason: String,
error: String,
},
RoundPaced {
turn: u32,
round: u32,
decision: crate::types::result::PaceDecision,
},
WorkflowNodesSubmitted {
turn: u32,
base: u32,
count: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
submitter: Option<String>,
},
NodesRejected {
turn: u32,
node_index: u32,
reason: String,
},
ToolGated {
turn: u32,
call_id: String,
tool: String,
reason: String,
},
SignalDeliveryDisposed {
turn: u32,
operation_id: String,
delivery_id: String,
attempt: u32,
signal_id: String,
disposition: String,
queue_depth: u32,
},
SignalDisplaced {
turn: u32,
admitted_signal_id: String,
displaced_signal_id: String,
queue_depth: u32,
},
SignalExpired {
turn: u32,
signal_id: String,
queue_depth: u32,
},
SignalsPending {
turn: u32,
depth: u32,
},
BudgetExceeded {
turn: u32,
budget: String,
operation_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
reservation_id: Option<String>,
},
BudgetUsageReported {
operation_id: String,
reservation_id: String,
tokens: u64,
subagents: u32,
rounds: u32,
},
OperationCancelled {
turn: u32,
operation_id: String,
reason: CancellationReason,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pending_call_ids: Vec<String>,
},
Suspended {
turn: u32,
reason: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pending_calls: Vec<String>,
},
Resumed {
turn: u32,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
approved: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
denied: Vec<String>,
},
ApprovalResolutionFailed {
turn: u32,
error: String,
},
MemoryWritten {
turn: u32,
record_id: String,
scope: crate::mm::memory::MemoryScope,
memory_kind: crate::mm::memory::MemoryKind,
name: String,
size_bytes: u32,
},
MemoryValidationFailed {
turn: u32,
record_id: String,
error: String,
},
MemoryWriteFailed {
turn: u32,
record_id: String,
error: String,
},
MemoryQueried {
turn: u32,
scope: crate::mm::memory::MemoryScope,
query: String,
requested_k: usize,
requires_async_response: bool,
},
MemoryQueryFailed {
turn: u32,
scope: crate::mm::memory::MemoryScope,
query: String,
error: String,
},
MemoryRecalled {
turn: u32,
scope: crate::mm::memory::MemoryScope,
recalls: Vec<crate::mm::memory::MemoryRecallLifecycle>,
},
PromotionSuggested {
turn: u32,
record_id: String,
recall_count: u64,
},
LargeResultSpooled {
turn: u32,
call_id: String,
tool: String,
original_size: u32,
preview_size: u32,
spool_ref: Option<String>,
},
LargeResultSpoolFailed {
turn: u32,
call_id: String,
tool: String,
error: String,
},
PageOutArchived {
turn: u32,
action: KernelPressureAction,
summary: Option<String>,
tier: String,
message_count: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
archive_ref: Option<String>,
},
PageOutArchiveFailed {
turn: u32,
action: KernelPressureAction,
tier: String,
message_count: u32,
error: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum KernelPressureAction {
None,
SnipCompact,
MicroCompact,
ContextCollapse,
AutoCompact,
}
impl From<PressureAction> for KernelPressureAction {
fn from(action: PressureAction) -> Self {
match action {
PressureAction::None => Self::None,
PressureAction::SnipCompact => Self::SnipCompact,
PressureAction::MicroCompact => Self::MicroCompact,
PressureAction::ContextCollapse => Self::ContextCollapse,
PressureAction::AutoCompact => Self::AutoCompact,
}
}
}