trajectories 0.0.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/LICENSE +201 -0
- package/README.md +288 -2
- package/dist/cli.js +30210 -0
- package/dist/convert.d.ts +31 -0
- package/dist/convert.js +431 -0
- package/dist/deepagents_checkpoint.py +362 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +8402 -0
- package/dist/log-baseline.d.ts +26 -0
- package/dist/log-baseline.js +104 -0
- package/dist/log-content.d.ts +16 -0
- package/dist/log-content.js +80 -0
- package/dist/log-io.d.ts +10 -0
- package/dist/log-io.js +121 -0
- package/dist/log-read.d.ts +24 -0
- package/dist/log-read.js +117 -0
- package/dist/log-reconcile.d.ts +5 -0
- package/dist/log-reconcile.js +38 -0
- package/dist/log-records.d.ts +35 -0
- package/dist/log-records.js +85 -0
- package/dist/log-recovery.d.ts +18 -0
- package/dist/log-recovery.js +123 -0
- package/dist/log-stage.d.ts +14 -0
- package/dist/log-stage.js +138 -0
- package/dist/log-state.d.ts +89 -0
- package/dist/log-state.js +92 -0
- package/dist/log-switch.d.ts +3 -0
- package/dist/log-switch.js +98 -0
- package/dist/log-types.d.ts +129 -0
- package/dist/log-types.js +8 -0
- package/dist/log.d.ts +8 -0
- package/dist/log.js +62 -0
- package/dist/migrate.d.ts +29 -0
- package/dist/migrate.js +173 -0
- package/dist/normalization-runtime.d.ts +1 -0
- package/dist/normalization-runtime.js +3150 -0
- package/dist/portable.d.ts +9 -0
- package/dist/portable.js +26 -0
- package/dist/readers/canonical.d.ts +13 -0
- package/dist/readers/canonical.js +54 -0
- package/dist/readers/cline.d.ts +10 -0
- package/dist/readers/cline.js +107 -0
- package/dist/readers/cursor.d.ts +20 -0
- package/dist/readers/cursor.js +374 -0
- package/dist/readers/deepagents.d.ts +41 -0
- package/dist/readers/deepagents.js +163 -0
- package/dist/readers/fx.d.ts +11 -0
- package/dist/readers/fx.js +573 -0
- package/dist/readers/github-copilot.d.ts +8 -0
- package/dist/readers/github-copilot.js +216 -0
- package/dist/readers/grok-build.d.ts +12 -0
- package/dist/readers/grok-build.js +391 -0
- package/dist/readers/guards.d.ts +4 -0
- package/dist/readers/guards.js +121 -0
- package/dist/readers/pi-compaction.d.ts +7 -0
- package/dist/readers/pi-compaction.js +112 -0
- package/dist/readers/pi-context.d.ts +13 -0
- package/dist/readers/pi-context.js +64 -0
- package/dist/store.d.ts +25 -0
- package/dist/store.js +218 -0
- package/dist/types.d.ts +91 -0
- package/dist/types.js +1 -0
- package/dist/writers/claude-code.d.ts +5 -0
- package/dist/writers/claude-code.js +51 -0
- package/dist/writers/cline.d.ts +3 -0
- package/dist/writers/cline.js +77 -0
- package/dist/writers/codex.d.ts +3 -0
- package/dist/writers/codex.js +32 -0
- package/dist/writers/cursor.d.ts +3 -0
- package/dist/writers/cursor.js +197 -0
- package/dist/writers/deepagents.d.ts +3 -0
- package/dist/writers/deepagents.js +116 -0
- package/dist/writers/entropy.d.ts +5 -0
- package/dist/writers/entropy.js +33 -0
- package/dist/writers/fx.d.ts +3 -0
- package/dist/writers/fx.js +135 -0
- package/dist/writers/github-copilot.d.ts +3 -0
- package/dist/writers/github-copilot.js +71 -0
- package/dist/writers/grok-build.d.ts +3 -0
- package/dist/writers/grok-build.js +43 -0
- package/dist/writers/helpers.d.ts +17 -0
- package/dist/writers/helpers.js +75 -0
- package/dist/writers/omp.d.ts +3 -0
- package/dist/writers/omp.js +7 -0
- package/dist/writers/opencode.d.ts +3 -0
- package/dist/writers/opencode.js +65 -0
- package/dist/writers/pi.d.ts +3 -0
- package/dist/writers/pi.js +45 -0
- package/docs/cline-native-format.md +43 -0
- package/docs/copilot-native-format.md +78 -0
- package/docs/cursor-native-format.md +103 -0
- package/docs/deepagents-native-format.md +98 -0
- package/docs/evidence-api.md +170 -0
- package/docs/fx-native-format.md +87 -0
- package/docs/harness-parity-bootstrap.md +78 -0
- package/docs/harness-support-parity.md +107 -0
- package/docs/inspirations.md +103 -0
- package/docs/ledger-migration.md +141 -0
- package/docs/pi-compaction-context.md +75 -0
- package/docs/pi-provider-context.md +76 -0
- package/docs/research/2026-10-06-huihua-and-oar.md +176 -0
- package/docs/research/ledger/README.md +96 -0
- package/docs/research/ledger/confer/2026-09-13-round-10/README.md +61 -0
- package/docs/research/ledger/confer/2026-09-13-round-2/cass.md +72 -0
- package/docs/research/ledger/confer/2026-09-13-round-3/README.md +70 -0
- package/docs/research/ledger/confer/2026-09-13-round-5/README.md +61 -0
- package/docs/research/ledger/confer/2026-09-13-round-6/README.md +68 -0
- package/docs/research/ledger/confer/2026-09-13-round-7/README.md +64 -0
- package/docs/research/ledger/confer/2026-09-13-round-9/README.md +59 -0
- package/docs/research/ledger/confer/2026-09-14-wake/wake.md +146 -0
- package/docs/research/ledger/docs/inspirations.md +320 -0
- package/docs/research/ledger/docs/pr2-pr6-integration.md +53 -0
- package/docs/research/ledger/docs/research/2026-09-07-confer-inspirations.md +163 -0
- package/docs/research/ledger/docs/research/2026-09-07-letta-continuous-service.md +104 -0
- package/docs/research/ledger/docs/research/2026-09-08-claude-history-retrieval.md +117 -0
- package/docs/research/ledger/docs/research/2026-09-09-agentsview-native-discovery.md +87 -0
- package/docs/research/ledger/docs/research/2026-09-09-daemon-observation.md +231 -0
- package/docs/research/ledger/docs/research/2026-09-09-discovery-pagination.md +114 -0
- package/docs/research/ledger/docs/research/2026-09-09-harness-transcript-contract.md +145 -0
- package/docs/research/ledger/docs/research/2026-09-09-native-history-discovery.md +180 -0
- package/docs/research/ledger/docs/research/2026-09-09-native-history-implementation.md +137 -0
- package/docs/research/ledger/docs/research/2026-09-09-other-inspirations.md +279 -0
- package/docs/research/ledger/docs/research/2026-09-09-trajectory-and-letta-code.md +128 -0
- package/docs/research/ledger/docs/research/2026-09-13-diagnostics-coverage.md +116 -0
- package/docs/research/ledger/docs/research/2026-09-13-fts-rebuild.md +83 -0
- package/docs/research/ledger/docs/research/2026-09-13-read-path-split.md +74 -0
- package/docs/research/ledger/docs/research/2026-09-13-runtime-rollout.md +102 -0
- package/docs/research/ledger/docs/research/2026-09-13-scan-outcomes.md +80 -0
- package/docs/research/ledger/docs/research/2026-09-13-search-quality.md +105 -0
- package/docs/research/ledger/docs/research/2026-09-13-source-coverage.md +64 -0
- package/docs/research/ledger/docs/research/2026-09-13-sync-wall-budget.md +82 -0
- package/docs/research/ledger/docs/research/2026-09-13-typed-read-gate.md +81 -0
- package/docs/research/ledger/docs/research/2026-09-13-writer-lease.md +84 -0
- package/docs/research/ledger/docs/research/2026-09-16-c1-copy-fallback.md +227 -0
- package/docs/research/ledger/docs/research/2026-09-16-c10-kimi.md +414 -0
- package/docs/research/ledger/docs/research/2026-09-16-c2-derive-counter.md +216 -0
- package/docs/research/ledger/docs/research/2026-09-16-c3-echo-filter.md +305 -0
- package/docs/research/ledger/docs/research/2026-09-16-c4-title-index.md +349 -0
- package/docs/research/ledger/docs/research/2026-09-16-c5-trigram.md +268 -0
- package/docs/research/ledger/docs/research/2026-09-16-c6-recency-prior.md +438 -0
- package/docs/research/ledger/docs/research/2026-09-16-c7-tombstones.md +467 -0
- package/docs/research/ledger/docs/research/2026-09-16-c8-remote-truth.md +390 -0
- package/docs/research/ledger/docs/research/2026-09-16-c9-grok.md +380 -0
- package/docs/research/ledger/docs/research/2026-09-16-wake-port-program.md +38 -0
- package/docs/research/ledger/docs/research/2026-09-17-cursor-blobstore.md +157 -0
- package/docs/research/ledger/manifest.json +540 -0
- package/legacy/NOTICE.md +50 -0
- package/legacy/bin/ledger-core.ts +7 -0
- package/legacy/bin/ledger.ts +7 -0
- package/legacy/bin/traj-bridge.ts +7 -0
- package/legacy/scripts/import-claude-history.ts +130 -0
- package/legacy/scripts/native-history-smoke.ts +52 -0
- package/legacy/scripts/observe-daemon.ts +570 -0
- package/legacy/scripts/retitle-sessions.md +19 -0
- package/legacy/scripts/verify-runtime-rollout.ts +151 -0
- package/legacy/src/canonical.ts +108 -0
- package/legacy/src/cli.ts +1293 -0
- package/legacy/src/db.ts +2254 -0
- package/legacy/src/diagnostics.ts +87 -0
- package/legacy/src/discovery.ts +56 -0
- package/legacy/src/embeddings.ts +382 -0
- package/legacy/src/filter.ts +602 -0
- package/legacy/src/gemini_embeddings.ts +103 -0
- package/legacy/src/identity.ts +9 -0
- package/legacy/src/index.ts +10 -0
- package/legacy/src/ingest.ts +791 -0
- package/legacy/src/managed.ts +178 -0
- package/legacy/src/native/common.ts +68 -0
- package/legacy/src/native/cursor.ts +647 -0
- package/legacy/src/native/grok.ts +540 -0
- package/legacy/src/native/kimi.ts +706 -0
- package/legacy/src/native/opencode.ts +470 -0
- package/legacy/src/native/sqlite-ro.ts +281 -0
- package/legacy/src/native.ts +97 -0
- package/legacy/src/normalizer.ts +457 -0
- package/legacy/src/parentage.ts +562 -0
- package/legacy/src/pressure.ts +162 -0
- package/legacy/src/private-storage.ts +74 -0
- package/legacy/src/reader.ts +1255 -0
- package/legacy/src/scanner.ts +591 -0
- package/legacy/src/schema-gate.ts +263 -0
- package/legacy/src/schema.ts +829 -0
- package/legacy/src/semantic-top-k.ts +71 -0
- package/legacy/src/session-hash.ts +49 -0
- package/legacy/src/store-status.ts +218 -0
- package/legacy/src/sync-preparation.ts +45 -0
- package/legacy/src/sync-truth.ts +843 -0
- package/legacy/src/sync.ts +1233 -0
- package/legacy/src/types.ts +436 -0
- package/legacy/src/vocab.txt +30522 -0
- package/legacy/src/watch.ts +673 -0
- package/legacy/src/writer-lease.ts +310 -0
- package/ops/README.md +150 -0
- package/ops/install-cli.sh +63 -0
- package/ops/install-native.sh +35 -0
- package/ops/install-release.sh +49 -0
- package/ops/linux/install-systemd-user.sh +103 -0
- package/ops/macos/install-launchd.sh +72 -0
- package/ops/sync-settings.sh +37 -0
- package/package.json +21 -5
- package/skills/trajectories/SKILL.md +138 -0
- package/skills/trajectories/scripts/trajectories_recent.sh +26 -0
- package/skills/trajectories/scripts/trajectories_search.sh +26 -0
- package/index.js +0 -1
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# C3: the self-referential echo filter, 2026-09-16
|
|
2
|
+
|
|
3
|
+
## Outcome
|
|
4
|
+
|
|
5
|
+
An agent's own lookups of Ledger no longer come back as search hits. The
|
|
6
|
+
transcripts of `ledger search` / `status` / `sync` / `scan` / `build` / `watch`
|
|
7
|
+
and of the Ledger MCP tools are now **canonical but unsearchable**: `records`
|
|
8
|
+
keeps every row exactly as the adapter wrote it, `records_fts` never receives
|
|
9
|
+
them, and the store reports how many it keeps out (`ledger status`:
|
|
10
|
+
`Echo filtered: 188 records (agent lookups of this store; never searchable)`;
|
|
11
|
+
robot: `echo_filtered: 188`). The exclusion is one pure rule over three canonical
|
|
12
|
+
fields — `record_type`, `tool_name`, `tool_arguments_json` — so ingest, the
|
|
13
|
+
index re-derivation, the parity gate, and the status verb classify a record
|
|
14
|
+
identically, and the rule change rides the C2 derive counter as `FTS_FORMAT`
|
|
15
|
+
`"1"` → `"2"`: every store written before the filter re-derives itself once, on
|
|
16
|
+
its next writer pass.
|
|
17
|
+
|
|
18
|
+
On a byte-consistent copy of the production store (1,121,468,416 bytes; 227,474
|
|
19
|
+
records; 2,604 sessions) the whole self-echo footprint is **188 records** — 94
|
|
20
|
+
`assistant-tool-call`s and their 94 paired `tool` results, across 14 sessions
|
|
21
|
+
(this morning's scout measured 40; the store grew fourfold while the cycles
|
|
22
|
+
ran). The re-derivation costs **10.0–10.4 s** (whole-table re-projection
|
|
23
|
+
5.76 s · rule pass 0.37 s · derived-set insert 1 ms · index subtraction 0.16 s ·
|
|
24
|
+
parity + FTS5 integrity gates 2.82 s), parity is exact
|
|
25
|
+
(`227,474 − 188 = 227,286`), and a query whose rank-1 hit was the agent's own
|
|
26
|
+
previous identical search now returns only genuine records — with the genuine
|
|
27
|
+
hits intact. This repository's own 21 development sessions (9,176 records)
|
|
28
|
+
survive intact: every kept record is in the index (10/10 sampled by exact
|
|
29
|
+
`record_id`), every one of their 146 lookups is not (0/10 sampled), and
|
|
30
|
+
per-session `indexed = records − excluded` holds for all 21.
|
|
31
|
+
|
|
32
|
+
Measurements are timestamped by fixture: 40 at the morning scout, 188 on this
|
|
33
|
+
cycle's copy, 192 on the independent verifier's later copy — the store grows
|
|
34
|
+
while the cycles run; the rule's zero-false-positive property, not the count,
|
|
35
|
+
is the invariant.
|
|
36
|
+
|
|
37
|
+
The tree's own state proved the upgrade path once for real before this audit: the
|
|
38
|
+
production store already carried `fts_format = 2` and a `records_echo` of 188,
|
|
39
|
+
and re-running the rule against those records reproduced that set **exactly**
|
|
40
|
+
(188 stored, 188 recomputed, 0 on either side of the difference) — the shipped
|
|
41
|
+
rule is what derived the real store.
|
|
42
|
+
|
|
43
|
+
## The rule
|
|
44
|
+
|
|
45
|
+
A record is a **lookup** (and so excluded from `records_fts`) when it is an
|
|
46
|
+
`assistant-tool-call` that either
|
|
47
|
+
|
|
48
|
+
- **names a Ledger MCP tool**: the name *ends* with a Ledger tool name at a word
|
|
49
|
+
boundary (`ledger_search`, `ledger_recent`, …; client prefixes vary:
|
|
50
|
+
`mcp__ledger__ledger_search`, `mcp_ledger_ledger_recent`, bare
|
|
51
|
+
`ledger_search`; `awake_search` and `xledger_search` do not match), or carries
|
|
52
|
+
the Claude Code server segment `mcp__ledger__` — the server's own namespace,
|
|
53
|
+
whatever the tool is called; or
|
|
54
|
+
- **runs a Ledger binary in command position** in a shell tool's arguments: the
|
|
55
|
+
basename `ledger`, `ledger-core`, or `traj-bridge` (extension `.ts`/`.js`/
|
|
56
|
+
`.exe`, any path form) as the first word of a command, or the word after a
|
|
57
|
+
separator (`&&`, `||`, `;`, `|`, `&`, newline), a command substitution
|
|
58
|
+
(`$(`, backtick), a prefix word (`sudo`, `env`, `time`, `nohup`, `nice`,
|
|
59
|
+
`exec`, `command`, `then`, `do`, …), or a runner (`bun run ./bin/ledger.ts`).
|
|
60
|
+
|
|
61
|
+
A `tool` record is excluded only through its **call**: `tool_call_id` paired to
|
|
62
|
+
an excluded call in the same session — never a guess from the result text.
|
|
63
|
+
|
|
64
|
+
Everything else is kept, deliberately: user and assistant dialogue (however much
|
|
65
|
+
it talks about Ledger), `Read`/`Edit`/`Grep`/`Write` calls, a `git commit -m
|
|
66
|
+
"…ledger…"`, a `cd` into this checkout, `bun test`, `cat ledger.ts`, `grep
|
|
67
|
+
ledger`, a quoted alternation (`rg -n "ard|ledger|review"`), a heredoc body that
|
|
68
|
+
begins `ledger search …` (a document, not a run), and any command whose
|
|
69
|
+
`ledger` is an argument. Reads and writes are treated alike (`status`, `sync`,
|
|
70
|
+
`scan`, `build`, `watch`, `--help`, `--version` all count): an agent dumping the
|
|
71
|
+
store's own counters and cursors into the corpus pollutes it exactly as a search
|
|
72
|
+
does. Quoting is lexical — a separator inside quotes is text — and heredoc
|
|
73
|
+
bodies are removed before segmentation.
|
|
74
|
+
|
|
75
|
+
Two shapes are knowingly not seen, and both err toward indexing: a Ledger
|
|
76
|
+
invocation from *inside* a script (a `subprocess.run(['ledger', …])` in a python
|
|
77
|
+
heredoc — measured: **3** of 4,728 declined candidates), and a runner this rule
|
|
78
|
+
does not model (`node`, `npx`). They cost a noisy hit, never a hidden record.
|
|
79
|
+
Wake makes the same trade with a preview fallback (`command_names_binary`) this
|
|
80
|
+
port declines: when a shell tool carries no structured arguments there is
|
|
81
|
+
nothing here to read, so the call stays indexed — measurably free, since **0**
|
|
82
|
+
shell-tool calls in the store carry null arguments.
|
|
83
|
+
|
|
84
|
+
## Files
|
|
85
|
+
|
|
86
|
+
- `packages/core/src/filter.ts` (new) — the rule, and the three forms the store
|
|
87
|
+
uses it in: `isEchoCall` (pure), `echoExcludedIds` (one streaming pass over
|
|
88
|
+
`records`, used by the re-derivation, the parity gate, and the status count),
|
|
89
|
+
`echoExcludedBatchIds` (an ingest batch, resolving a result whose call sits in
|
|
90
|
+
an earlier batch through the same rule, and returning the call ids the writer
|
|
91
|
+
needs to repair a result committed before its call).
|
|
92
|
+
- `packages/core/src/schema.ts` — `FTS_FORMAT = "2"`; the `records_echo` DDL
|
|
93
|
+
(derived set, both its columns cascading on delete); `DERIVE_COUNTERS` grows an
|
|
94
|
+
`outputs` list so a counter that *matches* but whose output table is missing
|
|
95
|
+
still re-derives.
|
|
96
|
+
- `packages/core/src/db.ts` — open-time counter detection against the tables the
|
|
97
|
+
store already held (before the DDL creates the ones this build needs);
|
|
98
|
+
`insertRecordsInternal` writes canonical rows first, then consults
|
|
99
|
+
`echoExcludedBatchIds`, skipping the FTS row and writing the id into
|
|
100
|
+
`records_echo` — and taking back any already-committed result that pairs with
|
|
101
|
+
a call the batch just classified (index row deleted, id marked), so arrival
|
|
102
|
+
order cannot decide what a pair derives to; `rebuildRecordsFts` re-projects the whole index, recomputes the
|
|
103
|
+
rule set, rewrites `records_echo`, subtracts exactly those rows, and runs the
|
|
104
|
+
gates; `verifyRecordsFtsRebuild` checks `records − excluded == indexed` plus
|
|
105
|
+
“no excluded id without its record”.
|
|
106
|
+
- `packages/core/src/cli.ts` — `build` takes the pending re-derivation before its
|
|
107
|
+
import loop (and reports the wall time); `status` publishes `echo_filtered`
|
|
108
|
+
in both modes.
|
|
109
|
+
- `packages/core/src/watch.ts` — a drain takes the pending re-derivation under
|
|
110
|
+
the lease it already holds, before its batch, reporting the wall on stderr.
|
|
111
|
+
- `packages/core/src/store-status.ts` — the status field, published only when
|
|
112
|
+
the derivation under this build's rules has completed (see the gap below).
|
|
113
|
+
- `packages/core/tests/echo-filter.test.ts` (new) — the rule's positive and
|
|
114
|
+
negative table (15 lookups / 17 not-lookups), ingest
|
|
115
|
+
(canonical completeness + index exclusion + status count), the C2 bump
|
|
116
|
+
(flag → one-shot → parity → exactly once, including the status count's `null`
|
|
117
|
+
while the derivation is pending), and the counter-without-output state.
|
|
118
|
+
- Re-pinned to the new contract: `cli-canonical.test.ts` (`echo_filtered`),
|
|
119
|
+
`derive-counters.test.ts` (the refusal message now formats `FTS_FORMAT`),
|
|
120
|
+
`integration-migrations.test.ts` (the parity message names the excluded term).
|
|
121
|
+
|
|
122
|
+
## Ported mechanics (Wake MIT, `iAmCorey/Wake@d9f702b6` tag v0.6.5 — mechanics with attribution, no code copied)
|
|
123
|
+
|
|
124
|
+
Pin: `d9f702b6df0e85` (`Release v0.6.5`), `crates/wake-core/src/{adapters/mod.rs,db.rs}`,
|
|
125
|
+
MIT License © 2026 Corey Chiu.
|
|
126
|
+
|
|
127
|
+
| Ledger | Wake pin | What was kept |
|
|
128
|
+
| --- | --- | --- |
|
|
129
|
+
| The lookup is dropped from the derived unit; dialogue is not | `adapters/mod.rs#L425-L447` (`units_from_messages`) | a tool call that *is* a lookup contributes nothing to the unit; the message text around it stays, and a message with nothing left is dropped whole — Ledger's analogue is record-level: the call and its paired result leave the index, every other record stays |
|
|
130
|
+
| MCP tools by word-boundary suffix, not substring | `#L469-L535` (`is_wake_lookup`, `ends_with_word`) | client prefixes vary, so the name must *end* with the tool name at a word boundary (`awake_search` is not `wake_search`); the server segment (`mcp__ledger__`) is kept as its own stronger signal |
|
|
131
|
+
| Shell binaries named in command position of the structured `command` field | `#L469-L535` (`WAKE_BINARIES`, `command_names_binary`, `names_binary`) | the structured field is read for *any* tool that carries one (string or argv array), never a substring of the preview; the command is segmented so a binary in argument position (`grep ledger`) is not a run, and a `wake-cli-old`-style near-miss is not a name |
|
|
132
|
+
| The narrowness is a test, not a hope | `#L599-L653` (`wake_lookup_detection_is_narrow`): `cargo test` not a lookup; `man wake-cli-old` not, even with its description mentioning the binary; `Grep`/`Read` not | `echo-filter.test.ts` ports the shape: our own `bun test`, a commit message, a checkout, a quoted alternation, a heredoc body, a `Read` of `ledger.md`, an `Edit` of `ledger.ts` — all kept |
|
|
133
|
+
| A derivation rule change bumps an index-format counter; an old store flags and re-derives once | `db.rs#L134-L196` (`FTS_FORMAT`, whose doc says “'2' = 过滤自指回声”; `open_conn` stamps and flags when `stored != FTS_FORMAT` and sessions existed) | exactly the C2 machinery, consumed: the value is the rule's name, the flag is transient and durable across a crash, a reader serves the stale index while a writer re-derives. C3 is the first bump (below) |
|
|
134
|
+
|
|
135
|
+
## Deviations from the scout design (each with its reason)
|
|
136
|
+
|
|
137
|
+
- **The filter is a derived set (`records_echo`), not a SQL predicate inside
|
|
138
|
+
`FTS_REPOPULATE_SQL`.** The scout allowed “pure SQL or a derived marker”; this
|
|
139
|
+
is the marker, because the rule lives in TypeScript and a SQL restatement of it
|
|
140
|
+
would be a second implementation that can drift — the one thing a
|
|
141
|
+
self-referential filter must not do. The set is also what makes ingest and the
|
|
142
|
+
re-derivation *one decision*: the ingest path writes the id where it skips the
|
|
143
|
+
FTS row, the rebuild recomputes it from the same rule and subtracts it, the
|
|
144
|
+
parity gate and the status count read the same table back. The projection stays
|
|
145
|
+
whole-table on purpose — the rebuild is then a *repair*: re-project every
|
|
146
|
+
record, drop exactly what the rule names, and an index that drifted in either
|
|
147
|
+
direction comes back equal to `records` minus the filter.
|
|
148
|
+
- **The parity gate gained a second term, and an orphan check.** `records −
|
|
149
|
+
excluded == indexed` (scout) plus “every id in `records_echo` still backs a
|
|
150
|
+
record”. The foreign key already forbids an orphan, so the check exists to
|
|
151
|
+
catch the one thing the key cannot: a writer running with enforcement off.
|
|
152
|
+
- **A matched counter with a missing output re-derives.** `DERIVE_COUNTERS`
|
|
153
|
+
records the tables each rule derives, and a store whose counter names the
|
|
154
|
+
current rule but which cannot produce the rule's table is treated as an
|
|
155
|
+
unfinished derivation, not a derived store. This is beyond the scout sketch
|
|
156
|
+
but falls out of the second output: without it, a store that recorded `"2"`
|
|
157
|
+
and lost `records_echo` would report `0` filtered forever. The matching value
|
|
158
|
+
is left in place — it is not the counter that was wrong, the derivation is
|
|
159
|
+
incomplete — and the flag is raised for the next pass.
|
|
160
|
+
- **The rule is broader in shell shapes than the sketch.** Beyond first token /
|
|
161
|
+
separator / prefix word (`ledger/ledger-core/traj-bridge`, `bun run …`), the
|
|
162
|
+
port reads quoted strings correctly (`rg -n "a|ledger|b"` is one command
|
|
163
|
+
running `rg`), strips heredoc bodies, segments on newlines, `$(`, and
|
|
164
|
+
backticks, joins `bash -lc` argv arrays, and reads the Codex `cmd:"…"` strings
|
|
165
|
+
inside an `exec` input. Each extension is in the conservative direction —
|
|
166
|
+
structural command position, never a substring — and is pinned in the test
|
|
167
|
+
table.
|
|
168
|
+
- **`build` and `watch` take the pending re-derivation too.** C2's boundary note
|
|
169
|
+
left exactly this to C3 (`sync` was the only pass): a daemon-only machine
|
|
170
|
+
would otherwise keep serving an index derived under the old rules until some
|
|
171
|
+
sync ran. The watcher reports the wall time on stderr; `build` reports it in
|
|
172
|
+
its summary.
|
|
173
|
+
- **No per-query filtered count.** Per the contract and Wake: the envelope is
|
|
174
|
+
unchanged; the count lives where it is a property of the *store*, not of a
|
|
175
|
+
query.
|
|
176
|
+
|
|
177
|
+
## Audit against the scout design
|
|
178
|
+
|
|
179
|
+
| Contract item | State |
|
|
180
|
+
| --- | --- |
|
|
181
|
+
| (a) MCP tool naming, suffix + server namespace | as designed |
|
|
182
|
+
| (b) shell binary in command position (`&& ; \|\| \| sudo time env`, `bun` runner) | as designed, extended (quotes, heredocs, substitutions, wrappers, argv, embedded `cmd:`) |
|
|
183
|
+
| (c) result paired by `tool_call_id`, same session | as designed; batch form resolves a call from an earlier batch through the store, store-wide form buffers results so source order cannot matter |
|
|
184
|
+
| never filter dialogue / read / edit / grep; reads *and* writes are lookups | as designed; 4,728 LIKE-superset candidates declined, spot-audited (checkout, commit message, `bun test`, tool reads/writes, quoted alternation, heredoc bodies) |
|
|
185
|
+
| record-level granularity; `records` canonical; `sessionRecords`/export untouched | as designed; fixture proves all 7 rows stay; 10/10 sampled dev records still indexed |
|
|
186
|
+
| derivation-time placement: ingest skip + re-derivation exclusion | as designed, via the marker table (deviation above) |
|
|
187
|
+
| `FTS_FORMAT` `1` → `2`, one re-derivation per store | as designed |
|
|
188
|
+
| parity `records − excluded == indexed` | as designed, plus orphan check |
|
|
189
|
+
| `echo_filtered` in `status` robot output | **gap → fixed**: the count was published from the derived set alone, so a store whose derivation was *pending* (the critical-pressure skip `sync.ts` documents, or a crash between open and the pass) reported `0` while the served index still held the lookups. `echo_filtered` is now published only when `fts_format` equals this build's **and** no `fts_reindex` is pending; otherwise `null` (“unknown … the next writer pass derives it”) |
|
|
190
|
+
| daemon paths wired (`build`, watch drain) | as designed (C2's boundary #1 taken) |
|
|
191
|
+
| envelope: no per-query filtered count | as designed (`reader.ts` untouched) |
|
|
192
|
+
|
|
193
|
+
## Review round 2 (adversarial findings → fixes)
|
|
194
|
+
|
|
195
|
+
An adversarial review of this tree found four issues; all four are fixed here,
|
|
196
|
+
with regression tests for the first three and the API-surface document updated
|
|
197
|
+
for the fourth. None of them changes the design; three are ordering/coherence
|
|
198
|
+
holes in the rule's edges, one is documentation drift.
|
|
199
|
+
|
|
200
|
+
| # | Finding | Fix | Test |
|
|
201
|
+
| --- | --- | --- | --- |
|
|
202
|
+
| 1 (P2) | The batch form never repaired an **indexed result whose call arrives later**: a result committed in an earlier batch stayed in `records_fts` forever (the marker set cannot see it, and the parity gate trusts the set, so `records − excluded == indexed` still held). | On a batch that classifies a call, the same transaction now takes back the committed `tool` rows pairing those `tool_call_id`s — delete from `records_fts`, `INSERT OR REPLACE` into `records_echo` — so arrival order cannot decide what a pair derives to. Rows of the batch itself are touched idempotently (the delete matches nothing yet; the marker insert precedes the loop's own). | `a result committed before its call is repaired when the call arrives in a later batch` (both orders) |
|
|
203
|
+
| 2 (P2) | `bash -lc "ledger search x"` was a **false negative**: the wrapper's quoted script reaches `segmentWords` as one word, and the quote-stripping `basename` then rejected it for containing spaces — while the argv form and a script with an internal separator were detected. Incoherent for the most common wrapper shape. | The extracted script is quote-stripped and judged by `commandSegments` (the same segmentation as every other command string), which also makes quoting *inside* the script meaningful — a quoted alternation (`bash -lc "rg -n 'ard|ledger|review' docs"`) is no longer split raw and mis-read as a run. | `a shell wrapper's quoted -c script is one more command string` |
|
|
204
|
+
| 3 (P3) | `echoCallOf` hardcoded `'assistant-tool-call'`, so a `tool` row carrying a Ledger tool name was excluded at ingest and then **flipped back into the index** on the next re-derivation — the batch and store-wide forms disagreeing about one row. | The record's own `record_type` decides (`echoCallOf` is now the single place the rule meets the row, used by both forms), and the store lookup selects `record_type` it had previously only typed. A tool row is never a call: an orphan result with a Ledger tool name stays indexed, errs toward indexing, and is stable across a re-derivation. | `a tool result that carries a Ledger tool name is not a call, at ingest or after a re-derivation` |
|
|
205
|
+
| 4 (P3) | `docs/api-surface.md` was stale where robots read the contract: the `status` envelope lacked `echo_filtered` (and its `null` semantics), §3.1 still called `fts_format` `"1"`, and the re-derivation was described as `sync`'s alone. | Updated, plus the C3 surface the same file now has to describe: `build`'s summary `deriveMs` and its `Re-derived` prose line, the `records_echo` table (new §3.2 entry), the parity count's excluded term, and both counter sections naming `sync`/`build`/`watch` as the executors. | — (documentation; covered by the two code tests above) |
|
|
206
|
+
|
|
207
|
+
Gates after the round: **`bun test` 568 pass / 0 fail across 39 files**,
|
|
208
|
+
`bun run typecheck` clean, `bun run build` clean.
|
|
209
|
+
|
|
210
|
+
## Measurements
|
|
211
|
+
|
|
212
|
+
Host: Apple M1, macOS 25.6, APFS. Fixture: a byte-consistent copy of the
|
|
213
|
+
production store (`sqlite3 …?mode=ro ".backup /tmp/ledger-c3/ledger.sqlite"`;
|
|
214
|
+
1,121,468,416 bytes, 227,474 records, 2,604 sessions, 0 embeddings) — the
|
|
215
|
+
production store is never opened for writing, and the measurement harnesses
|
|
216
|
+
(throwaway scripts under `/tmp/ledger-c3`) were deleted afterwards.
|
|
217
|
+
|
|
218
|
+
The store was found **already derived** (`fts_format = 2`, `records_echo` = 188,
|
|
219
|
+
`records_fts` = 227,286) — the C3 build had run against it during development.
|
|
220
|
+
The “before” state below is therefore reconstructed from the store's own
|
|
221
|
+
projection: drop `records_echo` and `records_fts`, recreate the v11 DDL,
|
|
222
|
+
re-project every record whole (`5,783 ms`), set `fts_format = '1'` — a
|
|
223
|
+
bit-consistent stand-in for what a pre-C3 build leaves.
|
|
224
|
+
|
|
225
|
+
**OBSERVED**
|
|
226
|
+
|
|
227
|
+
| measurement | value |
|
|
228
|
+
| --- | --- |
|
|
229
|
+
| echo set, production store | **188 records** = 94 calls + 94 results, in **14 sessions** (omp 146, codex 40, claude-code 2); the calls are `bash` 73, `exec` 20, `Bash` 1 |
|
|
230
|
+
| scout's count this morning vs now | 40 → **188** (this repo's own sessions supplied the growth) |
|
|
231
|
+
| rule pass over the copy (`echoExcludedIds`) | **2,979 ms** first (cold) read, **372–500 ms** warm |
|
|
232
|
+
| stored set vs fresh rule computation | 188 vs 188, **0 differences** either way (the real store's derivation is exactly the shipped rule) |
|
|
233
|
+
| false positives | **0** across all 188: every call runs a Ledger binary in command position or names an MCP Ledger tool; every result pairs to a call in its own session. An independent separator-anchored pattern matched **92/94**; the two exceptions are path-prefixed runners (`/Users/mac/.bun/bin/bun …/ledger-core.ts`), which the rule normalizes by basename and the check's pattern did not |
|
|
234
|
+
| declined candidates (LIKE superset the rule keeps) | **4,728**; among them 3 script-embedded invocations (documented blind spot) and 7 direct `sqlite3` reads of the store file (kept by design, not lookups) |
|
|
235
|
+
| dev sessions of this checkout (`cwd` match) | **21 sessions, 9,176 records, 146 excluded**; per-session `indexed = records − excluded` for **all 21**, 0 mismatches; exact-membership sample: **10/10 kept records in `records_fts`, 0/10 excluded** |
|
|
236
|
+
| writer open of the pre-C3 copy | **3 ms**; `needsFtsReindex()` true (stored `"1"` ≠ `"2"`) |
|
|
237
|
+
| `reindexRecordsFts()`, four runs | **10,049 / 10,051 / 10,162 / 10,374 ms** |
|
|
238
|
+
| phase breakdown (same statements, timed individually) | repopulate (drop + DDL + `INSERT … SELECT`) **5,759 ms** · rule pass **372 ms** · derived-set insert **1 ms** · index subtraction (188 deletes) **159 ms** · parity + FTS5 `integrity-check` **2,818 ms** |
|
|
239
|
+
| state after | `records` 227,474 · `records_fts` 227,286 · `records_echo` 188 · parity true · `verifyRecordsFtsRebuild` passed · `schema_meta`: `fts_format=2`, `embeddings_format=1`, flag cleared |
|
|
240
|
+
| searches, before → after (totals bounded at 1000; marker = the rule's set) | `search_meta` **95 → 91** (4 echo results in the top 200 → 0) · `coverage` 1000 → 1000 (**16 → 0**) · `canonical trajectory` **30 → 28** (2 echo calls in the top 5 → 0; the previous identical search was rank 1) · `writer lease` **230 → 229** · `record_candidate` **45 → 43** (echo at rank 4 → gone) · `rebase token passthrough` **3 → 2** (echo at rank 1 → gone) · `ledger` 1000 → 1000 (**2 → 0**). Every non-echo hit stayed; the top-5 list of `canonical trajectory` went from `[ECHO, real, real, ECHO, real]` to five genuine tool records |
|
|
241
|
+
| CLI, copy | `status --robot` → `"echo_filtered":188`; human → `Echo filtered: 188 records (agent lookups of this store; never searchable)`; `search "canonical trajectory" --limit 3` → 3 genuine hits across 2 sessions, no echo |
|
|
242
|
+
| gap fix, verified live in the reconstructed pending state | raw set 0, served index still returning the rank-1 echo record, `status.echoFiltered` → **null** (was `0`); after the one-shot → **188** and parity |
|
|
243
|
+
| existing suite | **565 pass / 0 fail** across 39 files (`bun test`), `bun run typecheck` clean, `bun run build` clean — the round-1 tree; after the review round's three regression tests the same gates are **568 pass / 0 fail**, clean (see “Review round 2”) |
|
|
244
|
+
|
|
245
|
+
**INFERRED** (not directly measured, listed so reviewers can attack them)
|
|
246
|
+
|
|
247
|
+
- The 10.0–10.4 s band is **warm** and idle-host: the copy had been fully read
|
|
248
|
+
and rewritten several times before the timed runs. C2 measured the same
|
|
249
|
+
re-projection path at 19.4–33.3 s on the older store; the C3 addition is only
|
|
250
|
+
the rule pass + subtraction (~0.5 s of the 10 s), so the difference between the
|
|
251
|
+
two bands is page cache and host load, not a different mechanism. The first
|
|
252
|
+
cold post-merge pass on a user's machine should be expected in the C2 band.
|
|
253
|
+
- The status gate's *motivation* is derived from code, not from a failing
|
|
254
|
+
scenario observed in production: the pending state is reachable by design
|
|
255
|
+
(`sync` skips the one-shot under critical pressure, and the flag is durable)
|
|
256
|
+
and by crash between open and pass; only the reconstructed window was
|
|
257
|
+
measured. Its other reachable case — an older build's writer pass writing the
|
|
258
|
+
index back whole, leaving `fts_format` different from this build's — is the
|
|
259
|
+
version-crossing cost C2 already documents as “one more re-derivation”; the
|
|
260
|
+
gate now reports `null` instead of a stale count while it lasts.
|
|
261
|
+
- Any *further* change to the rule with `FTS_FORMAT` left at `"2"` would not
|
|
262
|
+
re-derive anything: the set is derived state keyed by the counter, not hashed
|
|
263
|
+
from the rule. The bump is the contract (as in Wake); the follow-up below
|
|
264
|
+
records it.
|
|
265
|
+
- The vectors lane is unaffected and cannot currently surface echo records: the
|
|
266
|
+
store holds 0 embeddings, so semantic/hybrid search has nothing to rank. If
|
|
267
|
+
the lane ships, a lookup's embedding would need the same exclusion — the set
|
|
268
|
+
is the natural input, and no decision is needed until then.
|
|
269
|
+
|
|
270
|
+
## Position in the program (C3 row, `docs/research/2026-09-16-wake-port-program.md`)
|
|
271
|
+
|
|
272
|
+
- Failure it fixes: *“We index the transcripts of the agents that call `ledger
|
|
273
|
+
search` — your own queries come back as hits. Unnamed bug, observed daily.”*
|
|
274
|
+
- Open-source case: *“Self-hosting agents are our primary users; a store that
|
|
275
|
+
echoes itself reads as broken. Wake ships the same filter (`wake_*` tools,
|
|
276
|
+
`FTS_FORMAT` counter).”*
|
|
277
|
+
- Ordering: C3 follows C2 because it changes index shape and needs the one-shot;
|
|
278
|
+
it bumps `FTS_FORMAT` and is the first real consumer of the counter, plus the
|
|
279
|
+
first cycle to take C2's own boundary — the daemon passes now re-derive too.
|
|
280
|
+
C4 and C5 will bump the same counter and can rely on the marker-table pattern
|
|
281
|
+
introduced here (a derived set beside the index, maintained by the same
|
|
282
|
+
decision that writes the index).
|
|
283
|
+
|
|
284
|
+
## Boundaries and follow-ups (not in this cycle)
|
|
285
|
+
|
|
286
|
+
1. **In-script invocations stay unfiltered** (3 measured: python heredocs
|
|
287
|
+
running `subprocess.run(['ledger','sync',…])`), as do unmodelled runners
|
|
288
|
+
(`node`, `npx`) and — unlike Wake — an unstructured shell tool whose only
|
|
289
|
+
text is a preview. All err toward indexing; the test table pins the current
|
|
290
|
+
shape, so widening it is a deliberate change plus a bump.
|
|
291
|
+
2. **Rule changes need the bump.** The rule's identity is `FTS_FORMAT`, not a
|
|
292
|
+
hash of `filter.ts`. A future edit to the rule without a bump leaves every
|
|
293
|
+
store's set as-is; the cycle that edits it must bump.
|
|
294
|
+
3. **The vectors lane, if it ships, inherits the question**: a lookup's
|
|
295
|
+
embedding would be rankable while its text is not. The natural input is
|
|
296
|
+
`records_echo` (delete the embeddings with the record, or exclude at query
|
|
297
|
+
time); not decided here, because the store holds no vectors today.
|
|
298
|
+
4. **`status` pays one extra presence check + one `schema_meta` row read** on a
|
|
299
|
+
store that has the table (both trivial, both read-only); the read path
|
|
300
|
+
(`search`, `recent`, …) is untouched, and the envelope carries no filtered
|
|
301
|
+
count by design.
|
|
302
|
+
5. **The echo set is one row per excluded record.** At 188 rows for 227k records
|
|
303
|
+
it is nothing; a store where lookups dominate would grow it linearly with
|
|
304
|
+
their count (one row per pair, two small text columns), and the rebuild's
|
|
305
|
+
subtraction step is bounded by the set size, not the store.
|