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 let e = Error::AgentNoPriorRequest {
272 agent_instance_hierarchy: hierarchy.clone(),
273 };
274 let tee = crate::db::logs::ConversationTee::spawn(
275 ctx.filesystem.state_dir(),
276 );
277 crate::command::agents::spawn::note_error(
278 &ctx, &tee, Some(&hierarchy), None, &e,
279 )
280 .await;
281 yield Err(e);
283 return;
284 }
285 Err(e) => {
286 let e: Error = e.into();
287 let tee = crate::db::logs::ConversationTee::spawn(
288 ctx.filesystem.state_dir(),
289 );
290 crate::command::agents::spawn::note_error(
291 &ctx, &tee, Some(&hierarchy), None, &e,
292 )
293 .await;
294 yield Err(e);
295 return;
296 }
297 };
298
299 yield Ok(ResponseItem::AgentSpawned(AgentSpawnedResponseItem {
300 r#type: AgentSpawnedType::AgentSpawned,
301 agent_instance_hierarchy: hierarchy.clone(),
302 }));
303
304 let inner = crate::command::agents::spawn::run_multi_pass(
309 ctx.clone(),
310 Vec::new(),
311 lookup.agent,
312 None,
313 lookup.continuation,
314 None,
315 vec![crate::db::laboratory_attachments::Target::Aih(hierarchy.clone())],
316 None,
317 registry,
318 );
319 let mut inner = Box::pin(inner);
320 while let Some(item) = inner.next().await {
321 match item {
322 Ok(spawn_item) => {
323 yield Ok(ResponseItem::Value(ValueResponseItem {
324 agent_instance_hierarchy: hierarchy.clone(),
325 value: Box::new(RootResponseItem::Agents(
326 AgentsResponseItem::Spawn(spawn_item),
327 )),
328 }));
329 }
330 Err(e) => yield Err(e),
331 }
332 }
333 }
334}
335
336fn deliver_one_tag(
347 ctx: Context,
348 agent_tag: String,
349) -> impl Stream<Item = Result<ResponseItem, Error>> + Send {
350 async_stream::stream! {
351 let state_dir = ctx.filesystem.state_dir();
352 let pool = match ctx.db_client().await {
355 Ok(pool) => pool,
356 Err(e) => {
357 yield Err(e);
358 return;
359 }
360 };
361 let (agent, tag_group_id) = match crate::db::tags::lookup(pool, &agent_tag).await {
362 Ok(crate::db::tags::LookupState::Grouped { agent_spec, tag_group_id, .. }) => {
363 (agent_spec, tag_group_id)
364 }
365 Ok(crate::db::tags::LookupState::Bound { agent_instance_hierarchy }) => {
366 let mut inner = Box::pin(deliver_one_hierarchy(
368 ctx.clone(),
369 agent_instance_hierarchy,
370 ));
371 while let Some(item) = inner.next().await {
372 yield item;
373 }
374 return;
375 }
376 Ok(crate::db::tags::LookupState::Absent) => {
377 yield Err(Error::TagNotFound(agent_tag));
378 return;
379 }
380 Err(e) => {
381 yield Err(e.into());
382 return;
383 }
384 };
385
386 let registry = match crate::command::agents::locks::try_acquire_family(
390 ctx.agent_locks(),
391 pool,
392 &state_dir,
393 crate::command::agents::locks::Family::Group(tag_group_id),
394 )
395 .await
396 {
397 Ok(Some(fam)) => {
398 let mut registry = AgentInstanceRegistry::new(state_dir, ctx.agent_locks_arc());
399 registry.hold_tag_claims(fam.tags);
400 registry
401 }
402 Ok(None) => {
403 yield Ok(ResponseItem::TagActive(TagActiveResponseItem {
404 r#type: TagActiveType::TagActive,
405 agent_tag,
406 }));
407 return;
408 }
409 Err(e) => {
410 yield Err(e);
411 return;
412 }
413 };
414
415 yield Ok(ResponseItem::TagSpawned(TagSpawnedResponseItem {
416 r#type: TagSpawnedType::TagSpawned,
417 agent_tag: agent_tag.clone(),
418 }));
419
420 let lab_targets = vec![crate::db::laboratory_attachments::Target::Tag(agent_tag.clone())];
427 let inner = crate::command::agents::spawn::run_multi_pass(
428 ctx.clone(),
429 Vec::new(),
430 agent,
431 None,
432 None,
433 Some(agent_tag),
434 lab_targets,
435 None,
436 registry,
437 );
438 let mut inner = Box::pin(inner);
439 let mut minted: Option<String> = None;
443 while let Some(item) = inner.next().await {
444 match item {
445 Ok(spawn_item) => {
446 if let SpawnResponseItem::Id(id) = &spawn_item {
447 minted = Some(id.clone());
448 }
449 let aih = minted.clone().unwrap_or_default();
450 yield Ok(ResponseItem::Value(ValueResponseItem {
451 agent_instance_hierarchy: aih,
452 value: Box::new(RootResponseItem::Agents(
453 AgentsResponseItem::Spawn(spawn_item),
454 )),
455 }));
456 }
457 Err(e) => yield Err(e),
458 }
459 }
460 }
461}
462
463pub mod request_schema {
464 use objectiveai_sdk::cli::command::agents::queue::deliver as sdk;
465 use objectiveai_sdk::cli::command::agents::queue::deliver::request_schema::{
466 Request, Response,
467 };
468
469 use crate::context::Context;
470 use crate::error::Error;
471
472 pub async fn execute(_ctx: &Context, _request: Request) -> Result<Response, Error> {
473 Ok(objectiveai_sdk::cli::command::ResponseSchema(schemars::schema_for!(sdk::Request)))
474 }
475}
476
477pub mod response_schema {
478 use objectiveai_sdk::cli::command::agents::queue::deliver as sdk;
479 use objectiveai_sdk::cli::command::agents::queue::deliver::response_schema::{
480 Request, Response,
481 };
482
483 use crate::context::Context;
484 use crate::error::Error;
485
486 pub async fn execute(_ctx: &Context, _request: Request) -> Result<Response, Error> {
487 Ok(objectiveai_sdk::cli::command::ResponseSchema(schemars::schema_for!(sdk::ResponseItem)))
488 }
489}