@andespindola/brainlink 0.1.0-alpha.8 → 0.1.0-beta.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.
@@ -1,4 +1,5 @@
1
1
  import { createEmbeddingBuckets } from '../../domain/embeddings.js';
2
+ const toTitleKey = (title) => title.toLowerCase();
2
3
  export const createIndexWriter = (database) => ({
3
4
  reset: () => {
4
5
  database.exec(`
@@ -27,8 +28,8 @@ export const createIndexWriter = (database) => ({
27
28
  VALUES (?, ?)
28
29
  `);
29
30
  const insertLink = database.prepare(`
30
- INSERT INTO links (from_document_id, to_title, to_document_id)
31
- VALUES (?, ?, ?)
31
+ INSERT INTO links (from_document_id, to_title, to_title_key, to_document_id, weight, priority)
32
+ VALUES (?, ?, ?, ?, ?, ?)
32
33
  `);
33
34
  const transaction = database.transaction(() => {
34
35
  documents.forEach(({ document, chunks, links }) => {
@@ -41,7 +42,7 @@ export const createIndexWriter = (database) => ({
41
42
  });
42
43
  documents.forEach(({ links }) => {
43
44
  links.forEach((link) => {
44
- insertLink.run(link.fromDocumentId, link.toTitle, link.toDocumentId);
45
+ insertLink.run(link.fromDocumentId, link.toTitle, toTitleKey(link.toTitle), link.toDocumentId, link.weight, link.priority);
45
46
  });
46
47
  });
47
48
  });
@@ -4,9 +4,12 @@ const toGraphLink = (row) => ({
4
4
  fromTitle: row.from_title,
5
5
  fromPath: row.from_path,
6
6
  toTitle: row.to_title,
7
- toPath: row.to_path
7
+ toPath: row.to_path,
8
+ weight: row.weight,
9
+ priority: row.priority
8
10
  });
9
11
  const normalizeAgentFilter = (agentId) => agentId ? sanitizeAgentId(agentId) : undefined;
12
+ const toTitleKey = (title) => title.toLowerCase();
10
13
  export const createGraphReader = (database) => ({
11
14
  listLinks: (agentId) => {
12
15
  const normalizedAgentId = normalizeAgentFilter(agentId);
@@ -18,12 +21,14 @@ export const createGraphReader = (database) => ({
18
21
  source.title AS from_title,
19
22
  source.path AS from_path,
20
23
  COALESCE(target.title, links.to_title) AS to_title,
21
- target.path AS to_path
24
+ target.path AS to_path,
25
+ links.weight AS weight,
26
+ links.priority AS priority
22
27
  FROM links
23
28
  JOIN documents source ON source.id = links.from_document_id
24
29
  LEFT JOIN documents target ON target.id = links.to_document_id
25
30
  ${agentFilter}
26
- ORDER BY source.title, to_title
31
+ ORDER BY source.title, links.weight DESC, to_title
27
32
  `)
28
33
  .all(...(normalizedAgentId ? [normalizedAgentId] : []));
29
34
  return rows.map(toGraphLink);
@@ -31,6 +36,7 @@ export const createGraphReader = (database) => ({
31
36
  listBacklinks: (title, agentId) => {
32
37
  const normalizedAgentId = normalizeAgentFilter(agentId);
33
38
  const agentFilter = normalizedAgentId ? 'AND source.agent_id = ?' : '';
39
+ const titleKey = toTitleKey(title);
34
40
  const rows = database
35
41
  .prepare(`
36
42
  SELECT
@@ -38,15 +44,17 @@ export const createGraphReader = (database) => ({
38
44
  source.title AS from_title,
39
45
  source.path AS from_path,
40
46
  COALESCE(target.title, links.to_title) AS to_title,
41
- target.path AS to_path
47
+ target.path AS to_path,
48
+ links.weight AS weight,
49
+ links.priority AS priority
42
50
  FROM links
43
51
  JOIN documents source ON source.id = links.from_document_id
44
52
  LEFT JOIN documents target ON target.id = links.to_document_id
45
- WHERE (lower(links.to_title) = lower(?) OR lower(target.title) = lower(?))
53
+ WHERE links.to_title_key = ?
46
54
  ${agentFilter}
47
- ORDER BY source.title
55
+ ORDER BY links.weight DESC, source.title
48
56
  `)
49
- .all(...(normalizedAgentId ? [title, title, normalizedAgentId] : [title, title]));
57
+ .all(...(normalizedAgentId ? [titleKey, normalizedAgentId] : [titleKey]));
50
58
  return rows.map(toGraphLink);
51
59
  },
52
60
  getGraph: (agentId) => {
@@ -66,11 +74,13 @@ export const createGraphReader = (database) => ({
66
74
  SELECT
67
75
  links.from_document_id AS source,
68
76
  links.to_document_id AS target,
69
- links.to_title AS target_title
77
+ links.to_title AS target_title,
78
+ links.weight AS weight,
79
+ links.priority AS priority
70
80
  FROM links
71
81
  JOIN documents source ON source.id = links.from_document_id
72
82
  ${edgeAgentFilter}
73
- ORDER BY links.from_document_id, links.to_title
83
+ ORDER BY links.from_document_id, links.weight DESC, links.to_title
74
84
  `)
75
85
  .all(...(normalizedAgentId ? [normalizedAgentId] : []));
76
86
  const nodes = nodeRows.map((row) => ({
@@ -84,7 +94,9 @@ export const createGraphReader = (database) => ({
84
94
  const edges = edgeRows.map((row) => ({
85
95
  source: row.source,
86
96
  target: row.target,
87
- targetTitle: row.target_title
97
+ targetTitle: row.target_title,
98
+ weight: row.weight,
99
+ priority: row.priority
88
100
  }));
89
101
  return {
90
102
  nodes,
@@ -1,7 +1,8 @@
1
- const schemaVersion = 4;
1
+ const schemaVersion = 5;
2
2
  const requiredTableColumns = {
3
3
  documents: ['id', 'agent_id', 'title', 'path', 'content', 'tags_json', 'frontmatter_json', 'created_at', 'updated_at'],
4
4
  chunks: ['id', 'document_id', 'ordinal', 'content', 'token_count', 'embedding_provider', 'embedding_json'],
5
+ links: ['from_document_id', 'to_title', 'to_title_key', 'to_document_id', 'weight', 'priority'],
5
6
  chunks_fts: ['chunk_id', 'document_id', 'agent_id', 'title', 'content']
6
7
  };
7
8
  const getStoredSchemaVersion = (database) => {
@@ -65,6 +66,9 @@ export const createSchema = (database) => {
65
66
  FOREIGN KEY (document_id) REFERENCES documents(id) ON DELETE CASCADE
66
67
  );
67
68
 
69
+ CREATE INDEX IF NOT EXISTS idx_documents_agent_title ON documents(agent_id, title);
70
+ CREATE INDEX IF NOT EXISTS idx_chunks_document_ordinal ON chunks(document_id, ordinal);
71
+
68
72
  CREATE TABLE IF NOT EXISTS embedding_buckets (
69
73
  bucket TEXT NOT NULL,
70
74
  chunk_id TEXT NOT NULL,
@@ -77,11 +81,18 @@ export const createSchema = (database) => {
77
81
  CREATE TABLE IF NOT EXISTS links (
78
82
  from_document_id TEXT NOT NULL,
79
83
  to_title TEXT NOT NULL,
84
+ to_title_key TEXT NOT NULL,
80
85
  to_document_id TEXT,
86
+ weight INTEGER NOT NULL,
87
+ priority TEXT NOT NULL,
88
+ PRIMARY KEY (from_document_id, to_title_key),
81
89
  FOREIGN KEY (from_document_id) REFERENCES documents(id) ON DELETE CASCADE,
82
90
  FOREIGN KEY (to_document_id) REFERENCES documents(id) ON DELETE SET NULL
83
91
  );
84
92
 
93
+ CREATE INDEX IF NOT EXISTS idx_links_to_document_id ON links(to_document_id);
94
+ CREATE INDEX IF NOT EXISTS idx_links_to_title_key ON links(to_title_key);
95
+
85
96
  CREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(
86
97
  chunk_id UNINDEXED,
87
98
  document_id UNINDEXED,
@@ -9,7 +9,12 @@ export const openSqliteIndex = (vaultPath) => {
9
9
  const databasePath = join(vaultPath, '.brainlink', 'brainlink.db');
10
10
  const database = new Database(databasePath);
11
11
  chmodSync(databasePath, 0o600);
12
- database.exec('PRAGMA foreign_keys = ON;');
12
+ database.exec(`
13
+ PRAGMA foreign_keys = ON;
14
+ PRAGMA journal_mode = WAL;
15
+ PRAGMA synchronous = NORMAL;
16
+ PRAGMA temp_store = MEMORY;
17
+ `);
13
18
  createSchema(database);
14
19
  return {
15
20
  ...createIndexWriter(database),
@@ -2,7 +2,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { readFileSync } from 'node:fs';
3
3
  import { dirname, join } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
- import { addNoteInputSchema, addNoteTool, brokenLinksInputSchema, brokenLinksTool, contextInputSchema, contextTool, graphInputSchema, graphTool, indexInputSchema, indexTool, orphansInputSchema, orphansTool, searchInputSchema, searchTool, validateInputSchema, validateTool } from './tools.js';
5
+ import { addNoteInputSchema, addNoteTool, brokenLinksInputSchema, brokenLinksTool, contextInputSchema, contextTool, graphInputSchema, graphTool, indexInputSchema, indexTool, orphansInputSchema, orphansTool, searchInputSchema, searchTool, statsInputSchema, statsTool, syncInputSchema, syncTool, validateInputSchema, validateTool } from './tools.js';
6
6
  const readPackageVersion = () => {
7
7
  const packagePath = join(dirname(fileURLToPath(import.meta.url)), '../../package.json');
8
8
  const metadata = JSON.parse(readFileSync(packagePath, 'utf8'));
@@ -27,7 +27,7 @@ export const createBrainlinkMcpServer = () => {
27
27
  }, searchTool);
28
28
  server.registerTool('brainlink_add_note', {
29
29
  title: 'Add Brainlink Note',
30
- description: 'Write durable Markdown memory, then reindex the vault. Include explicit [[wiki links]] for connected graph memory.',
30
+ description: 'Write durable Markdown memory, then reindex the vault. Include explicit [[wiki links]] for connected graph memory. Add priority markers near links, such as priority: high, #important or #critical, when a relationship should be weighted higher.',
31
31
  inputSchema: addNoteInputSchema
32
32
  }, addNoteTool);
33
33
  server.registerTool('brainlink_index', {
@@ -35,14 +35,24 @@ export const createBrainlinkMcpServer = () => {
35
35
  description: 'Rebuild the local Brainlink index from Markdown notes.',
36
36
  inputSchema: indexInputSchema
37
37
  }, indexTool);
38
+ server.registerTool('brainlink_stats', {
39
+ title: 'Get Brainlink Vault Stats',
40
+ description: 'Read indexed vault statistics, including node, edge and tag totals.',
41
+ inputSchema: statsInputSchema
42
+ }, statsTool);
38
43
  server.registerTool('brainlink_validate', {
39
44
  title: 'Validate Brainlink Vault',
40
45
  description: 'Validate indexed graph health, including broken links and orphan notes.',
41
46
  inputSchema: validateInputSchema
42
47
  }, validateTool);
48
+ server.registerTool('brainlink_sync', {
49
+ title: 'Run Brainlink Sync Flow',
50
+ description: 'Run index, stats, validate, broken-links and orphans checks in one call. Optionally run context probe.',
51
+ inputSchema: syncInputSchema
52
+ }, syncTool);
43
53
  server.registerTool('brainlink_graph', {
44
54
  title: 'Read Brainlink Graph',
45
- description: 'Read indexed graph nodes and wiki-link edges.',
55
+ description: 'Read indexed graph nodes and wiki-link edges. Edges include weight and priority fields so agents can rank importance and priority.',
46
56
  inputSchema: graphInputSchema
47
57
  }, graphTool);
48
58
  server.registerTool('brainlink_broken_links', {
package/dist/mcp/tools.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { getBrokenLinksReport, getOrphansReport, validateVault } from '../application/analyze-vault.js';
2
+ import { getBrokenLinksReport, getOrphansReport, getStats, validateVault } from '../application/analyze-vault.js';
3
3
  import { addNote } from '../application/add-note.js';
4
4
  import { buildContextPackage } from '../application/build-context.js';
5
5
  import { getGraph } from '../application/get-graph.js';
@@ -18,14 +18,24 @@ const vaultInput = {
18
18
  vault: z.string().min(1).optional().describe('Vault directory. Omit to use the configured Brainlink default vault.')
19
19
  };
20
20
  const agentInput = {
21
- agent: z.string().min(1).optional().describe('Agent memory namespace. Omit to read shared/default indexed memory.')
21
+ agent: z
22
+ .string()
23
+ .min(1)
24
+ .optional()
25
+ .describe('Agent memory namespace. Omit to use Brainlink.config defaultAgent, otherwise read all agent namespaces.')
22
26
  };
23
27
  const searchModeInput = {
24
28
  mode: z.enum(['fts', 'semantic', 'hybrid']).optional().describe('Search mode. Defaults to the Brainlink config value.')
25
29
  };
26
- const resolveVault = async (vault) => {
30
+ const resolveExecutionContext = async (input) => {
27
31
  const config = await loadBrainlinkConfig();
28
- return assertVaultAllowed(vault ?? config.vault, config.allowedVaults);
32
+ const vault = await assertVaultAllowed(input.vault ?? config.vault, config.allowedVaults);
33
+ const agent = input.agent ?? config.defaultAgent;
34
+ return {
35
+ vault,
36
+ config,
37
+ agent
38
+ };
29
39
  };
30
40
  const jsonResult = (value) => ({
31
41
  content: [
@@ -57,8 +67,8 @@ export const addNoteInputSchema = {
57
67
  content: z
58
68
  .string()
59
69
  .min(1)
60
- .describe('Durable Markdown memory. Include explicit [[wiki links]] and #tags when the memory should be connected.'),
61
- agent: z.string().min(1).optional().default('shared').describe('Agent memory namespace. Defaults to shared.'),
70
+ .describe('Durable Markdown memory. Include explicit [[wiki links]] and #tags when the memory should be connected. Put priority markers near important links, for example priority: high, #important or #critical.'),
71
+ agent: z.string().min(1).optional().describe('Agent memory namespace.'),
62
72
  allowSensitive: z.boolean().optional().default(false).describe('Allow content that looks like a secret.')
63
73
  };
64
74
  export const indexInputSchema = {
@@ -80,26 +90,36 @@ export const orphansInputSchema = {
80
90
  ...vaultInput,
81
91
  ...agentInput
82
92
  };
93
+ export const statsInputSchema = {
94
+ ...vaultInput,
95
+ ...agentInput
96
+ };
97
+ export const syncInputSchema = {
98
+ ...vaultInput,
99
+ ...agentInput,
100
+ contextQuery: z.string().min(1).optional().describe('Optional context smoke query. Omit to skip context probe.'),
101
+ mode: z.enum(['fts', 'semantic', 'hybrid']).optional().describe('Search mode for the optional context probe. Defaults to config value.'),
102
+ contextLimit: positiveInteger(12).describe('Context smoke result limit when contextQuery is provided.'),
103
+ contextTokens: positiveInteger(2000).describe('Context smoke token target when contextQuery is provided.')
104
+ };
83
105
  export const contextTool = async (input) => {
84
- const vault = await resolveVault(input.vault);
85
- const config = await loadBrainlinkConfig();
86
- const mode = sanitizeSearchMode(input.mode, config.defaultSearchMode);
87
- const contextPackage = await buildContextPackage(vault, input.query, input.limit, input.tokens, input.agent, mode);
106
+ const context = await resolveExecutionContext(input);
107
+ const mode = sanitizeSearchMode(input.mode, context.config.defaultSearchMode);
108
+ const contextPackage = await buildContextPackage(context.vault, input.query, input.limit, input.tokens, context.agent, mode);
88
109
  return jsonResult({
89
- vault,
90
- agent: input.agent,
110
+ vault: context.vault,
111
+ agent: context.agent,
91
112
  mode,
92
113
  ...contextPackage
93
114
  });
94
115
  };
95
116
  export const searchTool = async (input) => {
96
- const vault = await resolveVault(input.vault);
97
- const config = await loadBrainlinkConfig();
98
- const mode = sanitizeSearchMode(input.mode, config.defaultSearchMode);
99
- const results = await searchKnowledge(vault, input.query, input.limit, input.agent, mode);
117
+ const context = await resolveExecutionContext(input);
118
+ const mode = sanitizeSearchMode(input.mode, context.config.defaultSearchMode);
119
+ const results = await searchKnowledge(context.vault, input.query, input.limit, context.agent, mode);
100
120
  return jsonResult({
101
- vault,
102
- agent: input.agent,
121
+ vault: context.vault,
122
+ agent: context.agent,
103
123
  query: input.query,
104
124
  limit: input.limit,
105
125
  mode,
@@ -107,60 +127,98 @@ export const searchTool = async (input) => {
107
127
  });
108
128
  };
109
129
  export const addNoteTool = async (input) => {
110
- const vault = await resolveVault(input.vault);
111
- const path = await addNote(vault, input.title, input.content, input.agent, {
130
+ const context = await resolveExecutionContext(input);
131
+ const path = await addNote(context.vault, input.title, input.content, context.agent, {
112
132
  allowSensitive: input.allowSensitive
113
133
  });
114
- const index = await indexVault(vault);
134
+ const index = await indexVault(context.vault);
115
135
  return jsonResult({
116
- vault,
136
+ vault: context.vault,
117
137
  title: input.title,
118
- agent: input.agent,
138
+ agent: context.agent,
119
139
  path,
120
140
  index
121
141
  });
122
142
  };
123
143
  export const indexTool = async (input) => {
124
- const vault = await resolveVault(input.vault);
125
- const result = await indexVault(vault);
144
+ const context = await resolveExecutionContext(input);
145
+ const result = await indexVault(context.vault);
126
146
  return jsonResult({
127
- vault,
147
+ vault: context.vault,
128
148
  ...result
129
149
  });
130
150
  };
131
151
  export const validateTool = async (input) => {
132
- const vault = await resolveVault(input.vault);
133
- const validation = await validateVault(vault, input.agent);
152
+ const context = await resolveExecutionContext(input);
153
+ const validation = await validateVault(context.vault, context.agent);
134
154
  return jsonResult({
135
- vault,
136
- agent: input.agent,
155
+ vault: context.vault,
156
+ agent: context.agent,
137
157
  ...validation
138
158
  });
139
159
  };
140
160
  export const graphTool = async (input) => {
141
- const vault = await resolveVault(input.vault);
142
- const graph = await getGraph(vault, input.agent);
161
+ const context = await resolveExecutionContext(input);
162
+ const graph = await getGraph(context.vault, context.agent);
143
163
  return jsonResult({
144
- vault,
145
- agent: input.agent,
164
+ vault: context.vault,
165
+ agent: context.agent,
146
166
  ...graph
147
167
  });
148
168
  };
149
169
  export const brokenLinksTool = async (input) => {
150
- const vault = await resolveVault(input.vault);
151
- const brokenLinks = await getBrokenLinksReport(vault, input.agent);
170
+ const context = await resolveExecutionContext(input);
171
+ const brokenLinks = await getBrokenLinksReport(context.vault, context.agent);
152
172
  return jsonResult({
153
- vault,
154
- agent: input.agent,
173
+ vault: context.vault,
174
+ agent: context.agent,
155
175
  brokenLinks
156
176
  });
157
177
  };
158
178
  export const orphansTool = async (input) => {
159
- const vault = await resolveVault(input.vault);
160
- const orphans = await getOrphansReport(vault, input.agent);
179
+ const context = await resolveExecutionContext(input);
180
+ const orphans = await getOrphansReport(context.vault, context.agent);
161
181
  return jsonResult({
162
- vault,
163
- agent: input.agent,
182
+ vault: context.vault,
183
+ agent: context.agent,
164
184
  orphans
165
185
  });
166
186
  };
187
+ export const statsTool = async (input) => {
188
+ const context = await resolveExecutionContext(input);
189
+ const stats = await getStats(context.vault, context.agent);
190
+ return jsonResult({
191
+ vault: context.vault,
192
+ agent: context.agent,
193
+ stats
194
+ });
195
+ };
196
+ export const syncTool = async (input) => {
197
+ const context = await resolveExecutionContext(input);
198
+ const index = await indexVault(context.vault);
199
+ const stats = await getStats(context.vault, context.agent);
200
+ const validation = await validateVault(context.vault, context.agent);
201
+ const brokenLinks = await getBrokenLinksReport(context.vault, context.agent);
202
+ const orphans = await getOrphansReport(context.vault, context.agent);
203
+ const response = {
204
+ vault: context.vault,
205
+ agent: context.agent,
206
+ index,
207
+ stats,
208
+ validation,
209
+ brokenLinks,
210
+ orphans
211
+ };
212
+ if (!input.contextQuery) {
213
+ return jsonResult(response);
214
+ }
215
+ const mode = sanitizeSearchMode(input.mode, context.config.defaultSearchMode);
216
+ const contextPackage = await buildContextPackage(context.vault, input.contextQuery, input.contextLimit, input.contextTokens, context.agent, mode);
217
+ return jsonResult({
218
+ ...response,
219
+ context: {
220
+ mode,
221
+ ...contextPackage
222
+ }
223
+ });
224
+ };
@@ -41,6 +41,8 @@ $HOME/.brainlink/vault
41
41
 
42
42
  Use `--vault <path>` for a one-off custom vault, or set `vault` in `brainlink.config.json` / `.brainlink.json` for a workspace-level custom default. Set `BRAINLINK_HOME` when the whole Brainlink home directory should live somewhere else.
43
43
 
44
+ 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.
45
+
44
46
  ## Agent Namespaces
45
47
 
46
48
  Each agent writes into a dedicated namespace under `agents/<agent-id>/`:
@@ -133,6 +135,7 @@ Rules:
133
135
 
134
136
  - Use a clear title.
135
137
  - Use `[[Note Title]]` for relationships.
138
+ - Put priority markers near links when the relationship is important.
136
139
  - Use tags for retrieval.
137
140
  - Keep each note focused.
138
141
  - Prefer summaries over raw transcripts.
@@ -144,6 +147,15 @@ Brainlink only builds graph edges from Markdown `[[wiki links]]`.
144
147
 
145
148
  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.
146
149
 
150
+ 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:
151
+
152
+ ```md
153
+ - [ ] Review [[Architecture]] priority: high
154
+ Related: [[Incident Runbook]] #critical
155
+ ```
156
+
157
+ Agents should use weighted graph output to sort relationships by importance. Edges expose `weight` and `priority`, where priority is one of `low`, `normal`, `high` or `critical`.
158
+
147
159
  Required write behavior:
148
160
 
149
161
  1. Choose a clear title for the new note.
@@ -196,6 +208,54 @@ If the context is empty or weak:
196
208
  3. Inspect links and backlinks.
197
209
  4. Only then answer from general reasoning.
198
210
 
211
+ ## Optimized Agent Workflow (1 to 7)
212
+
213
+ Use this exact loop for higher signal and lower noise:
214
+
215
+ 1. Read memory before decisions:
216
+ - `blink context "<task>" --agent "$BLINK_AGENT" --json`
217
+ - Add `--mode hybrid` for mixed retrieval.
218
+ 2. Keep vault structure deterministic:
219
+ - Keep shared knowledge in `agents/shared`.
220
+ - Keep private work-in-progress in your own agent namespace.
221
+ 3. Write durable notes only, with explicit links and tags:
222
+ - include at least one `[[...]]` link
223
+ - include `#tags` for retrieval
224
+ 4. Store only stable decisions and update an existing note when possible.
225
+ 5. Use cache-conscious read/refresh cycle:
226
+ - prefer targeted queries over broad dumps.
227
+ - avoid re-indexing unless note set changed.
228
+ 6. Run guardrails regularly:
229
+ - `npm run brainlink:sync -- --vault ./vault --agent "$BLINK_AGENT"`.
230
+ - the sync flow runs `index`, `stats`, `validate`, `broken-links`, `orphans` and a quick context probe.
231
+ 7. Before responding:
232
+ - cite sources from context output
233
+ - keep output anchored in retrieved references.
234
+
235
+ Templates are available in `docs/templates` for quick note creation.
236
+
237
+ Recommended template:
238
+
239
+ ```bash
240
+ cp docs/templates/agent-note-template.md /tmp/agent-note.md
241
+ ```
242
+
243
+ ### MCP Usage for the Optimized Flow
244
+
245
+ When using MCP, use this compact sequence for the same memory discipline:
246
+
247
+ 1. Bootstrap context:
248
+ - `brainlink_context` with `agent`, `query`, `mode: hybrid`, `limit`.
249
+ 2. Capture durable decisions:
250
+ - `brainlink_add_note` with explicit `[[wiki links]]` and `#tags`.
251
+ 3. Run maintenance before handoff or before the next step:
252
+ - `brainlink_sync` with `agent`, `contextQuery`, `mode: hybrid`.
253
+ 4. Diagnose graph issues only when needed:
254
+ - `brainlink_validate`, `brainlink_broken_links`, `brainlink_orphans`.
255
+ 5. Inspect relationships:
256
+ - `brainlink_graph`.
257
+ 6. Use `brainlink_stats` for a quick health snapshot.
258
+
199
259
  ## Examples For Common Coding Agents
200
260
 
201
261
  These examples assume the agent can run shell commands in the user workspace.
@@ -464,6 +524,8 @@ Available MCP tools:
464
524
 
465
525
  MCP clients can pass `vault` and `agent` arguments per tool call. Set `BRAINLINK_ALLOWED_VAULTS` when exposing Brainlink to an external agent process so a tool cannot pass arbitrary vault paths:
466
526
 
527
+ `brainlink_graph` returns weighted edges. Agents should prefer higher `weight` and stronger `priority` when deciding which related notes matter most.
528
+
467
529
  ```bash
468
530
  export BRAINLINK_ALLOWED_VAULTS="/absolute/path/to/project-vault"
469
531
  ```
@@ -552,4 +614,5 @@ Weak retrieval usually means:
552
614
  - Local embeddings are deterministic and provider-free; remote embedding providers are not implemented yet.
553
615
  - MCP integration is available through the `brainlink-mcp` stdio server.
554
616
  - HTTP API is local and unauthenticated.
555
- - Watch mode depends on platform filesystem watcher behavior.
617
+ - Bucket vaults support S3-compatible `s3://bucket/prefix` URIs and use a local cache for SQLite indexes.
618
+ - Watch mode depends on platform filesystem watcher behavior and is only supported for local filesystem vaults.
@@ -105,13 +105,15 @@ Application code depends on domain rules and infrastructure interfaces.
105
105
  The infrastructure layer handles side effects:
106
106
 
107
107
  - reading Markdown files from disk
108
+ - mirroring S3-compatible bucket Markdown into a local cache
108
109
  - writing Markdown notes
109
110
  - creating `.brainlink`
110
111
  - writing and querying SQLite
111
112
  - running FTS, semantic and hybrid retrieval
112
113
  - narrowing semantic candidates through SQLite embedding buckets before cosine scoring
113
114
 
114
- SQLite is an index, not the canonical storage model.
115
+ SQLite is an index, not the canonical storage model. For bucket vaults, Markdown
116
+ objects in the bucket remain canonical and SQLite is still local derived data.
115
117
 
116
118
  ## Indexing Flow
117
119
 
@@ -216,6 +218,23 @@ source note -> target note
216
218
 
217
219
  The `backlinks` command queries indexed links pointing to a target title. With `--agent`, it only returns links from that namespace.
218
220
 
221
+ ## Weighted Links
222
+
223
+ Each indexed wiki link is stored as a graph edge with:
224
+
225
+ - `weight`: numeric relationship strength.
226
+ - `priority`: one of `low`, `normal`, `high` or `critical`.
227
+
228
+ The parser derives weight from repeated links, task-list context, heading context and priority markers on the same line as a wiki link. Examples:
229
+
230
+ ```md
231
+ Related: [[Architecture]]
232
+ - [ ] Review [[Architecture]] priority: high
233
+ Escalate [[Incident Runbook]] #critical
234
+ ```
235
+
236
+ Backlink and graph readers return those fields to CLI JSON, HTTP API and MCP clients. Backlink queries use the normalized `to_title_key` column instead of applying `lower(...)` at read time.
237
+
219
238
  ## Context Building
220
239
 
221
240
  `context` uses search results and selects one chunk per document while staying inside an estimated token budget.
@@ -240,6 +259,7 @@ Relevant content
240
259
  Permanent:
241
260
 
242
261
  - Markdown files
262
+ - S3-compatible Markdown objects when the vault is `s3://bucket/prefix`
243
263
  - optional Git history around the vault
244
264
 
245
265
  Canonical agent memory lives under:
@@ -251,6 +271,7 @@ vault/agents/<agent-id>/**/*.md
251
271
  Rebuildable:
252
272
 
253
273
  - `.brainlink/brainlink.db`
274
+ - `$BRAINLINK_HOME/bucket-cache`
254
275
  - FTS records
255
276
  - local embedding vectors
256
277
  - local embedding bucket index
package/docs/RELEASE.md CHANGED
@@ -32,7 +32,7 @@ blink context "release smoke" --vault ./tmp-vault --mode hybrid --json
32
32
  blink server --vault ./tmp-vault --host 127.0.0.1 --port 4321
33
33
  ```
34
34
 
35
- 9. Verify the server refuses accidental public binds:
35
+ 9. Verify the server refuses public binds:
36
36
 
37
37
  ```bash
38
38
  blink server --vault ./tmp-vault --host 0.0.0.0
@@ -0,0 +1,27 @@
1
+ # Namespace Bootstrap for New Agent
2
+
3
+ # <agent-id> Agent Notes
4
+
5
+ ## Purpose
6
+
7
+ - Scope:
8
+ - Owner:
9
+ - Active project contexts:
10
+
11
+ ## Core Notes
12
+
13
+ - [[Architecture]]
14
+ - [[Coding Conventions]]
15
+ - [[Runbook]]
16
+ - [[Decision Log]]
17
+ - [[Open Questions]]
18
+
19
+ ## Process
20
+
21
+ 1. Before task: context "<task>" --agent <agent-id> --json
22
+ 2. During task: document durable findings with explicit [[wiki links]]
23
+ 3. After task: add note + index + validation run
24
+
25
+ ## Tags
26
+
27
+ #agent #<agent-id> #process
@@ -0,0 +1,31 @@
1
+ # Agent Note Template
2
+
3
+ # <Title>
4
+
5
+ ## Context
6
+
7
+ - What was decided or discovered.
8
+ - Why this happened.
9
+ - Expected impact or expected behavior.
10
+
11
+ ## References
12
+
13
+ - [[Parent concept]]
14
+ - [[Related decision]]
15
+
16
+ ## Notes
17
+
18
+ #decision #<domain> #agent
19
+
20
+ ---
21
+
22
+ ## Changelog
23
+
24
+ - YYYY-MM-DD: created
25
+
26
+ ## Durability checklist
27
+
28
+ - The note has a clear title.
29
+ - It links to at least one existing note (unless this is a root concept).
30
+ - It has at least one `#tag`.
31
+ - The content is stable knowledge, not one-off chat or session noise.