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:
+31
-2
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user