You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
agent_send is synchronous-only: tool_agent_send (crates/librefang-runtime/src/tool_runner/agent.rs:94-111) awaits kh.send_to_agent_as(...), which runs the target agent's full loop and returns its final response as the tool result. That await is wrapped by the tool-execution timeout (tool_timeout_secs, default 120s), so any delegation that takes longer than the timeout always fails — and even within the budget it blocks the caller's loop, so a Telegram-facing agent goes unresponsive while it waits.
Real failure (production proteo)
deannatroi delegated a research task to agent Viajecinio via agent_send. Viajecinio's nested loop did legitimate multi-step web research (DuckDuckGo + page fetches + several deepseek-v4 turns) that ran past 120s, so deannatroi's agent_send was killed:
WARN run_agent_loop: Tool execution timed out after 120s tool=agent_send agent.name=deannatroi
From deannatroi's side the delegation simply "failed", even though Viajecinio was working fine. This is the exact class of failure that #4983 (async task tracker) was created to solve — but #4983 was only wired to workflows, never to agent_send.
What already exists (the template)
#4983 / its PR shipped a complete async-completion path that workflow_start already uses:
KernelHandle::start_workflow_async_tracked(workflow_id, input, caller_agent_id, caller_session_id) (crates/librefang-kernel-handle/src/workflow_runner.rs:283) — registers the run on the async-task tracker, spawns it detached, returns a run_id immediately.
On completion the kernel calls complete_async_task(...) (crates/librefang-kernel/src/kernel/task_registry.rs:257), which injects a TaskCompletionEvent back into the originating (agent, session) — mid-turn (AgentLoopSignal::TaskCompleted, Feature: Implement message injection between tool calls #956 channel) if the loop is live, else wake-idle (spawns a fresh turn with a [System] [ASYNC_RESULT] message), with the trigger-lane + per-agent concurrency semaphores applied.
agent_send has no equivalent — there is no async variant and no register_async_task wiring.
Proposal
Extend the existing#4983 tracker to inter-agent delegation (no new transport needed — reuse register_async_task / complete_async_task):
Add a non-blocking mode to agent_send — an async: bool param (default false, fully backward-compatible) on the existing tool, OR a sibling agent_start tool mirroring the workflow_run / workflow_start split. (Leaning toward the async param to avoid a confusing new tool name next to agent_spawn.)
Add a KernelHandle method analogous to start_workflow_async_tracked (e.g. send_to_agent_async_tracked(agent_id, message, caller_agent_id, caller_session_id, conversation_key)) that registers a TaskKind for the delegation, spawns the callee loop detached, and on completion calls complete_async_task with the callee's response.
Return a task_id immediately so the caller's LLM frees its turn; the reply arrives later via the existing mid-turn / wake-idle injection.
Depth guard (AGENT_CALL_DEPTH, agent.rs:85-92) and taint checks stay; the async spawn must propagate the depth scope.
Acceptance
agent_send with async=true returns a task_id without blocking; the target's reply is injected back into the caller's session on completion (mid-turn and wake-idle both covered).
Blocking agent_send (default) is byte-for-byte unchanged.
A long (> tool_timeout_secs) delegation no longer hits the tool timeout in async mode.
Integration test mirroring async_task_tracker_runtime_test.rs but for an agent_send delegation.
Problem
agent_sendis synchronous-only:tool_agent_send(crates/librefang-runtime/src/tool_runner/agent.rs:94-111) awaitskh.send_to_agent_as(...), which runs the target agent's full loop and returns its final response as the tool result. That await is wrapped by the tool-execution timeout (tool_timeout_secs, default 120s), so any delegation that takes longer than the timeout always fails — and even within the budget it blocks the caller's loop, so a Telegram-facing agent goes unresponsive while it waits.Real failure (production proteo)
deannatroidelegated a research task to agentViajecinioviaagent_send. Viajecinio's nested loop did legitimate multi-step web research (DuckDuckGo + page fetches + several deepseek-v4 turns) that ran past 120s, so deannatroi'sagent_sendwas killed:From deannatroi's side the delegation simply "failed", even though Viajecinio was working fine. This is the exact class of failure that #4983 (async task tracker) was created to solve — but #4983 was only wired to workflows, never to
agent_send.What already exists (the template)
#4983 / its PR shipped a complete async-completion path that
workflow_startalready uses:KernelHandle::start_workflow_async_tracked(workflow_id, input, caller_agent_id, caller_session_id)(crates/librefang-kernel-handle/src/workflow_runner.rs:283) — registers the run on the async-task tracker, spawns it detached, returns arun_idimmediately.complete_async_task(...)(crates/librefang-kernel/src/kernel/task_registry.rs:257), which injects aTaskCompletionEventback into the originating(agent, session)— mid-turn (AgentLoopSignal::TaskCompleted, Feature: Implement message injection between tool calls #956 channel) if the loop is live, else wake-idle (spawns a fresh turn with a[System] [ASYNC_RESULT]message), with the trigger-lane + per-agent concurrency semaphores applied.agent_sendhas no equivalent — there is no async variant and noregister_async_taskwiring.Proposal
Extend the existing #4983 tracker to inter-agent delegation (no new transport needed — reuse
register_async_task/complete_async_task):agent_send— anasync: boolparam (defaultfalse, fully backward-compatible) on the existing tool, OR a siblingagent_starttool mirroring theworkflow_run/workflow_startsplit. (Leaning toward theasyncparam to avoid a confusing new tool name next toagent_spawn.)KernelHandlemethod analogous tostart_workflow_async_tracked(e.g.send_to_agent_async_tracked(agent_id, message, caller_agent_id, caller_session_id, conversation_key)) that registers aTaskKindfor the delegation, spawns the callee loop detached, and on completion callscomplete_async_taskwith the callee's response.task_idimmediately so the caller's LLM frees its turn; the reply arrives later via the existing mid-turn / wake-idle injection.AGENT_CALL_DEPTH,agent.rs:85-92) and taint checks stay; the async spawn must propagate the depth scope.Acceptance
agent_sendwithasync=truereturns atask_idwithout blocking; the target's reply is injected back into the caller's session on completion (mid-turn and wake-idle both covered).agent_send(default) is byte-for-byte unchanged.tool_timeout_secs) delegation no longer hits the tool timeout in async mode.async_task_tracker_runtime_test.rsbut for anagent_senddelegation.