@andespindola/brainlink 0.1.0-beta.13 → 0.1.0-beta.131
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/AGENTS.md +8 -5
- package/CHANGELOG.md +26 -2
- package/CONTRIBUTING.md +2 -2
- package/COPYRIGHT.md +5 -0
- package/README.md +143 -20
- package/SECURITY.md +1 -1
- package/dist/application/analyze-vault.js +1 -15
- package/dist/application/build-context.js +64 -3
- package/dist/application/dedupe-notes.js +226 -0
- package/dist/application/frontend/client-css.js +93 -45
- package/dist/application/frontend/client-html.js +34 -25
- package/dist/application/frontend/client-js.js +2790 -162
- package/dist/application/frontend/client-worker-js.js +66 -0
- package/dist/application/get-graph-layout.js +39 -6
- package/dist/application/get-graph-node.js +3 -3
- package/dist/application/get-graph-summary.js +3 -3
- package/dist/application/get-graph-view.js +243 -0
- package/dist/application/get-graph.js +3 -3
- package/dist/application/import-legacy-sqlite.js +296 -0
- package/dist/application/index-vault.js +253 -25
- package/dist/application/list-agents.js +3 -3
- package/dist/application/list-links.js +5 -5
- package/dist/application/offline-pack-backup.js +44 -0
- package/dist/application/search-graph-node-ids.js +3 -3
- package/dist/application/search-knowledge.js +6 -6
- package/dist/application/server/routes.js +105 -1
- package/dist/application/start-server.js +75 -4
- package/dist/application/watch-vault.js +23 -2
- package/dist/benchmarks/large-vault.js +1 -1
- package/dist/cli/commands/agent-commands.js +7 -0
- package/dist/cli/commands/write-commands.js +842 -8
- package/dist/domain/context.js +54 -11
- package/dist/domain/graph-layout.js +181 -3
- package/dist/domain/markdown.js +29 -9
- package/dist/domain/middle-out.js +18 -0
- package/dist/infrastructure/config.js +38 -0
- package/dist/infrastructure/file-index.js +358 -0
- package/dist/infrastructure/file-system-vault.js +15 -0
- package/dist/infrastructure/index-state.js +58 -0
- package/dist/infrastructure/private-pack-codec.js +71 -10
- package/dist/infrastructure/search-packs.js +313 -17
- package/dist/infrastructure/volatile-memory.js +100 -0
- package/dist/mcp/server.js +21 -1
- package/dist/mcp/tools.js +96 -0
- package/docs/AGENT_USAGE.md +101 -18
- package/docs/ARCHITECTURE.md +22 -27
- package/docs/QUICKSTART.md +7 -0
- package/package.json +6 -4
- package/dist/infrastructure/sqlite/document-writer.js +0 -51
- package/dist/infrastructure/sqlite/graph-reader.js +0 -267
- package/dist/infrastructure/sqlite/recovery.js +0 -163
- package/dist/infrastructure/sqlite/schema.js +0 -114
- package/dist/infrastructure/sqlite/search-reader.js +0 -188
- package/dist/infrastructure/sqlite/types.js +0 -1
- package/dist/infrastructure/sqlite-index.js +0 -38
package/dist/mcp/tools.js
CHANGED
|
@@ -4,12 +4,14 @@ import { z } from 'zod';
|
|
|
4
4
|
import { getBrokenLinksReport, getOrphansReport, getStats, validateVault } from '../application/analyze-vault.js';
|
|
5
5
|
import { addNoteWithMetadata } from '../application/add-note.js';
|
|
6
6
|
import { buildContextPackage } from '../application/build-context.js';
|
|
7
|
+
import { resolveDuplicateNotes, scanDuplicateNotes } from '../application/dedupe-notes.js';
|
|
7
8
|
import { getGraph } from '../application/get-graph.js';
|
|
8
9
|
import { indexVault } from '../application/index-vault.js';
|
|
9
10
|
import { searchKnowledge } from '../application/search-knowledge.js';
|
|
10
11
|
import { resolveAgentRuntimeDefaults, sanitizeSearchMode } from '../infrastructure/config.js';
|
|
11
12
|
import { loadBrainlinkConfig } from '../infrastructure/config.js';
|
|
12
13
|
import { assertVaultAllowed } from '../infrastructure/file-system-vault.js';
|
|
14
|
+
import { addVolatileMemory, clearVolatileMemory } from '../infrastructure/volatile-memory.js';
|
|
13
15
|
import { getBootstrapPolicy, getBootstrapSessionStatus, getContextSessionStatus, setBootstrapPolicy, touchBootstrapSession, touchContextSession } from '../infrastructure/session-state.js';
|
|
14
16
|
const positiveInteger = (fallback) => z
|
|
15
17
|
.number()
|
|
@@ -236,6 +238,20 @@ export const addNoteInputSchema = {
|
|
|
236
238
|
allowSensitive: z.boolean().optional().default(false).describe('Allow content that looks like a secret.'),
|
|
237
239
|
autoIndex: z.boolean().optional().default(true).describe('Reindex vault after writing note.')
|
|
238
240
|
};
|
|
241
|
+
export const volatileAddInputSchema = {
|
|
242
|
+
...vaultInput,
|
|
243
|
+
...agentInput,
|
|
244
|
+
content: z
|
|
245
|
+
.string()
|
|
246
|
+
.min(1)
|
|
247
|
+
.describe('Temporary agent-decided memory. Use for current task state, hypotheses, transient user preferences and unconfirmed findings.'),
|
|
248
|
+
ttlMinutes: optionalPositiveInteger().describe('Minutes before this volatile memory expires. Defaults to 240.'),
|
|
249
|
+
tags: z.array(z.string()).optional().default([]).describe('Optional tags for volatile retrieval.')
|
|
250
|
+
};
|
|
251
|
+
export const volatileClearInputSchema = {
|
|
252
|
+
...vaultInput,
|
|
253
|
+
...agentInput
|
|
254
|
+
};
|
|
239
255
|
export const addFileInputSchema = {
|
|
240
256
|
...vaultInput,
|
|
241
257
|
...agentInput,
|
|
@@ -311,6 +327,20 @@ export const recommendationsInputSchema = {
|
|
|
311
327
|
limit: optionalPositiveInteger().describe('Optional context limit override for generated recommendations.'),
|
|
312
328
|
tokens: optionalPositiveInteger().describe('Optional context token budget override for generated recommendations.')
|
|
313
329
|
};
|
|
330
|
+
export const dedupeInputSchema = {
|
|
331
|
+
...vaultInput,
|
|
332
|
+
...agentInput,
|
|
333
|
+
limit: optionalPositiveInteger().describe('Maximum duplicate candidate pairs to return.'),
|
|
334
|
+
minScore: z.number().min(0).max(1).optional().describe('Minimum semantic similarity score between 0 and 1.'),
|
|
335
|
+
semantic: z.boolean().optional().default(true).describe('Enable semantic duplicate detection in addition to exact content hash matches.')
|
|
336
|
+
};
|
|
337
|
+
export const dedupeResolveInputSchema = {
|
|
338
|
+
...vaultInput,
|
|
339
|
+
leftPath: z.string().min(1).describe('Left note path from dedupe results.'),
|
|
340
|
+
rightPath: z.string().min(1).describe('Right note path from dedupe results.'),
|
|
341
|
+
action: z.enum(['merge', 'link', 'ignore']).describe('Resolution action.'),
|
|
342
|
+
autoIndex: z.boolean().optional().default(true).describe('Reindex after duplicate resolution.')
|
|
343
|
+
};
|
|
314
344
|
export const contextTool = async (input) => {
|
|
315
345
|
const context = await resolveExecutionContext(input);
|
|
316
346
|
const readiness = await ensureBootstrapReady(context, input, 'brainlink_context');
|
|
@@ -364,6 +394,14 @@ export const addNoteTool = async (input) => {
|
|
|
364
394
|
allowSensitive: input.allowSensitive
|
|
365
395
|
});
|
|
366
396
|
const index = shouldIndex ? await indexVault(context.vault) : undefined;
|
|
397
|
+
const focusPath = added.path.includes('agents/') ? added.path.slice(added.path.indexOf('agents/')).replaceAll('\\', '/') : undefined;
|
|
398
|
+
const possibleDuplicates = await scanDuplicateNotes(context.vault, {
|
|
399
|
+
agentId: context.agent,
|
|
400
|
+
focusPath,
|
|
401
|
+
limit: 5,
|
|
402
|
+
minSemanticScore: 0.92,
|
|
403
|
+
includeSemantic: true
|
|
404
|
+
});
|
|
367
405
|
return jsonResult({
|
|
368
406
|
vault: context.vault,
|
|
369
407
|
title: input.title,
|
|
@@ -374,9 +412,29 @@ export const addNoteTool = async (input) => {
|
|
|
374
412
|
linkTarget: added.linkTarget,
|
|
375
413
|
guaranteedEdge: true
|
|
376
414
|
},
|
|
415
|
+
possibleDuplicates,
|
|
377
416
|
...(index ? { index } : {})
|
|
378
417
|
});
|
|
379
418
|
};
|
|
419
|
+
export const volatileAddTool = async (input) => {
|
|
420
|
+
const context = await resolveExecutionContext(input);
|
|
421
|
+
const entry = await addVolatileMemory(context.vault, input.content, context.agent ?? 'shared', input.ttlMinutes ?? 240, input.tags);
|
|
422
|
+
return jsonResult({
|
|
423
|
+
vault: context.vault,
|
|
424
|
+
agent: context.agent,
|
|
425
|
+
volatile: true,
|
|
426
|
+
entry
|
|
427
|
+
});
|
|
428
|
+
};
|
|
429
|
+
export const volatileClearTool = async (input) => {
|
|
430
|
+
const context = await resolveExecutionContext(input);
|
|
431
|
+
const cleared = await clearVolatileMemory(context.vault, context.agent);
|
|
432
|
+
return jsonResult({
|
|
433
|
+
vault: context.vault,
|
|
434
|
+
agent: context.agent,
|
|
435
|
+
cleared
|
|
436
|
+
});
|
|
437
|
+
};
|
|
380
438
|
export const addFileTool = async (input) => {
|
|
381
439
|
const context = await resolveExecutionContext(input);
|
|
382
440
|
const content = await readFile(input.filePath, 'utf8');
|
|
@@ -792,6 +850,17 @@ export const recommendationsTool = async (input) => {
|
|
|
792
850
|
tokens
|
|
793
851
|
}
|
|
794
852
|
},
|
|
853
|
+
{
|
|
854
|
+
tool: 'brainlink_dedupe',
|
|
855
|
+
reason: 'Detect and resolve duplicate durable notes to keep memory quality high.',
|
|
856
|
+
args: {
|
|
857
|
+
vault: context.vault,
|
|
858
|
+
...(context.agent ? { agent: context.agent } : {}),
|
|
859
|
+
limit: 10,
|
|
860
|
+
minScore: 0.92,
|
|
861
|
+
semantic: true
|
|
862
|
+
}
|
|
863
|
+
},
|
|
795
864
|
{
|
|
796
865
|
tool: 'brainlink_add_note',
|
|
797
866
|
reason: 'Persist durable outcomes after task completion (write responses include connectivity metadata).',
|
|
@@ -818,3 +887,30 @@ export const recommendationsTool = async (input) => {
|
|
|
818
887
|
recommendations
|
|
819
888
|
});
|
|
820
889
|
};
|
|
890
|
+
export const dedupeTool = async (input) => {
|
|
891
|
+
const context = await resolveExecutionContext(input);
|
|
892
|
+
const duplicates = await scanDuplicateNotes(context.vault, {
|
|
893
|
+
agentId: context.agent,
|
|
894
|
+
limit: input.limit ?? 25,
|
|
895
|
+
minSemanticScore: input.minScore ?? 0.92,
|
|
896
|
+
includeSemantic: input.semantic !== false
|
|
897
|
+
});
|
|
898
|
+
return jsonResult({
|
|
899
|
+
vault: context.vault,
|
|
900
|
+
agent: context.agent,
|
|
901
|
+
duplicates
|
|
902
|
+
});
|
|
903
|
+
};
|
|
904
|
+
export const dedupeResolveTool = async (input) => {
|
|
905
|
+
const context = await resolveExecutionContext(input);
|
|
906
|
+
const result = await resolveDuplicateNotes(context.vault, {
|
|
907
|
+
leftPath: input.leftPath,
|
|
908
|
+
rightPath: input.rightPath,
|
|
909
|
+
action: input.action,
|
|
910
|
+
autoIndex: isTruthy(input.autoIndex)
|
|
911
|
+
});
|
|
912
|
+
return jsonResult({
|
|
913
|
+
vault: context.vault,
|
|
914
|
+
...result
|
|
915
|
+
});
|
|
916
|
+
};
|
package/docs/AGENT_USAGE.md
CHANGED
|
@@ -18,7 +18,7 @@ The correct dependency direction is:
|
|
|
18
18
|
agent -> Brainlink CLI -> Markdown vault + derived index
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
Agents should never depend on
|
|
21
|
+
Agents should never depend on internal index persistence files as a public API.
|
|
22
22
|
|
|
23
23
|
The installed CLI exposes two equivalent binaries:
|
|
24
24
|
|
|
@@ -52,6 +52,8 @@ Use `blink config where` and `blink config doctor` to inspect active paths and e
|
|
|
52
52
|
|
|
53
53
|
You can also set `defaultAgent` in `brainlink.config.json` / `.brainlink.json` (for example `"defaultAgent": "coding-agent"`). When set, CLI commands and MCP calls reuse it when `--agent`/`agent` is not passed.
|
|
54
54
|
You can set `agentProfiles` to define per-agent defaults for `defaultSearchMode`, `defaultSearchLimit` and `defaultContextTokens`.
|
|
55
|
+
You can tune search-pack compression with `searchPack.rowChunkSize`, `searchPack.compressionLevel` and `searchPack.useDictionary`.
|
|
56
|
+
Guardrails for benchmark acceptance are configured with `searchPack.guardrailMinSavingsPercent` and `searchPack.guardrailMaxLatencyRegressionPercent`.
|
|
55
57
|
|
|
56
58
|
`autoIndexOnWrite` (default: `true`) controls whether `add` and MCP write tools index right after writing.
|
|
57
59
|
|
|
@@ -159,7 +161,7 @@ Brainlink only builds graph edges from Markdown `[[wiki links]]`.
|
|
|
159
161
|
|
|
160
162
|
The `context` command is read-only. It retrieves indexed notes and returns a compact package for the model, but it does not write memory, create backlinks, infer relationships or modify the graph. If an agent reads context and then learns something durable, the agent must write a note with explicit links before that knowledge becomes connected memory.
|
|
161
163
|
|
|
162
|
-
Graph edges are weighted during indexing. Repeated links increase weight. Links inside headings or task-list lines receive a small boost. Priority markers on the same line as a link raise its priority:
|
|
164
|
+
Graph edges are weighted during indexing. Repeated links increase weight. Links inside headings or task-list lines receive a small boost. Priority markers on the same line as a link raise its priority. The graph relationship model promotes only representative links per note: high-priority and high-weight links win, structural hub links such as `Memory Hub`, `Knowledge Root`, `MOC` and map notes are not promoted when stronger direct links exist, and each note emits a small bounded set of graph edges. Older indexes without the current graph link model version are automatically rebuilt on the next index run.
|
|
163
165
|
|
|
164
166
|
```md
|
|
165
167
|
- [ ] Review [[Architecture]] priority: high
|
|
@@ -180,16 +182,16 @@ Required write behavior:
|
|
|
180
182
|
Good linked note:
|
|
181
183
|
|
|
182
184
|
```bash
|
|
183
|
-
blink add "
|
|
185
|
+
blink add "Index Rebuild" \
|
|
184
186
|
--agent coding-agent \
|
|
185
|
-
--content "
|
|
187
|
+
--content "Derived index artifacts are rebuildable and disposable. Related: [[Architecture]], [[Agent Namespaces]]. #index #architecture #decision"
|
|
186
188
|
blink validate --agent coding-agent
|
|
187
189
|
```
|
|
188
190
|
|
|
189
191
|
Poor disconnected note:
|
|
190
192
|
|
|
191
193
|
```bash
|
|
192
|
-
blink add "
|
|
194
|
+
blink add "Index Rebuild" \
|
|
193
195
|
--agent coding-agent \
|
|
194
196
|
--content "We rebuild old indexes now."
|
|
195
197
|
```
|
|
@@ -377,6 +379,18 @@ blink migrate-vault --from ~/.brainlink/vault --to ./team-vault --report ./migra
|
|
|
377
379
|
|
|
378
380
|
Use `--dry-run` to preview `copied`, `conflicted`, `unchanged` before writing files.
|
|
379
381
|
|
|
382
|
+
### Import Legacy SQLite DB
|
|
383
|
+
|
|
384
|
+
```bash
|
|
385
|
+
blink db-import --vault ./team-vault
|
|
386
|
+
blink db-import --vault ./team-vault --db ./legacy/brainlink.db
|
|
387
|
+
blink db-import --vault ./team-vault --db ./legacy/brainlink.db --table legacy_notes --dry-run
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
`db-import` migrates rows from legacy SQLite memory into Markdown notes in the current vault and indexes the result by default.
|
|
391
|
+
Without `--db`, Brainlink auto-detects common legacy database paths.
|
|
392
|
+
Use `--agent` to force namespace, `--limit` for staged migration, `--dry-run` to preview writes, and `--no-index` to postpone indexing.
|
|
393
|
+
|
|
380
394
|
### Install Agent Integration
|
|
381
395
|
|
|
382
396
|
```bash
|
|
@@ -390,6 +404,7 @@ blink agent status
|
|
|
390
404
|
```
|
|
391
405
|
|
|
392
406
|
`agent install` configures Brainlink MCP in `~/.codex/config.toml` so compatible agents can use Brainlink by default.
|
|
407
|
+
`agent install` and `agent upgrade` automatically apply the `fully-auto` MCP bootstrap policy (`enforceBootstrap=true`, `enforceContextFirst=true`, `autoBootstrapOnRead=true`, `autoBootstrapOnStartup=true`) so all plug-and-play Brainlink features start enabled.
|
|
393
408
|
Use `agent upgrade` on legacy installations to reapply the latest defaults and run self-test diagnostics.
|
|
394
409
|
Use `agent policy --preset fully-auto` to keep startup/read auto-bootstrap enabled, or `agent policy --preset strict` to force explicit bootstrap calls.
|
|
395
410
|
|
|
@@ -417,6 +432,25 @@ This creates a slugged Markdown file with frontmatter and a heading.
|
|
|
417
432
|
|
|
418
433
|
The CLI blocks common secret patterns by default. Do not use `--allow-sensitive` unless the vault is intentionally protected.
|
|
419
434
|
Brainlink also auto-connects notes that have no `[[wiki links]]` by adding a fallback edge to an agent hub note, so new memory does not stay disconnected.
|
|
435
|
+
`add` also returns `possibleDuplicates` (exact hash + semantic candidates) so agents can decide duplicate resolution immediately.
|
|
436
|
+
|
|
437
|
+
### Detect Duplicate Notes
|
|
438
|
+
|
|
439
|
+
```bash
|
|
440
|
+
blink dedupe --vault ./vault --json
|
|
441
|
+
blink dedupe --vault ./vault --agent coding-agent --limit 20 --min-score 0.92 --json
|
|
442
|
+
blink dedupe --vault ./vault --no-semantic --json
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### Resolve Duplicate Notes
|
|
446
|
+
|
|
447
|
+
```bash
|
|
448
|
+
blink dedupe-resolve --vault ./vault --left agents/shared/a.md --right agents/shared/b.md --action merge --json
|
|
449
|
+
blink dedupe-resolve --vault ./vault --left agents/shared/a.md --right agents/shared/b.md --action link --json
|
|
450
|
+
blink dedupe-resolve --vault ./vault --left agents/shared/a.md --right agents/shared/b.md --action ignore --json
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
`dedupe-resolve` keeps connectivity: non-merge actions still create a low-priority related edge (`#related-to`).
|
|
420
454
|
|
|
421
455
|
For agent-private memory:
|
|
422
456
|
|
|
@@ -446,6 +480,37 @@ This scans Markdown files and rebuilds:
|
|
|
446
480
|
- links
|
|
447
481
|
- full-text search records
|
|
448
482
|
|
|
483
|
+
### Benchmark Indexing Realtime
|
|
484
|
+
|
|
485
|
+
```bash
|
|
486
|
+
blink bench --vault ./vault
|
|
487
|
+
blink bench --vault ./vault --watch
|
|
488
|
+
blink bench --vault ./vault --watch --debounce 500
|
|
489
|
+
blink bench --vault ./vault --json
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
`bench` runs indexing with realtime phase events and prints a run summary with:
|
|
493
|
+
|
|
494
|
+
- indexed totals (documents, chunks, links)
|
|
495
|
+
- elapsed time and changed document count
|
|
496
|
+
- pack rebuild status and reason
|
|
497
|
+
- pack compression metrics (`inputBytes`, `outputBytes`, ratio/saved percentage)
|
|
498
|
+
- objective guardrails (`guardrailMinSavingsPercent`, `guardrailMaxLatencyRegressionPercent`)
|
|
499
|
+
|
|
500
|
+
Use `--watch` for continuous benchmark runs while editing notes. Watch mode is supported only for local filesystem vaults.
|
|
501
|
+
If pack manifest metadata is missing but encrypted `.blpk` files are present, Brainlink repairs manifest metadata before deciding rebuild policy to avoid unnecessary full repacks on small updates.
|
|
502
|
+
|
|
503
|
+
### Create Offline Pack Backup
|
|
504
|
+
|
|
505
|
+
```bash
|
|
506
|
+
blink pack-backup --vault ./vault
|
|
507
|
+
blink pack-backup --vault ./vault --output ./vault/.brainlink/backups/custom.blpkbak.gz
|
|
508
|
+
blink pack-backup --vault ./vault --json
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
`pack-backup` creates an offline artifact with second-stage compression on top of encrypted `.blpk` packs.
|
|
512
|
+
This is outside the online retrieval path (`index`, `search`, `context`), which keeps a single compression stage.
|
|
513
|
+
|
|
449
514
|
### Search Knowledge
|
|
450
515
|
|
|
451
516
|
```bash
|
|
@@ -460,11 +525,12 @@ If `--mode`/`--limit` are omitted, Brainlink resolves those values from the acti
|
|
|
460
525
|
|
|
461
526
|
Search modes:
|
|
462
527
|
|
|
463
|
-
- `hybrid`: default; combines
|
|
464
|
-
- `fts`: lexical
|
|
465
|
-
- `semantic`: local deterministic embedding similarity
|
|
528
|
+
- `hybrid`: default; combines lexical matching and local embedding similarity.
|
|
529
|
+
- `fts`: lexical full-text matching only.
|
|
530
|
+
- `semantic`: local deterministic embedding similarity.
|
|
466
531
|
|
|
467
|
-
Hybrid results are cached in-memory for a short TTL and invalidated when `.brainlink/
|
|
532
|
+
Hybrid results are cached in-memory for a short TTL and invalidated when `.brainlink/index.json` changes.
|
|
533
|
+
Context assembly uses middle-out ordering inside each note: the highest-scoring chunk is selected first, then nearby chunks are expanded while token budget allows.
|
|
468
534
|
|
|
469
535
|
### Build Agent Context
|
|
470
536
|
|
|
@@ -523,15 +589,26 @@ shared: 30 documents
|
|
|
523
589
|
```bash
|
|
524
590
|
blink server --host 127.0.0.1 --port 4321
|
|
525
591
|
blink server --vault ./vault --host 127.0.0.1 --port 4321
|
|
592
|
+
blink server --vault ./vault --host 127.0.0.1 --port 4321 --no-open
|
|
526
593
|
```
|
|
527
594
|
|
|
528
595
|
This starts a local frontend for inspecting the knowledge graph.
|
|
596
|
+
By default it tries to open the graph in a native desktop GUI window:
|
|
597
|
+
- macOS: Swift + WebKit
|
|
598
|
+
- Windows: PowerShell WinForms WebBrowser
|
|
599
|
+
- Linux: optional Python GTK + WebKit2 (requires `python3` + `gi` + `WebKit2`)
|
|
600
|
+
|
|
601
|
+
On Linux, native GUI is disabled by default for better startup performance. Enable it with `BRAINLINK_LINUX_NATIVE_GUI=1`.
|
|
602
|
+
If native GUI launch is unavailable, it falls back to dedicated app-window mode and then to the default browser.
|
|
603
|
+
Use `--no-open` to keep the server headless.
|
|
604
|
+
When native GUI is active, the GUI window closes automatically when the `blink server` process stops.
|
|
529
605
|
|
|
530
606
|
Without `--vault`, the graph UI serves `$HOME/.brainlink/vault`.
|
|
531
607
|
|
|
532
|
-
The frontend includes an agent selector. Selecting an agent calls the same read APIs with `agent=<agent-id>` and renders that namespace instead of merging every agent into one graph.
|
|
608
|
+
The frontend includes an agent selector that shows only the agent id. Selecting an agent calls the same read APIs with `agent=<agent-id>` and renders that namespace instead of merging every agent into one graph.
|
|
533
609
|
|
|
534
|
-
Graph navigation controls include zoom in, zoom out, fit visible nodes and reset-to-fit-all nodes. Mouse wheel zoom is anchored to the cursor. Totals for notes, links and tags stay visible as floating metrics under the Brainlink title, and node details open on click in a modal (tags, outgoing links, backlinks and Markdown content).
|
|
610
|
+
Graph navigation controls include zoom in, zoom out, fit visible nodes and reset-to-fit-all nodes. Mouse wheel zoom (including `cmd+scroll` and `ctrl+scroll`) is anchored to the cursor. Keyboard shortcuts are `+` (zoom in), `-` (zoom out) and `0` (reset fit). Double-click on canvas zooms in at cursor position. Totals for notes, links and tags stay visible as floating metrics under the Brainlink title, and node details open on click in a modal (tags, outgoing links, backlinks and Markdown content). Vaults above 1000 notes also expose stable hierarchy groups that fill each visible graph level toward 1000 nodes while keeping every group capped at 1000 child nodes; zoom-out renders the macro level as a sparse strongest-link graph of group nodes, zoom-in fits the focused node's full child-graph circumference with extra margin before expanding, and groups with child groups open another graph level instead of jumping directly to leaf notes.
|
|
611
|
+
During graph filtering, Brainlink keeps hub context nodes visible (`Memory Hub`/`MOC`/high-degree fallback) so filtered views still show relationship anchors.
|
|
535
612
|
|
|
536
613
|
The command reindexes by default, then serves:
|
|
537
614
|
|
|
@@ -589,8 +666,12 @@ Available MCP tools:
|
|
|
589
666
|
- `brainlink_recommendations`
|
|
590
667
|
- `brainlink_context`
|
|
591
668
|
- `brainlink_search`
|
|
669
|
+
- `brainlink_dedupe`
|
|
670
|
+
- `brainlink_resolve_duplicate`
|
|
592
671
|
- `brainlink_add_note`
|
|
593
672
|
- `brainlink_add_file`
|
|
673
|
+
- `brainlink_volatile_add`
|
|
674
|
+
- `brainlink_volatile_clear`
|
|
594
675
|
- `brainlink_index`
|
|
595
676
|
- `brainlink_stats`
|
|
596
677
|
- `brainlink_validate`
|
|
@@ -610,6 +691,7 @@ MCP clients can pass `vault` and `agent` arguments per tool call. Set `BRAINLINK
|
|
|
610
691
|
|
|
611
692
|
`brainlink_graph` returns weighted edges. Agents should prefer higher `weight` and stronger `priority` when deciding which related notes matter most.
|
|
612
693
|
`brainlink_add_note` and `brainlink_add_file` return `writeConnectivity` metadata and guarantee at least one edge for new notes.
|
|
694
|
+
Agents should use `brainlink_volatile_add` for temporary task state, hypotheses, local execution details and unconfirmed findings. Volatile memory is included in `brainlink_context` with `Volatile: true`, expires by TTL and does not create durable Markdown notes or graph edges.
|
|
613
695
|
|
|
614
696
|
```bash
|
|
615
697
|
export BRAINLINK_ALLOWED_VAULTS="/absolute/path/to/project-vault"
|
|
@@ -620,6 +702,7 @@ export BRAINLINK_ALLOWED_VAULTS="/absolute/path/to/project-vault"
|
|
|
620
702
|
```txt
|
|
621
703
|
GET /api/graph
|
|
622
704
|
GET /api/graph-layout
|
|
705
|
+
GET /api/graph-view?x=<x>&y=<y>&w=<width>&h=<height>&scale=<scale>
|
|
623
706
|
GET /api/graph-node?id=<node-id>
|
|
624
707
|
GET /api/graph-filter?q=<query>&limit=<n>
|
|
625
708
|
GET /api/search?q=<query>&limit=10&mode=hybrid
|
|
@@ -634,8 +717,8 @@ GET /api/validate
|
|
|
634
717
|
|
|
635
718
|
The HTTP API is read-only. Use the CLI for writes and indexing.
|
|
636
719
|
|
|
637
|
-
|
|
638
|
-
|
|
720
|
+
Indexing writes private encrypted search packs at `.brainlink/search-packs/*.blpk` for resilient retrieval and portability.
|
|
721
|
+
Pack search now uses compressed-space prefiltering (token bloom index per pack) before decrypting/reading pack payloads.
|
|
639
722
|
Pack decryption keys are resolved from `$BRAINLINK_HOME/keys` (or `BRAINLINK_SEARCH_PACK_KEY` when explicitly set).
|
|
640
723
|
|
|
641
724
|
## Agent Integration Contract
|
|
@@ -669,9 +752,9 @@ Non-goals:
|
|
|
669
752
|
## Operational Rules
|
|
670
753
|
|
|
671
754
|
- Re-run `index` after modifying notes.
|
|
672
|
-
- Treat `.brainlink/
|
|
673
|
-
- Commit Markdown notes, not local
|
|
674
|
-
- Do not manually edit
|
|
755
|
+
- Treat `.brainlink/index.json` and `.brainlink/search-packs/` as disposable.
|
|
756
|
+
- Commit Markdown notes, not local index files.
|
|
757
|
+
- Do not manually edit generated index artifacts.
|
|
675
758
|
- Keep generated context short enough for the target model.
|
|
676
759
|
- Prefer specific queries over broad queries.
|
|
677
760
|
- Write explicit `[[wiki links]]` when durable memory should be connected.
|
|
@@ -701,9 +784,9 @@ Weak retrieval usually means:
|
|
|
701
784
|
|
|
702
785
|
## Current Limits
|
|
703
786
|
|
|
704
|
-
- Search supports FTS, local semantic embeddings
|
|
787
|
+
- Search supports FTS, local semantic embeddings and hybrid ranking.
|
|
705
788
|
- Local embeddings are deterministic and provider-free; remote embedding providers are not implemented yet.
|
|
706
789
|
- MCP integration is available through the `brainlink-mcp` stdio server.
|
|
707
790
|
- HTTP API is local and unauthenticated.
|
|
708
|
-
- Bucket vaults support S3-compatible `s3://bucket/prefix` URIs and use
|
|
791
|
+
- Bucket vaults support S3-compatible `s3://bucket/prefix` URIs and use local cache/index artifacts.
|
|
709
792
|
- Watch mode depends on platform filesystem watcher behavior and is only supported for local filesystem vaults.
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -8,7 +8,7 @@ CLI -> application use cases -> domain functions -> infrastructure adapters
|
|
|
8
8
|
|
|
9
9
|
The core rule is simple:
|
|
10
10
|
|
|
11
|
-
Domain code must not know about the CLI, filesystem, or
|
|
11
|
+
Domain code must not know about the CLI, filesystem, or index persistence format.
|
|
12
12
|
|
|
13
13
|
## Modules
|
|
14
14
|
|
|
@@ -53,14 +53,11 @@ src/
|
|
|
53
53
|
types.ts
|
|
54
54
|
|
|
55
55
|
infrastructure/
|
|
56
|
-
|
|
57
|
-
document-writer.ts
|
|
58
|
-
graph-reader.ts
|
|
59
|
-
schema.ts
|
|
60
|
-
search-reader.ts
|
|
56
|
+
file-index.ts
|
|
61
57
|
file-system-vault.ts
|
|
58
|
+
private-pack-codec.ts
|
|
59
|
+
search-packs.ts
|
|
62
60
|
session-state.ts
|
|
63
|
-
sqlite-index.ts
|
|
64
61
|
|
|
65
62
|
mcp/
|
|
66
63
|
main.ts
|
|
@@ -80,7 +77,6 @@ The domain layer contains pure knowledge rules:
|
|
|
80
77
|
- extract `#tags`
|
|
81
78
|
- split documents into chunks
|
|
82
79
|
- create deterministic local embeddings
|
|
83
|
-
- create deterministic embedding buckets for semantic candidate retrieval
|
|
84
80
|
- calculate cosine similarity
|
|
85
81
|
- estimate token counts
|
|
86
82
|
- select context sections
|
|
@@ -116,12 +112,11 @@ The infrastructure layer handles side effects:
|
|
|
116
112
|
- mirroring S3-compatible bucket Markdown into a local cache
|
|
117
113
|
- writing Markdown notes
|
|
118
114
|
- creating `.brainlink`
|
|
119
|
-
- writing and querying
|
|
120
|
-
- running
|
|
121
|
-
- narrowing semantic candidates through SQLite embedding buckets before cosine scoring
|
|
115
|
+
- writing and querying file-based indexes
|
|
116
|
+
- running lexical, semantic and hybrid retrieval
|
|
122
117
|
|
|
123
|
-
|
|
124
|
-
objects in the bucket remain canonical and
|
|
118
|
+
|
|
119
|
+
Index artifacts are rebuildable and are not canonical storage. For bucket vaults, Markdown objects in the bucket remain canonical and local index files are derived data.
|
|
125
120
|
|
|
126
121
|
## Indexing Flow
|
|
127
122
|
|
|
@@ -132,11 +127,9 @@ read markdown files
|
|
|
132
127
|
-> resolve links
|
|
133
128
|
-> split chunks
|
|
134
129
|
-> create chunk embeddings
|
|
135
|
-
-> reset
|
|
130
|
+
-> reset file index
|
|
136
131
|
-> persist documents, chunks and links
|
|
137
|
-
->
|
|
138
|
-
-> persist embedding vectors
|
|
139
|
-
-> persist embedding buckets
|
|
132
|
+
-> persist chunks, links and embeddings in file index
|
|
140
133
|
```
|
|
141
134
|
|
|
142
135
|
## Retrieval Flow
|
|
@@ -145,8 +138,10 @@ read markdown files
|
|
|
145
138
|
question
|
|
146
139
|
-> selected mode: fts | semantic | hybrid
|
|
147
140
|
-> optional query embedding
|
|
148
|
-
->
|
|
141
|
+
-> optional compressed pack prefilter (token bloom)
|
|
142
|
+
-> lexical scoring and/or semantic cosine scoring
|
|
149
143
|
-> cosine similarity over candidate chunks
|
|
144
|
+
-> middle-out context expansion around strongest chunk
|
|
150
145
|
-> ranked chunks with textScore and semanticScore
|
|
151
146
|
-> token-budget selection
|
|
152
147
|
-> Markdown context package
|
|
@@ -163,7 +158,7 @@ server command
|
|
|
163
158
|
-> browser renders graph canvas
|
|
164
159
|
```
|
|
165
160
|
|
|
166
|
-
The graph UI is intentionally read-only. Markdown remains the write interface and
|
|
161
|
+
The graph UI is intentionally read-only. Markdown remains the write interface and index artifacts remain derived data.
|
|
167
162
|
|
|
168
163
|
## HTTP API Flow
|
|
169
164
|
|
|
@@ -171,7 +166,7 @@ The graph UI is intentionally read-only. Markdown remains the write interface an
|
|
|
171
166
|
HTTP request
|
|
172
167
|
-> route handler
|
|
173
168
|
-> application use case
|
|
174
|
-
-> filesystem and
|
|
169
|
+
-> filesystem and index adapters
|
|
175
170
|
-> JSON response
|
|
176
171
|
```
|
|
177
172
|
|
|
@@ -282,11 +277,10 @@ vault/agents/<agent-id>/**/*.md
|
|
|
282
277
|
|
|
283
278
|
Rebuildable:
|
|
284
279
|
|
|
285
|
-
- `.brainlink/
|
|
280
|
+
- `.brainlink/index.json`
|
|
281
|
+
- `.brainlink/search-packs/*.blpk`
|
|
286
282
|
- `$BRAINLINK_HOME/bucket-cache`
|
|
287
|
-
- FTS records
|
|
288
283
|
- local embedding vectors
|
|
289
|
-
- local embedding bucket index
|
|
290
284
|
- chunks
|
|
291
285
|
- resolved links
|
|
292
286
|
|
|
@@ -296,13 +290,14 @@ Rebuildable:
|
|
|
296
290
|
|
|
297
291
|
Markdown keeps the system portable, inspectable, Git-friendly, and compatible with Obsidian-like workflows.
|
|
298
292
|
|
|
299
|
-
###
|
|
293
|
+
### File Index As Local Index
|
|
300
294
|
|
|
301
|
-
|
|
295
|
+
Brainlink uses a local JSON index plus encrypted pack exports for fast rebuildable retrieval without external infrastructure.
|
|
302
296
|
Hybrid retrieval also uses a short-lived in-memory cache keyed by vault/query/agent and invalidated by index file mtime to reduce repeated query latency.
|
|
303
|
-
|
|
304
|
-
|
|
297
|
+
Indexing exports private encrypted pack files (`.brainlink/search-packs/*.blpk`) from indexed chunks for fast retrieval and recovery continuity.
|
|
298
|
+
Pack manifests include compressed-space token bloom metadata so retrieval can skip unrelated packs before decryption.
|
|
305
299
|
Pack encryption keys are resolved from `$BRAINLINK_HOME/keys` or from `BRAINLINK_SEARCH_PACK_KEY` when configured.
|
|
300
|
+
Legacy `.jsonl.gz` search packs are auto-upgraded to `.blpk` on first retrieval flow.
|
|
306
301
|
|
|
307
302
|
### CLI First
|
|
308
303
|
|
package/docs/QUICKSTART.md
CHANGED
|
@@ -102,3 +102,10 @@ S3 target:
|
|
|
102
102
|
```bash
|
|
103
103
|
blink migrate-vault --from ~/.brainlink/vault --to "s3://my-memory-bucket/brainlink" --dry-run
|
|
104
104
|
```
|
|
105
|
+
|
|
106
|
+
Legacy SQLite import:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
blink db-import --vault ./team-vault
|
|
110
|
+
blink db-import --vault ./team-vault --db ./legacy/brainlink.db --dry-run
|
|
111
|
+
```
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andespindola/brainlink",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.131",
|
|
4
4
|
"description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"author": "
|
|
7
|
+
"author": "Substructa",
|
|
8
8
|
"homepage": "https://github.com/andersonflima/brainlink#readme",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"dist",
|
|
33
33
|
"assets",
|
|
34
34
|
"README.md",
|
|
35
|
+
"COPYRIGHT.md",
|
|
35
36
|
"LICENSE",
|
|
36
37
|
"CHANGELOG.md",
|
|
37
38
|
"CONTRIBUTING.md",
|
|
@@ -58,12 +59,13 @@
|
|
|
58
59
|
"dependencies": {
|
|
59
60
|
"@aws-sdk/client-s3": "^3.1038.0",
|
|
60
61
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
61
|
-
"better-sqlite3": "^12.9.0",
|
|
62
62
|
"commander": "^14.0.2",
|
|
63
63
|
"zod": "^4.3.6"
|
|
64
64
|
},
|
|
65
|
+
"overrides": {
|
|
66
|
+
"qs": "6.15.2"
|
|
67
|
+
},
|
|
65
68
|
"devDependencies": {
|
|
66
|
-
"@types/better-sqlite3": "^7.6.13",
|
|
67
69
|
"@types/node": "^24.9.2",
|
|
68
70
|
"tsx": "^4.21.0",
|
|
69
71
|
"typescript": "^5.9.3",
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { createEmbeddingBuckets } from '../../domain/embeddings.js';
|
|
2
|
-
const toTitleKey = (title) => title.toLowerCase();
|
|
3
|
-
export const createIndexWriter = (database) => ({
|
|
4
|
-
reset: () => {
|
|
5
|
-
database.exec(`
|
|
6
|
-
DELETE FROM embedding_buckets;
|
|
7
|
-
DELETE FROM chunks_fts;
|
|
8
|
-
DELETE FROM links;
|
|
9
|
-
DELETE FROM chunks;
|
|
10
|
-
DELETE FROM documents;
|
|
11
|
-
`);
|
|
12
|
-
},
|
|
13
|
-
saveDocuments: (documents) => {
|
|
14
|
-
const insertDocument = database.prepare(`
|
|
15
|
-
INSERT INTO documents (id, agent_id, title, path, content, tags_json, frontmatter_json, created_at, updated_at)
|
|
16
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
17
|
-
`);
|
|
18
|
-
const insertChunk = database.prepare(`
|
|
19
|
-
INSERT INTO chunks (id, document_id, ordinal, content, token_count, embedding_provider, embedding_json)
|
|
20
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
21
|
-
`);
|
|
22
|
-
const insertChunkFts = database.prepare(`
|
|
23
|
-
INSERT INTO chunks_fts (chunk_id, document_id, agent_id, title, content)
|
|
24
|
-
VALUES (?, ?, ?, ?, ?)
|
|
25
|
-
`);
|
|
26
|
-
const insertEmbeddingBucket = database.prepare(`
|
|
27
|
-
INSERT OR IGNORE INTO embedding_buckets (bucket, chunk_id)
|
|
28
|
-
VALUES (?, ?)
|
|
29
|
-
`);
|
|
30
|
-
const insertLink = database.prepare(`
|
|
31
|
-
INSERT INTO links (from_document_id, to_title, to_title_key, to_document_id, weight, priority)
|
|
32
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
33
|
-
`);
|
|
34
|
-
const transaction = database.transaction(() => {
|
|
35
|
-
documents.forEach(({ document, chunks, links }) => {
|
|
36
|
-
insertDocument.run(document.id, document.agentId, document.title, document.path, document.content, JSON.stringify(document.tags), JSON.stringify(document.frontmatter), document.createdAt, document.updatedAt);
|
|
37
|
-
chunks.forEach((chunk) => {
|
|
38
|
-
insertChunk.run(chunk.id, chunk.documentId, chunk.ordinal, chunk.content, chunk.tokenCount, chunk.embeddingProvider, JSON.stringify(chunk.embedding));
|
|
39
|
-
insertChunkFts.run(chunk.id, chunk.documentId, document.agentId, document.title, chunk.content);
|
|
40
|
-
createEmbeddingBuckets(chunk.embedding).forEach((bucket) => insertEmbeddingBucket.run(bucket, chunk.id));
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
documents.forEach(({ links }) => {
|
|
44
|
-
links.forEach((link) => {
|
|
45
|
-
insertLink.run(link.fromDocumentId, link.toTitle, toTitleKey(link.toTitle), link.toDocumentId, link.weight, link.priority);
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
transaction();
|
|
50
|
-
}
|
|
51
|
-
});
|