deepstrike_core/runtime/kernel/wire/
binding.rs1use super::checkpoint::{
15 CheckpointCandidate, KernelCheckpoint, LogicalKernelState, LogicalStateProjection,
16};
17use super::config::ConfigDefaults;
18use super::driver::{CanonicalOperationDriver, PlannedStep};
19use super::effect::{Digest, KernelEffect};
20use super::envelope::{
21 OperationLifecycle, WireEnvelope, WireRejection, WireRejectionKind, decode_envelope_json,
22};
23use super::fault::{
24 KernelFault, KernelFaultCode, KernelPreparation, PrepareToken, RejectedTransition,
25};
26use super::record::{KernelRecord, RecordPreparation};
27use super::restore::{RestoreCost, restore_operation};
28use super::terminal::KernelTerminal;
29use super::transaction::{
30 CheckpointBoundary, CommittedTransition, DurableHead, InMemoryRecordIndex, KernelTransaction,
31 TailUsage,
32};
33
34type CanonicalTransaction = KernelTransaction<PlannedStep, InMemoryRecordIndex>;
35
36enum DriverRestorePoint {
37 Fresh,
38 Logical(Box<LogicalKernelState>),
39}
40
41pub struct CanonicalKernel {
47 defaults: ConfigDefaults,
48 transaction: CanonicalTransaction,
49 driver: CanonicalOperationDriver,
50 before_candidate: Option<DriverRestorePoint>,
51}
52
53impl std::fmt::Debug for CanonicalKernel {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 f.debug_struct("CanonicalKernel")
56 .field("operation_id", &self.transaction.operation_id())
57 .field("head", &self.transaction.head())
58 .field("lifecycle", &self.transaction.lifecycle())
59 .field("has_candidate", &self.transaction.has_candidate())
60 .finish()
61 }
62}
63
64impl Default for CanonicalKernel {
65 fn default() -> Self {
66 Self::new(ConfigDefaults::default())
67 }
68}
69
70impl CanonicalKernel {
71 pub fn new(defaults: ConfigDefaults) -> Self {
73 Self {
74 transaction: KernelTransaction::new(defaults.clone(), InMemoryRecordIndex::new()),
75 driver: CanonicalOperationDriver::new(),
76 defaults,
77 before_candidate: None,
78 }
79 }
80
81 pub fn prepare_json(&mut self, input_json: &str) -> RecordPreparation<PlannedStep> {
86 match decode_envelope_json(input_json, &self.defaults.bootstrap_limits) {
87 Ok(envelope) => self.prepare(&envelope),
88 Err(rejection) => KernelPreparation::Rejected(RejectedTransition {
89 fault: rejection_fault(rejection),
90 }),
91 }
92 }
93
94 pub fn prepare(&mut self, envelope: &WireEnvelope) -> RecordPreparation<PlannedStep> {
96 if self.before_candidate.is_some() {
99 let Self {
100 transaction,
101 driver,
102 ..
103 } = self;
104 return transaction.prepare(envelope, |context| driver.plan(context));
105 }
106
107 let restore_point = match self.capture_driver_restore_point() {
108 Ok(restore_point) => restore_point,
109 Err(fault) => {
110 return KernelPreparation::Rejected(RejectedTransition { fault });
111 }
112 };
113 let preparation = {
114 let Self {
115 transaction,
116 driver,
117 ..
118 } = self;
119 transaction.prepare(envelope, |context| driver.plan(context))
120 };
121
122 if matches!(preparation, KernelPreparation::Prepared(_)) {
123 self.before_candidate = Some(restore_point);
124 return preparation;
125 }
126
127 if let Err(fault) = self.restore_driver(restore_point) {
131 return KernelPreparation::Rejected(RejectedTransition { fault });
132 }
133 preparation
134 }
135
136 pub fn commit(
138 &mut self,
139 token: &PrepareToken,
140 appended_head: &Digest,
141 ) -> Result<CommittedTransition<PlannedStep>, KernelFault> {
142 let committed = self.transaction.commit(token, appended_head)?;
143 let step_seq = committed.step_seq;
144 self.before_candidate = None;
145 self.driver.note_committed(step_seq)?;
146 Ok(committed)
147 }
148
149 pub fn abort(&mut self, token: &PrepareToken) -> Result<KernelRecord, KernelFault> {
151 let record = self.transaction.abort(token)?;
152 let restore_point = self.before_candidate.take().ok_or_else(|| {
153 KernelFault::new(
154 KernelFaultCode::TransactionConflict,
155 "the transaction aborted a candidate but the canonical driver has no restore point",
156 )
157 })?;
158 self.restore_driver(restore_point)?;
159 Ok(record)
160 }
161
162 pub fn restore(
167 &mut self,
168 checkpoint: Option<&KernelCheckpoint>,
169 records: &[KernelRecord],
170 ) -> Result<RestoreCost, KernelFault> {
171 let restored = restore_operation(
172 checkpoint,
173 records,
174 self.defaults.clone(),
175 InMemoryRecordIndex::from_records(records),
176 )?;
177 let cost = restored.cost;
178 self.transaction = restored.transaction;
179 self.driver = restored.driver;
180 self.before_candidate = None;
181 Ok(cost)
182 }
183
184 pub fn restore_bytes(
186 &mut self,
187 checkpoint_bytes: Option<&[u8]>,
188 record_bytes: &[Vec<u8>],
189 ) -> Result<RestoreCost, KernelFault> {
190 let checkpoint = checkpoint_bytes
191 .map(KernelCheckpoint::from_checkpoint_bytes)
192 .transpose()
193 .map_err(|error| error.fault())?;
194 let records = record_bytes
195 .iter()
196 .map(|bytes| {
197 KernelRecord::from_record_bytes(bytes)
198 .map_err(|error| KernelFault::new(error.code(), error.message().to_string()))
199 })
200 .collect::<Result<Vec<_>, _>>()?;
201 self.restore(checkpoint.as_ref(), &records)
202 }
203
204 pub fn checkpoint_candidate(&self) -> Result<CheckpointCandidate, KernelFault> {
206 self.transaction
207 .checkpoint_candidate(self.driver.project_logical_state())
208 }
209
210 pub fn checkpoint_rebase(
212 &self,
213 base: &KernelCheckpoint,
214 ) -> Result<CheckpointCandidate, KernelFault> {
215 self.transaction
216 .checkpoint_rebase(&base.boundary(), base.logical_state().clone())
217 }
218
219 pub fn note_checkpoint_acked(
221 &mut self,
222 boundary: &CheckpointBoundary,
223 ) -> Result<TailUsage, KernelFault> {
224 self.transaction.note_checkpoint_acked(boundary)
225 }
226
227 pub fn head(&self) -> Option<DurableHead> {
228 self.transaction.head()
229 }
230
231 pub fn lifecycle(&self) -> OperationLifecycle {
232 self.transaction.lifecycle()
233 }
234
235 pub fn pending_effects(&self) -> impl Iterator<Item = &KernelEffect> {
236 self.transaction.pending_effects()
237 }
238
239 pub fn pending_effects_in_order(&self) -> Vec<&KernelEffect> {
242 self.transaction.pending_effects_in_order()
243 }
244
245 pub fn current_projection(
248 &self,
249 ) -> Result<super::projection::CurrentProjection, super::projection::ProjectionError> {
250 super::projection::project_current_pending_action(
251 self.transaction.terminal(),
252 self.transaction.pending_effects_in_order(),
253 )
254 }
255
256 pub fn terminal(&self) -> Option<&KernelTerminal> {
257 self.transaction.terminal()
258 }
259
260 pub fn attempt_id(&self, task_id: &str) -> Option<super::scalar::AttemptId> {
262 self.driver.attempt_id(task_id).cloned()
263 }
264
265 pub fn turn(&self) -> u32 {
267 self.driver.engine().map_or(0, |engine| engine.turn)
268 }
269
270 pub fn recovery_content_bytes(&self) -> Option<usize> {
272 self.driver.engine().map(|engine| {
273 let tokens = engine
274 .ctx
275 .config
276 .recovery_content_tokens(engine.ctx.max_tokens);
277 engine.ctx.engine.token_budget_to_bytes(tokens)
278 })
279 }
280
281 pub fn preserved_refs(&self) -> Vec<String> {
283 self.driver
284 .engine()
285 .map(|engine| engine.ctx.partitions.task_state.preserved_refs.clone())
286 .unwrap_or_default()
287 }
288
289 pub fn count_tokens(&self, text: &str) -> Option<u32> {
291 self.driver
292 .engine()
293 .map(|engine| engine.ctx.engine.count(text))
294 }
295
296 pub fn local_subagents_spawned(&self) -> u32 {
298 self.driver
299 .engine()
300 .map_or(0, |engine| engine.local_subagents_spawned())
301 }
302
303 pub fn new_messages(&self) -> Vec<crate::types::message::Message> {
305 self.driver
306 .engine()
307 .map(|engine| engine.drain_new_messages())
308 .unwrap_or_default()
309 }
310
311 pub fn poison(&self) -> Option<&KernelFault> {
312 self.transaction.poison().or_else(|| self.driver.poison())
313 }
314
315 fn capture_driver_restore_point(&self) -> Result<DriverRestorePoint, KernelFault> {
316 if self.transaction.config().is_none() {
317 return Ok(DriverRestorePoint::Fresh);
318 }
319 let LogicalStateProjection {
320 root_kind,
321 focus,
322 syscall,
323 scheduler,
324 context_vm,
325 } = self.driver.project_logical_state();
326 let transition = self
327 .transaction
328 .transition_state_for_restore(root_kind, focus)?;
329 Ok(DriverRestorePoint::Logical(Box::new(LogicalKernelState {
330 transition,
331 syscall,
332 scheduler,
333 context_vm,
334 })))
335 }
336
337 fn restore_driver(&mut self, restore_point: DriverRestorePoint) -> Result<(), KernelFault> {
338 self.driver = match restore_point {
339 DriverRestorePoint::Fresh => CanonicalOperationDriver::new(),
340 DriverRestorePoint::Logical(state) => CanonicalOperationDriver::restore_logical_state(
341 &state.transition.resolved_config,
342 &state,
343 )?,
344 };
345 Ok(())
346 }
347}
348
349fn rejection_fault(rejection: WireRejection) -> KernelFault {
350 let code = match rejection.kind {
351 WireRejectionKind::PolicyViolation => KernelFaultCode::InvalidConfig,
352 _ => KernelFaultCode::MalformedEnvelope,
353 };
354 KernelFault::new(code, rejection.message)
355}
356
357#[cfg(test)]
358mod tests {
359 use serde_json::Value;
360
361 use super::CanonicalKernel;
362 use crate::runtime::kernel::wire::{
363 KernelFaultCode, KernelPreparation, PrepareToken, WireEnvelope,
364 };
365
366 fn golden_agent_root() -> Value {
367 serde_json::from_str(include_str!(
368 "../../../../../../tests/fixtures/kernel-wire/golden_lifecycle_agent_root.json"
369 ))
370 .expect("golden fixture")
371 }
372
373 fn commit_input(kernel: &mut CanonicalKernel, input: &str) {
374 let prepared = kernel.prepare_json(input);
375 let KernelPreparation::Prepared(prepared) = prepared else {
376 panic!("expected prepared transition");
377 };
378 kernel
379 .commit(&prepared.token, prepared.record.record_digest())
380 .expect("commit");
381 }
382
383 #[test]
384 fn canonical_kernel_produces_the_shared_genesis_record() {
385 let fixture = golden_agent_root();
386 let mut kernel = CanonicalKernel::default();
387 let preparation = kernel.prepare_json(&fixture["links"][0]["envelope"].to_string());
388 let KernelPreparation::Prepared(prepared) = preparation else {
389 panic!("golden envelope must prepare");
390 };
391
392 assert_eq!(
393 prepared.record.record_digest().as_str(),
394 fixture["genesis_digest"].as_str().unwrap()
395 );
396 assert_eq!(
397 std::str::from_utf8(prepared.record.record_bytes().as_slice()).unwrap(),
398 serde_json::to_string(&fixture["links"][0]["record"]).unwrap()
399 );
400 }
401
402 #[test]
403 fn abort_restores_the_driver_before_the_next_prepare() {
404 let fixture = golden_agent_root();
405 let mut kernel = CanonicalKernel::default();
406 commit_input(&mut kernel, &fixture["links"][0]["envelope"].to_string());
407
408 let start = fixture["links"][1]["envelope"].clone();
409 let first = kernel.prepare_json(&start.to_string());
410 let KernelPreparation::Prepared(first) = first else {
411 panic!("start must prepare");
412 };
413 let first_digest = first.record.record_digest().clone();
414 kernel.abort(&first.token).expect("abort before append");
415
416 let second = kernel.prepare_json(&start.to_string());
417 let KernelPreparation::Prepared(second) = second else {
418 panic!("the same input must prepare after abort");
419 };
420 assert_eq!(second.record.record_digest(), &first_digest);
421 }
422
423 #[test]
424 fn malformed_and_unknown_envelopes_are_structured_rejections() {
425 let mut kernel = CanonicalKernel::default();
426 let malformed = kernel.prepare_json("{");
427 assert_eq!(
428 malformed.fault().map(|fault| fault.code),
429 Some(KernelFaultCode::MalformedEnvelope)
430 );
431
432 let fixture = golden_agent_root();
433 let mut unknown = fixture["links"][0]["envelope"].clone();
434 unknown
435 .as_object_mut()
436 .unwrap()
437 .insert("session_id".to_string(), Value::String("host-only".into()));
438 let rejected = kernel.prepare_json(&unknown.to_string());
439 assert_eq!(
440 rejected.fault().map(|fault| fault.code),
441 Some(KernelFaultCode::MalformedEnvelope)
442 );
443 }
444
445 #[test]
446 fn restore_replaces_the_same_typed_handle() {
447 let fixture = golden_agent_root();
448 let mut kernel = CanonicalKernel::default();
449 commit_input(&mut kernel, &fixture["links"][0]["envelope"].to_string());
450 let checkpoint = kernel
451 .checkpoint_candidate()
452 .expect("configured operation checkpoints")
453 .decode()
454 .expect("checkpoint verifies");
455
456 let start: WireEnvelope =
457 serde_json::from_value(fixture["links"][1]["envelope"].clone()).unwrap();
458 let KernelPreparation::Prepared(prepared) = kernel.prepare(&start) else {
459 panic!("start prepares");
460 };
461 let post_checkpoint_record = prepared.record.clone();
462 let expected_head = prepared.record.record_digest().clone();
463 kernel
464 .commit(&prepared.token, prepared.record.record_digest())
465 .unwrap();
466
467 let handle_address = std::ptr::addr_of!(kernel);
468 kernel
469 .restore(Some(&checkpoint), &[post_checkpoint_record])
470 .expect("restore");
471 assert_eq!(std::ptr::addr_of!(kernel), handle_address);
472 assert_eq!(kernel.head().unwrap().digest, expected_head);
473
474 let no_candidate = PrepareToken::new("no-candidate").unwrap();
475 assert!(kernel.abort(&no_candidate).is_err());
476 }
477}