forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patheval.py
More file actions
executable file
·2273 lines (2029 loc) · 85.7 KB
/
Copy patheval.py
File metadata and controls
executable file
·2273 lines (2029 loc) · 85.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python3
"""Quality eval for Context for Claude's MCP surface.
`swift test` proves the code does what it was written to do. It proves nothing about whether the
*answers* are any good — and the central claim of this product is epistemic: that a model reading a
tool result can tell "this did not happen" from "this was not captured". That claim can only be
checked by asking the shipping binary real questions against a known corpus and reading what comes
back.
So this drives the real `context-for-claude-mcp` over stdio, against a throwaway database it seeds
itself, and scores nine classes of behaviour. Most of them are about honesty under adversarial
conditions rather than recall.
python3 scripts/eval.py # build if needed, run, print a summary
python3 scripts/eval.py --only ranking # one class
python3 scripts/eval.py --floor 0.9 # stricter gate
Exit codes: 0 pass · 1 below the floor or a critical check failed · 2 the harness could not run
(build failure, or isolation could not be proven).
Hermetic by construction:
* its own temp home, seeded from scratch, deleted afterwards;
* `CFFIXED_USER_HOME` is what actually redirects an AppKit-less macOS binary — `HOME` alone does
**not**: `FileManager.urls(for: .applicationSupportDirectory)` resolves the real home through
the password database and cheerfully ignores `HOME`. That was verified the hard way, so the
first thing this script does is make the binary *print* the database path it opened and refuse
to score anything unless that path is inside the temp root;
* the child process gets an explicitly built environment, never this one's, so no
`CONTEXT_OMI_MCP_KEY` can leak in. With no credential reachable the Omi half never runs, which
is what makes the run offline — and is itself the "one half could not be searched" condition
several checks are about.
No dependencies beyond the standard library.
"""
from __future__ import annotations
import argparse
import json
import math
import os
import queue
import re
import shutil
import sqlite3
import subprocess
import sys
import tempfile
import threading
import time
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Callable, Iterable
PKG_ROOT = Path(__file__).resolve().parent.parent
DEFAULT_BINARY = PKG_ROOT / ".build" / "release" / "context-for-claude-mcp"
RESULTS_PATH = PKG_ROOT / "dist" / "eval-results.json"
# The overall gate. Set just under the score measured at the commit that introduced this file, so
# it catches a regression rather than blessing one. Ratchet it upward as classes are fixed; never
# lower it to make a build pass.
OVERALL_FLOOR = 0.93
# Checks that fail today, each with the defect behind it.
#
# A known failure still scores zero — the overall number tells the truth about the product and rises
# when one is fixed — but it does not turn the build red, so the gate stays usable while these are
# worked through. Anything failing that is *not* listed here is a new regression and does fail the
# build. `--strict` ignores this list. Delete an entry the moment its check passes; the run prints a
# nudge when one does.
KNOWN_FAILURES: dict[str, str] = {
# Empty on purpose. Every entry here suppresses gating on a check that is genuinely failing,
# so an entry that outlives its defect silently disarms the harness. Add one only with the
# cause written down, and delete it in the same change that fixes the cause.
}
# Weights say what this product is for. Filter integrity and confabulation are doubled because both
# make a reader believe something false about the user's life; findability is only the price of
# admission.
CLASS_WEIGHTS = {
"findability": 1.5,
"no_confabulation": 2.0,
"ranking": 1.0,
"dedup": 1.0,
"uncertainty": 1.0,
"filter_integrity": 2.0,
"bad_input": 1.0,
"coherence": 1.0,
"protocol": 0.75,
"traceability": 0.75,
}
CLASS_ORDER = list(CLASS_WEIGHTS)
UNCERTAIN_MARKER = "(uncertain — may be background audio, not speech)"
# --------------------------------------------------------------------------------- corpus
def describe(epoch: float) -> str:
"""The Swift side's `ContextTime.describe`, so a check can assert the exact printed boundary."""
return time.strftime("%a %-d %b %Y at %-I:%M %p", time.localtime(epoch))
def iso(epoch: float) -> str:
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(epoch))
LOREM = (
"the roadmap review covered staffing latency and the migration order agenda items were "
"carried over from the previous week nobody objected to the proposed sequencing and the "
"owner of each workstream confirmed the dates before the meeting closed with a short "
"discussion of hiring and the budget for the following quarter which remains unchanged"
).split()
def filler(seed: int, words: int) -> str:
"""Deterministic filler prose. Distinct per seed so two frames are never accidental duplicates."""
out = []
for i in range(words):
out.append(LOREM[(seed * 7 + i * 3) % len(LOREM)])
return " ".join(out)
@dataclass
class Corpus:
"""The seeded world, plus every fact a check needs to state its expectation exactly."""
a0: float # region A — the content corpus
b0: float # region B — the filter probes
sessions: list[tuple[int, float, float, str]] = field(default_factory=list)
segments: list[dict] = field(default_factory=list)
frames: list[dict] = field(default_factory=list)
def frame(self, at: float, app: str, window: str, ocr: str) -> None:
self.frames.append({"at": at, "app": app, "window": window, "ocr": ocr})
def segment(self, session: int, at: float, source: str, text: str, confidence) -> None:
self.segments.append(
{
"session": session,
"at": at,
"source": source,
"text": text,
"confidence": confidence,
}
)
def frames_at(self, lo: float, hi: float) -> list[dict]:
return [f for f in self.frames if lo <= f["at"] <= hi]
# Nonces. Every filter probe carries one, so a range check is a set comparison against rows this
# script placed itself rather than a guess about what the renderer meant.
NONCE = {
"a1": "nqxalphaone",
"a2": "nqxalphatwo",
"b1": "nqxbetaone",
"b2": "nqxbetatwo",
"g1": "nqxgammaone",
"g2": "nqxgammatwo",
}
ALL_PROBE_NONCES = set(NONCE.values())
# Region A's distinctive terms, each seeded exactly once unless noted.
TERM_HALYARD = "Halyard" # speech, one occurrence
TERM_PRIYANKA = "Priyanka" # speech, one occurrence
TERM_ZEPHYRINE = "Zephyrine" # speech, two occurrences
TERM_THERMOPYLAE = "Thermopylae" # screen, window title only
TERM_BASILISK = "basilisk" # screen, buried mid-OCR
TERM_WARP_NOVEL = "quicksilver" # the frame that must not collapse
TERM_UNSEEN = "flibbertigibbet" # never captured
TERM_NEAR_MISS = "basilica" # lexically near `basilisk`, never captured
TERM_KESTREL = "Kestrel" # ranking: full-coverage match, oldest, against partial matches
TERM_GAP = "tailwatcher" # two identical frames either side of the moment gap
def build_corpus(now: float) -> Corpus:
"""Two disjoint time regions.
Region A is the content corpus — findability, ranking, dedup, confidence — and its text is kept
clean so nothing distorts bm25. Region B holds the filter probes, every row tagged with a nonce,
so `since` / `until` / `app` can be scored as exact set equality instead of eyeballed.
"""
t0 = math.floor(now / 60) * 60 - 8 * 3600
c = Corpus(a0=t0, b0=t0 + 4 * 3600)
a, b = c.a0, c.b0
# -- speech ------------------------------------------------------------------
c.sessions = [
(1, a + 0, a + 600, "Zoom"),
(2, a + 1200, a + 1400, "Slack"),
(3, a + 2000, a + 2100, "Meet"),
]
# Session 1 carries the confidence spread: high, low, unknown, very high.
c.segment(1, a + 10, "mic", f"We agreed to ship the {TERM_HALYARD} migration on Friday.", 0.92)
c.segment(1, a + 40, "system", "the invoice came to three thousand two hundred euros", 0.41)
c.segment(1, a + 70, "mic", "Let us park the pricing discussion until next week.", None)
c.segment(
1, a + 100, "mic", f"Remind me to email {TERM_PRIYANKA} about the vendor contract.", 0.99
)
c.segment(2, a + 1210, "mic", f"The codename for the launch is {TERM_ZEPHYRINE}.", 0.88)
c.segment(2, a + 1240, "system", f"I will send the {TERM_ZEPHYRINE} brief tonight.", 0.95)
# Session 3 is the confidence boundary, one line per case. The floor is `<`, so a score sitting
# exactly on it is certain enough; a score outside 0…1 is not a probability on this scale at all
# and must say nothing rather than mark everything the day someone stores log-probabilities.
c.segment(3, a + 2000, "mic", "Boundary sample exactlyatfloor please ignore.", 0.65)
c.segment(3, a + 2010, "mic", "Boundary sample justunderfloor please ignore.", 0.649)
c.segment(3, a + 2020, "mic", "Boundary sample zeroscore please ignore.", 0.0)
c.segment(3, a + 2030, "mic", "Boundary sample perfectscore please ignore.", 1.0)
c.segment(3, a + 2040, "mic", "Boundary sample negativelogprob please ignore.", -3.2)
c.segment(3, a + 2050, "mic", "Boundary sample aboveone please ignore.", 5.0)
# -- ranking: one genuine match, six incidental ones, all of them newer -------
c.frame(
a + 1500,
"Cursor",
"SCA-219: Parity Pack v0 — omi",
"SCA-219 Parity Pack v0: one PR (dev whitelist capture). Reviewers asked for a smaller diff "
"before the parity pack lands.",
)
decoys = [
"Inbox (12) - Gmail",
"Hacker News",
"Amazon | Bulk pack deals",
"Notion - Sprint board",
"GitHub - pull requests",
"YouTube",
]
for i, title in enumerate(decoys):
c.frame(
a + 1600 + i * 60,
"Arc",
title,
"Bookmarks Bar: Pack Tracker - Battery Pack - Pack Reviews - Vacation packing list "
f"- Six Pack Fitness. {filler(i, 40)}",
)
# -- ranking, second shape: decoys strong enough to clear the relevance floor -
#
# The SCA-219 cluster is floored out entirely, so it tests the floor rather than the order.
# Here the decoys share two of three query words and *do* survive, and the genuine match is the
# oldest row in the corpus — so only relevance, not recency, can put it first.
c.frame(
a + 300,
"Notion",
f"{TERM_KESTREL} deployment checklist — Notion",
f"{TERM_KESTREL} deployment checklist: rollback plan, canary window, alert owners.",
)
for i, title in enumerate(
["Deployment guide - docs", "Checklist template", "Deployment status", "Checklist archive"]
):
c.frame(
a + 3000 + i * 60,
"Chrome",
title,
f"deployment checklist steps and deployment notes. {filler(i + 20, 40)}",
)
# -- findability: title-only, and buried mid-body ----------------------------
c.frame(
a + 2200,
"Obsidian",
f"{TERM_THERMOPYLAE} notes — Obsidian",
"Meeting agenda. Follow up on the roadmap next week. No further detail recorded here.",
)
body = filler(3, 60) + f" the {TERM_BASILISK} is a legendary serpent " + filler(9, 60)
c.frame(a + 2300, "Safari", "Mythology - Wikipedia", body)
# -- dedup: twelve near-identical frames of one window, then a real change ----
spinners = ["✳", "⠂", "⠐", "⠠", "⢀", "⡀", "⠄", "⠆", "⠇", "⠋", "⠙", "⠹"]
base = filler(5, 60).split()
for i in range(12):
words = list(base)
words[i % len(words)] = f"tick{i}" # one word of churn, the way an OCR pass wobbles
c.frame(a + 2500 + i * 3, "Warp", f"{spinners[i]} claude — omi", " ".join(words))
c.frame(
a + 2600,
"Warp",
"⠸ claude — omi",
f"{TERM_WARP_NOVEL} " + filler(31, 60),
)
# -- dedup, the other direction: identical text either side of the moment gap -
# Same window, same words; only the eleven-minute gap makes them two events.
gap_ocr = f"{TERM_GAP} tail -f server.log waiting for the next line to arrive"
c.frame(a + 3300, "Terminal", "logs — tail", gap_ocr)
c.frame(a + 3300 + 700, "Terminal", "logs — tail", gap_ocr)
# -- region B: filter probes -------------------------------------------------
probes = [
(b + 0, "ProbeAlpha", "Alpha One", NONCE["a1"]),
(b + 300, "ProbeAlpha", "Alpha Two", NONCE["a2"]),
(b + 1200, "ProbeBeta", "Beta One", NONCE["b1"]),
(b + 1500, "ProbeBeta", "Beta Two", NONCE["b2"]),
(b + 2400, "ProbeGamma", "Gamma One", NONCE["g1"]),
(b + 2700, "ProbeGamma", "Gamma Two", NONCE["g2"]),
]
for i, (at, app, window, nonce) in enumerate(probes):
c.frame(at, app, window, f"probeword {nonce} filter probe row {filler(i + 40, 25)}")
# Two dense runs, far enough apart to be separate activity blocks.
for k in range(8):
c.frame(b + 3300 + k * 30, "ProbeDelta", "Delta Work", f"deltaword {filler(k + 60, 20)}")
for k in range(8):
c.frame(b + 3900 + k * 30, "ProbeEpsilon", "Eps Work", f"epsilonword {filler(k + 70, 20)}")
return c
# --------------------------------------------------------------------------------- seeding
# The schema of `Sources/ContextCore/Store.swift` (migrations v1, v3, v4), written directly rather
# than through a Swift shim so this script stays dependency-free. The FTS5 tables must keep their
# exact column order: `Queries.matchedFrames` passes positional bm25 weights to
# `frames_fts(ocrText, windowTitle, appName)`, and reordering them silently reweights the ranker.
SCHEMA = """
CREATE TABLE sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
startedAt DOUBLE NOT NULL,
endedAt DOUBLE,
appHint TEXT
);
CREATE INDEX idx_sessions_startedAt ON sessions(startedAt);
CREATE TABLE segments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sessionId INTEGER NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
startedAt DOUBLE NOT NULL,
endedAt DOUBLE NOT NULL,
source TEXT NOT NULL,
speaker TEXT NOT NULL,
text TEXT NOT NULL,
confidence DOUBLE,
speakerLabel TEXT,
personId TEXT
);
CREATE INDEX idx_segments_startedAt ON segments(startedAt);
CREATE INDEX idx_segments_sessionId ON segments(sessionId, startedAt);
CREATE TABLE frames (
id INTEGER PRIMARY KEY AUTOINCREMENT,
capturedAt DOUBLE NOT NULL,
appName TEXT,
windowTitle TEXT,
ocrText TEXT,
imagePath TEXT
);
CREATE INDEX idx_frames_capturedAt ON frames(capturedAt);
CREATE INDEX idx_frames_app ON frames(appName, capturedAt);
CREATE VIRTUAL TABLE segments_fts USING fts5(
text, content='segments', content_rowid='id', tokenize='porter unicode61'
);
CREATE VIRTUAL TABLE frames_fts USING fts5(
ocrText, windowTitle, appName, content='frames', content_rowid='id',
tokenize='porter unicode61'
);
"""
def seed_database(path: Path, corpus: Corpus | None) -> None:
"""Writes a fixture database.
Left in rollback-journal mode on purpose. GRDB's reader opens read-only, and a WAL database with
no live writer has no `-shm` file that a read-only connection is allowed to create — SQLite
answers `error 14: unable to open database file`. In production the app is the writer holding
those sidecars open; here there is no writer, so the fixture must not be in WAL.
"""
path.parent.mkdir(parents=True, exist_ok=True)
db = sqlite3.connect(path)
try:
db.executescript(SCHEMA)
if corpus:
for sid, started, ended, hint in corpus.sessions:
db.execute(
"INSERT INTO sessions(id, startedAt, endedAt, appHint) VALUES (?,?,?,?)",
(sid, started, ended, hint),
)
for s in corpus.segments:
speaker = "me" if s["source"] == "mic" else "them"
db.execute(
"INSERT INTO segments(sessionId, startedAt, endedAt, source, speaker, text,"
" confidence) VALUES (?,?,?,?,?,?,?)",
(
s["session"],
s["at"],
s["at"] + 5,
s["source"],
speaker,
s["text"],
s["confidence"],
),
)
for f in corpus.frames:
db.execute(
"INSERT INTO frames(capturedAt, appName, windowTitle, ocrText, imagePath)"
" VALUES (?,?,?,?,NULL)",
(f["at"], f["app"], f["window"], f["ocr"]),
)
# External-content FTS: the base tables stay the only copy of the text.
db.execute("INSERT INTO segments_fts(rowid, text) SELECT id, text FROM segments")
db.execute(
"INSERT INTO frames_fts(rowid, ocrText, windowTitle, appName)"
" SELECT id, ocrText, windowTitle, appName FROM frames"
)
db.commit()
finally:
db.close()
def write_heartbeat(home: Path, capturing: bool, capabilities: list[tuple[str, bool, str]]) -> None:
state = {
"capturing": capturing,
"pausedReason": None if capturing else "Paused for the eval",
"capabilities": [
{"name": n, "granted": g, "detail": d} for n, g, d in capabilities
],
"updatedAt": time.time(),
}
support = home / "Library" / "Application Support" / "ContextForClaude"
support.mkdir(parents=True, exist_ok=True)
(support / "capture-state.json").write_text(json.dumps(state))
GRANTED = [
("microphone", True, "Granted"),
("systemAudio", True, "Granted"),
("screen", True, "Granted"),
]
SCREEN_DENIED = [
("microphone", True, "Granted"),
("systemAudio", True, "Granted"),
("screen", False, "Screen Recording is off in System Settings"),
]
# --------------------------------------------------------------------------------- stdio client
class ProtocolViolation(RuntimeError):
pass
class Server:
"""One `context-for-claude-mcp` process, spoken to over line-delimited JSON-RPC.
Every line the server writes to stdout is recorded, because "stdout carries the protocol and
nothing else" is itself one of the things under test: a stray `print` anywhere in the binary
corrupts the stream and Claude drops the connection.
"""
def __init__(self, binary: Path, home: Path):
env = {
# `CFFIXED_USER_HOME` is the one that works; `HOME` is set too so anything reading it
# directly agrees. Nothing else is inherited — an inherited CONTEXT_OMI_MCP_KEY would
# put this run on the network and against a real account.
"HOME": str(home),
"CFFIXED_USER_HOME": str(home),
"TMPDIR": str(home / "tmp"),
"PATH": "/usr/bin:/bin",
"LANG": "en_US.UTF-8",
}
(home / "tmp").mkdir(parents=True, exist_ok=True)
self.proc = subprocess.Popen(
[str(binary)],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env,
cwd=str(home),
)
self.lines: "queue.Queue[str]" = queue.Queue()
self.raw_stdout: list[str] = []
self.stderr: list[str] = []
self._next_id = 1
threading.Thread(target=self._pump_stdout, daemon=True).start()
threading.Thread(target=self._pump_stderr, daemon=True).start()
def _pump_stdout(self) -> None:
assert self.proc.stdout
for raw in self.proc.stdout:
line = raw.decode("utf-8", "replace").rstrip("\n")
if not line:
continue
self.raw_stdout.append(line)
self.lines.put(line)
def _pump_stderr(self) -> None:
assert self.proc.stderr
for raw in self.proc.stderr:
self.stderr.append(raw.decode("utf-8", "replace").rstrip("\n"))
def send_raw(self, payload: bytes) -> None:
assert self.proc.stdin
self.proc.stdin.write(payload)
self.proc.stdin.flush()
def send(self, obj: dict) -> None:
self.send_raw((json.dumps(obj) + "\n").encode("utf-8"))
def read(self, timeout: float = 30.0) -> dict:
try:
line = self.lines.get(timeout=timeout)
except queue.Empty as exc:
raise ProtocolViolation("no response within %.0fs" % timeout) from exc
try:
return json.loads(line)
except json.JSONDecodeError as exc:
raise ProtocolViolation("stdout line is not JSON: %r" % line[:200]) from exc
def request(self, method: str, params: dict | None = None, timeout: float = 30.0) -> dict:
rid = self._next_id
self._next_id += 1
frame = {"jsonrpc": "2.0", "id": rid, "method": method}
if params is not None:
frame["params"] = params
self.send(frame)
response = self.read(timeout)
if response.get("id") != rid:
raise ProtocolViolation(f"id mismatch: asked {rid}, got {response.get('id')}")
return response
def initialize(self) -> dict:
return self.request(
"initialize",
{
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {"name": "eval", "version": "0"},
},
)
def call(self, tool: str, arguments: dict | None = None) -> "ToolResult":
response = self.request("tools/call", {"name": tool, "arguments": arguments or {}})
return ToolResult(tool, arguments or {}, response)
def close(self) -> None:
try:
if self.proc.stdin:
self.proc.stdin.close()
self.proc.wait(timeout=10)
except Exception:
self.proc.kill()
@dataclass
class ToolResult:
tool: str
arguments: dict
response: dict
@property
def text(self) -> str:
content = self.response.get("result", {}).get("content") or []
return "\n".join(part.get("text", "") for part in content)
@property
def is_error(self) -> bool:
return bool(self.response.get("result", {}).get("isError"))
@property
def rpc_error(self) -> dict | None:
return self.response.get("error")
def label(self) -> str:
return f"{self.tool}({json.dumps(self.arguments, ensure_ascii=False)})"
# --------------------------------------------------------------------------------- parsing
HIT_RE = re.compile(
r"^- \*\*(?P<time>[^*]+)\*\* · \*(?P<kind>[a-z ]+)\*"
r"(?: · (?P<origin>live|omi))?"
r"(?: \((?P<ctx>.*?)\))?"
r"(?:: (?P<body>.*))?$"
)
FRAMES_RE = re.compile(r"^\[(\d+) frames · ")
@dataclass
class Hit:
raw: str
kind: str
origin: str | None
app: str | None
window: str | None
body: str
uncertain: bool
frames: int
def parse_hits(text: str) -> list[Hit]:
hits = []
for line in text.splitlines():
m = HIT_RE.match(line.strip())
if not m:
continue
ctx = m.group("ctx") or ""
uncertain = UNCERTAIN_MARKER in line
app = window = None
if m.group("kind") == "screen" and ctx:
if " — " in ctx:
app, window = ctx.split(" — ", 1)
window = window.strip('"')
else:
app = ctx
body = m.group("body") or ""
fm = FRAMES_RE.match(body)
hits.append(
Hit(
raw=line.strip(),
kind=m.group("kind"),
origin=m.group("origin"),
app=app,
window=window,
body=body,
uncertain=uncertain,
frames=int(fm.group(1)) if fm else 1,
)
)
return hits
def nonces_in(text: str) -> set[str]:
lowered = text.lower()
return {n for n in ALL_PROBE_NONCES if n in lowered}
def stem_prefix(term: str) -> str:
"""A crude stem, only ever used to ask 'does this hit contain any query word at all'."""
term = re.sub(r"[^a-z0-9]", "", term.lower())
return term[: max(4, len(term) - 3)]
def contains_any_term(hit_text: str, query: str) -> bool:
lowered = re.sub(r"[^a-z0-9 ]", " ", hit_text.lower())
for word in query.split():
stem = stem_prefix(word)
if len(stem) >= 3 and stem in lowered:
return True
return False
def has_any(text: str, phrases: Iterable[str]) -> bool:
lowered = text.lower()
return any(p.lower() in lowered for p in phrases)
# --------------------------------------------------------------------------------- reporting
@dataclass
class Check:
cls: str
name: str
ok: bool
reason: str
critical: bool = False
weight: float = 1.0
excerpt: str = ""
class Report:
def __init__(self) -> None:
self.checks: list[Check] = []
self.metrics: dict[str, Any] = {}
def add(
self,
cls: str,
name: str,
ok: bool,
reason: str,
*,
critical: bool = False,
weight: float = 1.0,
excerpt: str = "",
) -> None:
self.checks.append(
Check(cls, name, bool(ok), reason, critical, weight, excerpt[:400] if not ok else "")
)
def metric(self, name: str, value: Any) -> None:
self.metrics[name] = value
def class_score(self, cls: str) -> tuple[float, int, int]:
rows = [c for c in self.checks if c.cls == cls]
if not rows:
return (1.0, 0, 0)
total = sum(c.weight for c in rows)
passed = sum(c.weight for c in rows if c.ok)
return (passed / total, sum(1 for c in rows if c.ok), len(rows))
def overall(self) -> float:
num = den = 0.0
for cls in CLASS_ORDER:
rows = [c for c in self.checks if c.cls == cls]
if not rows:
continue
score, _, _ = self.class_score(cls)
weight = CLASS_WEIGHTS[cls]
num += score * weight
den += weight
return num / den if den else 0.0
def critical_failures(self, strict: bool = False) -> list[Check]:
"""Critical failures that gate the build. Tracked ones are excluded unless `strict`."""
return [
c
for c in self.checks
if c.critical and not c.ok and (strict or c.name not in KNOWN_FAILURES)
]
def unexpected_failures(self, strict: bool = False) -> list[Check]:
return [c for c in self.checks if not c.ok and (strict or c.name not in KNOWN_FAILURES)]
def resolved_known_failures(self) -> list[str]:
return sorted(c.name for c in self.checks if c.ok and c.name in KNOWN_FAILURES)
# --------------------------------------------------------------------------------- checks
def check_findability(report: Report, s: Server, c: Corpus) -> None:
cls = "findability"
cases = [
("single-occurrence spoken term", TERM_HALYARD, "halyard", "me"),
("term in a window title only", TERM_THERMOPYLAE, "thermopylae", "screen"),
("term buried mid-OCR", TERM_BASILISK, "basilisk", "screen"),
("repeated spoken term, lowercased query", "zephyrine", "zephyrine", "me"),
]
for label, query, needle, kind in cases:
r = s.call("recall", {"query": query})
hits = parse_hits(r.text)
found = [h for h in hits if needle in h.raw.lower()]
report.add(
cls,
f"recall finds {label}",
bool(found),
f'"{query}" → {len(hits)} hit(s), {len(found)} containing "{needle}"',
excerpt=r.text,
)
if found:
report.add(
cls,
f"{label} comes back as the right kind",
any(h.kind == kind for h in found),
f"expected a *{kind}* hit, got {[h.kind for h in found]}",
excerpt=r.text,
)
r = s.call("recall", {"query": "vendor contract Priyanka"})
hits = parse_hits(r.text)
report.add(
cls,
"multi-word query returns the one document holding all three words",
bool(hits) and "priyanka" in hits[0].raw.lower(),
f"top hit: {hits[0].raw[:90] if hits else '(none)'}",
excerpt=r.text,
)
r = s.call("recall", {"query": "migrations"})
report.add(
cls,
"porter stemming finds an inflected form",
any("halyard" in h.raw.lower() for h in parse_hits(r.text)),
'"migrations" should reach the seeded "migration"',
excerpt=r.text,
)
# A term matched only through the app name column.
r = s.call("recall", {"query": "Obsidian"})
report.add(
cls,
"recall matches on the app name",
any(h.app == "Obsidian" for h in parse_hits(r.text)),
f"{len(parse_hits(r.text))} hit(s) for an app-name query",
excerpt=r.text,
)
# Punctuated identifiers. The query sanitizer strips FTS operator characters and joins what is
# left ("SCA-219" → the single term `sca219`), while the unicode61 index split the very same
# string into `sca` and `219`. The two readings never meet, so the exact ticket the user was
# looking at is unfindable by its own name — and the empty answer is phrased as a searched-and-
# found-nothing negative, which is the licence to say it never happened.
r = s.call("recall", {"query": "SCA-219"})
report.add(
cls,
"a punctuated identifier finds the window that shows it",
any("sca-219" in h.raw.lower() for h in parse_hits(r.text)),
'searching "SCA-219" returns nothing while "219" returns the frame — query-side and '
"index-side tokenisation disagree",
excerpt=r.text,
)
r = s.call("recall", {"query": "219"})
report.add(
cls,
"the split reading of that identifier still finds it",
any("sca-219" in h.raw.lower() for h in parse_hits(r.text)),
"even the bare number fails, so the frame is unreachable by any reading",
excerpt=r.text,
)
# A possessive is how a person actually types a name into a question. The apostrophe is not an
# FTS operator character, so it survives into the phrase `"priyanka's"`, which unicode61 splits
# into two adjacent tokens — and no document says "priyanka s".
r = s.call("recall", {"query": "Priyanka's"})
report.add(
cls,
"a possessive form still finds the name",
any("priyanka" in h.raw.lower() for h in parse_hits(r.text)),
"\"Priyanka's\" becomes the FTS phrase \"priyanka s\", which matches no document",
excerpt=r.text,
)
r = s.call("recall", {"query": "Halyard?"})
report.add(
cls,
"trailing punctuation does not break a query",
any("halyard" in h.raw.lower() for h in parse_hits(r.text)),
"a question mark on the end of a word lost the match",
excerpt=r.text,
)
# `recent` covers a window that ends well before now, so it must be empty — the interesting
# property is that it says so as a local-only tool rather than implying an empty account.
r = s.call("recent", {"minutes": 5})
report.add(
cls,
"recent over an empty window returns no fabricated hits",
not parse_hits(r.text),
f"{len(parse_hits(r.text))} hit(s) in a window with nothing seeded",
excerpt=r.text,
)
r = s.call("recent", {"minutes": 9 * 60})
report.add(
cls,
"recent over a covered window returns the corpus",
len(parse_hits(r.text)) >= 6,
f"{len(parse_hits(r.text))} hit(s) across 9 hours of seeded capture",
excerpt=r.text,
)
r = s.call("screen", {"limit": 60})
screen_hits = parse_hits(r.text)
apps = {h.app for h in screen_hits if h.app}
report.add(
cls,
"screen reaches every seeded app",
{"Cursor", "Arc", "Obsidian", "Safari", "Warp"} <= apps,
f"apps returned: {sorted(a for a in apps if a)}",
excerpt=r.text,
)
def check_no_confabulation(report: Report, servers: dict[str, Server], c: Corpus) -> None:
cls = "no_confabulation"
s = servers["main"]
for label, query in [
("a term never captured", TERM_UNSEEN),
("a near neighbour of a captured term", TERM_NEAR_MISS),
]:
r = s.call("recall", {"query": query})
hits = parse_hits(r.text)
report.add(
cls,
f"recall returns nothing for {label}",
not hits,
f'"{query}" → {len(hits)} hit(s); nearest-neighbour output would be a fabrication',
critical=True,
excerpt=r.text,
)
report.add(
cls,
f"the empty answer for {label} says the search ran",
has_any(r.text, ["no results", "were found in", "no match"]),
"an empty answer must state that a search happened",
excerpt=r.text,
)
report.add(
cls,
f"the empty answer for {label} names the half that did not run",
has_any(r.text, ["**not** searched", "no omi mcp api key is configured"]),
"with no Omi credential the account half was not searched and must say so",
critical=True,
excerpt=r.text,
)
# Every hit that does come back must literally contain a query word. This is the general form of
# "do not present a nearest neighbour as a match".
offenders = []
for query in [TERM_HALYARD, "zephyrine", "vendor contract Priyanka", "parity pack", "probeword"]:
r = s.call("recall", {"query": query})
for h in parse_hits(r.text):
if not contains_any_term(h.raw, query):
offenders.append((query, h.raw[:80]))
report.add(
cls,
"every returned hit contains at least one query word",
not offenders,
f"{len(offenders)} hit(s) matched nothing in their query: {offenders[:2]}",
critical=True,
)
# Absence claimed outside the coverage window is the failure the whole design exists to prevent.
r = s.call(
"recall", {"query": TERM_UNSEEN, "since": iso(c.a0 - 2 * 365 * 86400), "until": iso(c.a0)}
)
report.add(
cls,
"an empty answer outside the coverage window states the coverage window",
"covering **" in r.text or "coverage" in r.text.lower(),
"a reader must be told what was actually recorded before reading absence as evidence",
critical=True,
excerpt=r.text,
)
report.add(
cls,
"an empty answer hedges rather than asserting the thing never happened",
has_any(
r.text,
[
"before telling the user it did not happen",
"rules anything out",
"not captured",
"before concluding",
],
),
"no hedge found in an out-of-coverage empty answer",
excerpt=r.text,
)
# No database at all: a search that never ran is not a negative result.
nodb = servers["nodb"]
r = nodb.call("recall", {"query": TERM_HALYARD})
report.add(
cls,
"with no database, recall says the local half was not searched",
has_any(r.text, ["was not searched", "not searched"]),
"must not read as a negative result",
critical=True,
excerpt=r.text,
)
report.add(
cls,
"with no database, recall never claims it looked and found nothing",
"found no match" not in r.text.lower(),
'output claims "found no match" for a search that could not run',
critical=True,
excerpt=r.text,
)
# A database that opens but cannot answer — the third state, and the one a `try?` erases.
broken = servers["broken"]
r = broken.call("recall", {"query": TERM_HALYARD})
report.add(
cls,
"with an unreadable database, recall reports a reader fault",
has_any(r.text, ["was not searched", "could not be read"]),
"an unreadable database must not render as an empty history",
critical=True,
excerpt=r.text,
)
r = broken.call("status", {})
report.add(
cls,
"status distinguishes 'opened but unreadable' from 'nothing captured'",
has_any(r.text, ["could not be read", "fault in this reader"]),
"status must not report an empty history for a database it failed to query",
critical=True,
excerpt=r.text,
)
report.add(
cls,
"status does not assert an empty history from a reader fault",
"recorded nothing locally" not in r.text.lower(),
"reader fault rendered as 'recorded nothing'",
excerpt=r.text,
)
# An empty database with a denied permission: missing, not absent.
empty = servers["empty"]
r = empty.call("recall", {"query": TERM_HALYARD})
report.add(
cls,
"an empty database is reported as 'nothing recorded', not as 'did not happen'",
has_any(r.text, ["recorded nothing", "rules anything out"]),
"empty capture must not read as evidence of absence",
excerpt=r.text,
)
r = empty.call("screen", {})
report.add(
cls,
"a denied capture permission is named in the empty answer",