Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion skills/.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"network_info.py": "bd776b619cf7c18d67fe03cb0f0456cf9c4f9bf71475740a233a9ca1e6672fcd",
"notes.py": "7d50d1544ea955f59917a1f0e7902d115e9dbccd3188b641057a55b6a5b2803a",
"notification_reader.py": "681208ae4253dfe549512cce4c722c76ca85f2bbbdb8737e37c026ec9444c972",
"observer_recall.py": "3744907b24ffbfbb385a630791c08ecd440f6107f21894af80ceb07191f5eb1f",
"observer_recall.py": "f7084d11c069603d815e648dd765628bfed6d31c1df4ac59f0dac35259aa6fe2",
"password_generator.py": "f11a917299e14cbd2560111da0bb748cd08792cf715cc4098c64eb62da8c54e3",
"philips_hue.py": "fa831712c39dc6327c84199d8f0aeb09a169a264ce3d936cc337a5c5d6632f7e",
"pilot.py": "f9967890f138bc7a48ae4abc8b4170dca13961b81659ef4e530dc619c1cf90d4",
Expand Down
45 changes: 45 additions & 0 deletions skills/observer_recall.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,37 @@ def _load_entries() -> tuple[list, str]:
return [], ""


# Absolute / clock-time asks the buffer can NEVER answer: it's a RAM ring of the
# last N snapshots (~10 min at the active cadence), not a day log. Without this
# check, "between 11am and 1pm" parsed as no-window → summarised the last few
# minutes and looked like a confident, wrong answer.
_ABSOLUTE_TIME_RE = re.compile(
r"\b\d{1,2}\s*(?:am|pm)\b"
r"|\b\d{1,2}:\d{2}\b"
r"|\b(?:today|yesterday|this morning|this afternoon|this evening|"
r"last night|earlier today|tonight)\b",
re.IGNORECASE,
)


def _is_absolute_time_request(task: str) -> bool:
"""True for clock-time / calendar-day asks ("between 11am and 1pm", "today",
"this morning") — windows the ring buffer structurally cannot cover."""
return bool(_ABSOLUTE_TIME_RE.search(task or ""))


def _coverage(entries: list) -> str:
"""Human span the buffer actually covers, e.g. "06:59–07:32 (33 min)"."""
if not entries:
return "nothing"
first, last = _parse_ts(entries[0]), _parse_ts(entries[-1])
if not first or not last:
return f"{len(entries)} snapshots"
mins = max(0, int((last - first).total_seconds() // 60))
return (f"{first.astimezone().strftime('%H:%M')}–"
f"{last.astimezone().strftime('%H:%M')}, about {mins} min")


def _window_seconds(task: str) -> int | None:
"""Parse a lookback window from the task. Returns seconds, or None for 'all'.

Expand Down Expand Up @@ -140,6 +171,20 @@ def run(task: str, context: str = "") -> str:
"clipboard, and recent files each minute."
)

# Clock-time / calendar asks ("between 11am and 1pm", "today", "this
# morning") are structurally unanswerable: the observer is a RAM ring of the
# last N snapshots, not a day log. Say so plainly — the old code fell through
# to "summarise everything" and returned the last few minutes as if it had
# answered the question.
if _is_absolute_time_request(task):
return (
f"I can't recall that window. The observer is a rolling in-memory "
f"buffer of the last {len(entries)} snapshots — right now it only "
f"covers {_coverage(entries)} — not a full-day log, and it's wiped "
f"whenever the service restarts. Here's everything it currently "
f"holds:\n\n{_summarise(entries)}"
)

win = _window_seconds(task)
if win is not None:
cutoff = datetime.now(timezone.utc).timestamp() - win
Expand Down