mirror of
https://github.com/andrewyng/openworker.git
synced 2026-11-04 13:27:11 +00:00
The per-turn `<system-context>` block opened with `Now: YYYY-MM-DD HH:MM` (2c9fb74, 2026-08-20) so that absolute scheduling had a clock. The block is glued onto the last user message — the task prompt in a tool loop — and a provider's prompt cache is reusable only up to the first byte that differs, so every minute crossing rewrote message one and re-processed the whole conversation. 9d6a716 worked around it with a trailing-message shape, an Anthropic-only relocation of that message, and a `live_clock` switch that headless runs set off. This commit removes the cause instead and deletes the workarounds; the shape is the same on every provider again. - The block carries no clock, on any provider. What is left (folders, skill menu, mode notices, memory-off notice, disable countermand) changes only when the user changes something, so the outbound history is byte-identical turn to turn. - `current_time` tool (coworker/clock.py), registered for every persona: local time with offset and zone, the same instant in UTC, weekday. The environment block's "Today's date" now says it is a session-start snapshot and points at the tool. - `sleep_for(seconds)` is back beside `sleep_until` (a relative wait needs no clock arithmetic; the four lead personas' manifests still told the model to call sleep_for, which2c9fb74had removed). `sleep_until`'s description points at current_time for the date/timezone. Timer wake messages carry their fire time. - Deleted: `live_clock` config + COWORKER_LIVE_CLOCK, the trailing-message shape in `_outbound_messages`, `_relocate_ephemeral_note` in the Anthropic provider. The cache-diagnostics instrument (off by default) stays. Validation runs (one long tool-heavy task, one attempt each on Fable 5.1 and Kimi K3, compared with the 9d6a716 runs of the same task) are recorded on OPE-192. Tests: tests/test_clock_tool.py (new), tests/test_context_block_placement.py and tests/test_cache_breakpoint_placement.py rewritten for the single shape, tests/test_live_clock.py removed; self-wake, attachments, message-source and multiroot tests updated. Full suite unchanged against the Windows baseline.
64 lines
2.6 KiB
Python
64 lines
2.6 KiB
Python
"""OPE-192: the time is a tool (`current_time`), not a line in the per-turn context block.
|
|
The block must be byte-identical turn to turn so the provider's prompt cache keeps
|
|
working; a value that changes by itself has no place in it."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
from coworker.clock import clock_tools, current_time
|
|
|
|
|
|
def test_current_time_reports_local_and_utc_for_the_same_instant():
|
|
before = datetime.now(timezone.utc)
|
|
out = current_time()
|
|
after = datetime.now(timezone.utc)
|
|
local = datetime.fromisoformat(out["local"])
|
|
utc = datetime.fromisoformat(out["utc"].replace("Z", "+00:00"))
|
|
assert local.tzinfo is not None and utc.tzinfo is not None
|
|
assert local == utc # same instant, two renderings
|
|
assert before.replace(microsecond=0) <= utc <= after
|
|
assert out["weekday"] == local.strftime("%A")
|
|
assert isinstance(out["timezone"], str)
|
|
|
|
|
|
def test_clock_tools_exposes_exactly_current_time():
|
|
assert [t.__name__ for t in clock_tools()] == ["current_time"]
|
|
|
|
|
|
def test_current_time_is_registered_for_every_persona(tmp_path, monkeypatch):
|
|
from coworker.agent import build_engine
|
|
from coworker.agents.registry import get_agent
|
|
from coworker.permissions import Mode
|
|
|
|
monkeypatch.setenv("COWORKER_STATE_DIR", str(tmp_path / "state"))
|
|
monkeypatch.setenv("COWORKER_SCRATCH_BASE", str(tmp_path / "scratch"))
|
|
ws = tmp_path / "ws"
|
|
ws.mkdir()
|
|
for persona in ("cowork", "code"):
|
|
engine = build_engine(agent=get_agent(persona), workspace=ws, model="gpt-5.5", mode=Mode("bypass-approvals"))
|
|
assert "current_time" in set(engine.registry.names()), persona
|
|
|
|
|
|
def test_context_block_carries_no_clock_and_is_byte_stable(tmp_path, monkeypatch):
|
|
from coworker.agent import build_engine
|
|
from coworker.agents.registry import get_agent
|
|
from coworker.permissions import Mode
|
|
|
|
monkeypatch.setenv("COWORKER_STATE_DIR", str(tmp_path / "state"))
|
|
monkeypatch.setenv("COWORKER_SCRATCH_BASE", str(tmp_path / "scratch"))
|
|
ws = tmp_path / "ws"
|
|
ws.mkdir()
|
|
engine = build_engine(agent=get_agent("cowork"), workspace=ws, model="gpt-5.5", mode=Mode("bypass-approvals"))
|
|
first = engine.context_provider()
|
|
assert "Now:" not in first
|
|
assert str(ws) in first # the folders are still there
|
|
assert engine.context_provider() == first # byte-identical turn to turn
|
|
|
|
|
|
def test_environment_block_points_at_the_tool(tmp_path):
|
|
from coworker.environment import environment_context
|
|
|
|
block = environment_context(tmp_path)
|
|
assert "Today's date:" in block and "current_time" in block
|