Skip to content

Commit df42936

Browse files
committed
fix multi-agent coordination
1 parent 63ad963 commit df42936

File tree

3 files changed

+122
-4
lines changed

3 files changed

+122
-4
lines changed

crates/example-eventage-claw/src/agent.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use crate::tools::{
1717
ScheduleTaskTool, TaskState, UpdateTaskTool, WebFetchTool, WebSearchTool, WriteFileTool,
1818
load_tasks, new_group_registry, new_task_state,
1919
};
20-
use crate::workers::{ChannelOutputWorker, RelayWorker, SchedulerWorker};
20+
use crate::workers::{ChannelOutputWorker, DelegationReplyWorker, RelayWorker, SchedulerWorker};
2121
use eventage::{
2222
agent::{ContextAssembler, DefaultContextAssembler},
2323
llm::OpenAiProvider,
@@ -429,6 +429,10 @@ fn build_group_agent(
429429

430430
// ── Per-group workers ──────────────────────────────────────────────────────
431431
let mut group_workers = WorkerSet::new();
432+
group_workers = group_workers.add_worker(DelegationReplyWorker::new(
433+
shared_bus.clone(),
434+
&group_config.name,
435+
));
432436
if let Some(ref webhook_url) = config.webhook_url {
433437
group_workers = group_workers.add_worker(ChannelOutputWorker::new(
434438
webhook_url.clone(),

crates/example-eventage-claw/src/workers.rs

Lines changed: 112 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use eventage::{
1212
EventBus,
1313
};
1414
use serde_json::json;
15-
use std::collections::HashMap;
15+
use std::collections::{HashMap, VecDeque};
1616
use std::sync::Arc;
1717
use tokio::sync::Mutex;
1818
use tracing::{debug, info, warn};
@@ -297,9 +297,9 @@ impl EventWorker for RelayWorker {
297297
if let Some(target_bus) = self.group_buses.get(target) {
298298
target_bus
299299
.publish(Event::new(
300-
kinds::USER_MESSAGE,
300+
kinds::AGENT_MESSAGE,
301301
json!({
302-
"text": format!("[Message from group '{source}']\n{content}"),
302+
"text": format!("[Agent '{}' says]\n{content}", source),
303303
"source_group": source,
304304
"message_id": msg_id,
305305
"via": "relay",
@@ -314,3 +314,112 @@ impl EventWorker for RelayWorker {
314314
Ok(())
315315
}
316316
}
317+
318+
// ── DelegationReplyWorker ─────────────────────────────────────────────────────
319+
320+
/// Runs on each group's **per-group bus**.
321+
///
322+
/// Closes the reply loop for `MessageGroupTool(await_reply=true)`:
323+
/// 1. Detects inbound relay requests (`agent.message` with `via: "relay"`)
324+
/// 2. Captures the agent's final response (`assistant.message` content)
325+
/// 3. On `agent.cycle.end`, publishes `CLAW_GROUP_MESSAGE` on the shared bus
326+
/// with `in_reply_to` set, unblocking the calling agent's `await_reply`.
327+
///
328+
/// A `VecDeque` queue handles back-to-back delegations arriving before the
329+
/// agent finishes its current cycle.
330+
pub struct DelegationReplyWorker {
331+
pub shared_bus: EventBus,
332+
pub group_name: String,
333+
pending: Arc<Mutex<VecDeque<PendingReply>>>,
334+
last_content: Arc<Mutex<Option<String>>>,
335+
}
336+
337+
struct PendingReply {
338+
source_group: String,
339+
message_id: String,
340+
}
341+
342+
impl DelegationReplyWorker {
343+
pub fn new(shared_bus: EventBus, group_name: impl Into<String>) -> Self {
344+
Self {
345+
shared_bus,
346+
group_name: group_name.into(),
347+
pending: Arc::new(Mutex::new(VecDeque::new())),
348+
last_content: Arc::new(Mutex::new(None)),
349+
}
350+
}
351+
}
352+
353+
#[async_trait]
354+
impl EventWorker for DelegationReplyWorker {
355+
fn subscribed_kinds(&self) -> Vec<String> {
356+
vec![
357+
kinds::AGENT_MESSAGE.to_string(),
358+
kinds::ASSISTANT_MESSAGE.to_string(),
359+
kinds::AGENT_CYCLE_END.to_string(),
360+
]
361+
}
362+
363+
async fn handle(&self, event: &Event, _bus: &EventBus) -> Result<(), WorkerError> {
364+
match event.kind.as_str() {
365+
k if k == kinds::AGENT_MESSAGE => {
366+
// Only track relay requests, not broadcasts from this group's own agent.
367+
if event.payload.get("via").and_then(|v| v.as_str()) != Some("relay") {
368+
return Ok(());
369+
}
370+
let source = event.payload["source_group"].as_str().unwrap_or("").to_string();
371+
let msg_id = event.payload["message_id"].as_str().unwrap_or("").to_string();
372+
if !msg_id.is_empty() {
373+
debug!(
374+
group = %self.group_name,
375+
source = %source,
376+
msg_id = %msg_id,
377+
"DelegationReplyWorker: queued relay request"
378+
);
379+
self.pending.lock().await.push_back(PendingReply { source_group: source, message_id: msg_id });
380+
}
381+
}
382+
383+
k if k == kinds::ASSISTANT_MESSAGE => {
384+
if let Some(content) = event.payload.get("content").and_then(|v| v.as_str()) {
385+
if !content.trim().is_empty() {
386+
*self.last_content.lock().await = Some(content.to_string());
387+
}
388+
}
389+
}
390+
391+
k if k == kinds::AGENT_CYCLE_END => {
392+
let reply = self.pending.lock().await.pop_front();
393+
let Some(PendingReply { source_group, message_id }) = reply else {
394+
return Ok(());
395+
};
396+
397+
let content = self.last_content.lock().await.take().unwrap_or_default();
398+
399+
info!(
400+
group = %self.group_name,
401+
target = %source_group,
402+
msg_id = %message_id,
403+
"DelegationReplyWorker: routing reply back to caller"
404+
);
405+
406+
self.shared_bus
407+
.publish(Event::new(
408+
CLAW_GROUP_MESSAGE,
409+
json!({
410+
"target_group": source_group,
411+
"source_group": self.group_name,
412+
"content": content,
413+
"in_reply_to": message_id,
414+
}),
415+
))
416+
.await
417+
.map_err(WorkerError::Bus)?;
418+
}
419+
420+
_ => {}
421+
}
422+
423+
Ok(())
424+
}
425+
}

src/agent/context.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,11 @@ pub fn events_to_messages(events: &[Event]) -> Vec<ChatMessage> {
150150
messages.push(ChatMessage::tool_result(id, result));
151151
}
152152
}
153+
kinds::AGENT_MESSAGE => {
154+
if let Some(text) = event.payload.get("text").and_then(|v| v.as_str()) {
155+
messages.push(ChatMessage::user(text));
156+
}
157+
}
153158
_ => {}
154159
}
155160
}

0 commit comments

Comments
 (0)