- Updated Memory section to always use session_id='hope' for remember/recall
- This makes memory operations instant (0.3s) instead of 20+ seconds
- Fixed tool_call logging bug (FunctionToolCall has .name, not .function.name)
Reduces tool count from 10 to 8 for the LLM, removing the conflict
between 'use dispatch for everything' and having web_search available.
The task_worker has its own web tools for background tasks.
- Remove memory_mcp.py (stdio markdown-file memory)
- Add Cognee MCP as remote Streamable HTTP toolset at 192.168.86.2:8003/mcp
- Filter to only remember/recall/forget tools via allowed_tools
- Update system prompt: Memory section moved to top priority with
explicit 'call recall FIRST' instructions and examples
- Add COGNEE_MCP_URL env var to docker-compose
- Remove /memory volume mount (no longer needed)
- Rewrite memory tests to use Cognee HTTP client fixture
- 28/28 tests passing
- token_server: generate voice-{uuid8} room name per request so each
browser session creates a fresh room and triggers agent dispatch
- token claim: add jobType JT_ROOM to roomConfig.agents
- livekit.yaml: revert empty_timeout to default (300s)
- agent.py: system prompt now mandates dispatch_task for all non-trivial
tasks (not just research); removed inline web_search instructions;
added tool_call logging in _ReasoningFallbackWrapper
The FastMCP server never started listening on stdio because the file was
missing 'if __name__ == "__main__": mcp.run(transport="stdio")'. The
LiveKit agent's MCP client got 'Connection closed' during initialize,
which killed the entire toolset setup — ALL tools (weather, memory,
skills, dispatch) were unavailable. This is why Hope could hear you but
never called any tools.
Set room.empty_timeout to 86400 (24h) in livekit.yaml. Previously the
default 300s timeout destroyed the room when all participants left,
which killed the agent job and left no worker registered for the next
join — causing 'assistant not ready retry' on tab reopen.
In the container, agent.py is at /opt/voice-agent/agent.py (flat, not a
package). The 'from agent.task_registry import ...' raised ModuleNotFoundError
which killed the job before session.start(), causing 'assistant not ready
retry' in the browser. Added try/except ImportError fallback to bare import.
- 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.
The 4B model was responding conversationally instead of calling memory_save/
memory_recall. Added imperative language (MUST call), concrete examples of
trigger phrases, and explicit instructions to never skip the tool call.
Verified: model now reliably generates tool_calls for save/recall/list.
When conversation exceeds 24 items (~12 turns), Gemma summarizes the older
messages into a 2-4 sentence recap that replaces them, keeping the last 10
items verbatim. Also triggers after 5 minutes of idle time so returning
users get a compacted context rather than a bloated one.
Falls back to hard truncation if the summary call fails.
Compaction:
- GemmaLLM.chat() truncates ChatContext to last 30 items (~15 turns)
before sending to LLM, preventing context window overflow on long
conversations. Preserves system prompt and removes orphaned tool calls.
Skills:
- agent/skills_mcp.py: MCP server with skill_save, skill_recall,
skill_list, skill_update tools backed by .md files in /skills
- skills/ dir bind-mounted into container, git-trackable
- System prompt instructs Hope to save repeatable procedures as skills
and recall them before performing tasks she's done before
- Distinct from memory (facts) — skills are learned *procedures*
- agent/memory_mcp.py: MCP server with memory_recall, memory_save, memory_list
tools backed by .md files in /memory (simple keyword matching for v1)
- memory/ dir bind-mounted into container, persists across rebuilds,
easily backed up via git
- System prompt instructs Hope to recall on past references and save
personal info/preferences naturally without announcing it
- Dockerfile: copy memory_mcp.py; compose: ./memory:/memory volume
- preemptive_generation was incompatible with MCP tool calls: it starts
generating before the turn finalizes, breaking the tool execution loop
(agent said 'let me check' then hung forever)
- Add get_time(location?) to weather_mcp.py for date/time queries
- Update system prompt with Weather & Time section
- lk.transcription handler now uses addPartial() (in-place update)
instead of addMessage() (new row per word)
- addMessage() clears stale partials before rendering the final
- New agent/weather_mcp.py: stdio MCP server with get_weather(location)
that returns a short conversational summary (temp, conditions, high/low)
- Register weather toolset in build_mcp_toolsets() alongside web-access
- Update system prompt with Weather section
- agent/web_mcp.py: stdio MCP server exposing web_search and web_scrape,
backed by the self-hosted Firecrawl stack on xNAS (no API key needed)
- agent.py: Agent now attaches mcp_servers built from config; EXTRA_MCP_SERVERS
env var allows adding arbitrary HTTP/SSE MCP servers as JSON
- Dockerfile: installs livekit-agents[mcp], copies web_mcp.py
- .env.example: WEB_MCP_ENABLED, FIRECRAWL_BASE, EXTRA_MCP_SERVERS documented
- nginx serves the UI over HTTPS on 8090 with a self-signed cert
(browsers require a secure context for microphone access)
- added /token endpoint (tiny Python HTTP server) that signs LiveKit
JWTs server-side, keeping the API secret out of the browser
- app.js now fetches a signed token from /token and uses wss:// when
the page is served over HTTPS
- supervisord runs the token-server as a fourth process