Skip to main content

active_rpc/
kernel.rs

1//! The kernel: one agent's capability table, promise graph, lanes, channels and
2//! dispatch loop.
3//!
4//! The kernel is a deterministic state machine. Nothing here does I/O, timing or
5//! threading: providers are ordinary objects, the clock is a counter, and the
6//! dispatch loop is driven by [`Agent::run`]. That makes capability semantics,
7//! attenuation and pipelining observable and reproducible in tests — and it
8//! means a transport is a thin adapter over the same machine (ADR-0002 in this
9//! crate).
10//!
11//! # Dispatch and round-trips
12//!
13//! A *batch* is every queued job for one provider, ordered so that a job whose
14//! promise dependencies are produced in the same batch runs after its producer.
15//! One batch is one provider round-trip. A client that waits for each result and
16//! then issues the next call produces one batch per call; a client that passes
17//! promises as arguments produces one batch for the whole chain. That difference
18//! is what the pipelining tests measure.
19
20use std::cell::RefCell;
21use std::collections::{BTreeMap, BTreeSet, VecDeque};
22use std::rc::Rc;
23
24use crate::budget::{Budget, BudgetMeter};
25use crate::cap::{CapAttrs, CapControl, CapHandle, CapId, CapRecord, ProviderId, ToolPolicy};
26use crate::channel::{Channel, ChannelId, Direction, Message};
27use crate::error::{Error, Result};
28use crate::lane::{Lane, LaneId};
29use crate::promise::{PromiseId, PromiseRecord, PromiseState};
30use crate::value::{Arg, Call, Response, Value};
31
32/// Global ceiling on lanes in one kernel.
33///
34/// Lane creation is a local operation, but a caller that can create lanes
35/// without bound can exhaust memory. The ceiling is high enough for any real
36/// agent and finite, so a hostile or buggy caller fails closed.
37pub const MAX_LANES: usize = 4_096;
38
39/// Global ceiling on channels in one kernel.
40pub const MAX_CHANNELS: usize = 4_096;
41
42/// Global ceiling on live promise and job records in one kernel.
43///
44/// The ceiling covers a kernel that keeps issuing calls on long-lived lanes and
45/// is a backpressure valve, not a lifetime quota: after each drain the kernel
46/// trims settled promise records to [`MAX_SETTLED_PROMISES`] and
47/// [`MAX_SETTLED_BYTES`], and completed jobs are removed immediately.
48pub const MAX_PROMISES: usize = 100_000;
49
50/// Target number of settled promise records retained after a drain.
51///
52/// Retention is handle-pinned: a record with a live [`PromiseHandle`] is never
53/// evicted, so a caller that holds handles for a large batch can read every
54/// result. Only records with no live handle are candidates; pending records are
55/// never evicted.
56pub const MAX_SETTLED_PROMISES: usize = 1_024;
57
58/// Target bytes of settled values retained per kernel after a drain.
59///
60/// Values can be large; this bounds the payload memory an *unreferenced*
61/// completed call chain pins. Handle-pinned records are exempt because the
62/// caller can still read them.
63pub const MAX_SETTLED_BYTES: usize = 64 << 20;
64
65/// Ceiling on the retained event log.
66///
67/// The log is diagnostic, not a durable audit trail. Keeping it finite means a
68/// peer that issues calls forever cannot grow kernel memory without bound; the
69/// oldest events are dropped first and [`Metrics`] keeps the totals.
70pub const MAX_EVENTS: usize = 16_384;
71
72/// A provider of capabilities: a module, a tool, a peer.
73pub trait Provider {
74    /// Stable adapter name.
75    fn name(&self) -> &'static str;
76    /// Methods this provider answers (introspection).
77    fn methods(&self) -> Vec<String>;
78    /// Whether this provider answers `method`.
79    fn has_method(&self, method: &str) -> bool {
80        self.methods().iter().any(|name| name == method)
81    }
82    /// Whether `method` mutates provider state (refused on read-only caps).
83    fn mutating(&self, method: &str) -> bool;
84    /// Invoke a method on `object`.
85    fn invoke(&mut self, object: u64, call: &Call) -> Result<Response>;
86    /// Called when the last capability referencing `object` is dropped.
87    fn on_drop(&mut self, _object: u64, _cap: CapId) {}
88
89    /// Drain provider-side drop notifications recorded by [`Provider::on_drop`].
90    ///
91    /// The kernel owns providers, so tests and operators read the notifications
92    /// through [`Agent::take_drops`].
93    fn take_drop_notifications(&mut self) -> Vec<(u64, CapId)> {
94        Vec::new()
95    }
96}
97
98/// Observable kernel events. Tests and operators assert on these.
99#[derive(Debug, Clone, PartialEq)]
100pub enum Event {
101    /// A capability was minted.
102    CapMinted {
103        /// New capability.
104        cap: CapId,
105        /// Owning provider.
106        provider: ProviderId,
107    },
108    /// A capability handle gained a reference.
109    CapRetained(CapId),
110    /// A capability handle dropped a reference.
111    CapReleased(CapId),
112    /// The last reference to a capability went away; the provider was notified.
113    CapDropped(CapId),
114    /// A capability was revoked.
115    CapRevoked(CapId),
116    /// A lane was created.
117    LaneCreated(LaneId),
118    /// A lane was destroyed.
119    LaneDestroyed(LaneId),
120    /// A call was queued.
121    CallQueued {
122        /// Job id.
123        job: u64,
124        /// Issuing lane.
125        lane: LaneId,
126        /// Target capability.
127        cap: CapId,
128    },
129    /// A batch of jobs was dispatched to a provider (one round-trip).
130    BatchDispatched {
131        /// Provider.
132        provider: ProviderId,
133        /// Number of jobs in the batch.
134        jobs: usize,
135        /// Lanes represented in the batch.
136        lanes: BTreeSet<u64>,
137    },
138    /// A job completed.
139    JobCompleted {
140        /// Job id.
141        job: u64,
142        /// Issuing lane.
143        lane: LaneId,
144    },
145    /// A promise settled.
146    PromiseResolved(PromiseId),
147    /// A promise was rejected.
148    PromiseRejected {
149        /// Promise id.
150        promise: PromiseId,
151        /// Stable code.
152        code: String,
153    },
154    /// A message was sent on a channel.
155    ChannelSend {
156        /// Channel.
157        channel: ChannelId,
158        /// Sequence number.
159        sequence: u64,
160    },
161    /// A message was received from a channel.
162    ChannelRecv {
163        /// Channel.
164        channel: ChannelId,
165        /// Sequence number.
166        sequence: u64,
167    },
168    /// A capability was transferred through a channel.
169    CapTransferred {
170        /// Capability.
171        cap: CapId,
172        /// Channel.
173        channel: ChannelId,
174    },
175    /// A call was refused before the provider saw it.
176    CallRefused {
177        /// Issuing lane.
178        lane: LaneId,
179        /// Target capability.
180        cap: CapId,
181        /// Stable error code (`rpc/...`).
182        code: String,
183    },
184    /// A budget ceiling refused work.
185    BudgetExhausted {
186        /// Capability whose budget (or lane ceiling) fired.
187        cap: CapId,
188        /// Dimension: `call`, `byte`, `lane call`, `promises`, ...
189        dimension: String,
190    },
191    /// A tool policy refused work.
192    ToolRefused {
193        /// Capability whose policy fired.
194        cap: CapId,
195        /// Stable reason string; never contains argument values.
196        reason: String,
197    },
198}
199
200/// Point-in-time kernel counters for operators and metrics exporters.
201///
202/// Counters carry no capability ids, arguments or secret material: they are
203/// safe to export by default. Per-capability detail stays in [`Event`].
204#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
205pub struct Metrics {
206    /// Calls accepted into the queue.
207    pub calls_queued: u64,
208    /// Calls refused before dispatch.
209    pub calls_refused: u64,
210    /// Refusals caused by a budget ceiling.
211    pub budget_exhaustions: u64,
212    /// Refusals caused by tool policy.
213    pub tool_refusals: u64,
214    /// Capabilities revoked.
215    pub revocations: u64,
216    /// Promises resolved.
217    pub promises_resolved: u64,
218    /// Promises rejected.
219    pub promises_rejected: u64,
220    /// Provider round trips.
221    pub round_trips: u64,
222    /// Capability transfers through channels.
223    pub transfers: u64,
224    /// Settlements attempted on an already-settled promise.
225    pub promises_double_settled: u64,
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq)]
229enum JobState {
230    Queued,
231    Done,
232}
233
234#[derive(Debug, Clone)]
235struct Job {
236    #[allow(dead_code)]
237    id: u64,
238    lane: LaneId,
239    cap: CapId,
240    call: Call,
241    deps: Vec<PromiseId>,
242    promise: PromiseId,
243    state: JobState,
244}
245
246/// The deterministic kernel: providers, capabilities, budgets and promises.
247///
248/// It performs no I/O of its own; transports hand it envelopes and take the
249/// results back. Holding the kernel is the only way to reach a provider.
250pub struct Kernel {
251    secret: [u8; 32],
252    providers: BTreeMap<ProviderId, Box<dyn Provider>>,
253    caps: BTreeMap<CapId, CapRecord>,
254    meters: BTreeMap<CapId, BudgetMeter>,
255    promises: BTreeMap<PromiseId, PromiseRecord>,
256    lanes: BTreeMap<LaneId, Lane>,
257    channels: BTreeMap<ChannelId, Channel>,
258    jobs: BTreeMap<u64, Job>,
259    queue: VecDeque<u64>,
260    events: VecDeque<Event>,
261    /// Settled promises in settlement order, with the retained value size.
262    settled: VecDeque<(PromiseId, usize)>,
263    settled_bytes: usize,
264    metrics: Metrics,
265    round_trips: u64,
266    next_promise: u64,
267    next_job: u64,
268    next_lane: u64,
269    next_channel: u64,
270    next_serial: u64,
271    next_provider: u64,
272    now_ms: u64,
273    latency_ms: u64,
274    transfers: u64,
275}
276
277/// Control half of a capability handle: reference counting and provider
278/// notification. Keeping it separate from the kernel cell lets handles hold an
279/// `Rc<dyn CapControl>` without exposing the kernel itself.
280pub(crate) struct HandleControl {
281    cell: KernelCell,
282}
283
284impl HandleControl {
285    fn new(cell: KernelCell) -> Self {
286        Self { cell }
287    }
288}
289
290impl CapControl for HandleControl {
291    fn retain(&self, id: CapId) {
292        if let Ok(mut kernel) = self.cell.try_borrow_mut() {
293            if let Some(record) = kernel.caps.get_mut(&id) {
294                record.refs = record.refs.saturating_add(1);
295                kernel.push_event(Event::CapRetained(id));
296            }
297        }
298    }
299
300    fn release(&self, id: CapId) {
301        let dropped = {
302            let Ok(mut kernel) = self.cell.try_borrow_mut() else {
303                return;
304            };
305            let Some(record) = kernel.caps.get_mut(&id) else {
306                return;
307            };
308            record.refs = record.refs.saturating_sub(1);
309            let last = record.refs == 0;
310            kernel.push_event(Event::CapReleased(id));
311            last
312        };
313        if !dropped {
314            return;
315        }
316        let mut kernel = self.cell.borrow_mut();
317        let Some(record) = kernel.caps.remove(&id) else {
318            return;
319        };
320        kernel.meters.remove(&id);
321        kernel.push_event(Event::CapDropped(id));
322        if let Some(provider) = kernel.providers.get_mut(&record.provider) {
323            provider.on_drop(record.object, id);
324        }
325    }
326}
327
328/// Shared kernel cell.
329pub type KernelCell = Rc<RefCell<Kernel>>;
330
331/// One agent: an owner of lanes, channels, capabilities and promises.
332#[derive(Clone)]
333pub struct Agent {
334    cell: KernelCell,
335    control: Rc<dyn CapControl>,
336}
337
338impl Default for Agent {
339    fn default() -> Self {
340        Self::new()
341    }
342}
343
344impl Agent {
345    /// Create an agent kernel with an unpredictable capability secret.
346    ///
347    /// The secret comes from the operating system's CSPRNG. A clock-derived
348    /// seed would make capability ids guessable to a peer that can estimate the
349    /// start time, which defeats unforgeability.
350    pub fn new() -> Self {
351        let mut seed = [0u8; 32];
352        getrandom::fill(&mut seed).expect("the operating system CSPRNG must be available");
353        Self::with_secret(seed)
354    }
355
356    /// Create an agent with a fixed secret (tests, deterministic replays).
357    pub fn with_secret(secret: [u8; 32]) -> Self {
358        let kernel = Kernel {
359            secret,
360            providers: BTreeMap::new(),
361            caps: BTreeMap::new(),
362            meters: BTreeMap::new(),
363            promises: BTreeMap::new(),
364            lanes: BTreeMap::new(),
365            channels: BTreeMap::new(),
366            jobs: BTreeMap::new(),
367            queue: VecDeque::new(),
368            events: VecDeque::new(),
369            settled: VecDeque::new(),
370            settled_bytes: 0,
371            metrics: Metrics::default(),
372            round_trips: 0,
373            next_promise: 0,
374            next_job: 0,
375            next_lane: 0,
376            next_channel: 0,
377            next_serial: 0,
378            next_provider: 0,
379            now_ms: 0,
380            latency_ms: 1,
381            transfers: 0,
382        };
383        let cell: KernelCell = Rc::new(RefCell::new(kernel));
384        let control: Rc<dyn CapControl> = Rc::new(HandleControl::new(Rc::clone(&cell)));
385        Agent { cell, control }
386    }
387
388    /// The shared cell, for transports and adapters that need direct access.
389    pub fn cell(&self) -> &KernelCell {
390        &self.cell
391    }
392
393    /// Register a provider and return its id.
394    pub fn register(&self, provider: Box<dyn Provider>) -> ProviderId {
395        let mut kernel = self.cell.borrow_mut();
396        let id = ProviderId(kernel.next_provider);
397        kernel.next_provider += 1;
398        kernel.providers.insert(id, provider);
399        id
400    }
401
402    /// Configure the simulated provider latency (milliseconds per batch).
403    pub fn set_latency(&self, latency_ms: u64) {
404        self.cell.borrow_mut().latency_ms = latency_ms;
405    }
406
407    /// Mint a first-generation capability.
408    pub fn mint(&self, provider: ProviderId, object: u64, attrs: CapAttrs) -> Result<CapHandle> {
409        attrs.budget.check()?;
410        if attrs.methods.is_empty() {
411            return Err(Error::InvalidArgument("capability grants no methods"));
412        }
413        let mut kernel = self.cell.borrow_mut();
414        if !kernel.providers.contains_key(&provider) {
415            return Err(Error::InvalidArgument("unknown provider"));
416        }
417        let serial = kernel.next_serial;
418        kernel.next_serial += 1;
419        let id = CapId::derive(&kernel.secret, serial, None);
420        let record = CapRecord::new(id, provider, object, attrs.clone());
421        kernel.meters.insert(id, BudgetMeter::new(attrs.budget));
422        kernel.caps.insert(id, record);
423        kernel.push_event(Event::CapMinted { cap: id, provider });
424        Ok(CapHandle::new(Rc::clone(&self.control), id))
425    }
426
427    /// Attenuate a capability. Refuses any widening.
428    pub fn attenuate(&self, parent: &CapHandle, attrs: CapAttrs) -> Result<CapHandle> {
429        let parent_id = parent.id();
430        let mut kernel = self.cell.borrow_mut();
431        let record = kernel
432            .caps
433            .get(&parent_id)
434            .ok_or(Error::UnknownCapability(cap_token(parent_id)))?
435            .clone();
436        if record.revoked {
437            return Err(Error::Revoked(cap_token(parent_id)));
438        }
439        attrs.budget.check()?;
440        let attrs = record.attrs.attenuate(attrs)?;
441        let serial = kernel.next_serial;
442        kernel.next_serial += 1;
443        let id = CapId::derive(&kernel.secret, serial, Some(&parent_id));
444        let mut child = CapRecord::new(id, record.provider, record.object, attrs.clone());
445        child.parent = Some(parent_id);
446        kernel.meters.insert(id, BudgetMeter::new(attrs.budget));
447        kernel.caps.insert(id, child);
448        kernel.push_event(Event::CapMinted {
449            cap: id,
450            provider: record.provider,
451        });
452        Ok(CapHandle::new(Rc::clone(&self.control), id))
453    }
454
455    /// Import a capability id without owning a reference.
456    ///
457    /// This is how a peer handle is reconstructed from the wire: the id must
458    /// already exist in this kernel, otherwise the first call fails with
459    /// `rpc/unknown-capability`. Importing never proves authority; the registry
460    /// does.
461    pub fn import(&self, id: CapId) -> CapHandle {
462        CapHandle::borrowed(Rc::clone(&self.control), id)
463    }
464
465    /// Revoke a capability and every capability derived from it.
466    pub fn revoke(&self, cap: &CapHandle) {
467        let mut kernel = self.cell.borrow_mut();
468        let mut doomed = BTreeSet::new();
469        let mut stack = vec![cap.id()];
470        while let Some(id) = stack.pop() {
471            if !doomed.insert(id) {
472                continue;
473            }
474            for (child, record) in &kernel.caps {
475                if record.parent == Some(id) {
476                    stack.push(*child);
477                }
478            }
479        }
480        for id in doomed {
481            if let Some(record) = kernel.caps.get_mut(&id) {
482                record.revoked = true;
483            }
484            kernel.push_event(Event::CapRevoked(id));
485            kernel.metrics.revocations = kernel.metrics.revocations.saturating_add(1);
486        }
487    }
488
489    /// Create a lane.
490    pub fn create_lane(&self, name: impl Into<String>, budget: Budget) -> Result<LaneId> {
491        budget.check()?;
492        let mut kernel = self.cell.borrow_mut();
493        let open = kernel.lanes.values().filter(|lane| !lane.closed).count();
494        if open >= MAX_LANES {
495            return Err(Error::BudgetExceeded("lanes"));
496        }
497        let id = LaneId(kernel.next_lane);
498        kernel.next_lane += 1;
499        kernel.lanes.insert(id, Lane::new(id, name, budget));
500        kernel.push_event(Event::LaneCreated(id));
501        Ok(id)
502    }
503
504    /// Attenuate a lane's budget.
505    pub fn attenuate_lane(&self, lane: LaneId, budget: Budget) -> Result<()> {
506        budget.check()?;
507        let mut kernel = self.cell.borrow_mut();
508        let record = kernel
509            .lanes
510            .get_mut(&lane)
511            .ok_or(Error::UnknownLane(lane.0))?;
512        record.attenuate(budget)
513    }
514
515    /// Destroy a lane and drop its queued work.
516    ///
517    /// Queued jobs are removed from the dispatch queue and their promises are
518    /// rejected, so a destroyed lane cannot keep spending budget or produce side
519    /// effects after the fact.
520    pub fn destroy_lane(&self, lane: LaneId) -> Result<()> {
521        let mut kernel = self.cell.borrow_mut();
522        {
523            let record = kernel
524                .lanes
525                .get_mut(&lane)
526                .ok_or(Error::UnknownLane(lane.0))?;
527            record.destroy();
528        }
529        let doomed: Vec<u64> = kernel
530            .jobs
531            .iter()
532            .filter(|(_, job)| job.lane == lane && job.state == JobState::Queued)
533            .map(|(id, _)| *id)
534            .collect();
535        kernel.queue.retain(|id| !doomed.contains(id));
536        for job_id in doomed {
537            kernel.fail_job(job_id, Error::UnknownLane(lane.0));
538        }
539        // Reclaim the dispatch records this lane owns. Promise records stay:
540        // a caller that holds a handle must still see the settled value (or a
541        // rejected one) rather than an unrelated `unknown-promise`. The promise
542        // ceiling bounds them; the kernel itself is the lifetime.
543        kernel.jobs.retain(|_, job| job.lane != lane);
544        for channel in kernel.channels.values_mut() {
545            channel.subscribers.remove(&lane.0);
546        }
547        kernel.lanes.remove(&lane);
548        kernel.push_event(Event::LaneDestroyed(lane));
549        Ok(())
550    }
551
552    /// Destroy a channel and remove it from every lane's subscription set.
553    pub fn destroy_channel(&self, channel: ChannelId) -> Result<()> {
554        let mut kernel = self.cell.borrow_mut();
555        kernel
556            .channels
557            .remove(&channel)
558            .ok_or(Error::UnknownChannel(channel.0))?;
559        for lane in kernel.lanes.values_mut() {
560            lane.channels.remove(&channel.0);
561        }
562        Ok(())
563    }
564
565    /// Create a channel.
566    pub fn create_channel(
567        &self,
568        name: impl Into<String>,
569        direction: Direction,
570    ) -> Result<ChannelId> {
571        let mut kernel = self.cell.borrow_mut();
572        if kernel.channels.len() >= MAX_CHANNELS {
573            return Err(Error::BudgetExceeded("channels"));
574        }
575        let id = ChannelId(kernel.next_channel);
576        kernel.next_channel += 1;
577        kernel
578            .channels
579            .insert(id, Channel::new(id, name, direction));
580        Ok(id)
581    }
582
583    /// Subscribe a lane to a channel.
584    pub fn subscribe(&self, lane: LaneId, channel: ChannelId) -> Result<()> {
585        let mut kernel = self.cell.borrow_mut();
586        let lane_record = kernel.lanes.get(&lane).ok_or(Error::UnknownLane(lane.0))?;
587        if lane_record.closed {
588            return Err(Error::UnknownLane(lane.0));
589        }
590        let record = kernel
591            .channels
592            .get_mut(&channel)
593            .ok_or(Error::UnknownChannel(channel.0))?;
594        record.subscribers.insert(lane.0);
595        if let Some(lane) = kernel.lanes.get_mut(&lane) {
596            lane.channels.insert(channel.0);
597        }
598        Ok(())
599    }
600
601    /// Send a message, transferring capabilities with it.
602    pub fn send(
603        &self,
604        lane: LaneId,
605        channel: ChannelId,
606        payload: Value,
607        caps: &[CapHandle],
608    ) -> Result<u64> {
609        let mut kernel = self.cell.borrow_mut();
610        let lane_record = kernel.lanes.get(&lane).ok_or(Error::UnknownLane(lane.0))?;
611        if lane_record.closed {
612            return Err(Error::UnknownLane(lane.0));
613        }
614        let mut cap_ids = Vec::with_capacity(caps.len());
615        for cap in caps {
616            let record = kernel
617                .caps
618                .get(&cap.id())
619                .ok_or(Error::InvalidArgument("unknown capability"))?;
620            if record.revoked {
621                return Err(Error::Revoked(cap_token(cap.id())));
622            }
623            // A channel shares (borrows) the capability: the id travels with
624            // the message and the sender keeps its handle. No reference is
625            // created, so a channel cannot keep a capability alive after its
626            // holder drops it (see `channel.rs`).
627            cap_ids.push(cap.id());
628        }
629        let record = kernel
630            .channels
631            .get_mut(&channel)
632            .ok_or(Error::UnknownChannel(channel.0))?;
633        let message = record.send(lane, payload, cap_ids.clone())?;
634        kernel.push_event(Event::ChannelSend {
635            channel,
636            sequence: message.sequence,
637        });
638        for id in cap_ids {
639            kernel.transfers += 1;
640            kernel.push_event(Event::CapTransferred { cap: id, channel });
641        }
642        Ok(message.sequence)
643    }
644
645    /// Receive the next message on a channel.
646    pub fn recv(&self, channel: ChannelId) -> Result<Option<Message>> {
647        let mut kernel = self.cell.borrow_mut();
648        let record = kernel
649            .channels
650            .get_mut(&channel)
651            .ok_or(Error::UnknownChannel(channel.0))?;
652        let message = record.recv()?;
653        if let Some(message) = &message {
654            kernel.push_event(Event::ChannelRecv {
655                channel,
656                sequence: message.sequence,
657            });
658        }
659        Ok(message)
660    }
661
662    /// Issue a call. The returned promise is pending until the kernel runs.
663    ///
664    /// Refusals are recorded as [`Event::CallRefused`] plus a specific
665    /// budget/tool event, and in [`Metrics`]. Argument values never enter the
666    /// event log.
667    pub fn call(
668        &self,
669        lane: LaneId,
670        cap: &CapHandle,
671        method: &str,
672        args: Vec<Arg>,
673    ) -> Result<PromiseHandle> {
674        let cap_id = cap.id();
675        let result = self.call_inner(lane, cap, method, args);
676        if let Err(error) = &result {
677            let mut kernel = self.cell.borrow_mut();
678            kernel.metrics.calls_refused = kernel.metrics.calls_refused.saturating_add(1);
679            kernel.push_event(Event::CallRefused {
680                lane,
681                cap: cap_id,
682                code: error.code().to_string(),
683            });
684            match error {
685                Error::BudgetExceeded(dimension) => {
686                    kernel.metrics.budget_exhaustions =
687                        kernel.metrics.budget_exhaustions.saturating_add(1);
688                    kernel.push_event(Event::BudgetExhausted {
689                        cap: cap_id,
690                        dimension: (*dimension).to_string(),
691                    });
692                }
693                Error::ToolPolicy(reason) => {
694                    kernel.metrics.tool_refusals = kernel.metrics.tool_refusals.saturating_add(1);
695                    kernel.push_event(Event::ToolRefused {
696                        cap: cap_id,
697                        reason: (*reason).to_string(),
698                    });
699                }
700                _ => {}
701            }
702        }
703        result
704    }
705
706    fn call_inner(
707        &self,
708        lane: LaneId,
709        cap: &CapHandle,
710        method: &str,
711        args: Vec<Arg>,
712    ) -> Result<PromiseHandle> {
713        let mut kernel = self.cell.borrow_mut();
714        let cap_id = cap.id();
715        let record = kernel
716            .caps
717            .get(&cap_id)
718            .ok_or(Error::UnknownCapability(cap_token(cap_id)))?
719            .clone();
720        if record.revoked {
721            return Err(Error::Revoked(cap_token(cap_id)));
722        }
723        let (lane_closed, lane_capacity, lane_budget) = {
724            let lane_record = kernel.lanes.get(&lane).ok_or(Error::UnknownLane(lane.0))?;
725            (
726                lane_record.closed,
727                lane_record.has_capacity(),
728                lane_record.budget,
729            )
730        };
731        if lane_closed {
732            return Err(Error::UnknownLane(lane.0));
733        }
734        if !lane_capacity {
735            return Err(Error::BudgetExceeded("lane call"));
736        }
737        if !record.attrs.grants(method) {
738            return Err(Error::MethodNotGranted {
739                method: method.to_string(),
740            });
741        }
742        let provider = kernel
743            .providers
744            .get(&record.provider)
745            .ok_or(Error::InvalidArgument("provider disappeared"))?;
746        if !provider.has_method(method) {
747            return Err(Error::MethodNotGranted {
748                method: method.to_string(),
749            });
750        }
751        if record.attrs.read_only && provider.mutating(method) {
752            return Err(Error::ReadOnly);
753        }
754        // Path scope is enforced here, before the provider sees the call.
755        if let Some(scope) = &record.attrs.path_scope {
756            for arg in &args {
757                if let Arg::Value(Value::Path(path)) = arg {
758                    if !crate::cap::is_path_prefix(scope, path) {
759                        return Err(Error::PathNotGranted(path.clone()));
760                    }
761                }
762            }
763        }
764        // Tool policy is enforced here too.
765        if let Some(policy) = &record.attrs.tool_policy {
766            if !policy.allowed.contains(method) {
767                return Err(Error::ToolPolicy("tool not allowed"));
768            }
769            // Count every argument shape, recursively: a nested list or map
770            // must not slip past the tool's payload ceiling.
771            let bytes: u64 = args.iter().map(Arg::wire_size).sum();
772            if bytes > policy.max_arg_bytes {
773                return Err(Error::ToolPolicy("argument too large"));
774            }
775        }
776        let call_bytes: u64 = args.iter().map(|arg| arg.wire_size()).sum();
777        let effective = record.attrs.budget.intersect(&lane_budget);
778        if call_bytes > effective.max_bytes {
779            return Err(Error::BudgetExceeded("byte"));
780        }
781        // Registry ceilings are checked before charging: a refused call must
782        // not consume budget, and the job/promise maps must stay finite even
783        // when the caller never destroys its lane.
784        if kernel.promises.len() >= MAX_PROMISES {
785            return Err(Error::BudgetExceeded("promises"));
786        }
787        if kernel.jobs.len() >= MAX_PROMISES {
788            return Err(Error::BudgetExceeded("jobs"));
789        }
790        let meter = kernel
791            .meters
792            .get_mut(&cap_id)
793            .ok_or(Error::InvalidArgument("capability has no meter"))?;
794        meter.charge(call_bytes)?;
795
796        let promise_id = PromiseId(kernel.next_promise);
797        kernel.next_promise += 1;
798        kernel
799            .promises
800            .insert(promise_id, PromiseRecord::new(promise_id, lane.0));
801
802        let job_id = kernel.next_job;
803        kernel.next_job += 1;
804        let deps: Vec<PromiseId> = args.iter().filter_map(|arg| arg.promise()).collect();
805        for dep in &deps {
806            if !kernel.promises.contains_key(dep) {
807                return Err(Error::UnknownPromise(dep.0));
808            }
809            if let Some(record) = kernel.promises.get_mut(dep) {
810                record.waiters += 1;
811            }
812        }
813        let call = Call {
814            method: method.to_string(),
815            args,
816            path_scope: record.attrs.path_scope.clone(),
817            redact: record.attrs.redact,
818            read_only: record.attrs.read_only,
819            tool_allows_process: record
820                .attrs
821                .tool_policy
822                .as_ref()
823                .map(|policy| policy.allow_process)
824                .unwrap_or(false),
825            budget: effective,
826        };
827        let job = Job {
828            id: job_id,
829            lane,
830            cap: cap_id,
831            call,
832            deps,
833            promise: promise_id,
834            state: JobState::Queued,
835        };
836        kernel.jobs.insert(job_id, job);
837        kernel.queue.push_back(job_id);
838        if let Some(lane_record) = kernel.lanes.get_mut(&lane) {
839            lane_record.pending += 1;
840        }
841        kernel.push_event(Event::CallQueued {
842            job: job_id,
843            lane,
844            cap: cap_id,
845        });
846        kernel.metrics.calls_queued = kernel.metrics.calls_queued.saturating_add(1);
847        // Budgets are charged here, before the call is queued. Dispatch
848        // re-checks existence and revocation; providers receive the effective
849        // budget in `Call` and tool adapters must enforce their wall ceiling.
850        //
851        // The handle below is the only owner of this promise now; set the
852        // reference immediately before constructing it, so an earlier error
853        // leaves an unreferenced (and therefore trimmable) record.
854        if let Some(record) = kernel.promises.get_mut(&promise_id) {
855            record.handles = 1;
856        }
857        Ok(PromiseHandle {
858            agent: self.clone(),
859            id: promise_id,
860        })
861    }
862
863    /// Run until no job is queued. Returns the number of provider round-trips.
864    pub fn run(&self) -> u64 {
865        loop {
866            let batch = {
867                let mut kernel = self.cell.borrow_mut();
868                if kernel.queue.is_empty() {
869                    kernel.trim_settled();
870                    return kernel.round_trips;
871                }
872                match kernel.next_batch() {
873                    Ok(batch) => batch,
874                    Err(stuck) => {
875                        // Only the jobs that cannot be ordered are failed; work
876                        // for other providers stays queued.
877                        for job_id in stuck {
878                            let error = kernel
879                                .jobs
880                                .get(&job_id)
881                                .map(|job| Error::UnknownCapability(cap_token(job.cap)))
882                                .unwrap_or(Error::PipelinedCycle);
883                            kernel.fail_job(job_id, error);
884                        }
885                        continue;
886                    }
887                }
888            };
889            self.dispatch(batch);
890        }
891    }
892
893    /// Round-trips observed so far.
894    pub fn round_trips(&self) -> u64 {
895        self.cell.borrow().round_trips
896    }
897
898    /// Await a promise, running the kernel until it settles.
899    pub fn await_promise(&self, promise: &PromiseHandle) -> Result<Value> {
900        loop {
901            {
902                let kernel = self.cell.borrow();
903                let record = kernel
904                    .promises
905                    .get(&promise.id)
906                    .ok_or(Error::UnknownPromise(promise.id.0))?;
907                if record.state.is_settled() {
908                    return record.value();
909                }
910            }
911            if self.cell.borrow().queue.is_empty() {
912                return Err(Error::UnresolvablePromise(promise.id.0));
913            }
914            self.run();
915        }
916    }
917
918    /// Resolve a pending promise manually (used by peers that answer out of band).
919    pub fn resolve(&self, promise: &PromiseHandle, value: Value) -> Result<()> {
920        let mut kernel = self.cell.borrow_mut();
921        let record = kernel
922            .promises
923            .get_mut(&promise.id)
924            .ok_or(Error::UnknownPromise(promise.id.0))?;
925        let settled = record.resolve(value);
926        if settled.is_err() {
927            kernel.metrics.promises_double_settled =
928                kernel.metrics.promises_double_settled.saturating_add(1);
929        }
930        settled?;
931        kernel.push_event(Event::PromiseResolved(promise.id));
932        kernel.metrics.promises_resolved = kernel.metrics.promises_resolved.saturating_add(1);
933        kernel.record_settled(promise.id);
934        Ok(())
935    }
936
937    /// Cancel a promise.
938    ///
939    /// A job that has not been dispatched yet is removed from the queue, so a
940    /// cancellation really prevents the provider call (and its side effects). A
941    /// job already in flight cannot be stopped; its result is discarded.
942    pub fn cancel(&self, promise: &PromiseHandle) -> Result<()> {
943        let mut kernel = self.cell.borrow_mut();
944        {
945            let record = kernel
946                .promises
947                .get_mut(&promise.id)
948                .ok_or(Error::UnknownPromise(promise.id.0))?;
949            record.cancel()?;
950        }
951        let queued: Option<u64> = kernel
952            .jobs
953            .iter()
954            .find(|(_, job)| job.promise == promise.id && job.state == JobState::Queued)
955            .map(|(id, _)| *id);
956        if let Some(job_id) = queued {
957            kernel.queue.retain(|id| *id != job_id);
958            let lane = kernel.jobs.get(&job_id).map(|job| job.lane);
959            kernel.jobs.remove(&job_id);
960            kernel.record_settled(promise.id);
961            if let Some(lane) = lane {
962                if let Some(lane_record) = kernel.lanes.get_mut(&lane) {
963                    lane_record.complete();
964                }
965            }
966        }
967        Ok(())
968    }
969
970    /// Reference a promise record from a handle.
971    fn retain_promise(&self, id: PromiseId) {
972        if let Ok(mut kernel) = self.cell.try_borrow_mut() {
973            if let Some(record) = kernel.promises.get_mut(&id) {
974                record.handles = record.handles.saturating_add(1);
975            }
976        }
977    }
978
979    /// Drop a handle's reference to a promise record.
980    fn release_promise(&self, id: PromiseId) {
981        if let Ok(mut kernel) = self.cell.try_borrow_mut() {
982            if let Some(record) = kernel.promises.get_mut(&id) {
983                record.handles = record.handles.saturating_sub(1);
984            }
985        }
986    }
987
988    /// Drain drop notifications recorded by one provider.
989    pub fn take_drops(&self, provider: ProviderId) -> Vec<(u64, CapId)> {
990        self.cell
991            .borrow_mut()
992            .providers
993            .get_mut(&provider)
994            .map(|provider| provider.take_drop_notifications())
995            .unwrap_or_default()
996    }
997
998    /// The event log.
999    pub fn events(&self) -> Vec<Event> {
1000        self.cell.borrow().events.iter().cloned().collect()
1001    }
1002
1003    /// Point-in-time counters. Contains no capability ids or payloads.
1004    pub fn metrics(&self) -> Metrics {
1005        let kernel = self.cell.borrow();
1006        Metrics {
1007            round_trips: kernel.round_trips,
1008            transfers: kernel.transfers,
1009            ..kernel.metrics
1010        }
1011    }
1012
1013    /// Number of live promise records (pending plus retained settled).
1014    pub fn promise_count(&self) -> usize {
1015        self.cell.borrow().promises.len()
1016    }
1017
1018    /// Number of live job records (queued or in flight).
1019    pub fn job_count(&self) -> usize {
1020        self.cell.borrow().jobs.len()
1021    }
1022
1023    /// Lane statistics.
1024    pub fn lane(&self, lane: LaneId) -> Result<Lane> {
1025        self.cell
1026            .borrow()
1027            .lanes
1028            .get(&lane)
1029            .cloned()
1030            .ok_or(Error::UnknownLane(lane.0))
1031    }
1032
1033    /// Channel statistics.
1034    pub fn channel(&self, channel: ChannelId) -> Result<Channel> {
1035        self.cell
1036            .borrow()
1037            .channels
1038            .get(&channel)
1039            .cloned()
1040            .ok_or(Error::UnknownChannel(channel.0))
1041    }
1042
1043    /// Capability attributes, for tests and policy tooling.
1044    pub fn cap_attrs(&self, cap: &CapHandle) -> Result<CapAttrs> {
1045        self.cell
1046            .borrow()
1047            .caps
1048            .get(&cap.id())
1049            .map(|record| record.attrs.clone())
1050            .ok_or(Error::UnknownCapability(cap_token(cap.id())))
1051    }
1052
1053    fn dispatch(&self, batch: Vec<u64>) {
1054        // No kernel borrow is held while a provider runs: a provider may hold an
1055        // `Agent` and call back into the kernel, and a re-entrant borrow would
1056        // panic (or, with `try_borrow_mut`, silently drop a refcount update).
1057        // Each phase takes a short borrow and returns owned data.
1058        let (provider_id, provider) = {
1059            let mut kernel = self.cell.borrow_mut();
1060            kernel.round_trips += 1;
1061            kernel.now_ms = kernel.now_ms.saturating_add(kernel.latency_ms);
1062            let Some(first) = batch.first().and_then(|id| kernel.jobs.get(id)) else {
1063                return;
1064            };
1065            let provider_id = kernel
1066                .caps
1067                .get(&first.cap)
1068                .map(|record| record.provider)
1069                .unwrap_or(ProviderId(u64::MAX));
1070            let lanes: BTreeSet<u64> = batch
1071                .iter()
1072                .filter_map(|id| kernel.jobs.get(id).map(|job| job.lane.0))
1073                .collect();
1074            kernel.push_event(Event::BatchDispatched {
1075                provider: provider_id,
1076                jobs: batch.len(),
1077                lanes,
1078            });
1079            let provider = kernel.providers.remove(&provider_id);
1080            (provider_id, provider)
1081        };
1082        let Some(mut provider) = provider else {
1083            let mut kernel = self.cell.borrow_mut();
1084            for job_id in batch {
1085                kernel.fail_job(
1086                    job_id,
1087                    Error::Provider {
1088                        code: "rpc/provider".to_string(),
1089                    },
1090                );
1091            }
1092            return;
1093        };
1094
1095        for job_id in &batch {
1096            // Phase 1: decide, under a read borrow.
1097            let prepared = {
1098                let kernel = self.cell.borrow();
1099                let Some(job) = kernel.jobs.get(job_id).cloned() else {
1100                    continue;
1101                };
1102                if job.state == JobState::Done {
1103                    continue;
1104                }
1105                let mut args = Vec::with_capacity(job.call.args.len());
1106                let mut failure = None;
1107                for arg in &job.call.args {
1108                    match arg {
1109                        Arg::Promise(id) => match kernel.promises.get(id) {
1110                            Some(record) if record.state.is_settled() => match record.value() {
1111                                Ok(value) => args.push(Arg::Value(value)),
1112                                Err(error) => {
1113                                    failure = Some(error);
1114                                    break;
1115                                }
1116                            },
1117                            Some(_) => {
1118                                failure = Some(Error::UnresolvablePromise(id.0));
1119                                break;
1120                            }
1121                            None => {
1122                                failure = Some(Error::UnknownPromise(id.0));
1123                                break;
1124                            }
1125                        },
1126                        other => args.push(other.clone()),
1127                    }
1128                }
1129                // Authority is re-checked at dispatch: a capability revoked
1130                // after the call was queued must not reach the provider.
1131                let record = kernel.caps.get(&job.cap).cloned();
1132                let failure = match &record {
1133                    None => Some(Error::UnknownCapability(cap_token(job.cap))),
1134                    Some(record) if record.revoked => Some(Error::Revoked(cap_token(record.id))),
1135                    Some(_) => failure,
1136                };
1137                Some((
1138                    job,
1139                    args,
1140                    failure,
1141                    record.map(|record| record.object).unwrap_or(0),
1142                ))
1143            };
1144            let Some((job, args, failure, object)) = prepared else {
1145                continue;
1146            };
1147
1148            // Phase 2: run provider code with no borrow held.
1149            let outcome = match failure {
1150                Some(error) => Err(error),
1151                None => {
1152                    let call = Call {
1153                        args,
1154                        ..job.call.clone()
1155                    };
1156                    provider.invoke(object, &call).map(Response::into_value)
1157                }
1158            };
1159
1160            // Phase 3: settle under a write borrow.
1161            let mut kernel = self.cell.borrow_mut();
1162            let mut settled_now = false;
1163            match &outcome {
1164                Ok(value) => {
1165                    if let Some(record) = kernel.promises.get_mut(&job.promise) {
1166                        if record.resolve(value.clone()).is_ok() {
1167                            settled_now = true;
1168                        } else {
1169                            kernel.metrics.promises_double_settled =
1170                                kernel.metrics.promises_double_settled.saturating_add(1);
1171                        }
1172                    }
1173                    kernel.push_event(Event::PromiseResolved(job.promise));
1174                    kernel.metrics.promises_resolved =
1175                        kernel.metrics.promises_resolved.saturating_add(1);
1176                }
1177                Err(error) => {
1178                    if let Some(record) = kernel.promises.get_mut(&job.promise) {
1179                        if record.reject(error.clone()).is_ok() {
1180                            settled_now = true;
1181                        } else {
1182                            kernel.metrics.promises_double_settled =
1183                                kernel.metrics.promises_double_settled.saturating_add(1);
1184                        }
1185                    }
1186                    kernel.push_event(Event::PromiseRejected {
1187                        promise: job.promise,
1188                        code: error.code().to_string(),
1189                    });
1190                    kernel.metrics.promises_rejected =
1191                        kernel.metrics.promises_rejected.saturating_add(1);
1192                }
1193            }
1194            if settled_now {
1195                kernel.record_settled(job.promise);
1196            }
1197            // A completed job no longer needs its Call or arguments. Removing it
1198            // here is what makes the jobs ceiling a transient bound rather than
1199            // a lifetime quota.
1200            kernel.jobs.remove(job_id);
1201            if let Some(lane_record) = kernel.lanes.get_mut(&job.lane) {
1202                lane_record.complete();
1203            }
1204            kernel.push_event(Event::JobCompleted {
1205                job: *job_id,
1206                lane: job.lane,
1207            });
1208        }
1209
1210        self.cell
1211            .borrow_mut()
1212            .providers
1213            .insert(provider_id, provider);
1214    }
1215}
1216
1217impl Kernel {
1218    /// Append an event, dropping the oldest when the log is full.
1219    fn push_event(&mut self, event: Event) {
1220        if self.events.len() >= MAX_EVENTS {
1221            self.events.pop_front();
1222        }
1223        self.events.push_back(event);
1224    }
1225
1226    /// Remember a settled promise for bounded retention.
1227    fn record_settled(&mut self, id: PromiseId) {
1228        let size = self
1229            .promises
1230            .get(&id)
1231            .and_then(|record| record.value().ok())
1232            .map(|value| value.wire_size() as usize)
1233            .unwrap_or(0);
1234        self.settled.push_back((id, size));
1235        self.settled_bytes = self.settled_bytes.saturating_add(size);
1236    }
1237
1238    /// Trim settled promises after the queue drains.
1239    ///
1240    /// Only called with no queued job, so no dependency can still need an
1241    /// evicted record. A record with a live handle is pinned and never evicted;
1242    /// if every record is pinned the limits are a target, not a reason to drop
1243    /// a result the caller can still read.
1244    fn trim_settled(&mut self) {
1245        while (self.settled.len() > MAX_SETTLED_PROMISES || self.settled_bytes > MAX_SETTLED_BYTES)
1246            && self.settled.len() > 1
1247        {
1248            let mut victim = None;
1249            for (index, (id, _)) in self.settled.iter().enumerate() {
1250                let pinned = self
1251                    .promises
1252                    .get(id)
1253                    .map(|record| record.handles > 0)
1254                    .unwrap_or(false);
1255                if !pinned {
1256                    victim = Some(index);
1257                    break;
1258                }
1259            }
1260            let Some(index) = victim else {
1261                break;
1262            };
1263            let Some((id, size)) = self.settled.remove(index) else {
1264                break;
1265            };
1266            self.settled_bytes = self.settled_bytes.saturating_sub(size);
1267            self.promises.remove(&id);
1268        }
1269    }
1270
1271    /// Build the next dispatch batch: all queued jobs for the head job's
1272    /// provider, ordered so promise producers precede consumers.
1273    fn next_batch(&mut self) -> std::result::Result<Vec<u64>, Vec<u64>> {
1274        let Some(head) = self.queue.front().copied() else {
1275            return Ok(Vec::new());
1276        };
1277        let Some(head_job) = self.jobs.get(&head) else {
1278            self.queue.pop_front();
1279            return Ok(Vec::new());
1280        };
1281        let Some(provider) = self.caps.get(&head_job.cap).map(|record| record.provider) else {
1282            // The capability vanished; fail this job and let the caller retry.
1283            self.queue.pop_front();
1284            return Err(vec![head]);
1285        };
1286        let mut candidates: Vec<u64> = self
1287            .queue
1288            .iter()
1289            .copied()
1290            .filter(|id| {
1291                self.jobs
1292                    .get(id)
1293                    .and_then(|job| self.caps.get(&job.cap))
1294                    .map(|record| record.provider == provider)
1295                    .unwrap_or(false)
1296            })
1297            .collect();
1298        // Producers first: order by promise dependencies inside the batch.
1299        let mut ordered: Vec<u64> = Vec::new();
1300        let mut pending = candidates.clone();
1301        let mut progress = true;
1302        while progress && !pending.is_empty() {
1303            progress = false;
1304            let mut index = 0;
1305            while index < pending.len() {
1306                let id = pending[index];
1307                let job = self.jobs.get(&id).cloned();
1308                let ready = match &job {
1309                    Some(job) => job.deps.iter().all(|dep| {
1310                        // Resolved already, or produced by an ordered job.
1311                        match self.promises.get(dep) {
1312                            Some(record) if record.state.is_settled() => true,
1313                            _ => ordered
1314                                .iter()
1315                                .any(|other| self.jobs.get(other).map(|j| j.promise) == Some(*dep)),
1316                        }
1317                    }),
1318                    None => true,
1319                };
1320                if ready {
1321                    ordered.push(id);
1322                    pending.remove(index);
1323                    progress = true;
1324                } else {
1325                    index += 1;
1326                }
1327            }
1328        }
1329        if !pending.is_empty() {
1330            // Unorderable: the remaining jobs form (or depend on) a cycle.
1331            for id in &pending {
1332                self.queue.retain(|queued| queued != id);
1333            }
1334            return Err(pending);
1335        }
1336        candidates.clear();
1337        self.queue.retain(|id| !ordered.contains(id));
1338        Ok(ordered)
1339    }
1340
1341    fn fail_job(&mut self, job_id: u64, error: Error) {
1342        if let Some(job) = self.jobs.get_mut(&job_id) {
1343            job.state = JobState::Done;
1344            let promise = job.promise;
1345            let lane = job.lane;
1346            let code = error.code().to_string();
1347            let mut settled_now = false;
1348            if let Some(record) = self.promises.get_mut(&promise) {
1349                if record.reject(error).is_ok() {
1350                    settled_now = true;
1351                } else {
1352                    self.metrics.promises_double_settled =
1353                        self.metrics.promises_double_settled.saturating_add(1);
1354                }
1355            }
1356            if let Some(lane_record) = self.lanes.get_mut(&lane) {
1357                lane_record.complete();
1358            }
1359            self.push_event(Event::PromiseRejected { promise, code });
1360            self.metrics.promises_rejected = self.metrics.promises_rejected.saturating_add(1);
1361            if settled_now {
1362                self.record_settled(promise);
1363            }
1364            self.jobs.remove(&job_id);
1365            self.push_event(Event::JobCompleted { job: job_id, lane });
1366        }
1367    }
1368}
1369
1370fn pad128(id: CapId) -> [u8; 16] {
1371    id.0
1372}
1373
1374fn cap_token(id: CapId) -> crate::cap::CapToken {
1375    crate::cap::CapToken(u128::from_be_bytes(pad128(id)))
1376}
1377
1378/// A handle to a pending or settled result.
1379pub struct PromiseHandle {
1380    agent: Agent,
1381    id: PromiseId,
1382}
1383
1384impl Clone for PromiseHandle {
1385    fn clone(&self) -> Self {
1386        self.agent.retain_promise(self.id);
1387        Self {
1388            agent: self.agent.clone(),
1389            id: self.id,
1390        }
1391    }
1392}
1393
1394impl Drop for PromiseHandle {
1395    fn drop(&mut self) {
1396        self.agent.release_promise(self.id);
1397    }
1398}
1399
1400impl PromiseHandle {
1401    /// Promise id.
1402    pub fn id(&self) -> PromiseId {
1403        self.id
1404    }
1405
1406    /// The owning agent.
1407    pub fn agent(&self) -> &Agent {
1408        &self.agent
1409    }
1410
1411    /// Await the value, running the kernel as needed.
1412    pub fn value(&self) -> Result<Value> {
1413        self.agent.await_promise(self)
1414    }
1415
1416    /// The settled value without running the kernel.
1417    pub fn peek(&self) -> Result<Value> {
1418        self.agent
1419            .cell
1420            .borrow()
1421            .promises
1422            .get(&self.id)
1423            .ok_or(Error::UnknownPromise(self.id.0))?
1424            .value()
1425    }
1426
1427    /// Whether the promise has settled.
1428    pub fn is_settled(&self) -> bool {
1429        self.agent
1430            .cell
1431            .borrow()
1432            .promises
1433            .get(&self.id)
1434            .map(|record| record.state.is_settled())
1435            .unwrap_or(false)
1436    }
1437}
1438
1439impl std::fmt::Debug for PromiseHandle {
1440    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1441        f.debug_struct("PromiseHandle")
1442            .field("id", &self.id)
1443            .field("settled", &self.is_settled())
1444            .finish()
1445    }
1446}
1447
1448/// Tool policy helper re-exported for callers building attenuated tools.
1449pub fn tool_policy_for(methods: &[&str]) -> ToolPolicy {
1450    ToolPolicy::allow(methods)
1451}
1452
1453/// Promise state accessor for tests and diagnostics.
1454pub fn promise_state(agent: &Agent, id: PromiseId) -> Option<PromiseState> {
1455    agent
1456        .cell
1457        .borrow()
1458        .promises
1459        .get(&id)
1460        .map(|record| record.state.clone())
1461}