From 738d29c4de4494c05274e99eec8ede0e726d7f92 Mon Sep 17 00:00:00 2001 From: Mickael Farina Date: Thu, 16 Jul 2026 12:44:29 +0200 Subject: [PATCH] fix(observer_recall): answer conversationally, not as a machine dump (beat 20) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Recall replied like a log printer: "Here's the last 20 min (from CODEC's observer): You were in: Claude. Timeline: 12:20 Claude Files touched: best-noise-cancelling-headphones-2026.md." Accurate but robotic — CODEC should answer like someone who was watching over your shoulder. Now: "Over the last 9 minutes, you were in Claude the whole time. You touched 4 files: …" Prose adapts to what actually happened (one app / two apps / moved around a fair bit), mentions the last thing on screen, and pluralises files properly. Kept deterministic — no second LLM call — so it stays instant and, more importantly, can't invent anything (the whole point of the earlier anti-fabrication work). The honest out-of-range answer for clock-time asks is unchanged. Manifest regenerated. 52 observer tests pass, ruff clean. Co-Authored-By: Claude Opus 4.8 --- skills/.manifest.json | 2 +- skills/observer_recall.py | 53 ++++++++++++++++++++++++++++++++++----- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/skills/.manifest.json b/skills/.manifest.json index 2adb7eb..22952dd 100644 --- a/skills/.manifest.json +++ b/skills/.manifest.json @@ -62,7 +62,7 @@ "network_info.py": "bd776b619cf7c18d67fe03cb0f0456cf9c4f9bf71475740a233a9ca1e6672fcd", "notes.py": "7d50d1544ea955f59917a1f0e7902d115e9dbccd3188b641057a55b6a5b2803a", "notification_reader.py": "681208ae4253dfe549512cce4c722c76ca85f2bbbdb8737e37c026ec9444c972", - "observer_recall.py": "f7084d11c069603d815e648dd765628bfed6d31c1df4ac59f0dac35259aa6fe2", + "observer_recall.py": "5234ab88d151003d8acb00b0e72139d668cfbcd5f091cdda52f247682698a9ff", "password_generator.py": "f11a917299e14cbd2560111da0bb748cd08792cf715cc4098c64eb62da8c54e3", "philips_hue.py": "fa831712c39dc6327c84199d8f0aeb09a169a264ce3d936cc337a5c5d6632f7e", "pilot.py": "f9967890f138bc7a48ae4abc8b4170dca13961b81659ef4e530dc619c1cf90d4", diff --git a/skills/observer_recall.py b/skills/observer_recall.py index 45a9a52..efd520d 100644 --- a/skills/observer_recall.py +++ b/skills/observer_recall.py @@ -105,6 +105,22 @@ def _parse_ts(entry: dict) -> datetime | None: return None +def _fmt_span(entries: list) -> str: + """Natural opener for how long the snapshots span, e.g. "Over the last + 25 minutes" / "In the last couple of minutes". "" if it can't be derived.""" + if len(entries) < 2: + return "" + first, last = _parse_ts(entries[0]), _parse_ts(entries[-1]) + if not first or not last: + return "" + mins = int((last - first).total_seconds() // 60) + if mins <= 1: + return "In the last minute or so" + if mins < 5: + return "Over the last couple of minutes" + return f"Over the last {mins} minutes" + + def _summarise(entries: list) -> str: """A compact, human timeline of the entries (oldest→newest).""" if not entries: @@ -112,6 +128,7 @@ def _summarise(entries: list) -> str: lines: list[str] = [] last_app = None + last_title_seen = "" apps_seen: list[str] = [] files_seen: set[str] = set() ocr_bits: list[str] = [] @@ -122,6 +139,8 @@ def _summarise(entries: list) -> str: win = e.get("active_window") or {} app = win.get("app") title = (win.get("title") or "").strip() + if title: + last_title_seen = title if app and app != last_app: label = app + (f" — {title[:60]}" if title else "") lines.append(f" {stamp} {label}") @@ -136,15 +155,35 @@ def _summarise(entries: list) -> str: if ocr and len(ocr) > 20: ocr_bits.append(ocr[:120]) + # Conversational prose, not a machine dump. The old format printed + # "You were in: X. / Timeline: / 12:20 X / Files touched: y" — accurate but + # robotic. CODEC should answer like a person who was watching over your + # shoulder. Deterministic templating (no second LLM call) keeps it instant + # and, crucially, keeps it from inventing anything. out = [] if apps_seen: - out.append("You were in: " + ", ".join(apps_seen[:6]) + ".") - if lines: - out.append("Timeline:\n" + "\n".join(lines[:12])) + span = _fmt_span(entries) + if len(apps_seen) == 1: + body = f"you were in {apps_seen[0]} the whole time" + elif len(apps_seen) == 2: + body = f"you went back and forth between {apps_seen[0]} and {apps_seen[1]}" + else: + shown = apps_seen[:4] + body = ("you moved around a fair bit — " + + ", ".join(shown[:-1]) + f" and {shown[-1]}") + opener = f"{span}, {body}" if span else body[0].upper() + body[1:] + if last_title_seen: + opener += f". Last thing on screen was \"{last_title_seen[:70]}\"" + out.append(opener + ".") if files_seen: - out.append("Files touched: " + ", ".join(sorted(files_seen)[:8]) + ".") + fl = sorted(files_seen) + if len(fl) == 1: + out.append(f"You touched one file: {fl[0]}.") + else: + out.append(f"You touched {len(fl)} files: " + ", ".join(fl[:6]) + + ("…" if len(fl) > 6 else "") + ".") if ocr_bits: - out.append('On screen (excerpt): "' + ocr_bits[-1] + '"') + out.append(f"Text on screen included: \"{ocr_bits[-1].strip()}\"") if not out: # Entries exist but carry no window/title/OCR/file content — the observer # is polling but capturing nothing. This is almost always a macOS @@ -204,4 +243,6 @@ def run(task: str, context: str = "") -> str: return (f"Nothing in {span} — the observer keeps roughly the last 10 " f"minutes, so I can't see that far back.{depth}") - return f"Here's {span} (from CODEC's observer):\n{_summarise(kept)}" + # _summarise already opens conversationally ("Over the last 25 minutes, you + # were in Claude…"), so no robotic "Here's X (from CODEC's observer):" header. + return _summarise(kept)