Skip to main content

wire/command/
kind.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//! Command kinds: names, steps, the wire enum.
7
8use kernel::model::{RunPolicy, Window};
9use kernel::{
10    Address, ApprovalId, Autonomy, Ceiling, DialectKind, Effort, GitOid, IdemKey, InputKinds,
11    McpServer, ModelTag, Origin, ResidentId, Restoration, Ruling, RunId, SandboxLimits, Sealed,
12    SessionName,
13};
14use serde::{Deserialize, Serialize};
15
16use crate::carried_name::{ProviderName, TemplateName, ToolkitSlug};
17use crate::command::shelf::Shelf;
18use crate::command::step::{
19    Carry, CitySettings, GovernedDocument, HaltScope, IdentityCard, ProposalDecisions, PursuitStep,
20    RangeWrite, RulesWrite, SpineDocument,
21};
22use crate::command::tuning::EndpointTuning;
23use crate::guide::GuideProgress;
24use crate::named_frames::named_frames;
25use crate::preference::PreferencePatch;
26
27named_frames! {
28/// Commands change state, require authorization, and are idempotent.
29///
30/// Deliberately *not* `#[non_exhaustive]`: the schema hash is this type's
31/// version mechanism, so the assembly layer must handle every one of them
32/// and a new one fails to compile until somebody decides what it does.
33/// That is the rule that keeps a button off the client until the city can
34/// answer the frame behind it.
35#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
36#[serde(rename_all = "snake_case")]
37#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
38#[cfg_attr(feature = "schema", schemars(rename = "Command"))]
39pub enum Command<Secret = Sealed<String>> {
40    Dispatch {
41        addr: Address,
42        task: String,
43        goal: String,
44        /// The four values the run works under: mode, write limit,
45        /// admission requirement and landing policy, all four stated
46        /// (`crates/wire/spec/Command.lean` §8-57). Written as sent into the run's
47        /// `run_started` line.
48        policy: RunPolicy,
49        idem: IdemKey,
50        /// What this session is called, when it is a new one.
51        ///
52        /// `Some` means `addr` names a building and the city opens a
53        /// room of this name under it; `None` means `addr` already
54        /// names the room, which is how an earlier session is
55        /// continued. Two dispatches to one room are one session with a
56        /// history, and that is a different thing from two sessions
57        /// sharing a folder.
58        session: Option<SessionName>,
59        /// How hard the model is asked to think in this session.
60        ///
61        /// `None` leaves the layer above to answer, which is the city's
62        /// own configuration and, failing that, the provider's default.
63        /// A value is written into the session's own configuration when
64        /// its room is opened, so it is chosen once and holds for every
65        /// run in that room (`crates/city/Spec.lean` §8-14).
66        effort: Option<Effort>,
67        /// Which registered model this one dispatch calls, by its id.
68        ///
69        /// `None` takes the model behind the `main` tag. A value names a
70        /// model the city registered under some tag, so it arrives with
71        /// the endpoint and the window it was registered with; an id the
72        /// city never registered is refused before anything is written
73        /// (`crates/wire/spec/Command.lean` §8-48c).
74        model: Option<String>,
75    },
76    /// Asks a base URL what it serves, and attaches nothing.
77    ///
78    /// A person cannot choose from a list they have not seen, so the
79    /// list is asked for apart from attaching; the answer lands as
80    /// `endpoint_probed`, which the page folds like any other fact.
81    ///
82    /// It carries the same `tuning` the attachment will, because a
83    /// probe that reached a gateway without the header that gateway
84    /// requires answers 401 for a key that is in fact good.
85    ProbeEndpoint {
86        name: ProviderName,
87        base_url: String,
88        dialect: DialectKind,
89        secret: Option<String>,
90        auth_header: Option<String>,
91        tuning: EndpointTuning,
92        idem: IdemKey,
93    },
94    /// What a building's runs may reach: the sandbox's limits, the
95    /// external servers its tools come from, and the windows on this
96    /// person's own machine its desktop connector may touch.
97    ///
98    /// Each field is optional and an absent one leaves that section
99    /// alone; an empty `mcp` list is a building that reaches no server,
100    /// which is a different statement from not saying.
101    ///
102    /// `desktop` is the allowlist's text and not a parsed value: the
103    /// connector that reads that file at start-up is the authority on
104    /// its syntax and fails closed, and a second parser here would be a
105    /// second authority (`crates/city/Spec.lean` §8-26).
106    ConfigureBuilding {
107        addr: Address,
108        sandbox: Option<SandboxLimits>,
109        mcp: Option<Vec<McpServer>>,
110        desktop: Option<String>,
111        /// Where the context reminder's second rung sits, as one layer
112        /// states it: a raw percent on the wire. The domain is
113        /// `kernel::config::SecondThreshold`'s one construction point,
114        /// and a deserializer that enforced it here would be the second
115        /// place that rule lives.
116        context_second_threshold: Option<u64>,
117        idem: IdemKey,
118    },
119    AttachEndpoint {
120        name: ProviderName,
121        /// The base URL as a provider's documentation prints it; the
122        /// dialect owns the path that hangs off it.
123        base_url: String,
124        dialect: DialectKind,
125        /// `secret:<realm>/<name>`, or absent for a local server that
126        /// asks for no credential.
127        secret: Option<String>,
128        /// Header name for providers that do not take a bearer token.
129        auth_header: Option<String>,
130        /// Which of the models this endpoint serves the city admits. An
131        /// empty list admits everything it serves, which is what a
132        /// person who did not look at the list meant.
133        admit: Vec<String>,
134        /// What this endpoint is called, how long it may take, and what
135        /// every request to it carries, kept beside the registration so a
136        /// call made a week later is made the way the person set it up.
137        tuning: EndpointTuning,
138        idem: IdemKey,
139    },
140    /// Point one tag at one model of an attached endpoint, with the two
141    /// facts no model list returns.
142    SelectModel {
143        endpoint: ProviderName,
144        model: String,
145        tag: ModelTag,
146        /// The model's window. Absent when nobody stated one, and the
147        /// context reminder then stays silent rather than measuring a
148        /// conversation against a number nobody gave it.
149        context_tokens: Option<Window>,
150        /// The model's output ceiling. Absent when the person did not
151        /// state one: the city then takes the catalogue's figure, and
152        /// where the catalogue has no row the model is registered
153        /// without a ceiling rather than with a zero one.
154        max_output_tokens: Option<Ceiling>,
155        /// What the person says the model accepts. Absent, the catalogue and
156        /// the preset table answer (`crates/gateway/Spec.lean` §8-37, D16).
157        input: Option<InputKinds>,
158        idem: IdemKey,
159    },
160    /// Starts a new session at an address: same room, a fresh stretch of conversation in it, so a
161    /// person who changed the model starts it on purpose rather than having the room's frozen shape
162    /// move behind their back (`crates/sprawling/Spec.lean` §8-82). [`Carry`] names what crosses; the default
163    /// keeps nothing. Writes `session_opened`.
164    OpenSession {
165        addr: Address,
166        carry: Carry,
167        /// What this session continues, when it continues something: a
168        /// branch is a session whose first run begins from a line of
169        /// another conversation, and everything else about it - model,
170        /// effort, handoff - is what [`Carry`] and the room already say
171        /// (`crates/sprawling/Spec.lean` §8-82).
172        from: Option<Origin>,
173        idem: IdemKey,
174    },
175    CreateBuilding {
176        addr: Address,
177        template: TemplateName,
178        idem: IdemKey,
179    },
180    /// Take a building out of the city. Its files move under the
181    /// reserved subtree and its history stays in the Ledger, so nothing
182    /// the person made is lost; a building with a run going is refused.
183    ///
184    /// Writes `building_removed`.
185    RemoveBuilding {
186        addr: Address,
187        idem: IdemKey,
188    },
189    /// The one Command with no byte form. `Secret` is `Sealed<String>` in
190    /// process and uninhabited on the wire.
191    PutSecret {
192        realm: String,
193        name: String,
194        value: Secret,
195    },
196    Steer {
197        run: RunId,
198        text: String,
199        idem: IdemKey,
200    },
201    Cancel {
202        run: RunId,
203        idem: IdemKey,
204    },
205    Halt {
206        scope: HaltScope,
207        idem: IdemKey,
208    },
209    /// Show a person this address in their own file manager. The
210    /// grammar is the guard: an `Address` cannot climb out of the city,
211    /// so there is no way to spell a request for anything else.
212    Reveal {
213        at: Address,
214        idem: IdemKey,
215    },
216    /// Put one recycle-bin row back. The frame carries the row's own
217    /// restoration rather than a path, because a path can be discarded
218    /// twice and the row already knows which commit holds the bytes.
219    RestoreDiscard {
220        restoration: Restoration,
221        idem: IdemKey,
222    },
223    /// Install one thing this machine lacks, named as the doctor's
224    /// answer names it.
225    ///
226    /// Only a recipe this city may run is run. A recipe that has to be
227    /// printed, and one that has no command at all, are refused with
228    /// what the person does instead: a script piped into a shell is
229    /// code nobody read, and that rule does not soften because the
230    /// request arrived from a page rather than from a terminal.
231    DoctorInstall {
232        item: String,
233        idem: IdemKey,
234    },
235    /// Look at this machine again, in place of the snapshot taken when
236    /// the city was served.
237    ///
238    /// [`Query::Doctor`](crate::Query::Doctor) answers that snapshot,
239    /// which a page that has just installed something must not be given.
240    /// Probing starts programs for seconds, so it runs here, where the
241    /// city already serialises work, rather than inside a read.
242    DoctorRefresh {
243        idem: IdemKey,
244    },
245    Release {
246        scope: HaltScope,
247        idem: IdemKey,
248    },
249    BatchByBuilding {
250        addr: Address,
251        idem: IdemKey,
252    },
253    Approve {
254        item: ApprovalId,
255        verdict: Ruling,
256        idem: IdemKey,
257    },
258    /// Give one waiting question to a resident to answer.
259    ///
260    /// A person who does not want to answer a question may name
261    /// somebody who will. This moves one item and leaves
262    /// [`Command::SetAutonomy`] to say who answers everything, because
263    /// handing over one question and appointing a standing delegate are
264    /// different decisions with different reach.
265    ///
266    /// Writes `question_handed`, after which the named resident may
267    /// answer this item even though it is not the standing delegate.
268    HandOff {
269        item: ApprovalId,
270        to: ResidentId,
271        idem: IdemKey,
272    },
273    SetAutonomy {
274        scope: HaltScope,
275        autonomy: Autonomy,
276        idem: IdemKey,
277    },
278    /// A goal the city keeps working towards until the work runs out.
279    ///
280    /// One frame with four steps rather than four frames: a person who can set a goal can pause it,
281    /// and splitting that into separate commands would let a client offer one without the other.
282    Pursue {
283        addr: Address,
284        step: PursuitStep,
285        idem: IdemKey,
286    },
287    /// Something happened outside. The city never asks whether anything
288    /// did; the service holding the connection pushes, and this is the
289    /// shape a push takes once it is inside.
290    ///
291    /// It carries no address. Where an arrival lands is the watch
292    /// table's answer and then triage's, so a caller that could name a
293    /// room would be a caller that could reach past the routing a person
294    /// wrote.
295    Wake {
296        source: String,
297        subject: String,
298        body: String,
299        idem: IdemKey,
300    },
301    /// Writes one of the three documents that govern this city, whole,
302    /// only if it still holds `base`: the raw editor and the identity
303    /// cards are two writers of one file (`crates/wire/spec/Answer/Identity.lean` §8-59).
304    PutDocument {
305        which: GovernedDocument,
306        base: String,
307        body: String,
308        idem: IdemKey,
309    },
310    /// One identity card: the city rewrites the card's keys in `base`'s
311    /// identity area and leaves every other byte (`crates/wire/spec/Answer/Identity.lean` §8-59).
312    PutIdentity {
313        card: IdentityCard,
314        base: String,
315        idem: IdemKey,
316    },
317    /// Writes a building's `RULES.toml` whole (`crates/wire/spec/Command/Step.lean` §8-60).
318    PutRules(RulesWrite),
319    /// Takes one file of the city's own tree back to what a checkpoint
320    /// holds, or away when it holds none (`crates/wire/spec/Answer/Automation.lean` §8-62).
321    RestoreFile {
322        at: Address,
323        point: GitOid,
324        idem: IdemKey,
325    },
326    /// Writes this city's first-run guide progress whole; the later of
327    /// two writes stays (`crates/wire/spec/Guide.lean` §8-68).
328    PutGuide {
329        progress: GuideProgress,
330        idem: IdemKey,
331    },
332    /// Writes the city's own layer (`crates/wire/spec/Command/Step.lean` §8-61).
333    ConfigureCity(CitySettings),
334    /// Writes one of a building's own spine documents.
335    ///
336    /// The body replaces the file whole. Unlike [`Command::PutDocument`]
337    /// these have a second writer - a resident reaches `Roadmap.md`
338    /// through `plan` and the others through `edit` - so `base` carries
339    /// the text the sender started from and a file that moved underneath
340    /// it is refused rather than overwritten. One guard for two writers,
341    /// and the same one `edit` holds resident-side.
342    PutSpine {
343        building: Address,
344        which: SpineDocument,
345        base: String,
346        body: String,
347        idem: IdemKey,
348    },
349    /// Saves edits on one document version (`crates/wire/spec/Command/Step.lean` §8-72).
350    PutRange(RangeWrite),
351    /// Decides proposal cards on one document (`crates/wire/spec/Answer/Proposals.lean` §8-73).
352    DecideProposals(ProposalDecisions),
353    /// Connects one outside application through the broker that holds its OAuth, answered with the
354    /// whole shelf: the broker was asked, so every row is fresh, and the pressed one carries its
355    /// consent page.
356    ///
357    /// **The client opens the consent page, never the city**, which may run on a machine nobody is
358    /// looking at. The `IdemKey` stops a second press from opening a second account on one
359    /// application.
360    ConnectToolkit {
361        toolkit: ToolkitSlug,
362        idem: IdemKey,
363    },
364    /// Change one fact about how this person reads their own city.
365    ///
366    /// One named fact per frame rather than a whole record: two screens settling different things
367    /// must not be able to overwrite each other's field on the way past.
368    PutPreferences {
369        patch: PreferencePatch,
370        idem: IdemKey,
371    },
372    /// Write one skill or script onto a shelf.
373    ///
374    /// The shelf and the name are separate because the city owns where a shelf lives: the two
375    /// shelves sit inside the reserved subtree, which no write domain reaches, so this is the door
376    /// a person writes there through and there is no path to spell. `name` may carry
377    /// sub-directories, which is how a script keeps its folder.
378    ///
379    /// The body replaces the file whole, for the reason [`Command::PutDocument`] gives. Writes
380    /// `shelved_document_written`.
381    PutShelved {
382        shelf: Shelf,
383        name: String,
384        text: String,
385        idem: IdemKey,
386    },
387    /// Presenting a pairing token. Read-only, hence no `IdemKey`; the token is plain here because a
388    /// token that must cross a wire has, by definition, no secrecy left to protect in transit - it
389    /// is sealed the moment it lands (see `server::decide_handshake`).
390    Auth {
391        token: String,
392    },
393}
394
395/// The Command surface, in declaration order — the order the handshake
396/// hash mixes these names in.
397pub const COMMAND_NAMES;
398}