diff --git a/src/octopal/runtime/temporal_context.py b/src/octopal/runtime/temporal_context.py new file mode 100644 index 0000000..e7fb14a --- /dev/null +++ b/src/octopal/runtime/temporal_context.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +from collections.abc import Mapping +from datetime import UTC, datetime +from typing import Any + +from octopal.utils import utc_now + + +def build_temporal_context(now: datetime | None = None) -> dict[str, Any]: + utc_dt = now or utc_now() + if utc_dt.tzinfo is None: + utc_dt = utc_dt.replace(tzinfo=UTC) + utc_dt = utc_dt.astimezone(UTC) + local_dt = utc_dt.astimezone() + timezone_name = local_dt.tzname() or local_dt.strftime("%z") or "local" + return { + "current_date": local_dt.date().isoformat(), + "current_time": local_dt.strftime("%H:%M:%S"), + "current_datetime": local_dt.isoformat(), + "current_weekday": local_dt.strftime("%A"), + "local_date": local_dt.date().isoformat(), + "local_time": local_dt.strftime("%H:%M:%S"), + "local_datetime": local_dt.isoformat(), + "local_weekday": local_dt.strftime("%A"), + "local_timezone": timezone_name, + "utc_date": utc_dt.date().isoformat(), + "utc_time": utc_dt.strftime("%H:%M:%S"), + "utc_datetime": utc_dt.isoformat(), + "utc_weekday": utc_dt.strftime("%A"), + } + + +def format_temporal_context_prompt(context: Mapping[str, Any] | None = None) -> str: + ctx = dict(context or build_temporal_context()) + return ( + "Temporal context:\n" + f"- Current local date: {ctx['local_date']} ({ctx['local_weekday']})\n" + f"- Current local time: {ctx['local_time']} ({ctx['local_timezone']})\n" + f"- Current local datetime: {ctx['local_datetime']}\n" + f"- Current UTC datetime: {ctx['utc_datetime']}\n" + "- Treat relative dates like today, yesterday, tomorrow, this morning, and this " + "week relative to this context unless the task gives a more specific date." + ) + diff --git a/src/octopal/runtime/workers/agent_worker.py b/src/octopal/runtime/workers/agent_worker.py index 57b0466..e5f5060 100644 --- a/src/octopal/runtime/workers/agent_worker.py +++ b/src/octopal/runtime/workers/agent_worker.py @@ -24,6 +24,7 @@ from octopal.infrastructure.config.settings import load_settings from octopal.infrastructure.providers.litellm_provider import LiteLLMProvider +from octopal.runtime.temporal_context import format_temporal_context_prompt from octopal.runtime.tool_errors import ToolBridgeError from octopal.runtime.tool_loop import ( _detect_tool_loop, @@ -699,8 +700,12 @@ async def mcp_proxy_handler(args: dict, ctx: dict, s_id=s_id, t_name=t_name): has_child_spawn_tools=has_child_spawn_tools ) + temporal_context_prompt = format_temporal_context_prompt() + system_prompt = f"""{spec.system_prompt} +{temporal_context_prompt} + Available tools: {tool_descriptions} diff --git a/tests/test_agent_loop_improvements.py b/tests/test_agent_loop_improvements.py index da23c2e..08ae114 100644 --- a/tests/test_agent_loop_improvements.py +++ b/tests/test_agent_loop_improvements.py @@ -846,6 +846,8 @@ async def _fake_call_llm(provider, messages, tools): assert "request_instruction" in tool_names assert "answer_worker_instruction" not in tool_names system_prompt = str(messages[0]["content"]) + assert "Temporal context:" in system_prompt + assert "Current local date:" in system_prompt assert "Worker coordination:" in system_prompt assert "Parent-worker coordination:" not in system_prompt assert "normal tool calls" in system_prompt diff --git a/tests/test_temporal_context.py b/tests/test_temporal_context.py new file mode 100644 index 0000000..b6a3a99 --- /dev/null +++ b/tests/test_temporal_context.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from octopal.runtime.temporal_context import format_temporal_context_prompt + +_CONTEXT = { + "local_date": "2026-05-15", + "local_time": "09:30:00", + "local_datetime": "2026-05-15T09:30:00+00:00", + "local_weekday": "Friday", + "local_timezone": "UTC", + "utc_date": "2026-05-15", + "utc_time": "09:30:00", + "utc_datetime": "2026-05-15T09:30:00+00:00", + "utc_weekday": "Friday", +} + + +def test_temporal_context_formats_worker_prompt() -> None: + prompt = format_temporal_context_prompt(_CONTEXT) + + assert "Temporal context:" in prompt + assert "Current local date: 2026-05-15" in prompt + assert "Current UTC datetime: 2026-05-15T09:30:00+00:00" in prompt + assert "relative dates" in prompt