fix(contextual): inject date_context + language in run_contextual_stream

Use _build_system_prompt helper so the contextual agent gets the
same system-prompt slots as home/floating runners — most importantly
{date_context} so the agent can reason about due dates when
creating/updating tasks.

Also makes the session_id contract on run_contextual_stream explicit
(was reading via context['_debug']) and tightens the tool-list test.
This commit is contained in:
Roberto
2026-05-14 21:17:54 +02:00
parent 2b71469e86
commit 5e42b2abb1
2 changed files with 16 additions and 7 deletions

View File

@@ -404,6 +404,12 @@ Rules:
5. Notes: you can read note bodies via `get_page_details({entityType:'note'})`. You CANNOT edit, summarize-to-replace, or append. Tell the user "note editing is coming in a later release" if asked. 5. Notes: you can read note bodies via `get_page_details({entityType:'note'})`. You CANNOT edit, summarize-to-replace, or append. Tell the user "note editing is coming in a later release" if asked.
6. Be concise. Default to 1-3 short paragraphs. Bullet lists fine. Don't restate the user's request. 6. Be concise. Default to 1-3 short paragraphs. Bullet lists fine. Don't restate the user's request.
7. Never expose ids in prose. Use names. Ids only travel through tool calls. 7. Never expose ids in prose. Use names. Ids only travel through tool calls.
# Date context
{date_context}
# Language
{language_instruction}
""" """
_TASK_BRIEF_RESEARCH_SYSTEM_PROMPT = """\ _TASK_BRIEF_RESEARCH_SYSTEM_PROMPT = """\
@@ -1600,21 +1606,22 @@ async def run_contextual_stream(
Mirrors run_floating_stream's plumbing but injects the rendered scope Mirrors run_floating_stream's plumbing but injects the rendered scope
block into the system prompt and exposes the contextual tool set. block into the system prompt and exposes the contextual tool set.
Note-edit tools (propose_note_edit) are intentionally excluded. Note-edit tools (propose_note_edit) are intentionally excluded.
*context contract*: callers MUST include ``context["_debug"]["session_id"]``
(a non-empty str) so that ``_session_id_from_context`` can extract it for
tracing and episode storage downstream. The WS handler in device_ws.py
satisfies this by always populating ``_debug`` before calling this function.
""" """
from app.schemas.contextual import ContextualScope, render_scope_block # noqa: PLC0415 from app.schemas.contextual import ContextualScope, render_scope_block # noqa: PLC0415
prepared_context = await _prepare_context(message, context) prepared_context = await _prepare_context(message, context)
trace_id = _trace_id_from_context(prepared_context) trace_id = _trace_id_from_context(prepared_context)
template, langfuse_prompt = get_prompt_or_fallback( system_prompt, langfuse_prompt = _build_system_prompt(
"contextual_system", _CONTEXTUAL_SYSTEM_PROMPT, "contextual_system", _CONTEXTUAL_SYSTEM_PROMPT, prepared_context,
) )
scope_block = render_scope_block(scope) scope_block = render_scope_block(scope)
# Build system prompt: Langfuse template (or fallback) + scope injection. system_prompt = system_prompt + f"\n\n## Current view\n{scope_block}"
# The contextual prompt has no per-request slots like {date_context}, so
# we just append the scope block directly.
system_prompt = template + f"\n\n## Current view\n{scope_block}"
system_prompt += _language_instruction(prepared_context)
tools = _contextual_tools(user_id, trace_id) tools = _contextual_tools(user_id, trace_id)

View File

@@ -70,5 +70,7 @@ async def test_run_contextual_stream_includes_scope_block(monkeypatch):
"at least one entity-create tool must be present" "at least one entity-create tool must be present"
) )
assert "create_timeline" in names, "create_timeline tool must be included"
# Note edit tools must NOT be exposed. # Note edit tools must NOT be exposed.
assert "propose_note_edit" not in names, "propose_note_edit must be excluded" assert "propose_note_edit" not in names, "propose_note_edit must be excluded"