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
Agents need a way to launch async operations (workflows, agent_send delegations, external calls) and receive results without blocking their conversation loop or requiring a separate orchestrator agent burning tokens on continuous polling.
Motivation: real-world failure on production Rodela instance
Agent ltdata (Telegram assistant) launched a workflow via workflow_run. The workflow took several minutes. Result:
workflow_run blocked ltdata's agent loop for the entire duration
The user sent follow-up messages during this time — ltdata was unresponsive
The workflow eventually timed out
ltdata received no run_id, no partial status, no error context — just a timeout
The agent was effectively "bricked" for the duration, unable to serve any user
The alternative workflow_start (async) returns a run_id immediately, but ltdata has no mechanism to check back on it. The run_id is returned to the LLM as a tool result, but by the next user message the agent has moved on and forgotten about it.
The current workaround is the orchestrator agent pattern: a separate agent on continuous mode (every 120s) that polls for pending work. This costs ~$2/day in LLM calls even when idle, and introduces coordination complexity between the orchestrator and the actual agents.
Problem statement
There are three async patterns in LibreFang, all with the same gap:
Pattern
Launch
Result delivery
Problem
workflow_run
Blocking tool call
Returns when done
Agent frozen, user ignored, timeout kills it
workflow_start
Returns run_id
Nothing — fire and forget
Agent forgets, user never gets result
agent_send
Sends to peer agent
Peer response in sender's session
Blocking — sender waits for full response
None of these allow: "launch → continue serving user → get notified when done → act on result."
Proposed: kernel-level async task tracker
Architecture
Agent launches async op → kernel registers (agent_id, task_type, task_id)
→ agent continues normally (responds to user, handles new messages)
→ async op completes → kernel injects result into agent's session as synthetic message
→ agent processes result on next turn (or wakes up if idle)
Kernel task registry
structPendingTask{agent_id:AgentId,session_id:SessionId,task_type:TaskType,// Workflow, AgentSend, Webhook, etc.task_id:String,// run_id, message_id, etc.launched_at:Instant,timeout:Option<Duration>,callback_channel:Option<String>,// channel to notify user through}enumTaskType{WorkflowRun{workflow_id:String},AgentDelegation{target_agent:AgentId},ExternalWebhook{url:String},}
Event injection
When the async op completes, the kernel injects a synthetic message into the agent's session:
If the agent is idle (no active turn), this triggers a new turn — the agent wakes up, processes the result, and notifies the user through whatever channel the conversation is on.
If the agent is mid-turn, the result queues and is processed after the current turn completes (same as mid-turn message injection, #956).
New tool: async_run
Replace the blocking workflow_run with a unified async launcher:
workflow_start(workflow_id, input) → returns run_id (existing, unchanged)
→ kernel auto-tracks, will inject result
The difference from today: workflow_start already exists, but the kernel doesn't track the pending task or inject the result. The change is in the kernel, not the tool.
On timeout: inject a timeout event into the session so the agent can inform the user ("the workflow is taking longer than expected, I'll keep checking").
Before
workflow_run: agent blocks, user ignored, timeout = dead end
workflow_start: fire and forget, user never gets result
Workaround: $2/day orchestrator agent polling every 120s
Summary
Agents need a way to launch async operations (workflows, agent_send delegations, external calls) and receive results without blocking their conversation loop or requiring a separate orchestrator agent burning tokens on continuous polling.
Motivation: real-world failure on production Rodela instance
Agent
ltdata(Telegram assistant) launched a workflow viaworkflow_run. The workflow took several minutes. Result:workflow_runblocked ltdata's agent loop for the entire durationThe alternative
workflow_start(async) returns a run_id immediately, but ltdata has no mechanism to check back on it. The run_id is returned to the LLM as a tool result, but by the next user message the agent has moved on and forgotten about it.The current workaround is the
orchestratoragent pattern: a separate agent oncontinuousmode (every 120s) that polls for pending work. This costs ~$2/day in LLM calls even when idle, and introduces coordination complexity between the orchestrator and the actual agents.Problem statement
There are three async patterns in LibreFang, all with the same gap:
workflow_runworkflow_startagent_sendNone of these allow: "launch → continue serving user → get notified when done → act on result."
Proposed: kernel-level async task tracker
Architecture
Kernel task registry
Event injection
When the async op completes, the kernel injects a synthetic message into the agent's session:
{ "role": "system", "content": "[ASYNC_RESULT] workflow 'publish_article' (run abc-123) completed.\nOutput: ...\nArtifacts: [image_url, ...]", "metadata": {"task_type": "workflow", "task_id": "abc-123", "status": "completed"} }If the agent is idle (no active turn), this triggers a new turn — the agent wakes up, processes the result, and notifies the user through whatever channel the conversation is on.
If the agent is mid-turn, the result queues and is processed after the current turn completes (same as mid-turn message injection, #956).
New tool:
async_runReplace the blocking
workflow_runwith a unified async launcher:The difference from today:
workflow_startalready exists, but the kernel doesn't track the pending task or inject the result. The change is in the kernel, not the tool.Timeout handling
On timeout: inject a timeout event into the session so the agent can inform the user ("the workflow is taking longer than expected, I'll keep checking").
Before
workflow_run: agent blocks, user ignored, timeout = dead endworkflow_start: fire and forget, user never gets resultAfter
workflow_start: returns immediately, agent continues chattingFiles
crates/librefang-kernel/src/kernel/messaging.rs— event injection on async completioncrates/librefang-kernel/src/workflow.rs— emit completion event to task registrycrates/librefang-runtime/src/agent_loop.rs— process injected async resultscrates/librefang-kernel/src/kernel/mod.rs— PendingTask registryRelated