Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions ductor_bot/bus/adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,12 @@ def from_webhook_wake(chat_id: int, prompt: str) -> Envelope:
# -- Inter-agent ---------------------------------------------------------------


def from_interagent_result(result: AsyncInterAgentResult, chat_id: int) -> Envelope:
def from_interagent_result(
result: AsyncInterAgentResult,
chat_id: int,
*,
injection_prompt: str = "",
) -> Envelope:
"""Convert an async inter-agent result.

Uses ``result.chat_id`` / ``result.topic_id`` when available so that
Expand All @@ -150,6 +155,10 @@ def from_interagent_result(result: AsyncInterAgentResult, chat_id: int) -> Envel

Error results are delivered without lock or injection.
Success results acquire the lock and inject into the active session.
``injection_prompt`` must be supplied by the caller (e.g.
``app.on_async_interagent_result``) so that ``bus._process`` can
actually invoke the CLI injection step. When empty, injection is
skipped and only the raw ``result_text`` is delivered.
"""
delivery_chat_id = result.chat_id or chat_id
meta = {
Expand Down Expand Up @@ -181,12 +190,13 @@ def from_interagent_result(result: AsyncInterAgentResult, chat_id: int) -> Envel
origin=Origin.INTERAGENT,
chat_id=delivery_chat_id,
topic_id=result.topic_id,
prompt=injection_prompt,
prompt_preview=result.message_preview,
result_text=result.result_text,
status="success",
delivery=DeliveryMode.UNICAST,
lock_mode=LockMode.REQUIRED,
needs_injection=True,
needs_injection=bool(injection_prompt),
elapsed_seconds=result.elapsed_seconds,
session_name=result.session_name,
metadata=meta,
Expand Down
28 changes: 27 additions & 1 deletion ductor_bot/messenger/matrix/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -1206,7 +1206,33 @@ async def on_async_interagent_result(self, result: AsyncInterAgentResult) -> Non
text = result.result_text or f"Inter-agent result from {result.recipient}"
await self._notification_service.notify_all(text)
return
await self._bus.submit(from_interagent_result(result, chat_id))

injection_prompt = ""
if result.success:
recipient = result.recipient or result.sender
session_hint = (
f"\nThe recipient processed this in session `{result.session_name}`. "
f"via `@{result.session_name} <message>`."
if result.session_name
else ""
)
task_context = (
f"\n\nOriginal task you sent to '{recipient}':\n{result.original_message}"
if result.original_message
else ""
)
injection_prompt = (
f"[ASYNC INTER-AGENT RESPONSE from '{recipient}'"
f" (task {result.task_id})]\n"
f"{result.result_text}\n"
f"[END ASYNC INTER-AGENT RESPONSE]{session_hint}{task_context}\n\n"
f"You are agent '{self._agent_name}'. Process this response from agent "
f"'{recipient}' and communicate the relevant results to the user."
)

await self._bus.submit(
from_interagent_result(result, chat_id, injection_prompt=injection_prompt)
)

async def on_task_result(self, result: TaskResult) -> None:
from ductor_bot.bus.adapters import from_task_result
Expand Down
36 changes: 35 additions & 1 deletion ductor_bot/messenger/telegram/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -1474,7 +1474,41 @@ async def on_async_interagent_result(self, result: AsyncInterAgentResult) -> Non
logger.warning("No chat_id available for async interagent result delivery")
return
set_log_context(operation="ia-async", chat_id=chat_id)
await self._bus.submit(from_interagent_result(result, chat_id))

injection_prompt = ""
if result.success:
recipient = result.recipient or result.sender
session_hint = (
f"\nThe recipient processed this in session `{result.session_name}`. "
f"The user can continue this session in the recipient's Telegram chat "
f"via `@{result.session_name} <message>`."
if result.session_name
else ""
)
task_context = (
f"\n\nOriginal task you sent to '{recipient}':\n{result.original_message}"
if result.original_message
else ""
)
injection_prompt = (
f"[ASYNC INTER-AGENT RESPONSE from '{recipient}'"
f" (task {result.task_id})]\n"
f"{result.result_text}\n"
f"[END ASYNC INTER-AGENT RESPONSE]{session_hint}{task_context}\n\n"
f"You are agent '{self._agent_name}'. Process this response from agent "
f"'{recipient}' and communicate the relevant results to the user "
f"in your Telegram chat."
)
logger.info(
"ia-async inject: task=%s from=%s prompt_len=%d",
result.task_id,
recipient,
len(injection_prompt),
)

await self._bus.submit(
from_interagent_result(result, chat_id, injection_prompt=injection_prompt)
)

async def on_task_result(self, result: TaskResult) -> None:
"""Handle background task result via the message bus."""
Expand Down
16 changes: 14 additions & 2 deletions tests/bus/test_adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,17 +160,29 @@ def test_from_webhook_wake() -> None:
assert env.lock_mode == LockMode.REQUIRED


def test_from_interagent_success() -> None:
def test_from_interagent_success_without_prompt_no_injection() -> None:
"""rr#18: without injection_prompt, needs_injection must be False (raw deliver)."""
env = from_interagent_result(_FakeInterAgentResult(), chat_id=100)
assert env.origin == Origin.INTERAGENT
assert env.chat_id == 100
assert env.status == "success"
assert env.delivery == DeliveryMode.UNICAST
assert env.lock_mode == LockMode.REQUIRED
assert env.needs_injection
assert not env.needs_injection
assert env.prompt == ""
assert env.metadata["sender"] == "agent-a"


def test_from_interagent_success_with_prompt_enables_injection() -> None:
"""rr#18: injection_prompt wires needs_injection=True and sets envelope.prompt."""
prompt = "[ASYNC INTER-AGENT RESPONSE from 'dev' (task t1)]\nresult\n[END]"
env = from_interagent_result(_FakeInterAgentResult(), chat_id=100, injection_prompt=prompt)
assert env.origin == Origin.INTERAGENT
assert env.needs_injection
assert env.prompt == prompt
assert env.lock_mode == LockMode.REQUIRED


def test_from_interagent_error() -> None:
env = from_interagent_result(_FakeInterAgentResult(success=False, error="timeout"), chat_id=100)
assert env.status == "error"
Expand Down
Loading