Skip to main content

objectiveai_sdk/cli/command/agents/queue/deliver/
mod.rs

1//! `agents queue deliver` — wake every queue-pending descendant
2//! agent of the caller.
3//!
4//! An optional repeatable `--key` filters the targets: when one or
5//! more keys are given, only targets that have an active pending
6//! deliverable carrying one of those keys are woken; with no keys
7//! (the default), every pending target is woken.
8//!
9//! The handler enumerates two kinds of targets with active queued
10//! prompts in the caller's subtree: unique AIHs that are STRICT
11//! descendants of the caller (direct rows + rows against BOUND
12//! tags), and un-upgraded (GROUPED) tags whose group parent sits in
13//! the subtree. Per target it try-acquires the agent's (or tag's)
14//! lock with no waiting: a live owner yields
15//! [`AgentActiveResponseItem`] / [`TagActiveResponseItem`]; winning
16//! the lock yields [`AgentSpawnedResponseItem`] /
17//! [`TagSpawnedResponseItem`] and runs the same spawn machinery
18//! `agents spawn` / `agents message` use (empty messages, plus the
19//! stored continuation for AIHs or the group's stored agent spec for
20//! tags), streaming each spawn item as a [`ValueResponseItem`] and
21//! releasing the lock when that task's stream ends. Once EVERY
22//! target has resolved (active or spawned), the bare string
23//! `"AllAgentsActive"` is emitted.
24//!
25//! Two modes, selected by `dangerous_advanced.stream_spawns`:
26//! * unset/false (the default, user-facing): re-exec the cli binary
27//!   as a detached orphan with `stream_spawns = true` and emit the
28//!   child's items up to and including `AllAgentsActive`, then
29//!   return — the orphan keeps running the spawns to completion.
30//! * true (the re-exec'd child): run the full delivery in-process and
31//!   stream everything, spawn output included.
32
33use crate::cli::command::CommandRequest;
34
35#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
36#[schemars(rename = "cli.command.agents.queue.deliver.Request")]
37pub struct Request {
38    pub path_type: Path,
39    /// Only deliver to targets with a pending deliverable carrying one
40    /// of these keys. `None` (the default) delivers to every pending
41    /// target.
42    #[serde(default, skip_serializing_if = "Option::is_none")]
43    #[schemars(extend("omitempty" = true))]
44    pub keys: Option<Vec<String>>,
45    pub dangerous_advanced: Option<RequestDangerousAdvanced>,
46    #[serde(flatten)]
47    pub base: crate::cli::command::RequestBase,
48}
49
50#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
51#[schemars(rename = "cli.command.agents.queue.deliver.Path")]
52pub enum Path {
53    #[serde(rename = "agents/queue/deliver")]
54    AgentsQueueDeliver,
55}
56
57#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
58#[schemars(rename = "cli.command.agents.queue.deliver.RequestDangerousAdvanced")]
59pub struct RequestDangerousAdvanced {
60    /// Run the delivery in-process and stream every spawned agent's
61    /// output to completion. Unset/false re-execs a detached child
62    /// with this set and returns at its `AllAgentsActive` marker.
63    #[serde(default, skip_serializing_if = "Option::is_none")]
64    #[schemars(extend("omitempty" = true))]
65    pub stream_spawns: Option<bool>,
66}
67
68impl CommandRequest for Request {
69    fn request_base(&self) -> &crate::cli::command::RequestBase {
70        &self.base
71    }
72
73    fn request_base_mut(&mut self) -> Option<&mut crate::cli::command::RequestBase> {
74        Some(&mut self.base)
75    }
76}
77
78/// One stream item from `agents queue deliver`. Untagged — the
79/// variants are disjoint on the wire: `Value` requires `value`,
80/// `AgentActive` / `AgentSpawned` / `TagActive` / `TagSpawned` carry
81/// distinct `type` markers, and `AllAgentsActive` is the bare string
82/// `"AllAgentsActive"`.
83#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
84#[serde(untagged)]
85#[schemars(rename = "cli.command.agents.queue.deliver.ResponseItem")]
86pub enum ResponseItem {
87    #[schemars(title = "Value")]
88    Value(ValueResponseItem),
89    #[schemars(title = "AgentActive")]
90    AgentActive(AgentActiveResponseItem),
91    #[schemars(title = "AgentSpawned")]
92    AgentSpawned(AgentSpawnedResponseItem),
93    #[schemars(title = "TagActive")]
94    TagActive(TagActiveResponseItem),
95    #[schemars(title = "TagSpawned")]
96    TagSpawned(TagSpawnedResponseItem),
97    #[schemars(title = "AllAgentsActive")]
98    AllAgentsActive(AllAgentsActive),
99}
100
101/// One output item from one delivered agent's spawn stream. `value`
102/// is the typed root [`crate::cli::command::ResponseItem`] (the spawn
103/// item wrapped at the root) — boxed because the root union
104/// transitively contains *this* type (`agents → queue → deliver`),
105/// and boxing is what makes the recursion sized.
106///
107/// The `value` field's JSON schema is opaqued to `serde_json::Value`
108/// (renders as bare `{}` aka JsonValue) so the published schema
109/// doesn't inline the entire root union — that's the TS7056 blowup
110/// the root and tier aggregates dodge by being `json_schema_ignore`.
111/// Downstream SDKs see `value: JsonValue` on the typed `execute`
112/// path; consumers that want to peer inside parse it case-by-case.
113#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
114#[schemars(rename = "cli.command.agents.queue.deliver.ValueResponseItem")]
115pub struct ValueResponseItem {
116    /// The delivered agent's `agent_instance_hierarchy`.
117    pub agent_instance_hierarchy: String,
118    /// The typed root item the spawn emitted.
119    #[schemars(with = "serde_json::Value")]
120    pub value: Box<crate::cli::command::ResponseItem>,
121}
122
123/// This agent's lock was held by a live owner — it is already active
124/// and will drain its own queue; nothing was spawned for it.
125#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
126#[schemars(rename = "cli.command.agents.queue.deliver.AgentActiveResponseItem")]
127pub struct AgentActiveResponseItem {
128    pub r#type: AgentActiveType,
129    pub agent_instance_hierarchy: String,
130}
131
132#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
133#[schemars(rename = "cli.command.agents.queue.deliver.AgentActiveType")]
134pub enum AgentActiveType {
135    #[serde(rename = "AgentActive")]
136    AgentActive,
137}
138
139/// This agent's lock was won and its spawn has started; its output
140/// follows as [`ValueResponseItem`]s (in `stream_spawns` mode).
141#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
142#[schemars(rename = "cli.command.agents.queue.deliver.AgentSpawnedResponseItem")]
143pub struct AgentSpawnedResponseItem {
144    pub r#type: AgentSpawnedType,
145    pub agent_instance_hierarchy: String,
146}
147
148#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
149#[schemars(rename = "cli.command.agents.queue.deliver.AgentSpawnedType")]
150pub enum AgentSpawnedType {
151    #[serde(rename = "AgentSpawned")]
152    AgentSpawned,
153}
154
155/// This un-upgraded tag's lock was held by a live owner — another
156/// process is already materializing it; the queued rows will reach
157/// the agent it mints. Nothing was spawned for it.
158#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
159#[schemars(rename = "cli.command.agents.queue.deliver.TagActiveResponseItem")]
160pub struct TagActiveResponseItem {
161    pub r#type: TagActiveType,
162    pub agent_tag: String,
163}
164
165#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
166#[schemars(rename = "cli.command.agents.queue.deliver.TagActiveType")]
167pub enum TagActiveType {
168    #[serde(rename = "TagActive")]
169    TagActive,
170}
171
172/// This un-upgraded tag's lock was won and a fresh spawn of the
173/// group's stored agent spec has started. The minted
174/// `agent_instance_hierarchy` isn't known yet at this point — it
175/// arrives as the FIRST inner item (the spawn `Id`) of the
176/// [`ValueResponseItem`]s that follow (in `stream_spawns` mode).
177#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
178#[schemars(rename = "cli.command.agents.queue.deliver.TagSpawnedResponseItem")]
179pub struct TagSpawnedResponseItem {
180    pub r#type: TagSpawnedType,
181    pub agent_tag: String,
182}
183
184#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
185#[schemars(rename = "cli.command.agents.queue.deliver.TagSpawnedType")]
186pub enum TagSpawnedType {
187    #[serde(rename = "TagSpawned")]
188    TagSpawned,
189}
190
191/// Every target has resolved to active-or-spawned. Wire shape is the
192/// bare string `"AllAgentsActive"` (a one-variant enum — a unit
193/// variant in the untagged [`ResponseItem`] would serialize as
194/// `null`, not the marker string). The detached default mode stops
195/// reading its child at this item.
196#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
197#[schemars(rename = "cli.command.agents.queue.deliver.AllAgentsActive")]
198pub enum AllAgentsActive {
199    AllAgentsActive,
200}
201
202#[derive(clap::Args)]
203pub struct Args {
204    /// Only deliver to targets with a pending deliverable carrying one
205    /// of these keys (repeatable). Empty = deliver to all pending
206    /// targets.
207    #[arg(long = "key")]
208    pub keys: Vec<String>,
209    /// Raw JSON for `RequestDangerousAdvanced` (e.g.
210    /// `{"stream_spawns":true}`).
211    #[arg(long)]
212    pub dangerous_advanced: Option<String>,
213    #[command(flatten)]
214    pub base: crate::cli::command::RequestBaseArgs,
215}
216
217#[derive(clap::Args)]
218#[command(args_conflicts_with_subcommands = true)]
219pub struct Command {
220    #[command(flatten)]
221    pub args: Args,
222    #[command(subcommand)]
223    pub schema: Option<Schema>,
224}
225
226#[derive(clap::Subcommand)]
227pub enum Schema {
228    /// Emit the JSON Schema for this leaf's `Request` type and exit.
229    RequestSchema(request_schema::Args),
230    /// Emit the JSON Schema for this leaf's `Response` type and exit.
231    ResponseSchema(response_schema::Args),
232}
233
234impl TryFrom<Args> for Request {
235    type Error = crate::cli::command::FromArgsError;
236    fn try_from(args: Args) -> Result<Self, Self::Error> {
237        let dangerous_advanced: Option<RequestDangerousAdvanced> =
238            if let Some(s) = args.dangerous_advanced {
239                let mut de = serde_json::Deserializer::from_str(&s);
240                let v = serde_path_to_error::deserialize(&mut de).map_err(|source| {
241                    crate::cli::command::FromArgsError {
242                        field: "dangerous_advanced",
243                        source: source.into(),
244                    }
245                })?;
246                Some(v)
247            } else {
248                None
249            };
250        Ok(Self {
251            path_type: Path::AgentsQueueDeliver,
252            keys: if args.keys.is_empty() {
253                None
254            } else {
255                Some(args.keys)
256            },
257            dangerous_advanced,
258            base: args.base.into(),
259        })
260    }
261}
262
263#[cfg(feature = "cli-executor")]
264pub async fn execute<E: crate::cli::command::CommandExecutor>(
265    executor: &E,
266    mut request: Request,
267    agent_arguments: Option<&crate::cli::command::AgentArguments>,
268) -> Result<E::Stream<ResponseItem>, E::Error> {
269    request.base.clear_transform();
270    executor.execute(request, agent_arguments).await
271}
272
273#[cfg(feature = "cli-executor")]
274pub async fn execute_transform<E: crate::cli::command::CommandExecutor>(
275    executor: &E,
276    mut request: Request,
277    transform: crate::cli::command::Transform,
278    agent_arguments: Option<&crate::cli::command::AgentArguments>,
279) -> Result<E::Stream<serde_json::Value>, E::Error> {
280    request.base.set_transform(transform);
281    executor.execute(request, agent_arguments).await
282}
283
284#[cfg(feature = "mcp")]
285impl crate::cli::command::CommandResponse for ResponseItem {
286    fn into_mcp(self) -> crate::cli::command::McpResponseItem {
287        crate::cli::command::McpResponseItem::JSONL(serde_json::to_value(self).unwrap())
288    }
289}
290
291pub mod request_schema;
292
293pub mod response_schema;