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