Skip to main content

kernel/
model.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4// Copyright (c) 2026 2youg1 and the sprawling contributors
5
6//! The model seam (seam registry ARCHITECTURE 3).
7//! Every provider call carries a `BuildingPolicy` value; the policy is
8//! *defined* here (kernel cannot name outer crates) and *evaluated* by
9//! `city::policy` — dependency inversion, same as the ledger seam.
10//! The part `crates/kernel/spec/Model.lean` specifies this module.
11
12mod image;
13mod mode;
14mod seam;
15mod usage;
16mod window;
17mod wire;
18
19pub use image::{ImageRef, ImageType};
20pub use mode::{AdmissionRequirement, LandingPolicy, Mode, RunPolicy};
21pub use seam::{ModelRequest, ModelReturn, content_from_message, message_payload};
22pub use usage::ModelUsage;
23pub use window::Window;
24pub use wire::{
25    BuildingPolicy, Ceiling, ChatMessage, ChatRequest, ChatResponse, ContentBlock, DialectKind,
26    Effort, Increment, MessageBreakpoint, ModelTag, Role, StopReason, SystemBlock, ToolDef,
27};
28
29use crate::error::AxError;
30use crate::tool::ToolCall;
31
32/// Text arriving from a provider before the call has finished.
33///
34/// One argument and no return value, because an increment is not a
35/// decision: nothing downstream may branch on it, and a sink that could
36/// refuse would make a display detail able to fail a call.
37///
38/// **What arrives here is not history.** The record of what the model
39/// said is written once, from [`ModelReturn`], after the call settles.
40/// Increments are a thing to look at while waiting, and the two can
41/// disagree — a provider may revise, and a cut stream leaves increments
42/// behind that no `ModelReturn` ever confirms. Where they disagree the
43/// settled text wins, and that rule is held on the far side of the wire
44/// by the client.
45pub type Increments<'a> = &'a mut dyn FnMut(&Increment);
46
47/// A tool call handed over the moment its block is complete, before the
48/// call it belongs to has settled.
49///
50/// Separate from [`Increments`] because a caller acts on it: it may start
51/// a read-only tool while the model is still generating. **What arrives
52/// here is not history either.** The ledger records tool calls from
53/// [`ModelReturn`] after the call settles; when the call fails, a caller
54/// discards whatever it derived from these. What a caller may start, and
55/// in which order it records the results, is fixed by
56/// `crates/runtime/spec/Turn/Speculation.lean`.
57pub type EarlyCalls<'a> = &'a mut dyn FnMut(&ToolCall);
58
59/// The model port. Production adapter: gateway::endpoint;
60/// second adapter: citysim scripted model. Implementations never sample
61/// clocks or read global state.
62pub trait Model {
63    fn call(&mut self, req: &ModelRequest) -> Result<ModelReturn, AxError>;
64
65    /// The same call, reporting text as it arrives.
66    ///
67    /// The default answers by calling [`Model::call`] and reporting
68    /// nothing, which is the honest behaviour for an adapter that has no
69    /// stream: a caller gets the same `ModelReturn` at the same moment
70    /// and simply sees no increments. An adapter that overrides this owes
71    /// the same `ModelReturn` it would have returned from `call`,
72    /// including the same failures — a stream cut halfway is a read
73    /// error, never a shortened answer.
74    ///
75    /// # Errors
76    /// Whatever [`Model::call`] would fail with.
77    fn call_streaming(
78        &mut self,
79        req: &ModelRequest,
80        _onto: Increments<'_>,
81    ) -> Result<ModelReturn, AxError> {
82        self.call(req)
83    }
84
85    /// The streaming call, also handing over each tool call as soon as it
86    /// is complete.
87    ///
88    /// The default hands nothing over and answers with
89    /// [`Model::call_streaming`], which is honest for an adapter whose
90    /// wire cannot say when a call is complete: the caller simply gets no
91    /// head start. An adapter that overrides this owes the same
92    /// `ModelReturn` and failures as `call_streaming`, and every call it
93    /// hands over equals the one in that `ModelReturn`.
94    ///
95    /// # Errors
96    /// Whatever [`Model::call_streaming`] would fail with.
97    fn call_speculating(
98        &mut self,
99        req: &ModelRequest,
100        onto: Increments<'_>,
101        _early: EarlyCalls<'_>,
102    ) -> Result<ModelReturn, AxError> {
103        self.call_streaming(req, onto)
104    }
105}
106
107#[cfg(feature = "conformance")]
108pub mod conformance;
109
110#[cfg(test)]
111#[allow(
112    clippy::unwrap_used,
113    clippy::expect_used,
114    clippy::panic,
115    clippy::indexing_slicing,
116    reason = "test code"
117)]
118mod tests;