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*
This commit is contained in:
Shane
2026-08-22 16:42:16 -04:00
parent de1a5d8400
commit ab6b5254ef
6 changed files with 250 additions and 2 deletions
+31 -2
View File
@@ -39,6 +39,7 @@ GEMMA_API_KEY = os.environ.get("GEMMA_API_KEY", "not-needed")
WEB_MCP_ENABLED = os.environ.get("WEB_MCP_ENABLED", "true").lower() in ("1", "true", "yes")
FIRECRAWL_BASE = os.environ.get("FIRECRAWL_BASE", "http://192.168.86.2:3002")
MEMORY_DIR = os.environ.get("MEMORY_DIR", "/memory")
SKILLS_DIR = os.environ.get("SKILLS_DIR", "/skills")
SYSTEM_PROMPT = textwrap.dedent("""\
You are Hope, a warm, conversational voice assistant. You are talking TO
@@ -89,8 +90,15 @@ SYSTEM_PROMPT = textwrap.dedent("""\
projects, anything they'd expect you to know later. Pick a short topic
name for each thing you save.
- Be natural about it. Never announce "I'm saving that to memory" — just
remember it and move on. If a recall comes up empty, don't mention the
search; just answer as if you'd never heard it before.
remember it and move on. If a recall comes up empty, don't mention the
search; just answer as if you'd never heard it before.
# Skills
You have a skill library of learned procedures. Use skill_recall when you need
to perform a task you've done before — it will give you the steps. When you
successfully complete a multi-step task or learn a new procedure from the user,
use skill_save to record it so you can follow it next time. Be selective: only
save skills for repeatable tasks, not one-off facts (those go in memory).
""")
@@ -164,6 +172,13 @@ class GemmaLLM(openai.LLM):
from livekit.agents.types import DEFAULT_API_CONNECT_OPTIONS
conn_options = DEFAULT_API_CONNECT_OPTIONS
# Compact the context before sending: keep the system prompt and the
# last N items so long conversations stay within Gemma's window.
MAX_CONTEXT_ITEMS = 30
if len(chat_ctx) > MAX_CONTEXT_ITEMS:
chat_ctx.truncate(max_items=MAX_CONTEXT_ITEMS)
stream = super().chat(
chat_ctx=chat_ctx,
tools=tools,
@@ -229,6 +244,20 @@ def build_mcp_toolsets() -> list[mcp.MCPToolset]:
)
logger.info("Memory MCP toolset enabled (%s)", MEMORY_DIR)
skills_mcp_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), "skills_mcp.py")
toolsets.append(
mcp.MCPToolset(
id="skills",
mcp_server=mcp.MCPServerStdio(
command=python_bin,
args=[skills_mcp_script],
env={**os.environ, "SKILLS_DIR": SKILLS_DIR},
client_session_timeout_seconds=30,
),
)
)
logger.info("Skills MCP toolset enabled (%s)", SKILLS_DIR)
extra = os.environ.get("EXTRA_MCP_SERVERS", "")
if extra:
try: