← Plan 2

Phase 8 — Date/Time + Location Tools

Goal: Give Gemma reliable grounding about when and where it is operating. Two small tools — no external APIs, no state, no deps beyond stdlib. The coding is straightforward; the design challenge is making a 2B-parameter model call them consistently.

Reliability — The Core Challenge

Small models fail to call tools in two patterns:

  1. Confident hallucination — answers with a plausible-but-stale date from training data instead of calling the tool.
  2. Implicit-query miss — the user's intent implies a need for the tool but doesn't state it explicitly ("recommend a restaurant" → needs location).

Why get_datetime is easier

Explicit time/date queries ("what time is it?", "what day is it?") are unambiguous. Gemma's training data is frozen — it knows it can't answer correctly — which pushes it toward tool use. These queries are the reliable case.

Why get_location is harder

Explicit queries ("where are you?", "what's my location?") should work. Implicit queries ("what's a good restaurant nearby?", "what's the weather?") will not reliably trigger get_location in a 2B model without very strong prompt guidance.

Three levers — in order of priority

LeverMechanismCoverage
1. System prompt rules Explicit imperative rules in config/generator.yaml — same pattern that makes search_memory reliable Explicit queries; works for both tools
2. Tool description wording Imperative "Call this tool when…" language in the schema description field Reinforces lever 1; helps model selection when multiple tools are registered
3. Context injection fallback GeneratorAgent._build_messages() prepends [Current datetime: …] to every system message — Gemma sees fresh data without needing to call the tool Guarantees correct answers regardless of tool-calling reliability; costs ~10 tokens per turn
Recommendation: Implement levers 1 and 2 first. Test with explicit queries. If Gemma still misses time/date questions after the system prompt rule, add lever 3 (context injection) as a fallback. Do not design the fallback upfront — wait to see whether it's needed.

Tool 1 — DateTimeTool (get_datetime)

Schema

{
  "type": "function",
  "function": {
    "name": "get_datetime",
    "description": "Returns the current local date, time, day of week, and timezone. Call this tool whenever the user asks about the current time, date, day, or timezone. Do not answer time or date questions from training data — always call this tool.",
    "parameters": {
      "type": "object",
      "properties": {},
      "required": []
    }
  }
}

Result format

"Tuesday 2026-06-03 09:17:42 PDT (UTC-7)"

Implementation

import time
from datetime import datetime
import zoneinfo  # stdlib Python 3.9+

def _get_datetime() -> str:
    now = datetime.now().astimezone()
    tz_name = now.strftime("%Z")          # e.g. "PDT"
    utc_offset = now.strftime("%z")       # e.g. "+0700" → format as UTC-7
    offset_h = int(utc_offset[:3])
    utc_str = f"UTC{offset_h:+d}"
    return now.strftime(f"%A %Y-%m-%d %H:%M:%S ") + f"{tz_name} ({utc_str})"

No config file needed. Follows the existing tool pattern: publishes schema on tool.schema at startup; responds to tool.request.get_datetime; publishes result on tool.result.get_datetime.

Tool 2 — LocationTool (get_location)

Schema

{
  "type": "function",
  "function": {
    "name": "get_location",
    "description": "Returns the user's current location (city, state, country, timezone, and optionally coordinates). Call this tool when the user asks where you are, where they are, or when answering a question that requires knowing the local area.",
    "parameters": {
      "type": "object",
      "properties": {},
      "required": []
    }
  }
}

Result format

"Cupertino, California, United States (America/Los_Angeles, 37.3230° N, 122.0322° W)"

Config file: config/location.yaml

city: "Cupertino"
state: "California"
country: "United States"
timezone: "America/Los_Angeles"
coordinates: "37.3230° N, 122.0322° W"   # optional — omit to exclude from result

If config/location.yaml is missing or empty, the tool returns "Location not configured — edit config/location.yaml" rather than failing.

System Prompt Rules

Add to config/generator.yaml system_prompt:

  - If the user asks about the current date, time, day of week, or timezone —
    call get_datetime. Never answer time or date questions from training data.
  - If the user asks where you are, what your location is, or asks a question
    that requires knowing the local area — call get_location.

