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