Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T02:01:07.661Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

command.rs

446 lines · 16.5 KB · rust
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
Served at tenant.openagents/omega Member data and write actions are omitted.