Lever 3 — Context Injection Fallback (implement only if needed)

If Gemma proves unreliable at calling get_datetime even with the system prompt rule, add a one-line prepend in GeneratorAgent._build_messages():

if self._inject_datetime:                   # config flag: inject_datetime: true
    now_str = _format_datetime()
    system_content = f"[Current datetime: {now_str}]\n\n" + system_content

This costs ~10 tokens per turn and makes correct date/time answers unconditional. Add inject_datetime: false to config/generator.yaml so it can be toggled without a code change.

New Files

FilePurpose
src/local/tools/datetime_tool.pyNEW — DateTimeTool
src/local/tools/location_tool.pyNEW — LocationTool
config/location.yamlNEW — user location config

Modified Files

FileChange
config/generator.yamlAdd get_datetime and get_location rules to system_prompt
run_local.pyStart datetime_tool and location_tool alongside other tools
Tools follow the same pattern as web_search_tool.py and web_fetch_tool.py: publish schema on tool.schema at startup, subscribe to tool.request.<name>, publish result on tool.result.<name>. The ToolWindow in the UI will spawn reactively when the schema arrives — no UI changes needed.

Tool Startup Pattern (reference)

class DateTimeTool:
    TOOL_NAME = "get_datetime"

    def __init__(self) -> None:
        self._pub, self._sub = make_participant_bus(
            [TOOL_SCHEMA_REQUEST, f"tool.request.{self.TOOL_NAME}"]
        )

    def run(self) -> None:
        self._announce_schema()
        while True:
            envelope = self._sub.receive()
            if envelope.subject == TOOL_SCHEMA_REQUEST:
                self._announce_schema()
            elif envelope.subject == f"tool.request.{self.TOOL_NAME}":
                self._handle_request(envelope)

    def _announce_schema(self) -> None:
        self._pub.publish(MessageEnvelope.create(
            message_type="tool_schema",
            subject=TOOL_SCHEMA,
            sender_id=self.TOOL_NAME,
            payload={"schema": SCHEMA},
        ))

    def _handle_request(self, envelope: MessageEnvelope) -> None:
        result = _get_datetime()
        self._pub.publish(MessageEnvelope.create(
            message_type="tool_result",
            subject=f"tool.result.{self.TOOL_NAME}",
            sender_id=self.TOOL_NAME,
            payload={"result": result},
            correlation_id=envelope.correlation_id,
        ))

run_local.py — Startup Order

# Tools start first; generator waits 0.5s after tools so schemas are registered
procs = [
    ("web_search",  [...]),
    ("web_fetch",   [...]),
    ("datetime",    ["python", "-m", "local.tools.datetime_tool"]),
    ("location",    ["python", "-m", "local.tools.location_tool"]),
    ("generator_a", [...]),
    ("generator_b", [...]),   # if dual-respondent enabled
    ...
]

Testing

Unit tests: tests/test_datetime_tool.py

Unit tests: tests/test_location_tool.py

Story S10 — Date/time + location grounding

TurnQueryAssertion
1"What day of the week is it?"Answer contains the correct day name (verified at test time)
2"What year is it?"Answer contains "2026"
3"Where are you located?"Answer contains the city from config/location.yaml

Build Order

  1. Add get_datetime and get_location system prompt rules to config/generator.yaml
  2. Build src/local/tools/datetime_tool.py
  3. Build src/local/tools/location_tool.py + config/location.yaml
  4. Wire both tools into run_local.py
  5. Unit tests for both tools
  6. Story S10; run full test suite
  7. Live smoke test: "what time is it?", "what day is it?", "where are you?"
  8. If Gemma misses date/time queries: add lever 3 (context injection) to _build_messages

Files Changed / Added

FileChange
src/local/tools/datetime_tool.pyNEW
src/local/tools/location_tool.pyNEW
config/location.yamlNEW
config/generator.yamlAdd system prompt rules for get_datetime / get_location
run_local.pyStart datetime_tool and location_tool
tests/test_datetime_tool.pyNEW
tests/test_location_tool.pyNEW
tests/stories/s10_datetime_location.yamlNEW