objectiveai_cli/command/agents/queue/
deliver.rs1use std::collections::HashSet;
45use std::pin::Pin;
46
47use futures::{Stream, StreamExt};
48use objectiveai_sdk::cli::command::ResponseItem as RootResponseItem;
49use objectiveai_sdk::cli::command::agents::ResponseItem as AgentsResponseItem;
50use objectiveai_sdk::cli::command::agents::queue::deliver::{
51 AgentActiveResponseItem, AgentActiveType, AgentSpawnedResponseItem, AgentSpawnedType,
52 AllAgentsActive, Request, RequestDangerousAdvanced, ResponseItem, TagActiveResponseItem,
53 TagActiveType, TagSpawnedResponseItem, TagSpawnedType, ValueResponseItem,
54};
55use objectiveai_sdk::cli::command::agents::spawn::ResponseItem as SpawnResponseItem;
56use objectiveai_sdk::cli::command::{BinaryExecutor, CommandExecutor};
57
58use crate::context::Context;
59use crate::db;
60use crate::error::Error;
61use crate::websockets::agent_registry::AgentInstanceRegistry;
62
63type ItemStream = Pin<Box<dyn Stream<Item = Result<ResponseItem, Error>> + Send>>;
64
65type TaggedStream =
69 Pin<Box<dyn Stream<Item = (usize, Result<ResponseItem, Error>)> + Send>>;
70
71pub async fn execute(ctx: &Context, request: Request) -> Result<ItemStream, Error> {
72 if request
73 .dangerous_advanced
74 .as_ref()
75 .and_then(|adv| adv.stream_spawns)
76 == Some(true)
77 {
78 execute_streaming(ctx, request).await
79 } else {
80 execute_detached(request).await
81 }
82}
83
84async fn execute_detached(request: Request) -> Result<ItemStream, Error> {
94 let mut child_request = request;
95 match child_request.dangerous_advanced.as_mut() {
96 Some(adv) => adv.stream_spawns = Some(true),
97 None => {
98 child_request.dangerous_advanced = Some(RequestDangerousAdvanced {
99 stream_spawns: Some(true),
100 })
101 }
102 }
103 crate::command::reexec::strip_inherited(&mut child_request.base);
105
106 let exe = std::env::current_exe()
107 .map_err(|e| Error::Spawn("current_exe".into(), e))?;
108 let executor = BinaryExecutor::from_path(exe).detach(true);
109
110 let mut stream = executor
111 .execute::<Request, ResponseItem>(child_request, None)
112 .await
113 .map_err(|e| Error::Instance(format!(
114 "self-respawn for agents queue deliver: {e}"
115 )))?;
116
117 let out = async_stream::stream! {
118 while let Some(item) = stream.next().await {
119 match item {
120 Ok(ResponseItem::Value(_)) => {}
123 Ok(item) => {
124 let done = matches!(item, ResponseItem::AllAgentsActive(_));
125 yield Ok(item);
126 if done {
127 break;
131 }
132 }
133 Err(e) => yield Err(Error::Instance(format!(
134 "self-respawn for agents queue deliver: {e}"
135 ))),
136 }
137 }
138 };
139 Ok(Box::pin(out))
140}
141
142async fn execute_streaming(ctx: &Context, request: Request) -> Result<ItemStream, Error> {
144 let caller = ctx.config.agent_instance_hierarchy.clone();
150 let targets = db::message_queue::list_delivery_targets(
151 ctx.db_client().await?,
152 &caller,
153 request.keys.as_deref().unwrap_or(&[]),
154 )
155 .await?;
156 let mut hierarchies: Vec<String> = Vec::new();
157 let mut tags: Vec<String> = Vec::new();
158 for target in targets {
159 match target {
160 db::message_queue::DeliveryTarget::Hierarchy { agent_instance_hierarchy } => {
161 if agent_instance_hierarchy != caller
162 && !hierarchies.contains(&agent_instance_hierarchy)
163 {
164 hierarchies.push(agent_instance_hierarchy);
165 }
166 }
167 db::message_queue::DeliveryTarget::GroupedTag { agent_tag } => {
168 if !tags.contains(&agent_tag) {
169 tags.push(agent_tag);
170 }
171 }
172 }
173 }
174
175 let n = hierarchies.len() + tags.len();
176 let mut select_all = futures::stream::SelectAll::new();
177 let mut idx = 0usize;
178 for hierarchy in hierarchies {
179 let i = idx;
180 idx += 1;
181 let tagged = deliver_one_hierarchy(ctx.clone(), hierarchy).map(move |item| (i, item));
182 select_all.push(Box::pin(tagged) as TaggedStream);
183 }
184 for tag in tags {
185 let i = idx;
186 idx += 1;
187 let tagged = deliver_one_tag(ctx.clone(), tag).map(move |item| (i, item));
188 select_all.push(Box::pin(tagged) as TaggedStream);
189 }
190
191 let out = async_stream::stream! {
192 if n == 0 {
193 yield Ok(ResponseItem::AllAgentsActive(AllAgentsActive::AllAgentsActive));
194 return;
195 }
196 let mut seen: HashSet<usize> = HashSet::new();
197 let mut merged = select_all;
198 while let Some((idx, item)) = merged.next().await {
199 let first = seen.insert(idx);
200 yield item;
201 if first && seen.len() == n {
206 yield Ok(ResponseItem::AllAgentsActive(AllAgentsActive::AllAgentsActive));
207 }
208 }
209 };
210 Ok(Box::pin(out))
211}
212
213fn deliver_one_hierarchy(
221 ctx: Context,
222 hierarchy: String,
223) -> impl Stream<Item = Result<ResponseItem, Error>> + Send {
224 async_stream::stream! {
225 let state_dir = ctx.filesystem.state_dir();
226 let pool = match ctx.db_client().await {
227 Ok(pool) => pool,
228 Err(e) => {
229 yield Err(e);
230 return;
231 }
232 };
233 let registry = match crate::command::agents::locks::try_acquire_family(
237 ctx.agent_locks(),
238 pool,
239 &state_dir,
240 crate::command::agents::locks::Family::Hierarchy(hierarchy.clone()),
241 )
242 .await
243 {
244 Ok(Some(fam)) => {
245 let mut registry = AgentInstanceRegistry::new(state_dir, ctx.agent_locks_arc());
246 if let Some((h, aih_lock)) = fam.aih {
247 registry.preseed(h, aih_lock);
248 }
249 registry.hold_tag_claims(fam.tags);
250 registry
251 }
252 Ok(None) => {
253 yield Ok(ResponseItem::AgentActive(AgentActiveResponseItem {
254 r#type: AgentActiveType::AgentActive,
255 agent_instance_hierarchy: hierarchy,
256 }));
257 return;
258 }
259 Err(e) => {
260 yield Err(e);
261 return;
262 }
263 };
264
265 let lookup = match crate::db::logs::lookup_session(pool, &hierarchy).await {
266 Ok(Some(lookup)) => lookup,
267 Ok(None) => {
268 yield Err(Error::AgentNoPriorRequest {
270 agent_instance_hierarchy: hierarchy,
271 });
272 return;
273 }
274 Err(e) => {
275 yield Err(e.into());
276 return;
277 }
278 };
279
280 yield Ok(ResponseItem::AgentSpawned(AgentSpawnedResponseItem {
281 r#type: AgentSpawnedType::AgentSpawned,
282 agent_instance_hierarchy: hierarchy.clone(),
283 }));
284
285 let inner = crate::command::agents::spawn::run_multi_pass(
290 ctx.clone(),
291 Vec::new(),
292 lookup.agent,
293 None,
294 lookup.continuation,
295 None,
296 vec![crate::db::laboratory_attachments::Target::Aih(hierarchy.clone())],
297 registry,
298 );
299 let mut inner = Box::pin(inner);
300 while let Some(item) = inner.next().await {
301 match item {
302 Ok(spawn_item) => {
303 yield Ok(ResponseItem::Value(ValueResponseItem {
304 agent_instance_hierarchy: hierarchy.clone(),
305 value: Box::new(RootResponseItem::Agents(
306 AgentsResponseItem::Spawn(spawn_item),
307 )),
308 }));
309 }
310 Err(e) => yield Err(e),
311 }
312 }
313 }
314}
315
316fn deliver_one_tag(
327 ctx: Context,
328 agent_tag: String,
329) -> impl Stream<Item = Result<ResponseItem, Error>> + Send {
330 async_stream::stream! {
331 let state_dir = ctx.filesystem.state_dir();
332 let pool = match ctx.db_client().await {
335 Ok(pool) => pool,
336 Err(e) => {
337 yield Err(e);
338 return;
339 }
340 };
341 let (agent, tag_group_id) = match crate::db::tags::lookup(pool, &agent_tag).await {
342 Ok(crate::db::tags::LookupState::Grouped { agent_spec, tag_group_id, .. }) => {
343 (agent_spec, tag_group_id)
344 }
345 Ok(crate::db::tags::LookupState::Bound { agent_instance_hierarchy }) => {
346 let mut inner = Box::pin(deliver_one_hierarchy(
348 ctx.clone(),
349 agent_instance_hierarchy,
350 ));
351 while let Some(item) = inner.next().await {
352 yield item;
353 }
354 return;
355 }
356 Ok(crate::db::tags::LookupState::Absent) => {
357 yield Err(Error::TagNotFound(agent_tag));
358 return;
359 }
360 Err(e) => {
361 yield Err(e.into());
362 return;
363 }
364 };
365
366 let registry = match crate::command::agents::locks::try_acquire_family(
370 ctx.agent_locks(),
371 pool,
372 &state_dir,
373 crate::command::agents::locks::Family::Group(tag_group_id),
374 )
375 .await
376 {
377 Ok(Some(fam)) => {
378 let mut registry = AgentInstanceRegistry::new(state_dir, ctx.agent_locks_arc());
379 registry.hold_tag_claims(fam.tags);
380 registry
381 }
382 Ok(None) => {
383 yield Ok(ResponseItem::TagActive(TagActiveResponseItem {
384 r#type: TagActiveType::TagActive,
385 agent_tag,
386 }));
387 return;
388 }
389 Err(e) => {
390 yield Err(e);
391 return;
392 }
393 };
394
395 yield Ok(ResponseItem::TagSpawned(TagSpawnedResponseItem {
396 r#type: TagSpawnedType::TagSpawned,
397 agent_tag: agent_tag.clone(),
398 }));
399
400 let lab_targets = vec![crate::db::laboratory_attachments::Target::Tag(agent_tag.clone())];
407 let inner = crate::command::agents::spawn::run_multi_pass(
408 ctx.clone(),
409 Vec::new(),
410 agent,
411 None,
412 None,
413 Some(agent_tag),
414 lab_targets,
415 registry,
416 );
417 let mut inner = Box::pin(inner);
418 let mut minted: Option<String> = None;
422 while let Some(item) = inner.next().await {
423 match item {
424 Ok(spawn_item) => {
425 if let SpawnResponseItem::Id(id) = &spawn_item {
426 minted = Some(id.clone());
427 }
428 let aih = minted.clone().unwrap_or_default();
429 yield Ok(ResponseItem::Value(ValueResponseItem {
430 agent_instance_hierarchy: aih,
431 value: Box::new(RootResponseItem::Agents(
432 AgentsResponseItem::Spawn(spawn_item),
433 )),
434 }));
435 }
436 Err(e) => yield Err(e),
437 }
438 }
439 }
440}
441
442pub mod request_schema {
443 use objectiveai_sdk::cli::command::agents::queue::deliver as sdk;
444 use objectiveai_sdk::cli::command::agents::queue::deliver::request_schema::{
445 Request, Response,
446 };
447
448 use crate::context::Context;
449 use crate::error::Error;
450
451 pub async fn execute(_ctx: &Context, _request: Request) -> Result<Response, Error> {
452 Ok(objectiveai_sdk::cli::command::ResponseSchema(schemars::schema_for!(sdk::Request)))
453 }
454}
455
456pub mod response_schema {
457 use objectiveai_sdk::cli::command::agents::queue::deliver as sdk;
458 use objectiveai_sdk::cli::command::agents::queue::deliver::response_schema::{
459 Request, Response,
460 };
461
462 use crate::context::Context;
463 use crate::error::Error;
464
465 pub async fn execute(_ctx: &Context, _request: Request) -> Result<Response, Error> {
466 Ok(objectiveai_sdk::cli::command::ResponseSchema(schemars::schema_for!(sdk::ResponseItem)))
467 }
468}