1use super::*;
2pub const KERNEL_ABI_VERSION: u32 = 2;
3pub const KERNEL_SNAPSHOT_VERSION: u32 = 2;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
6#[serde(rename_all = "snake_case")]
7pub enum KernelLifecycle {
8 Created,
9 Configured,
10 Running,
11 Suspended,
12 Completed,
13 Cancelled,
14 Failed,
15}
16
17impl KernelLifecycle {
18 pub fn is_terminal(self) -> bool {
19 matches!(self, Self::Completed | Self::Cancelled | Self::Failed)
20 }
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
27#[serde(rename_all = "snake_case")]
28pub enum PolicyAction {
29 Allow,
30 Deny,
31 AskUser,
32}
33
34impl From<PolicyAction> for crate::governance::permission::PermissionAction {
35 fn from(action: PolicyAction) -> Self {
36 match action {
37 PolicyAction::Allow => Self::Allow,
38 PolicyAction::Deny => Self::Deny,
39 PolicyAction::AskUser => Self::AskUser,
40 }
41 }
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct PolicyRule {
47 pub tool_pattern: String,
48 pub action: PolicyAction,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct RateLimitSpec {
55 pub tool: String,
56 pub max_calls: u32,
57 pub window_ms: u64,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
64#[serde(tag = "kind", rename_all = "snake_case")]
65pub enum ConstraintSpec {
66 Required { tool: String, path: String },
68 Enum {
70 tool: String,
71 path: String,
72 values: Vec<String>,
73 },
74 Range {
76 tool: String,
77 path: String,
78 #[serde(default, skip_serializing_if = "Option::is_none")]
79 min: Option<f64>,
80 #[serde(default, skip_serializing_if = "Option::is_none")]
81 max: Option<f64>,
82 },
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct KernelInput {
87 pub version: u32,
88 pub operation_id: String,
89 pub event_id: String,
90 pub observed_at_ms: u64,
91 pub event: KernelInputEvent,
92}
93
94impl KernelInput {
95 pub const LOCAL_OPERATION_ID: &'static str = "local-operation";
101
102 pub fn new(event: KernelInputEvent) -> Self {
105 use std::sync::atomic::{AtomicU64, Ordering};
106 static LOCAL_EVENT_SEQUENCE: AtomicU64 = AtomicU64::new(1);
107 let event_seq = LOCAL_EVENT_SEQUENCE.fetch_add(1, Ordering::Relaxed);
108 Self::correlated(
109 Self::LOCAL_OPERATION_ID,
110 format!("local-event-{event_seq}"),
111 0,
112 event,
113 )
114 }
115
116 pub fn correlated(
117 operation_id: impl Into<String>,
118 event_id: impl Into<String>,
119 observed_at_ms: u64,
120 event: KernelInputEvent,
121 ) -> Self {
122 Self {
123 version: KERNEL_ABI_VERSION,
124 operation_id: operation_id.into(),
125 event_id: event_id.into(),
126 observed_at_ms,
127 event,
128 }
129 }
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
134#[serde(rename_all = "snake_case")]
135pub enum KernelPreparationStatus {
136 Prepared,
138 Replayed,
140 Rejected,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct KernelPreparedStep {
149 pub status: KernelPreparationStatus,
150 pub base_generation: u64,
152 #[serde(default, skip_serializing_if = "Option::is_none")]
153 pub prepare_token: Option<String>,
154 pub input: KernelInput,
155 pub step: KernelStep,
156}
157
158#[derive(Debug, Clone, Default, Serialize, Deserialize)]
161pub struct GovernanceConfig {
162 #[serde(default, skip_serializing_if = "Option::is_none")]
163 pub default_action: Option<PolicyAction>,
164 #[serde(default, skip_serializing_if = "Vec::is_empty")]
165 pub rules: Vec<PolicyRule>,
166 #[serde(default, skip_serializing_if = "Vec::is_empty")]
167 pub vetoed_tools: Vec<String>,
168 #[serde(default, skip_serializing_if = "Vec::is_empty")]
169 pub rate_limits: Vec<RateLimitSpec>,
170 #[serde(default, skip_serializing_if = "Vec::is_empty")]
171 pub constraints: Vec<ConstraintSpec>,
172}
173
174pub const SIGNAL_POLICY_VERSION: u32 = 1;
175
176#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
178#[serde(deny_unknown_fields)]
179pub struct SignalPolicyConfig {
180 pub version: u32,
181 pub queue_max: u32,
182 #[serde(default, skip_serializing_if = "Option::is_none")]
183 pub ttl_ms: Option<u64>,
184 #[serde(default, skip_serializing_if = "Option::is_none")]
185 pub deadline_escalation: Option<bool>,
186}
187
188#[derive(Debug, Clone, Default, Serialize, Deserialize)]
191pub struct KernelReliabilityConfig {
192 #[serde(default, skip_serializing_if = "Option::is_none")]
193 pub event_replay_capacity: Option<usize>,
194 #[serde(default, skip_serializing_if = "Option::is_none")]
195 pub completed_effect_replay_capacity: Option<usize>,
196 #[serde(default, skip_serializing_if = "Option::is_none")]
197 pub provider_recovery_attempts: Option<u8>,
198 #[serde(default, skip_serializing_if = "Option::is_none")]
199 pub output_recovery_attempts: Option<u8>,
200 #[serde(default, skip_serializing_if = "Option::is_none")]
201 pub host_effect_retry_attempts: Option<u8>,
202 #[serde(default, skip_serializing_if = "Option::is_none")]
203 pub spool_threshold_bytes: Option<u32>,
204 #[serde(default, skip_serializing_if = "Option::is_none")]
205 pub spool_preview_bytes: Option<u32>,
206 #[serde(default, skip_serializing_if = "Option::is_none")]
208 pub snapshot_input_limit: Option<usize>,
209 #[serde(default, skip_serializing_if = "Option::is_none")]
211 pub max_input_bytes: Option<usize>,
212 #[serde(default, skip_serializing_if = "Option::is_none")]
214 pub snapshot_journal_bytes_limit: Option<usize>,
215}
216
217#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
220pub struct KernelDiagnostics {
221 pub lifecycle: KernelLifecycle,
222 pub next_step_seq: u64,
223 pub accepted_input_count: usize,
224 pub accepted_input_bytes: usize,
225 pub snapshot_input_limit: usize,
226 pub snapshot_journal_bytes_limit: usize,
227 pub max_input_bytes: usize,
228 pub snapshot_overflowed: bool,
229 pub recorded_event_count: usize,
230 pub completed_effect_count: usize,
231 pub pending_effect_count: usize,
232}
233
234#[derive(Debug, Clone, Serialize, Deserialize)]
237pub struct KernelSnapshot {
238 pub snapshot_version: u32,
239 pub abi_version: u32,
240 pub initial_policy: KernelSnapshotPolicy,
241 pub lifecycle: KernelLifecycle,
242 #[serde(default, skip_serializing_if = "Option::is_none")]
243 pub operation_id: Option<String>,
244 pub next_step_seq: u64,
245 pub snapshot_input_limit: usize,
246 pub max_input_bytes: usize,
247 pub snapshot_journal_bytes_limit: usize,
248 pub accepted_input_bytes: usize,
249 #[serde(default)]
250 pub accepted_inputs: Vec<KernelInput>,
251 #[serde(default, skip_serializing_if = "Option::is_none")]
252 pub last_step: Option<KernelStep>,
253}
254
255#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
258pub struct KernelSnapshotPolicy {
259 pub max_tokens: u32,
260 pub max_turns: u32,
261 pub max_total_tokens: String,
262 #[serde(default, skip_serializing_if = "Option::is_none")]
263 pub max_wall_ms: Option<String>,
264}
265
266impl From<&SchedulerBudget> for KernelSnapshotPolicy {
267 fn from(policy: &SchedulerBudget) -> Self {
268 Self {
269 max_tokens: policy.max_tokens,
270 max_turns: policy.max_turns,
271 max_total_tokens: policy.max_total_tokens.to_string(),
272 max_wall_ms: policy.max_wall_ms.map(|value| value.to_string()),
273 }
274 }
275}
276
277impl TryFrom<&KernelSnapshotPolicy> for SchedulerBudget {
278 type Error = String;
279
280 fn try_from(policy: &KernelSnapshotPolicy) -> Result<Self, Self::Error> {
281 Ok(Self {
282 max_tokens: policy.max_tokens,
283 max_turns: policy.max_turns,
284 max_total_tokens: policy.max_total_tokens.parse().map_err(|_| {
285 "snapshot max_total_tokens must be a u64 decimal string".to_string()
286 })?,
287 max_wall_ms: policy
288 .max_wall_ms
289 .as_deref()
290 .map(str::parse)
291 .transpose()
292 .map_err(|_| "snapshot max_wall_ms must be a u64 decimal string".to_string())?,
293 })
294 }
295}
296
297#[derive(Debug, Clone, Default, Serialize, Deserialize)]
301#[serde(deny_unknown_fields)]
302pub struct RunConfig {
303 #[serde(default, skip_serializing_if = "Option::is_none")]
304 pub reliability: Option<KernelReliabilityConfig>,
305 #[serde(default, skip_serializing_if = "Option::is_none")]
306 pub tools: Option<Vec<ToolSchema>>,
307 #[serde(default, skip_serializing_if = "Option::is_none")]
308 pub available_skills: Option<Vec<SkillMetadata>>,
309 #[serde(default, skip_serializing_if = "Option::is_none")]
310 pub stable_core_tools: Option<Vec<String>>,
311 #[serde(default, skip_serializing_if = "Option::is_none")]
312 pub memory_enabled: Option<bool>,
313 #[serde(default, skip_serializing_if = "Option::is_none")]
314 pub knowledge_enabled: Option<bool>,
315 #[serde(default, skip_serializing_if = "Option::is_none")]
316 pub plan_tool_enabled: Option<bool>,
317 #[serde(default, skip_serializing_if = "Option::is_none")]
319 pub tokenizer: Option<String>,
320 #[serde(default, skip_serializing_if = "Option::is_none")]
321 pub governance: Option<GovernanceConfig>,
322 #[serde(default, skip_serializing_if = "Option::is_none")]
323 pub signal_policy: Option<SignalPolicyConfig>,
324 #[serde(default, skip_serializing_if = "Option::is_none")]
327 pub prompt_budget: Option<crate::context::config::PromptBudgetConfig>,
328 #[serde(default, skip_serializing_if = "Option::is_none")]
330 pub context_policy: Option<crate::context::policy::ContextPolicyV1>,
331 #[serde(default, skip_serializing_if = "Option::is_none")]
332 pub scheduler_policy: Option<crate::scheduler::policy::SchedulerPolicyConfig>,
333 #[serde(default, skip_serializing_if = "Option::is_none")]
334 pub resource_quota: Option<crate::governance::quota::ResourceQuota>,
335 #[serde(default, skip_serializing_if = "Option::is_none")]
338 pub budget_grant: Option<BudgetGrant>,
339 #[serde(default, skip_serializing_if = "Option::is_none")]
342 pub repeat_fuse: Option<crate::governance::repeat_fuse::RepeatFuseConfig>,
343 #[serde(default, skip_serializing_if = "Option::is_none")]
345 pub criteria_gate: Option<bool>,
346 #[serde(default, skip_serializing_if = "Option::is_none")]
351 pub tool_dispatch_gate: Option<String>,
352 #[serde(default, skip_serializing_if = "Option::is_none")]
355 pub knowledge_budget_ratio: Option<f64>,
356 #[serde(default, skip_serializing_if = "Option::is_none")]
360 pub entropy_watch: Option<crate::scheduler::entropy::EntropyWatchConfig>,
361}
362
363#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
364pub struct BudgetGrant {
365 pub reservation_id: String,
366 #[serde(default, skip_serializing_if = "Option::is_none")]
367 pub tokens: Option<u64>,
368 #[serde(default, skip_serializing_if = "Option::is_none")]
369 pub subagents: Option<u32>,
370 #[serde(default, skip_serializing_if = "Option::is_none")]
371 pub rounds: Option<u32>,
372}
373
374#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
375#[serde(rename_all = "snake_case")]
376pub enum CancellationReason {
377 User,
378 Deadline,
379 LeaseLost,
380 HostShutdown,
381}
382
383pub(crate) fn build_governance_pipeline(
387 default_action: Option<PolicyAction>,
388 rules: Vec<PolicyRule>,
389 vetoed_tools: Vec<String>,
390 rate_limits: Vec<RateLimitSpec>,
391 constraints: Vec<ConstraintSpec>,
392) -> crate::governance::pipeline::GovernancePipeline {
393 use crate::governance::constraint::{ConstraintRule, ParamConstraint};
394 use crate::governance::permission::PermissionRule;
395 use crate::governance::rate_limit::RateLimit;
396 let default = default_action.unwrap_or(PolicyAction::Allow).into();
397 let mut pipeline = crate::governance::pipeline::GovernancePipeline::new(default);
398 for rule in rules {
399 pipeline.permission.add_rule(PermissionRule {
400 tool_pattern: rule.tool_pattern.into(),
401 action: rule.action.into(),
402 });
403 }
404 for tool in vetoed_tools {
405 pipeline.veto.block_tool(tool);
406 }
407 for rl in rate_limits {
408 pipeline.rate_limiter.set_limit(
409 rl.tool,
410 RateLimit {
411 max_calls: rl.max_calls,
412 window_ms: rl.window_ms,
413 },
414 );
415 }
416 for c in constraints {
417 let (tool_name, param_path, rule) = match c {
418 ConstraintSpec::Required { tool, path } => (tool, path, ConstraintRule::Required),
419 ConstraintSpec::Enum { tool, path, values } => {
420 (tool, path, ConstraintRule::Enum(values))
421 }
422 ConstraintSpec::Range {
423 tool,
424 path,
425 min,
426 max,
427 } => (tool, path, ConstraintRule::Range { min, max }),
428 };
429 pipeline.constraints.add(ParamConstraint {
430 tool_name,
431 param_path,
432 rule,
433 });
434 }
435 pipeline
436}
437
438#[derive(Debug, Clone, Serialize, Deserialize)]
439#[serde(tag = "kind", rename_all = "snake_case")]
440pub enum KernelInputEvent {
441 SetTools {
442 tools: Vec<ToolSchema>,
443 },
444 SetAvailableSkills {
445 skills: Vec<SkillMetadata>,
446 },
447 SkillActivated {
451 name: String,
452 #[serde(default, skip_serializing_if = "Option::is_none")]
456 lease_turns: Option<u32>,
457 },
458 SkillDeactivated {
463 name: String,
464 },
465 SetStableCoreTools {
468 tool_ids: Vec<String>,
469 },
470 SetMemoryEnabled {
471 enabled: bool,
472 },
473 SetKnowledgeEnabled {
474 enabled: bool,
475 },
476 SetPlanToolEnabled {
477 enabled: bool,
478 },
479 SetTokenizer {
480 name: String,
481 },
482 AddSystemMessage {
483 content: String,
484 tokens: u32,
485 },
486 AddKnowledgeMessage {
487 content: String,
488 tokens: u32,
489 #[serde(default, skip_serializing_if = "Option::is_none")]
492 key: Option<String>,
493 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
495 pinned: bool,
496 },
497 RemoveKnowledge {
500 key: String,
501 },
502 AddHistoryMessage {
503 message: Message,
504 tokens: Option<u32>,
505 },
506 PreloadHistory {
507 messages: Vec<Message>,
508 },
509 MountCapability {
510 capability: CapabilityDescriptor,
511 },
512 UnmountCapability {
513 capability_kind: CapabilityKind,
514 id: String,
515 },
516 LoadMilestoneContract {
517 contract: MilestoneContract,
518 },
519 LoadGovernancePolicy {
523 #[serde(default)]
524 default_action: Option<PolicyAction>,
525 #[serde(default, skip_serializing_if = "Vec::is_empty")]
526 rules: Vec<PolicyRule>,
527 #[serde(default, skip_serializing_if = "Vec::is_empty")]
528 vetoed_tools: Vec<String>,
529 #[serde(default, skip_serializing_if = "Vec::is_empty")]
532 rate_limits: Vec<RateLimitSpec>,
533 #[serde(default, skip_serializing_if = "Vec::is_empty")]
534 constraints: Vec<ConstraintSpec>,
535 },
536 SetSignalPolicy {
538 policy: SignalPolicyConfig,
539 },
540 ForceCompact,
541 UpdateTask {
542 update: TaskUpdate,
543 },
544 StartRun {
545 task: RuntimeTask,
546 #[serde(default, skip_serializing_if = "Option::is_none")]
547 run_spec: Option<AgentRunSpec>,
548 },
549 ConfigureRun {
553 config: RunConfig,
554 },
555 CapabilityCommand {
556 command: CapabilityCommand,
557 },
558 Resume,
561 ApprovalResult {
562 effect_id: String,
563 #[serde(default, skip_serializing_if = "Vec::is_empty")]
564 approved_calls: Vec<String>,
565 #[serde(default, skip_serializing_if = "Vec::is_empty")]
566 denied_calls: Vec<String>,
567 #[serde(default, skip_serializing_if = "Option::is_none")]
568 error: Option<String>,
569 },
570 WorkflowSpawnResult {
573 effect_id: String,
574 #[serde(default, skip_serializing_if = "Vec::is_empty")]
575 started_agent_ids: Vec<String>,
576 #[serde(default, skip_serializing_if = "Vec::is_empty")]
577 failures: Vec<WorkflowSpawnFailure>,
578 #[serde(default, skip_serializing_if = "Option::is_none")]
579 error: Option<String>,
580 },
581 PreemptResult {
582 effect_id: String,
583 #[serde(default, skip_serializing_if = "Option::is_none")]
584 error: Option<String>,
585 },
586 MemoryPersistResult {
587 effect_id: String,
588 #[serde(default, skip_serializing_if = "Option::is_none")]
589 error: Option<String>,
590 },
591 MemoryQueryResult {
592 effect_id: String,
593 #[serde(default, skip_serializing_if = "Vec::is_empty")]
594 hits: Vec<crate::mm::memory::MemoryRecall>,
595 #[serde(default, skip_serializing_if = "Option::is_none")]
596 error: Option<String>,
597 },
598 LargeResultSpoolResult {
599 effect_id: String,
600 #[serde(default, skip_serializing_if = "Option::is_none")]
601 spool_ref: Option<String>,
602 #[serde(default, skip_serializing_if = "Option::is_none")]
603 error: Option<String>,
604 },
605 PageOutArchiveResult {
606 effect_id: String,
607 #[serde(default, skip_serializing_if = "Option::is_none")]
608 archive_ref: Option<String>,
609 #[serde(default, skip_serializing_if = "Option::is_none")]
610 error: Option<String>,
611 },
612 SetKnowledgeBudget {
615 ratio: f64,
616 },
617 SetCriteriaGate {
620 enabled: bool,
621 },
622 SetRepeatFuse {
625 #[serde(default, skip_serializing_if = "Option::is_none")]
626 enabled: Option<bool>,
627 #[serde(default, skip_serializing_if = "Option::is_none")]
628 deny_after: Option<u32>,
629 #[serde(default, skip_serializing_if = "Option::is_none")]
630 terminate_after: Option<u32>,
631 },
632 SetEntropyWatch {
637 #[serde(default, skip_serializing_if = "Option::is_none")]
638 enabled: Option<bool>,
639 #[serde(default, skip_serializing_if = "Option::is_none")]
640 threshold: Option<f64>,
641 #[serde(default, skip_serializing_if = "Option::is_none")]
642 hysteresis: Option<f64>,
643 #[serde(default, skip_serializing_if = "Option::is_none")]
644 cooldown_turns: Option<u32>,
645 #[serde(default, skip_serializing_if = "Option::is_none")]
646 notify_model: Option<bool>,
647 },
648 SetSchedulerBudget {
652 #[serde(default, skip_serializing_if = "Option::is_none")]
653 max_wall_ms: Option<u64>,
654 },
655 SetResourceQuota {
661 quota: crate::governance::quota::ResourceQuota,
662 },
663 ProviderResult {
664 effect_id: String,
665 message: Message,
666 #[serde(default, skip_serializing_if = "Option::is_none")]
667 observed_input_tokens: Option<u32>,
668 #[serde(default, skip_serializing_if = "Option::is_none")]
669 observed_output_tokens: Option<u32>,
670 #[serde(default, skip_serializing_if = "Option::is_none")]
674 now_ms: Option<u64>,
675 #[serde(default, skip_serializing_if = "Option::is_none")]
679 stop_reason: Option<String>,
680 },
681 ToolResults {
682 effect_id: String,
683 results: Vec<ToolResult>,
684 },
685 ProviderError {
692 effect_id: String,
693 message: String,
694 },
695 DeliverSignal {
696 delivery_id: String,
697 attempt: u32,
698 signal: RuntimeSignal,
699 },
700 MilestoneResult {
701 effect_id: String,
702 result: MilestoneCheckResult,
703 },
704 SpawnSubAgent {
706 spec: AgentRunSpec,
707 parent_session_id: String,
708 },
709 LoadWorkflow {
714 spec: crate::orchestration::workflow::WorkflowSpec,
715 parent_session_id: String,
716 #[serde(default, skip_serializing_if = "Vec::is_empty")]
720 resumed_submissions: Vec<Vec<crate::orchestration::workflow::WorkflowNode>>,
721 #[serde(default)]
724 resumed_submission_bases: Vec<u32>,
725 #[serde(default, skip_serializing_if = "Vec::is_empty")]
728 resumed_outcomes: Vec<crate::orchestration::workflow::ResumedNodeOutcome>,
729 },
730 SubAgentCompleted {
732 result: SubAgentResult,
733 },
734 SubmitWorkflowNodes {
739 #[serde(default, skip_serializing_if = "Vec::is_empty")]
740 nodes: Vec<crate::orchestration::workflow::WorkflowNode>,
741 #[serde(default, skip_serializing_if = "Option::is_none")]
745 submitter_agent_id: Option<String>,
746 },
747 SubmitWorkflow {
753 spec: crate::orchestration::workflow::WorkflowSpec,
754 #[serde(default)]
756 parent_session_id: String,
757 #[serde(default, skip_serializing_if = "Option::is_none")]
760 submitter_agent_id: Option<String>,
761 },
762 PageIn {
765 #[serde(default, skip_serializing_if = "Vec::is_empty")]
766 entries: Vec<crate::mm::PageInEntry>,
767 },
768 SetMemoryPolicy {
771 #[serde(default)]
772 memory_path: String,
773 #[serde(default = "default_stale_days")]
774 stale_warning_days: u32,
775 #[serde(default = "default_top_k")]
776 retrieval_top_k: usize,
777 #[serde(default = "default_validation_enabled")]
778 validation_enabled: bool,
779 #[serde(default, skip_serializing_if = "Option::is_none")]
781 max_content_bytes: Option<u32>,
782 #[serde(default, skip_serializing_if = "Option::is_none")]
784 max_name_length: Option<usize>,
785 #[serde(default, skip_serializing_if = "Option::is_none")]
787 promotion_recall_threshold: Option<u64>,
788 },
789 WriteMemory {
791 memory: crate::mm::memory::MemoryRecord,
792 },
793 QueryMemory {
795 query: crate::mm::memory::MemoryQuery,
796 },
797 CompleteRun,
801 CancelOperation {
804 operation_id: String,
805 reason: CancellationReason,
806 #[serde(default, skip_serializing_if = "Vec::is_empty")]
807 pending_call_ids: Vec<String>,
808 },
809}
810
811fn default_stale_days() -> u32 {
812 2
813}
814fn default_top_k() -> usize {
815 5
816}
817fn default_validation_enabled() -> bool {
818 true
819}
820
821#[derive(Debug, Clone, Serialize, Deserialize)]
822pub struct KernelStep {
823 pub version: u32,
824 pub operation_id: String,
825 pub input_event_id: String,
826 pub step_seq: u64,
827 pub actions: Vec<KernelAction>,
828 pub observations: Vec<KernelObservation>,
829 #[serde(default, skip_serializing_if = "Vec::is_empty")]
830 pub faults: Vec<KernelFault>,
831}
832
833impl KernelStep {
834 pub(super) fn empty(
835 operation_id: String,
836 input_event_id: String,
837 step_seq: u64,
838 observations: Vec<KernelObservation>,
839 ) -> Self {
840 Self {
841 version: KERNEL_ABI_VERSION,
842 operation_id,
843 input_event_id,
844 step_seq,
845 actions: Vec::new(),
846 observations,
847 faults: Vec::new(),
848 }
849 }
850
851 pub(super) fn single(
852 operation_id: String,
853 input_event_id: String,
854 step_seq: u64,
855 action: LoopAction,
856 observations: Vec<KernelObservation>,
857 ) -> Self {
858 let effect_id = format!("{operation_id}:step:{step_seq}:effect:0");
859 Self {
860 version: KERNEL_ABI_VERSION,
861 operation_id,
862 input_event_id: input_event_id.clone(),
863 step_seq,
864 actions: vec![KernelAction::from_loop(effect_id, input_event_id, action)],
865 observations,
866 faults: Vec::new(),
867 }
868 }
869
870 pub(super) fn fault(
871 operation_id: String,
872 input_event_id: String,
873 step_seq: u64,
874 fault: KernelFault,
875 ) -> Self {
876 Self {
877 version: KERNEL_ABI_VERSION,
878 operation_id,
879 input_event_id,
880 step_seq,
881 actions: Vec::new(),
882 observations: Vec::new(),
883 faults: vec![fault],
884 }
885 }
886}
887
888#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
889#[serde(rename_all = "snake_case")]
890pub enum KernelFaultCode {
891 VersionMismatch,
892 OperationMismatch,
893 InvalidLifecycle,
894 InvalidConfig,
895 ResourceLimitExceeded,
896 DuplicateEventConflict,
897 UnexpectedEffectResult,
898 TransactionConflict,
899 SnapshotIncompatible,
900}
901
902#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
903pub struct KernelFault {
904 pub code: KernelFaultCode,
905 pub message: String,
906 #[serde(default, skip_serializing_if = "Option::is_none")]
907 pub operation_id: Option<String>,
908 #[serde(default, skip_serializing_if = "Option::is_none")]
909 pub event_id: Option<String>,
910 #[serde(default, skip_serializing_if = "Option::is_none")]
911 pub effect_id: Option<String>,
912}
913
914#[derive(Debug, Clone, Serialize, Deserialize)]
915pub struct KernelAction {
916 pub effect_id: String,
917 pub causation_id: String,
918 #[serde(flatten)]
919 pub effect: KernelEffect,
920}
921
922impl KernelAction {
923 fn from_loop(effect_id: String, causation_id: String, action: LoopAction) -> Self {
924 Self {
925 effect_id,
926 causation_id,
927 effect: action.into(),
928 }
929 }
930}
931
932#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
933pub struct WorkflowSpawnFailure {
934 pub agent_id: String,
935 pub error: String,
936}
937
938#[derive(Debug, Clone, Serialize, Deserialize)]
939#[serde(tag = "kind", rename_all = "snake_case")]
940pub enum KernelEffect {
941 CallProvider {
942 context: RenderedContext,
943 tools: Vec<ToolSchema>,
944 },
945 ExecuteTool {
946 calls: Vec<ToolCall>,
947 },
948 RequestApproval {
949 requests: Vec<crate::scheduler::state_machine::ApprovalRequest>,
950 },
951 SpawnWorkflow {
952 nodes: Vec<crate::orchestration::workflow::WorkflowSpawnInfo>,
953 #[serde(default, skip_serializing_if = "Option::is_none")]
954 budget: Option<crate::orchestration::workflow::WorkflowBudget>,
955 },
956 PreemptSubAgents {
957 agent_ids: Vec<String>,
958 reason: String,
959 },
960 PersistMemory {
961 memory: crate::mm::memory::MemoryRecord,
962 },
963 QueryMemory {
964 query: crate::mm::memory::MemoryQuery,
965 requested_k: usize,
966 },
967 SpoolLargeResult {
968 call_id: String,
969 tool: String,
970 output: String,
971 original_size: u32,
972 preview_size: u32,
973 },
974 ArchivePageOut {
975 turn: u32,
976 action: KernelPressureAction,
977 summary: Option<String>,
978 archived: Vec<Message>,
979 tier: String,
980 },
981 EvaluateMilestone {
982 phase_id: String,
983 criteria: Vec<String>,
984 #[serde(default, skip_serializing_if = "Option::is_none")]
985 verifier: Option<crate::types::milestone::MilestoneVerifier>,
986 #[serde(default, skip_serializing_if = "Vec::is_empty")]
987 required_evidence: Vec<String>,
988 },
989 Done {
990 result: LoopResult,
991 },
992}
993
994impl From<LoopAction> for KernelEffect {
995 fn from(action: LoopAction) -> Self {
996 match action {
997 LoopAction::AwaitingResume => {
998 panic!("AwaitingResume must not be converted to KernelEffect")
999 }
1000 LoopAction::CallLLM { context, tools } => Self::CallProvider { context, tools },
1001 LoopAction::ExecuteTools { calls } => Self::ExecuteTool { calls },
1002 LoopAction::RequestApproval { requests } => Self::RequestApproval { requests },
1003 LoopAction::SpawnWorkflow { nodes, budget } => Self::SpawnWorkflow { nodes, budget },
1004 LoopAction::PreemptSubAgents { agent_ids, reason } => {
1005 Self::PreemptSubAgents { agent_ids, reason }
1006 }
1007 LoopAction::PersistMemory { memory } => Self::PersistMemory { memory },
1008 LoopAction::QueryMemory { query, requested_k } => {
1009 Self::QueryMemory { query, requested_k }
1010 }
1011 LoopAction::SpoolLargeResult {
1012 call_id,
1013 tool,
1014 output,
1015 original_size,
1016 preview_size,
1017 } => Self::SpoolLargeResult {
1018 call_id,
1019 tool,
1020 output,
1021 original_size,
1022 preview_size,
1023 },
1024 LoopAction::ArchivePageOut {
1025 turn,
1026 action,
1027 summary,
1028 archived,
1029 tier,
1030 } => Self::ArchivePageOut {
1031 turn,
1032 action,
1033 summary,
1034 archived,
1035 tier,
1036 },
1037 LoopAction::EvaluateMilestone {
1038 phase_id,
1039 criteria,
1040 verifier,
1041 required_evidence,
1042 } => Self::EvaluateMilestone {
1043 phase_id,
1044 criteria,
1045 verifier,
1046 required_evidence,
1047 },
1048 LoopAction::Done { result } => Self::Done { result },
1049 }
1050 }
1051}
1052
1053#[derive(Debug, Clone, Serialize, Deserialize)]
1054#[serde(tag = "kind", rename_all = "snake_case")]
1055pub enum KernelObservation {
1056 Compressed {
1059 #[serde(default)]
1060 turn: u32,
1061 action: KernelPressureAction,
1062 rho_after: f64,
1063 summary: Option<String>,
1064 archived_count: u32,
1065 #[serde(default, skip_serializing_if = "Option::is_none")]
1069 invalidates_prefix_at: Option<usize>,
1070 },
1071 Renewed {
1072 sprint: u32,
1073 },
1074 ContextBudgetExceeded {
1077 turn: u32,
1078 overflow_kind: crate::context::renderer::ContextBudgetOverflowKind,
1079 required_tokens: u32,
1080 max_tokens: u32,
1081 },
1082 KnowledgeSwept {
1086 turn: u32,
1087 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1088 removed_keys: Vec<String>,
1089 tokens_freed: u32,
1090 },
1091 KnowledgeBudgetExceeded {
1095 turn: u32,
1096 used: u32,
1097 budget: u32,
1098 },
1099 Rollbacked {
1100 turn: u32,
1101 checkpoint_history_len: u32,
1102 #[serde(default, skip_serializing_if = "Option::is_none")]
1103 reason: Option<RollbackReason>,
1104 },
1105 ControlRequestRejected {
1109 turn: u32,
1110 operation: String,
1111 #[serde(default, skip_serializing_if = "Option::is_none")]
1112 subject: Option<String>,
1113 reason: String,
1114 },
1115 CapabilityChanged {
1116 turn: u32,
1117 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1118 added: Vec<String>,
1119 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1120 removed: Vec<String>,
1121 #[serde(default, skip_serializing_if = "Option::is_none")]
1122 change_kind: Option<String>,
1123 #[serde(default, skip_serializing_if = "Option::is_none")]
1124 capability_id: Option<String>,
1125 #[serde(default, skip_serializing_if = "Option::is_none")]
1126 version: Option<String>,
1127 #[serde(default, skip_serializing_if = "Option::is_none")]
1128 mounted_by: Option<String>,
1129 #[serde(default, skip_serializing_if = "Option::is_none")]
1130 mount_reason: Option<String>,
1131 },
1132 MilestoneAdvanced {
1133 turn: u32,
1134 phase_id: String,
1135 capabilities_unlocked: Vec<String>,
1136 },
1137 MilestoneBlocked {
1138 turn: u32,
1139 phase_id: String,
1140 reason: String,
1141 },
1142 CheckpointTaken {
1144 turn: u32,
1145 history_len: u32,
1146 },
1147 RepeatFuseTripped {
1151 turn: u32,
1152 signature: String,
1153 count: u32,
1154 action: String,
1155 },
1156 CriteriaGateFired {
1159 turn: u32,
1160 criteria: Vec<String>,
1161 },
1162 EntropySample {
1167 turn: u32,
1168 score: f64,
1169 score_version: u32,
1170 rho: f64,
1171 repeat_pressure: f64,
1172 failure_rate: f64,
1173 rollbacks_in_window: u32,
1174 window_turns: u32,
1175 },
1176 EntropyAlert {
1180 turn: u32,
1181 score: f64,
1182 threshold: f64,
1183 },
1184 AgentProcessChanged {
1186 turn: u32,
1187 agent_id: String,
1188 parent_session_id: String,
1189 role: String,
1190 isolation: String,
1191 context_inheritance: String,
1192 state: String,
1193 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1194 permitted_capability_ids: Vec<String>,
1195 #[serde(default, skip_serializing_if = "Option::is_none")]
1196 result_termination: Option<String>,
1197 },
1198 WorkflowBatchSpawned {
1201 turn: u32,
1202 nodes: Vec<crate::orchestration::workflow::WorkflowSpawnInfo>,
1203 #[serde(default, skip_serializing_if = "Option::is_none")]
1207 budget: Option<crate::orchestration::workflow::WorkflowBudget>,
1208 },
1209 WorkflowSpawnFailed {
1212 turn: u32,
1213 error: String,
1214 },
1215 WorkflowCompleted {
1217 turn: u32,
1218 node_outcomes: Vec<crate::orchestration::workflow::run::WorkflowNodeOutcome>,
1219 },
1220 AgentPreempted {
1226 turn: u32,
1227 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1228 agent_ids: Vec<String>,
1229 reason: String,
1230 },
1231 AgentPreemptFailed {
1232 turn: u32,
1233 agent_ids: Vec<String>,
1234 reason: String,
1235 error: String,
1236 },
1237 RoundPaced {
1239 turn: u32,
1240 round: u32,
1241 decision: crate::types::result::PaceDecision,
1242 },
1243 WorkflowNodesSubmitted {
1248 turn: u32,
1249 base: u32,
1250 count: u32,
1251 #[serde(default, skip_serializing_if = "Option::is_none")]
1254 submitter: Option<String>,
1255 },
1256 NodesRejected {
1258 turn: u32,
1259 node_index: u32,
1260 reason: String,
1261 },
1262 ToolGated {
1265 turn: u32,
1266 call_id: String,
1267 tool: String,
1268 reason: String,
1269 },
1270 SignalDeliveryDisposed {
1272 turn: u32,
1273 operation_id: String,
1274 delivery_id: String,
1275 attempt: u32,
1276 signal_id: String,
1277 disposition: String,
1278 queue_depth: u32,
1279 },
1280 SignalDisplaced {
1281 turn: u32,
1282 admitted_signal_id: String,
1283 displaced_signal_id: String,
1284 queue_depth: u32,
1285 },
1286 SignalExpired {
1287 turn: u32,
1288 signal_id: String,
1289 queue_depth: u32,
1290 },
1291 SignalsPending {
1292 turn: u32,
1293 depth: u32,
1294 },
1295 BudgetExceeded {
1297 turn: u32,
1298 budget: String,
1299 operation_id: String,
1300 #[serde(default, skip_serializing_if = "Option::is_none")]
1301 reservation_id: Option<String>,
1302 },
1303 BudgetUsageReported {
1305 operation_id: String,
1306 reservation_id: String,
1307 tokens: u64,
1308 subagents: u32,
1309 rounds: u32,
1310 },
1311 OperationCancelled {
1313 turn: u32,
1314 operation_id: String,
1315 reason: CancellationReason,
1316 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1317 pending_call_ids: Vec<String>,
1318 },
1319 Suspended {
1321 turn: u32,
1322 reason: String,
1323 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1324 pending_calls: Vec<String>,
1325 },
1326 Resumed {
1328 turn: u32,
1329 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1330 approved: Vec<String>,
1331 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1332 denied: Vec<String>,
1333 },
1334 ApprovalResolutionFailed {
1335 turn: u32,
1336 error: String,
1337 },
1338 MemoryWritten {
1340 turn: u32,
1341 record_id: String,
1342 scope: crate::mm::memory::MemoryScope,
1343 memory_kind: crate::mm::memory::MemoryKind,
1344 name: String,
1345 size_bytes: u32,
1346 },
1347 MemoryValidationFailed {
1349 turn: u32,
1350 record_id: String,
1351 error: String,
1352 },
1353 MemoryWriteFailed {
1354 turn: u32,
1355 record_id: String,
1356 error: String,
1357 },
1358 MemoryQueried {
1360 turn: u32,
1361 scope: crate::mm::memory::MemoryScope,
1362 query: String,
1363 requested_k: usize,
1364 requires_async_response: bool,
1365 },
1366 MemoryQueryFailed {
1367 turn: u32,
1368 scope: crate::mm::memory::MemoryScope,
1369 query: String,
1370 error: String,
1371 },
1372 MemoryRecalled {
1376 turn: u32,
1377 scope: crate::mm::memory::MemoryScope,
1378 recalls: Vec<crate::mm::memory::MemoryRecallLifecycle>,
1379 },
1380 PromotionSuggested {
1383 turn: u32,
1384 record_id: String,
1385 recall_count: u64,
1386 },
1387 LargeResultSpooled {
1389 turn: u32,
1390 call_id: String,
1391 tool: String,
1392 original_size: u32,
1393 preview_size: u32,
1394 spool_ref: Option<String>,
1395 },
1396 LargeResultSpoolFailed {
1397 turn: u32,
1398 call_id: String,
1399 tool: String,
1400 error: String,
1401 },
1402 PageOutArchived {
1403 turn: u32,
1404 action: KernelPressureAction,
1405 summary: Option<String>,
1406 tier: String,
1407 message_count: u32,
1408 #[serde(default, skip_serializing_if = "Option::is_none")]
1409 archive_ref: Option<String>,
1410 },
1411 PageOutArchiveFailed {
1412 turn: u32,
1413 action: KernelPressureAction,
1414 tier: String,
1415 message_count: u32,
1416 error: String,
1417 },
1418}
1419
1420#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1421#[serde(rename_all = "snake_case")]
1422pub enum KernelPressureAction {
1423 None,
1424 SnipCompact,
1425 MicroCompact,
1426 ContextCollapse,
1427 AutoCompact,
1428}
1429
1430impl From<PressureAction> for KernelPressureAction {
1431 fn from(action: PressureAction) -> Self {
1432 match action {
1433 PressureAction::None => Self::None,
1434 PressureAction::SnipCompact => Self::SnipCompact,
1435 PressureAction::MicroCompact => Self::MicroCompact,
1436 PressureAction::ContextCollapse => Self::ContextCollapse,
1437 PressureAction::AutoCompact => Self::AutoCompact,
1438 }
1439 }
1440}