31 Commits
Author SHA1 Message Date
Shane 3700b52883 fix: use session_id for instant memory operations
- 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)
2026-08-23 08:57:25 -04:00
Shane 65deb0e4ce refactor: remove web tools from agent (dispatch handles all web work)
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.
2026-08-23 08:13:37 -04:00
Shane c815bcb485 feat: replace custom memory MCP with Cognee remote server
- 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
2026-08-23 08:10:42 -04:00
Shane f80b9ebe3d fix: unique room names per session, JT_ROOM dispatch, dispatch-first prompt
- 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
2026-08-23 07:05:03 -04:00
Shane 98168f2876 fix: dispatch_mcp.py missing mcp.run() entry point
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.
2026-08-22 19:53:27 -04:00
Shane d08a4e1fcb fix: keep LiveKit rooms alive so agent survives reconnects
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.
2026-08-22 18:54:22 -04:00
Shane dd33837055 fix: import task_registry without 'agent.' package prefix in container
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.
2026-08-22 18:41:11 -04:00
Shane 44e8d05e2c 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.
2026-08-22 18:06:23 -04:00
Shane d372adca7d fix: strengthen memory tool prompt — Gemma 4B needs explicit examples
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.
2026-08-22 17:42:26 -04:00
Shane a1d59580f7 fix: ChatContext is not a sequence — use .items for len/list; add test suite
Bug fix:
- GemmaLLM.chat(): len(chat_ctx) → len(chat_ctx.items)
- GemmaLLM._compact_context(): list(chat_ctx) → list(chat_ctx.items)
  ChatContext in livekit-agents 1.7 is not iterable or sized directly;
  it exposes an .items list. The TypeError was silently killing all LLM
  responses (agent heard user but never spoke back).

Test suite (tests/):
- test_web_ui.py: static assets, token endpoint, LiveKit WS proxy
- test_llm_api.py: completion, tool calling, thinking-disabled latency, streaming
- test_mcp_tools.py: weather (Fahrenheit), time, memory, skills — all via
  docker exec + in-container MCP client
- test_agent_integration.py: process alive, worker registered, supervisord,
  container health, LiveKit API
- test_compaction.py: size-triggered compaction, system prompt preservation,
  short-context no-op
- conftest.py: shared fixtures (HTTPS client, token, LLM, docker exec helpers)
- Run with: .venv-tests/bin/python -m pytest tests/ -v
2026-08-22 17:09:29 -04:00
Shane 938629df83 feat: LLM-based context compaction with idle trigger
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.
2026-08-22 16:51:21 -04:00
Shane ab6b5254ef feat: context compaction + skills system
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*
2026-08-22 16:42:16 -04:00
Shane de1a5d8400 feat: markdown memory system — Hope learns from conversations
- 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
2026-08-22 16:33:00 -04:00
Shane 49d8577a4c feat: name the agent Hope 2026-08-22 16:26:01 -04:00
Shane f882b694b9 fix: report weather in Fahrenheit by default 2026-08-22 16:23:09 -04:00
Shane c620867854 fix: disable preemptive_generation for tool call compatibility; add get_time tool
- 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
2026-08-22 16:13:06 -04:00
Shane 24026e47de fix: live transcription partials update in place instead of stacking rows
- lk.transcription handler now uses addPartial() (in-place update)
  instead of addMessage() (new row per word)
- addMessage() clears stale partials before rendering the final
2026-08-22 15:57:27 -04:00
Shane c2f4c62aad feat: add mic mute button with visual state 2026-08-22 15:51:59 -04:00
Shane 510a577761 feat: add get_weather MCP tool using wttr.in API
- 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
2026-08-22 15:26:25 -04:00
Shane d3f9f2c4ed feat: implement full UPDATE.md review — critical fixes, UI upgrade, infra hardening
Critical frontend bugs:
- Add TrackSubscribed/attach() for agent audio playback
- Fix decodeToString TypeError with TextDecoder
- XSS fix: innerHTML -> textContent in addMessage
- Fresh token on reconnect retry

Agent fixes:
- GemmaLLM subclass with reasoning_content fallback wrapper
- Disable Gemma 4 thinking mode via chat_template_kwargs (6.8s -> 0.5s)
- Remove duplicate session-level LLM
- Replace global _active_session with closure-based handler
- asyncio.create_task instead of deprecated get_event_loop
- Explicit silero VAD, topic filter on voice-control

Infra:
- supervisord: all programs log to /dev/stdout
- Dockerfile: uv sync --frozen with committed uv.lock
- nginx config moved to real file, token_server.py no longer served
- entrypoint.sh: cert persisted, only regenerated on IP change
- compose: healthcheck + cert volume
- token_server: CORS removed, room pinned to voice-room

UI upgrade:
- Orb UI with state machine (idle/connecting/listening/thinking/speaking)
- Streaming transcripts via lk.transcription text streams
- Barge-in hint, thinking chip, audio visualizer
- Glassmorphism, chat bubbles, settings sheet, light mode
- PWA manifest, favicon, wake-lock, safe-area insets
- localStorage conversation history

Docs: AGENTS.md drift fixed
2026-08-22 15:21:59 -04:00
Shane d1eeb01f3d fix: set SSL_CERT_FILE to correct path for slim base image 2026-08-22 12:51:01 -04:00
Shane f7e799c9e9 fix: add WEB_MCP_ENABLED + FIRECRAWL_BASE to docker-compose env 2026-08-22 12:41:16 -04:00
Shane b55e6ed39a refactor: use MCPToolset instead of deprecated mcp_servers param
- Switch from Agent(mcp_servers=[...]) to Agent(tools=[MCPToolset(...)])
- Add WEB_MCP_ENABLED + FIRECRAWL_BASE to supervisord agent env
- Fixes deprecation warning in livekit-agents 1.7
2026-08-22 12:40:07 -04:00
Shane 045ddabac7 feat: web access via MCP (Firecrawl search/scrape) + mcp server attach support
- 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
2026-08-22 12:29:04 -04:00
Shane 6f2b231938 feat: HTTPS web frontend (self-signed) + server-side token endpoint
- 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
2026-08-22 08:31:19 -04:00
Shane e807ade45d fix: agent data_received handler + livekit config 2026-08-22 08:24:42 -04:00
Shane 18bbc75216 fix: livekit.yaml config (remove invalid bind_addr field) 2026-08-22 08:15:57 -04:00
Shane 456fd4e7e2 feat: web frontend with voice selector, transcript, LiveKit client 2026-08-22 07:43:31 -04:00
Shane 97633cc585 feat: Dockerfile, docker-compose, livekit.yaml, supervisord.conf 2026-08-22 07:41:58 -04:00
Shane bbca8505d7 feat: agent.py with Azure STT/TTS + Gemma LLM pipeline, voice switching 2026-08-22 07:40:58 -04:00
Shane f7ff1c61ac chore: init project with AGENTS.md, .env.example, .gitignore 2026-08-22 07:39:54 -04:00