@4fu/arbor-notes-sqlite 2026.1106.0 → 2026.1107.0

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/README.md CHANGED
@@ -27,9 +27,11 @@ everything that takes judgment, such as git, merges and conflicts, to the agent.
27
27
  guarantees what an agent cannot guarantee for itself - the record, isolation,
28
28
  exactly-once effects, the checks, dispatch. Everything that takes judgment,
29
29
  such as git, merges and conflicts, is the agent's.
30
- - **A directory per window.** Each window gets its own directory, and a
31
- continuation keeps working in its predecessor's directory in place. It is not
32
- bound to a repository; the agent clones and pushes what it needs.
30
+ - **A shared workspace with per-window checkouts.** The default workspace is
31
+ ovnfs: one shared folder every window reads, and a window opens its own
32
+ copy-on-write checkout of the project it needs, works there and merges back
33
+ with `apply`. It falls back to a plain directory per window when ovnfs is not
34
+ installed. It is not a sandbox: ovnfs isolates files, not processes.
33
35
  - **A self-changing environment.** The model adds and changes skills, MCP
34
36
  servers and resident services itself; a `reload` validates the change and
35
37
  broadcasts it to every live window as a delta of names and descriptions.
@@ -54,7 +56,9 @@ npm install @4fu/arbor-core @4fu/arbor-journal-sqlite @4fu/arbor-notes-sqlite \
54
56
  ```
55
57
 
56
58
  `@4fu/arbor-plugin-http` is optional; it is the example plugin below.
57
- `@4fu/arbor-segmenter-jieba` is an optional peer of the journal.
59
+ `@4fu/arbor-segmenter-jieba` is an optional peer of the lexical package, and
60
+ `@4fu/arbor-search-zvec` an optional peer of the journal: absent either one, the
61
+ journal searches lexically and nothing else changes.
58
62
 
59
63
  ## A minimal host
60
64
 
@@ -118,7 +122,10 @@ window gets, and how a continuation keeps working in it, are in
118
122
  | [`@4fu/arbor-journal-sqlite`](packages/journal-sqlite) | the default `Journal` and `BlobStore` on SQLite |
119
123
  | [`@4fu/arbor-notes-sqlite`](packages/notes-sqlite) | the default `NoteStore` on SQLite, beside the journal |
120
124
  | [`@4fu/arbor-segmenter-jieba`](packages/segmenter-jieba) | the default `Segmenter`: jieba's dictionary segmentation for the word index, optional |
121
- | [`@4fu/arbor-workspace-dir`](packages/workspace-dir) | the default `Workspace`: one directory per window, with a continuation working in its predecessor's directory in place |
125
+ | [`@4fu/arbor-lexical`](packages/lexical) | the lexical search the journal and notes share: word segmentation, the FTS5 helpers and the merge order |
126
+ | [`@4fu/arbor-search-zvec`](packages/search-zvec) | the optional semantic channel: event text embedded and searched by meaning with zvec |
127
+ | [`@4fu/arbor-workspace-dir`](packages/workspace-dir) | the fallback `Workspace`: one directory per window, with a continuation working in its predecessor's directory in place |
128
+ | [`@4fu/arbor-workspace-ovnfs`](packages/workspace-ovnfs) | the default `Workspace` when ovnfs is installed: one shared folder, per-window copy-on-write checkouts the model opens and applies back |
122
129
  | [`@4fu/arbor-environment-dir`](packages/environment-dir) | the default `Environment`: one `/env` directory, a revision per item, validation and deltas |
123
130
  | [`@4fu/arbor-supervisor-process`](packages/supervisor-process) | the default `Supervisor`: one host process per service, restart with backoff, logs |
124
131
  | [`@4fu/arbor-judge-jev`](packages/judge-jev) | the default `Judge`: Jev, TypeSafe AI's System One classifier, answering the dispatch decisions |
@@ -151,7 +158,7 @@ deployment can see what it would be taking on before it adopts one.
151
158
  | [docs/coordination.md](docs/coordination.md) | the board, expectations, deliveries and broadcasts, staying current, effects |
152
159
  | [docs/environment.md](docs/environment.md) | skills, MCP servers and services: using, changing, reload, built-in skills, deltas |
153
160
  | [docs/memory.md](docs/memory.md) | notes, the active set, anchors, links |
154
- | [docs/workspaces.md](docs/workspaces.md) | per-window directories: isolation, continuity, what the agent does |
161
+ | [docs/workspaces.md](docs/workspaces.md) | the shared folder and per-window checkouts: isolation, continuity, what the agent does |
155
162
  | [docs/tools.md](docs/tools.md) | direct tools and code mode modules |
156
163
  | [docs/prompting.md](docs/prompting.md) | how every model-facing text is written: layers, notes, deliveries, errors, judge questions, terms |
157
164
  | [docs/release.md](docs/release.md) | calendar versioning, the manual release steps, how a package is published |
@@ -168,6 +175,7 @@ tarball would contain ([docs/release.md](docs/release.md)).
168
175
  | `packages/errors` | the error shape every refusal uses: the rule and the next action |
169
176
  | `packages/journal-sqlite` | the default `Journal` and `BlobStore` on SQLite |
170
177
  | `packages/segmenter-jieba` | the default `Segmenter`: jieba's dictionary segmentation for the word index, optional |
178
+ | `packages/lexical` | the lexical search the journal and notes share: word segmentation, the FTS5 helpers and the merge order |
171
179
  | `packages/notes-sqlite` | the default `NoteStore` on SQLite, beside the journal |
172
180
  | `packages/judge-jev` | the default `Judge`: Jev, TypeSafe AI's System One classifier, answering the dispatch decisions |
173
181
  | `packages/workspace-dir` | the default `Workspace`: one directory per window, with a continuation working in its predecessor's directory in place, and the lookup of another window's directory it contributes to the catalog |
package/index.d.ts CHANGED
@@ -4,10 +4,12 @@
4
4
  export { SqliteNoteStore, SUMMARY_LIMIT, DEFAULT_BODY_LIMIT, MAX_BODY_LIMIT } from "./notes.ts";
5
5
  export { DEFAULT_MIN_INTERVAL_MS, DEFAULT_ACTIVE_BUDGET, DEFAULT_JUDGE_SHARE, DEFAULT_HALF_LIFE_MS, deriveSummary, } from "./notes.ts";
6
6
  export type { SqliteNoteStoreOptions } from "./notes.ts";
7
- export { SCHEMA_VERSION, FTS_TABLE, createSchema } from "./schema.ts";
7
+ export { SCHEMA_VERSION, FTS_TABLE, WORD_TABLE, createSchema } from "./schema.ts";
8
8
  export { noteIdOf, noteRefOf, parseNoteId, parseNoteRef, noteRefForms, storedWindowRef, storedEventRef, } from "./refs.ts";
9
+ export { fallbackSegmenter, resolveSegmenter, indexText } from "@4fu/arbor-lexical";
9
10
  export { USE_WEIGHTS, MAX_CREDIT_HOPS, decay, estimateTokens, linkedCredit, loadCredit, loadEdges, pickByBudget, applyJudgeShare, scoreNotes, } from "./score.ts";
10
11
  export type { ScoredNote } from "./score.ts";
11
12
  export { MIN_TRIGRAM_CHARS, substringClause, holdingIds, escapeLike, likePattern, quoteFts, } from "./search.ts";
12
13
  export type { SubstringClause } from "./search.ts";
13
14
  export { bodyDiff } from "./diff.ts";
15
+ export { resolveSemanticIndex } from "./semantic.ts";
package/index.js CHANGED
@@ -3,8 +3,10 @@
3
3
  */
4
4
  export { SqliteNoteStore, SUMMARY_LIMIT, DEFAULT_BODY_LIMIT, MAX_BODY_LIMIT } from "./notes.js";
5
5
  export { DEFAULT_MIN_INTERVAL_MS, DEFAULT_ACTIVE_BUDGET, DEFAULT_JUDGE_SHARE, DEFAULT_HALF_LIFE_MS, deriveSummary, } from "./notes.js";
6
- export { SCHEMA_VERSION, FTS_TABLE, createSchema } from "./schema.js";
6
+ export { SCHEMA_VERSION, FTS_TABLE, WORD_TABLE, createSchema } from "./schema.js";
7
7
  export { noteIdOf, noteRefOf, parseNoteId, parseNoteRef, noteRefForms, storedWindowRef, storedEventRef, } from "./refs.js";
8
+ export { fallbackSegmenter, resolveSegmenter, indexText } from "@4fu/arbor-lexical";
8
9
  export { USE_WEIGHTS, MAX_CREDIT_HOPS, decay, estimateTokens, linkedCredit, loadCredit, loadEdges, pickByBudget, applyJudgeShare, scoreNotes, } from "./score.js";
9
10
  export { MIN_TRIGRAM_CHARS, substringClause, holdingIds, escapeLike, likePattern, quoteFts, } from "./search.js";
10
11
  export { bodyDiff } from "./diff.js";
12
+ export { resolveSemanticIndex } from "./semantic.js";
package/notes.d.ts CHANGED
@@ -15,7 +15,11 @@
15
15
  * count and token budget, and frozen with the epoch's version, so every
16
16
  * window opened in one epoch has the same prefix and later windows see the
17
17
  * changes as a delta;
18
- * - every write recorded in the journal as a `note_revision` event.
18
+ * - every write recorded in the journal as a `note_revision` event;
19
+ * - lexical search: a word index with BM25, a trigram index for exact
20
+ * substrings, and a `LIKE` scan for one- and two-character queries, merged
21
+ * with exact substring hits first - and a semantic channel, note text embedded
22
+ * and searched by meaning, when `@4fu/arbor-search-zvec` is installed.
19
23
  *
20
24
  * It is synchronous inside: one `DatabaseSync`, one transaction per write, so
21
25
  * the revision a write checks and the revision it writes are one atomic step.
@@ -23,7 +27,7 @@
23
27
  * database and the journal. The interface is asynchronous because a `NoteStore`
24
28
  * may live behind a process boundary.
25
29
  */
26
- import type { ActiveCandidate, ActiveSetBudget, Journal, Note, NoteBody, NoteContext, NoteDelta, NoteQuery, NoteRef, NoteRevision, NoteSummary, NoteUseInput, NoteWriteInput, NotesSnapshot, NoteStore, Page, SnapshotOptions } from "@4fu/arbor-interfaces";
30
+ import type { ActiveCandidate, ActiveSetBudget, Journal, Note, NoteBody, NoteContext, NoteDelta, NoteQuery, NoteRef, NoteRevision, NoteSummary, NoteUseInput, NoteWriteInput, NotesSnapshot, NoteStore, Page, Segmenter, SemanticIndex, SnapshotOptions } from "@4fu/arbor-interfaces";
27
31
  /** A summary is at most this many characters (docs/memory.md). */
28
32
  export declare const SUMMARY_LIMIT = 200;
29
33
  /** How many characters of a body one read returns. */
@@ -46,6 +50,22 @@ export interface SqliteNoteStoreOptions {
46
50
  now?: () => number;
47
51
  /** Usage loses half of its weight this often. 24 hours. */
48
52
  usageHalfLifeMs?: number;
53
+ /** Word segmentation for the word index. Defaults to jieba, or the approximation. */
54
+ segmenter?: Segmenter;
55
+ /**
56
+ * The semantic channel: note text embedded and searched by meaning
57
+ * (docs/memory.md#search). Omitted, the zvec channel is used when
58
+ * `@4fu/arbor-search-zvec` is installed and its store opens, and there is no
59
+ * channel otherwise. `null` forces lexical search even when the package is
60
+ * installed.
61
+ */
62
+ semantic?: SemanticIndex | null;
63
+ /**
64
+ * One fixed string from the deployment's text table, so this implementation
65
+ * writes none of its own (docs/prompting.md#language-and-the-text-table).
66
+ * Defaults to the framework's own Chinese table.
67
+ */
68
+ text?: (key: string, args?: Record<string, string | number>) => string;
49
69
  }
50
70
  /** The opening of a body, when a write carries no summary of its own. */
51
71
  export declare function deriveSummary(body: string): string;
@@ -54,9 +74,17 @@ export declare class SqliteNoteStore implements NoteStore {
54
74
  private readonly db;
55
75
  private readonly journal;
56
76
  private readonly clock;
77
+ private readonly segmenter;
57
78
  private readonly halfLifeMs;
79
+ private readonly semantic;
80
+ /** The temporary directory an in-memory store's vector collection lives in. */
81
+ private readonly ownedVectorDirectory;
82
+ /** The table every refusal this store raises is written from. */
83
+ private readonly text;
58
84
  /** One write at a time: a write spans the notes database and the journal. */
59
85
  private writeQueue;
86
+ private semanticReady;
87
+ private semanticCaughtUp;
60
88
  private closed;
61
89
  constructor(options: SqliteNoteStoreOptions);
62
90
  write(input: NoteWriteInput): Promise<Note>;
@@ -92,7 +120,44 @@ export declare class SqliteNoteStore implements NoteStore {
92
120
  }): Promise<Page<ActiveCandidate>>;
93
121
  recordUse(input: NoteUseInput): Promise<void>;
94
122
  close(): Promise<void>;
123
+ /**
124
+ * Index the notes the semantic channel does not hold yet, and say how many.
125
+ *
126
+ * A restart over an existing database, a channel that was not ready when the
127
+ * notes were written, and a write whose indexing failed all leave notes to
128
+ * catch up on. A channel that is not ready yet holds what it was given and
129
+ * indexes that itself, so this answers 0 until it is.
130
+ */
131
+ catchUpSemantic(): Promise<number>;
95
132
  private ensureOpen;
133
+ /**
134
+ * Hand one note's text to the semantic channel, when there is one.
135
+ *
136
+ * The note is embedded as one text: its title, its summary and its body, in
137
+ * the order the lexical index weights them. The vector channel holds one
138
+ * vector per note and cannot weight a note's parts against each other the way
139
+ * BM25 weights the three columns, so the title - the note's name - leads the
140
+ * text and the body follows it, and a query that means either reaches the
141
+ * note (docs/memory.md#search).
142
+ */
143
+ private addToSemantic;
144
+ /** Take one note's text back out of the semantic channel, as a delete does. */
145
+ private removeFromSemantic;
146
+ /**
147
+ * The semantic channel's candidates, as ranks in the order it gave them.
148
+ *
149
+ * An absent channel, one that is not ready yet, and one that refuses all
150
+ * answer with nothing, and the search is exactly the lexical one. The note
151
+ * read's own filters decide which candidates count, so a channel that filters
152
+ * less than the store does still answers the read it was asked for.
153
+ */
154
+ private semanticRanks;
155
+ /**
156
+ * The notes among `ids` the read's own filters allow, in the order given. A
157
+ * deleted note is not among them, so a vector that outlived its note never
158
+ * reaches a page.
159
+ */
160
+ private allowedNoteIds;
96
161
  private noteRow;
97
162
  private revisionRow;
98
163
  private latestRevisionRow;
@@ -100,6 +165,8 @@ export declare class SqliteNoteStore implements NoteStore {
100
165
  /** The current summary and revision of several notes, in one read. */
101
166
  private noteStatesFor;
102
167
  private summaryPageBySql;
168
+ /** One page of an ordered id list, in the order the list gives. */
169
+ private summaryPage;
103
170
  private summaryPageByIds;
104
171
  /**
105
172
  * The notes about a target, or linked to a ref: the notes the events on that
@@ -121,6 +188,8 @@ export declare class SqliteNoteStore implements NoteStore {
121
188
  * the changes since reach it as a delta.
122
189
  */
123
190
  private epochAt;
191
+ /** What the active notes of one epoch cost, as the epoch froze them. */
192
+ private epochTokens;
124
193
  /** The active notes one epoch froze, by summary, sorted by id. */
125
194
  private epochSummaries;
126
195
  }