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
This commit is contained in:
Shane
2026-08-23 07:05:03 -04:00
parent 98168f2876
commit f80b9ebe3d
3 changed files with 29 additions and 24 deletions
+24 -16
View File
@@ -63,15 +63,6 @@ SYSTEM_PROMPT = textwrap.dedent("""\
- Never write more than three sentences in a row.
- Never read back URLs, file paths, or technical identifiers.
# Web access
You have web_search and web_scrape tools. Use them when the user asks about
current events, recent news, prices, sports scores, or anything
that may have changed since your training data. Search first, then scrape
a result only if you need more detail. Answer from what you find, in your
normal conversational style — don't cite sources formally, just mention the
source naturally ("according to..."). If a search comes up empty, say so
briefly and move on.
# Weather & Time
You can check the weather for any location. Use get_weather when the user
asks about current conditions, temperature, or forecasts. It returns a
@@ -79,6 +70,27 @@ SYSTEM_PROMPT = textwrap.dedent("""\
Use get_time when the user asks what time or day it is. If they mention
a city, pass it as the location argument.
# Background Tasks (dispatch_task) — USE FOR ALL NON-TRIVIAL TASKS
For ANY task that is not a simple one-sentence answer from your own
knowledge, you MUST call dispatch_task. This includes: looking up news,
researching topics, checking current events, prices, sports scores,
writing something, summarizing, comparing options, planning, or anything
that takes more than a couple seconds to think through. Do NOT use
web_search or web_scrape directly; always dispatch instead.
The only things you answer inline are: weather (get_weather), time
(get_time), memory operations, and trivial facts you already know.
Examples:
- User says "what's the latest news?" → CALL dispatch_task with
description="Find the top 5 international news headlines today"
- User says "look up the price of a PS5" → CALL dispatch_task with
description="Find the current retail price of a PlayStation 5"
- User says "plan a weekend trip to Denver" → CALL dispatch_task with
description="Plan a two-day weekend trip to Denver including activities"
After calling dispatch_task, tell the user "I'll get on that for you"
and keep the conversation going. The result comes back automatically — when
you receive it, share the findings naturally in your conversational style.
You can have multiple tasks running at once.
# Memory (CRITICAL — always use these tools)
You MUST call memory_save whenever the user tells you something to remember,
shares a preference, or says "remember that...". Do NOT just say "okay I'll
@@ -99,13 +111,6 @@ SYSTEM_PROMPT = textwrap.dedent("""\
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).
# Background Tasks (dispatch_task)
When the user asks you to research something, look up news, or do anything
that will take more than a few seconds, CALL dispatch_task with a clear
description of what to investigate. Tell the user "I'll take a look at that
for you" and keep talking. The result comes back automatically — when you
receive it, share the findings naturally. You can have multiple tasks running
at once.
""")
@@ -165,6 +170,9 @@ class _ReasoningFallbackWrapper:
self._last_reasoning = reasoning
if chunk.has_response():
self._has_content = True
tc = getattr(delta, "tool_calls", None)
if tc:
logger.info("LLM tool_call: %s", [t.function.name for t in tc])
return chunk
async def collect(self):
+2 -6
View File
@@ -7,9 +7,5 @@ rtc:
keys:
devkey: devsecret
# Keep rooms alive for 24h — the agent stays resident so reconnections
# don't require a fresh dispatch. Without this, LiveKit destroys the room
# after empty_timeout (default 300s), kills the agent job, and the next
# join finds no worker registered ("assistant not ready retry").
room:
empty_timeout: 86400
# Rooms are short-lived: each browser session gets a unique room name, so
# the agent is dispatched at room creation. Default 300s cleanup is fine.
+3 -2
View File
@@ -47,7 +47,7 @@ def make_token(room_name: str, identity: str) -> str:
"canPublishData": True,
},
"roomConfig": {
"agents": [{"agentName": AGENT_NAME}],
"agents": [{"agentName": AGENT_NAME, "jobType": "JT_ROOM"}],
},
}
h = b64url(json.dumps(header).encode())
@@ -66,7 +66,8 @@ class Handler(BaseHTTPRequestHandler):
length = int(self.headers.get("Content-Length", 0))
body = json.loads(self.rfile.read(length) or b"{}")
room_name = "voice-room"
# Unique room per session so LiveKit dispatches the agent at creation.
room_name = f"voice-{uuid.uuid4().hex[:8]}"
identity = body.get("identity") or f"user-{uuid.uuid4().hex[:8]}"
token = make_token(room_name, identity)