Skip to repository content446 lines · 16.5 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T03:03:31.539Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
command.rs
1//! Every command the Exo lane is allowed to run. `OMEGA-DELTA-0042`, omega#87.
2//!
3//! Omega owns Exo's process the way it owns any external tool, and it **never
4//! edits Exo's `.exo` root, agents, secrets, or configuration** — the same law
5//! it obeys around a Codex home. The way that law is kept is not a review
6//! convention: it is that there is no way to express a configuring command.
7//! [`ExoCommand`] is a closed enum of four operations, [`ExoCommand::argv`] is
8//! total over it, and [`ADMITTED_LANE_ARGV`] states each resulting shape
9//! exactly. A fifth operation does not compile until somebody writes its shape
10//! down, and writing it down is the record that a person decided it belonged.
11//!
12//! The set is deliberately small: send one turn, read the durable log, read the
13//! conversation, read the agent, read the model bindings. Four of the five are
14//! reads. The one that writes is `conversation send`, and what it writes is
15//! Exo's own record of the turn it just ran — Exo mutating Exo, which is the
16//! only mutation this lane causes and the only one it could cause without
17//! ceasing to be a lane.
18//!
19//! # The argument terminator is load-bearing
20//!
21//! Exo's global options are accepted **after** the subcommand: `exo
22//! conversation send --harness cursor <agent> <conv> <prompt>` is a valid
23//! invocation that changes which executor runs the turn. So a prompt is not
24//! merely a string Exo receives — without a terminator it is *argument syntax*,
25//! and the person typing it is writing Exo's command line.
26//!
27//! Driven against the pinned Exo, a prompt of `--help` on an otherwise correct
28//! `conversation send` **exits 0, prints Exo's usage text, and runs no turn.**
29//! A lane without the terminator would have read that usage text back as the
30//! model's reply. With `--` it is delivered as text and a real turn runs.
31//!
32//! So every value that could have come from a person or a model is emitted
33//! after `--`, and [`ExoArg`] makes that checkable rather than hoped for:
34//! arguments carry whether they are literal, Omega's own configuration, or
35//! text from outside, and `user_text_cannot_become_an_exo_flag` fails if one of
36//! the last kind is ever emitted before the terminator.
37
38/// Exo's state root, as a value Omega only ever *names*.
39///
40/// There is no method here that writes, creates, removes, or opens anything.
41/// That is the type's entire job: an `ExoRoot` can be passed to a command
42/// builder and cannot be used to touch the directory it names. Omega's process
43/// never writes inside `.exo`; only the `exo` binary does.
44#[derive(Clone, Debug, PartialEq, Eq)]
45pub struct ExoRoot(String);
46
47impl ExoRoot {
48 /// Name an existing Exo state root.
49 #[must_use]
50 pub fn at(path: impl Into<String>) -> Self {
51 Self(path.into())
52 }
53
54 /// The path, for putting on a command line.
55 #[must_use]
56 pub fn as_str(&self) -> &str {
57 &self.0
58 }
59}
60
61/// One argument, and where it came from.
62///
63/// The provenance is the point. See the module documentation.
64#[derive(Clone, Debug, PartialEq, Eq)]
65pub enum ExoArg {
66 /// A literal this file wrote.
67 Fixed(&'static str),
68 /// A value from Omega's own lane configuration — the state root, a limit.
69 /// Not literal, but not reachable from a turn either.
70 Config(String),
71 /// A value that could have been typed by a person or produced by a model.
72 /// Never emitted before the terminator.
73 UserText(String),
74}
75
76impl ExoArg {
77 /// The argument as it goes on the command line.
78 #[must_use]
79 pub fn value(&self) -> &str {
80 match self {
81 Self::Fixed(value) => value,
82 Self::Config(value) | Self::UserText(value) => value,
83 }
84 }
85
86 /// Whether this argument came from outside Omega's own configuration.
87 #[must_use]
88 pub const fn is_user_text(&self) -> bool {
89 matches!(self, Self::UserText(_))
90 }
91}
92
93/// The terminator, after which Exo stops reading arguments as options.
94pub const ARGUMENT_TERMINATOR: &str = "--";
95
96/// Every command the lane may run, and the exact argv each produces.
97///
98/// A closed list, in the same spirit as `EXECUTOR_DISCLOSURE_FIELDS`: the
99/// property that matters is "these shapes and nothing else". A denylist of
100/// dangerous flags would be a guess about which flags upstream will add next,
101/// in a project whose written house rule is that it does not keep backwards
102/// compatibility. Placeholders are `<name>`; every other token is literal.
103pub const ADMITTED_LANE_ARGV: &[(&str, &[&str])] = &[
104 (
105 "send_turn",
106 &[
107 "--root",
108 "<root>",
109 "conversation",
110 "send",
111 "--",
112 "<agent>",
113 "<conversation>",
114 "<prompt>",
115 ],
116 ),
117 (
118 "read_events",
119 &[
120 "--root",
121 "<root>",
122 "conversation",
123 "events",
124 "--limit",
125 "<limit>",
126 "--",
127 "<agent>",
128 "<conversation>",
129 ],
130 ),
131 (
132 "show_conversation",
133 &[
134 "--root",
135 "<root>",
136 "conversation",
137 "show",
138 "--",
139 "<agent>",
140 "<conversation>",
141 ],
142 ),
143 (
144 "show_agent",
145 &["--root", "<root>", "agent", "show", "--", "<agent>"],
146 ),
147 // No terminator, because this command carries no value from outside Omega.
148 // A terminator here would be decoration, and decoration in a security shape
149 // teaches a reader that the terminator is a habit rather than a mechanism.
150 ("list_models", &["--root", "<root>", "model", "list"]),
151];
152
153/// The five things the lane does.
154#[derive(Clone, Debug, PartialEq, Eq)]
155pub enum ExoCommand {
156 /// Run one turn. The whole of Tier A's execution: one shot, no stream.
157 SendTurn {
158 agent: String,
159 conversation: String,
160 prompt: String,
161 },
162 /// Read the durable event log, newest page, for tool activity after the
163 /// fact.
164 ReadEvents {
165 agent: String,
166 conversation: String,
167 limit: u32,
168 },
169 /// Read a conversation's record, for the model it resolves to.
170 ShowConversation {
171 agent: String,
172 conversation: String,
173 },
174 /// Read an agent's record, for its executor and its capability set. This is
175 /// what [`crate::capability`] refuses a turn on.
176 ShowAgent { agent: String },
177 /// Read the model bindings, so the disclosure can name the model that
178 /// actually served rather than the local alias the agent record carries.
179 ListModels,
180}
181
182impl ExoCommand {
183 /// The token this command is recorded and checked under.
184 #[must_use]
185 pub const fn shape(&self) -> &'static str {
186 match self {
187 Self::SendTurn { .. } => "send_turn",
188 Self::ReadEvents { .. } => "read_events",
189 Self::ShowConversation { .. } => "show_conversation",
190 Self::ShowAgent { .. } => "show_agent",
191 Self::ListModels => "list_models",
192 }
193 }
194
195 /// The arguments, with their provenance.
196 ///
197 /// Total: every variant produces a shape, and each one is stated in
198 /// [`ADMITTED_LANE_ARGV`].
199 #[must_use]
200 pub fn args(&self, root: &ExoRoot) -> Vec<ExoArg> {
201 let mut args = vec![
202 ExoArg::Fixed("--root"),
203 ExoArg::Config(root.as_str().to_owned()),
204 ];
205 match self {
206 Self::SendTurn {
207 agent,
208 conversation,
209 prompt,
210 } => {
211 args.extend([
212 ExoArg::Fixed("conversation"),
213 ExoArg::Fixed("send"),
214 ExoArg::Fixed(ARGUMENT_TERMINATOR),
215 ExoArg::UserText(agent.clone()),
216 ExoArg::UserText(conversation.clone()),
217 ExoArg::UserText(prompt.clone()),
218 ]);
219 }
220 Self::ReadEvents {
221 agent,
222 conversation,
223 limit,
224 } => {
225 args.extend([
226 ExoArg::Fixed("conversation"),
227 ExoArg::Fixed("events"),
228 ExoArg::Fixed("--limit"),
229 ExoArg::Config(limit.to_string()),
230 ExoArg::Fixed(ARGUMENT_TERMINATOR),
231 ExoArg::UserText(agent.clone()),
232 ExoArg::UserText(conversation.clone()),
233 ]);
234 }
235 Self::ShowConversation {
236 agent,
237 conversation,
238 } => {
239 args.extend([
240 ExoArg::Fixed("conversation"),
241 ExoArg::Fixed("show"),
242 ExoArg::Fixed(ARGUMENT_TERMINATOR),
243 ExoArg::UserText(agent.clone()),
244 ExoArg::UserText(conversation.clone()),
245 ]);
246 }
247 Self::ShowAgent { agent } => {
248 args.extend([
249 ExoArg::Fixed("agent"),
250 ExoArg::Fixed("show"),
251 ExoArg::Fixed(ARGUMENT_TERMINATOR),
252 ExoArg::UserText(agent.clone()),
253 ]);
254 }
255 Self::ListModels => {
256 args.extend([ExoArg::Fixed("model"), ExoArg::Fixed("list")]);
257 }
258 }
259 args
260 }
261
262 /// The command line, as strings.
263 #[must_use]
264 pub fn argv(&self, root: &ExoRoot) -> Vec<String> {
265 self.args(root)
266 .iter()
267 .map(|arg| arg.value().to_owned())
268 .collect()
269 }
270
271 /// One of every variant, filled with placeholder values.
272 ///
273 /// The exhaustiveness handle: a new variant that is not added here fails
274 /// `every_variant_has_a_written_shape`, and a new variant that *is* added
275 /// here fails until its shape is written into [`ADMITTED_LANE_ARGV`].
276 #[must_use]
277 pub fn every_shape() -> Vec<Self> {
278 vec![
279 Self::SendTurn {
280 agent: "<agent>".into(),
281 conversation: "<conversation>".into(),
282 prompt: "<prompt>".into(),
283 },
284 Self::ReadEvents {
285 agent: "<agent>".into(),
286 conversation: "<conversation>".into(),
287 limit: 0,
288 },
289 Self::ShowConversation {
290 agent: "<agent>".into(),
291 conversation: "<conversation>".into(),
292 },
293 Self::ShowAgent {
294 agent: "<agent>".into(),
295 },
296 Self::ListModels,
297 ]
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304
305 fn root() -> ExoRoot {
306 ExoRoot::at("<root>")
307 }
308
309 /// The shapes, asserted exactly. Not "contains no dangerous flag" — every
310 /// token, in order, against a written list.
311 ///
312 /// A `<name>` token in the written shape asserts a *slot*: that position
313 /// must be filled from a value, and a literal appearing there fails. Every
314 /// other token asserts itself. So the check covers both what the command
315 /// line says and which parts of it this file decided rather than received.
316 #[test]
317 fn every_variant_has_a_written_shape() {
318 let shapes = ExoCommand::every_shape();
319 assert_eq!(
320 shapes.len(),
321 ADMITTED_LANE_ARGV.len(),
322 "a command variant exists with no written argv shape, or the other way round"
323 );
324 for command in &shapes {
325 let (_, expected) = ADMITTED_LANE_ARGV
326 .iter()
327 .find(|(name, _)| *name == command.shape())
328 .unwrap_or_else(|| panic!("{} has no written shape", command.shape()));
329 let args = command.args(&root());
330 assert_eq!(args.len(), expected.len(), "{}", command.shape());
331 for (index, (arg, token)) in args.iter().zip(expected.iter()).enumerate() {
332 if token.starts_with('<') {
333 assert!(
334 !matches!(arg, ExoArg::Fixed(_)),
335 "{} argv[{index}] is a literal where the shape says {token}",
336 command.shape()
337 );
338 } else {
339 assert_eq!(
340 arg,
341 &ExoArg::Fixed(token),
342 "{} argv[{index}]",
343 command.shape()
344 );
345 }
346 }
347 }
348 }
349
350 /// The security law, stated over the whole closed set rather than over the
351 /// arguments one call site happened to build.
352 ///
353 /// A command that carries no text from outside needs no terminator; a
354 /// command that carries any must put all of it after one. Both halves are
355 /// checked, so neither "forgot the terminator" nor "added user text after
356 /// the fact" can land quietly.
357 #[test]
358 fn user_text_cannot_become_an_exo_flag() {
359 for command in ExoCommand::every_shape() {
360 let args = command.args(&root());
361 let terminator = args
362 .iter()
363 .position(|arg| arg == &ExoArg::Fixed(ARGUMENT_TERMINATOR));
364 let Some(terminator) = terminator else {
365 assert!(
366 !args.iter().any(ExoArg::is_user_text),
367 "{} carries text from outside Omega and emits no argument terminator",
368 command.shape()
369 );
370 continue;
371 };
372 for (index, arg) in args.iter().enumerate() {
373 assert!(
374 !(arg.is_user_text() && index <= terminator),
375 "{} puts text from outside Omega at argv[{index}], before the terminator",
376 command.shape()
377 );
378 }
379 }
380 }
381
382 /// The prompt that proved this against the real binary: `--help` on a
383 /// `conversation send` exits 0, prints usage, and runs no turn. After the
384 /// terminator it is a prompt.
385 #[test]
386 fn a_flag_shaped_prompt_is_still_a_prompt() {
387 let command = ExoCommand::SendTurn {
388 agent: "omega-lane".into(),
389 conversation: "tier-a".into(),
390 prompt: "--help".into(),
391 };
392 let argv = command.argv(&root());
393 let terminator = argv
394 .iter()
395 .position(|arg| arg == ARGUMENT_TERMINATOR)
396 .expect("terminated");
397 assert_eq!(argv.last().map(String::as_str), Some("--help"));
398 assert!(argv.len() - 1 > terminator);
399 }
400
401 /// Omega never configures Exo. The admitted verbs are three reads and one
402 /// send, and the ones that are absent are absent by name so a later edit
403 /// that adds `agent update` has to delete a line of this test to pass.
404 #[test]
405 fn the_lane_can_express_no_command_that_configures_exo() {
406 let admitted: Vec<&str> = ADMITTED_LANE_ARGV
407 .iter()
408 .map(|(_, argv)| argv[3]) // --root <root> <group> <verb>
409 .collect();
410 assert_eq!(admitted, ["send", "events", "show", "show", "list"]);
411 for forbidden in [
412 "create", "update", "delete", "mount", "set", "register", "configure", "serve", "repl",
413 "adapters", "fork", "run",
414 ] {
415 for (name, argv) in ADMITTED_LANE_ARGV {
416 assert!(
417 !argv.contains(&forbidden),
418 "{name} can reach Exo's {forbidden}"
419 );
420 }
421 }
422 }
423
424 /// The state root is named and never opened. If `ExoRoot` ever grows a way
425 /// to write, this is the test that has to be deleted for it to land.
426 #[test]
427 fn the_exo_root_is_a_name_and_not_a_handle() {
428 let root = ExoRoot::at("/tmp/exo/.exo");
429 assert_eq!(root.as_str(), "/tmp/exo/.exo");
430 let source = include_str!("command.rs");
431 let impl_block = source
432 .split_once("impl ExoRoot {")
433 .and_then(|(_, rest)| rest.split_once("\n}\n"))
434 .expect("ExoRoot has an impl block")
435 .0;
436 for writing in [
437 "fs::", "File", "create", "remove", "write", "OpenOptions", "std::io",
438 ] {
439 assert!(
440 !impl_block.contains(writing),
441 "ExoRoot grew a way to touch the directory it names: {writing}"
442 );
443 }
444 }
445}
446