feat: background task dispatch system with live UI panel
- agent/task_registry.py: file-based JSONL event registry (cross-process) - agent/task_worker.py: autonomous LLM loop with weather/time/memory/web tools - agent/dispatch_mcp.py: MCP tool exposing dispatch_task to the main agent - agent/agent.py: registers dispatch toolset, polls task events → room data - web: slide-out task panel (FAB button + badge), live step streaming via data channel topic 'tasks', status dots (running/completed/failed) - Dockerfile: copies new task_*.py and dispatch_mcp.py files The dispatch MCP runs in its own process; events flow through /tmp/tasks/events.jsonl which the main agent tails every second and forwards to the browser. Tasks run up to 10 LLM iterations with tool calls.
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
"""Dispatch MCP server — lets Hope spawn background tasks.
|
||||
|
||||
The main agent calls dispatch_task(description) when the user asks for
|
||||
something long-running. The tool returns immediately with a task ID; the
|
||||
actual work runs in a background asyncio task via TaskWorker.
|
||||
|
||||
Communication with the main agent process happens via /tmp/tasks/events.jsonl
|
||||
(the file-based registry).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
logger = logging.getLogger("voice-agent.dispatch")
|
||||
|
||||
mcp = FastMCP("dispatch")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def dispatch_task(description: str) -> str:
|
||||
"""Spawn a background task to research or look something up.
|
||||
|
||||
Use this when the user asks you to do something that will take more than
|
||||
a few seconds (research, look up news, multi-step investigation). The task
|
||||
runs in parallel while you continue the conversation. When it completes,
|
||||
the result will be delivered back to you automatically — you can tell the
|
||||
user "I'll let you know when I find out."
|
||||
|
||||
Returns a short confirmation with the task ID.
|
||||
"""
|
||||
try:
|
||||
from agent.task_registry import registry
|
||||
from agent.task_worker import TaskWorker
|
||||
except ImportError:
|
||||
from task_registry import registry
|
||||
from task_worker import TaskWorker
|
||||
|
||||
task = registry.create(description)
|
||||
worker = TaskWorker(task.id, description)
|
||||
asyncio.create_task(_run_worker(worker, task.id))
|
||||
logger.info("Dispatched task %s: %s", task.id, description)
|
||||
return f"Task {task.id} started. I'll report back when it's done."
|
||||
|
||||
|
||||
async def _run_worker(worker: "TaskWorker", task_id: str):
|
||||
try:
|
||||
from agent.task_registry import registry
|
||||
except ImportError:
|
||||
from task_registry import registry
|
||||
|
||||
try:
|
||||
await worker.run()
|
||||
except Exception as e:
|
||||
logger.exception("Task %s crashed: %s", task_id, e)
|
||||
registry.fail(task_id, str(e))
|
||||
Reference in New Issue
Block a user