@andespindola/brainlink 0.1.0-beta.165 → 0.1.0-beta.166

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.
@@ -50,7 +50,7 @@ export const getExtendedStats = async (vaultPath, agentId) => {
50
50
  await searchKnowledge(absoluteVaultPath, probeQuery, Math.min(defaults.defaultSearchLimit, 8), agentId, 'hybrid');
51
51
  const searchLatency = performance.now() - searchStart;
52
52
  const contextStart = performance.now();
53
- await buildContextPackage(absoluteVaultPath, probeQuery, Math.min(defaults.defaultSearchLimit, 8), defaults.defaultContextTokens, agentId, 'hybrid');
53
+ await buildContextPackage(absoluteVaultPath, probeQuery, Math.min(defaults.defaultSearchLimit, 8), defaults.defaultContextTokens, agentId, 'hybrid', undefined, defaults.defaultContextCacheTtlMs);
54
54
  const contextLatency = performance.now() - contextStart;
55
55
  return {
56
56
  stats,
@@ -5,7 +5,6 @@ import { readContextPack, writeContextPack } from '../infrastructure/context-pac
5
5
  import { indexStoragePath } from '../infrastructure/file-index.js';
6
6
  import { searchVolatileMemory, volatileMemoryStoragePath } from '../infrastructure/volatile-memory.js';
7
7
  import { searchKnowledge } from './search-knowledge.js';
8
- const contextCacheTtlMs = 45_000;
9
8
  const contextCacheMaxEntries = 200;
10
9
  const contextCache = new Map();
11
10
  const readFileSignature = async (path) => {
@@ -31,7 +30,7 @@ const withCacheMetadata = (context, cache) => ({
31
30
  ...context,
32
31
  cache
33
32
  });
34
- const contextCacheGet = (key, dataSignature) => {
33
+ const contextCacheGet = (key, dataSignature, contextCacheTtlMs) => {
35
34
  const entry = contextCache.get(key);
36
35
  if (!entry) {
37
36
  return undefined;
@@ -70,11 +69,11 @@ const emptyMetrics = (context, totalMs, overrides = {}) => ({
70
69
  estimatedTokens: estimateSectionTokens(context),
71
70
  ...overrides
72
71
  });
73
- export const buildContextPackage = async (vaultPath, query, limit, maxTokens, agentId, mode, strategy = 'rag') => {
72
+ export const buildContextPackage = async (vaultPath, query, limit, maxTokens, agentId, mode, strategy = 'rag', contextCacheTtlMs = 120_000) => {
74
73
  const totalStart = performance.now();
75
74
  const cacheKey = toCacheKey(vaultPath, query, limit, maxTokens, agentId, mode, strategy);
76
75
  const dataSignature = await readContextDataSignature(vaultPath);
77
- const cached = contextCacheGet(cacheKey, dataSignature);
76
+ const cached = contextCacheGet(cacheKey, dataSignature, contextCacheTtlMs);
78
77
  if (cached) {
79
78
  return cached;
80
79
  }
@@ -164,7 +163,7 @@ export const buildContextPackage = async (vaultPath, query, limit, maxTokens, ag
164
163
  });
165
164
  return contextWithMetrics;
166
165
  };
167
- export const buildContext = async (vaultPath, query, limit, maxTokens, agentId, mode, strategy) => {
168
- const contextPackage = await buildContextPackage(vaultPath, query, limit, maxTokens, agentId, mode, strategy);
166
+ export const buildContext = async (vaultPath, query, limit, maxTokens, agentId, mode, strategy, contextCacheTtlMs = 120_000) => {
167
+ const contextPackage = await buildContextPackage(vaultPath, query, limit, maxTokens, agentId, mode, strategy, contextCacheTtlMs);
169
168
  return contextPackage.content;
170
169
  };
@@ -28,6 +28,11 @@ const readContextStrategy = async (url) => {
28
28
  const defaults = resolveAgentRuntimeDefaults(config, readAgentQuery(url));
29
29
  return sanitizeContextStrategy(url.searchParams.get('strategy'), defaults.defaultContextStrategy);
30
30
  };
31
+ const readContextCacheTtlMs = async (url) => {
32
+ const config = await loadBrainlinkConfig();
33
+ const defaults = resolveAgentRuntimeDefaults(config, readAgentQuery(url));
34
+ return defaults.defaultContextCacheTtlMs;
35
+ };
31
36
  const hasInvalidSearchMode = (url) => {
32
37
  const mode = url.searchParams.get('mode');
33
38
  return mode !== null && !['fts', 'semantic', 'hybrid'].includes(mode);
@@ -381,13 +386,14 @@ export const route = async (request, url, vaultPath) => {
381
386
  const tokens = parsePositiveInteger(url.searchParams.get('tokens'), 2000);
382
387
  const mode = await readSearchMode(url);
383
388
  const strategy = await readContextStrategy(url);
389
+ const contextCacheTtlMs = await readContextCacheTtlMs(url);
384
390
  if (hasInvalidSearchMode(url)) {
385
391
  return createResponse(createJsonResponse({ error: 'Invalid mode. Use fts, semantic or hybrid.' }), 400, contentTypes['.json']);
386
392
  }
387
393
  if (hasInvalidContextStrategy(url)) {
388
394
  return createResponse(createJsonResponse({ error: 'Invalid strategy. Use rag, cag or auto.' }), 400, contentTypes['.json']);
389
395
  }
390
- return createResponse(createJsonResponse(await buildContextPackage(vaultPath, query, limit, tokens, readAgentQuery(url), mode, strategy)), 200, contentTypes['.json']);
396
+ return createResponse(createJsonResponse(await buildContextPackage(vaultPath, query, limit, tokens, readAgentQuery(url), mode, strategy, contextCacheTtlMs)), 200, contentTypes['.json']);
391
397
  }
392
398
  if (isReadMethod(request) && url.pathname === '/api/links') {
393
399
  return createResponse(createJsonResponse({ links: await listLinks(vaultPath, readAgentQuery(url)) }), 200, contentTypes['.json']);
@@ -69,7 +69,7 @@ export const registerReadCommands = (program) => {
69
69
  const resolved = await resolveOptions(options);
70
70
  const mode = sanitizeSearchMode(options.mode, resolved.defaults.defaultSearchMode);
71
71
  const strategy = sanitizeContextStrategy(options.strategy, resolved.defaults.defaultContextStrategy);
72
- const contextPackage = await buildContextPackage(resolved.vault, query, parsePositiveInteger(options.limit ?? String(resolved.defaults.defaultSearchLimit), resolved.defaults.defaultSearchLimit), parsePositiveInteger(options.tokens ?? String(resolved.defaults.defaultContextTokens), resolved.defaults.defaultContextTokens), resolved.agent, mode, strategy);
72
+ const contextPackage = await buildContextPackage(resolved.vault, query, parsePositiveInteger(options.limit ?? String(resolved.defaults.defaultSearchLimit), resolved.defaults.defaultSearchLimit), parsePositiveInteger(options.tokens ?? String(resolved.defaults.defaultContextTokens), resolved.defaults.defaultContextTokens), resolved.agent, mode, strategy, resolved.defaults.defaultContextCacheTtlMs);
73
73
  print(options.json, contextPackage, () => contextPackage.content);
74
74
  });
75
75
  program
@@ -1135,7 +1135,7 @@ export const registerWriteCommands = (program) => {
1135
1135
  const policy = await getBootstrapPolicy();
1136
1136
  const bootstrapStatus = await getBootstrapSessionStatus(resolved.vault, resolved.agent);
1137
1137
  const context = options.query
1138
- ? await buildContextPackage(resolved.vault, options.query, limit, tokens, resolved.agent, mode, strategy)
1138
+ ? await buildContextPackage(resolved.vault, options.query, limit, tokens, resolved.agent, mode, strategy, resolved.defaults.defaultContextCacheTtlMs)
1139
1139
  : null;
1140
1140
  const agentIntegration = options.installAgent === false
1141
1141
  ? null
@@ -14,6 +14,7 @@ export const defaultBrainlinkConfig = {
14
14
  defaultSearchLimit: 10,
15
15
  defaultContextTokens: 2000,
16
16
  defaultContextStrategy: 'rag',
17
+ defaultContextCacheTtlMs: 120_000,
17
18
  embeddingProvider: 'local',
18
19
  defaultSearchMode: 'hybrid',
19
20
  chunkSize: 1200,
@@ -90,11 +91,17 @@ const sanitizeAgentProfile = (value) => {
90
91
  const defaultContextStrategy = typeof value.defaultContextStrategy === 'string' && contextStrategies.has(value.defaultContextStrategy)
91
92
  ? value.defaultContextStrategy
92
93
  : undefined;
94
+ const defaultContextCacheTtlMs = typeof value.defaultContextCacheTtlMs === 'number' &&
95
+ Number.isFinite(value.defaultContextCacheTtlMs) &&
96
+ value.defaultContextCacheTtlMs > 0
97
+ ? Math.floor(value.defaultContextCacheTtlMs)
98
+ : undefined;
93
99
  const profile = {
94
100
  ...(defaultSearchLimit ? { defaultSearchLimit } : {}),
95
101
  ...(defaultContextTokens ? { defaultContextTokens } : {}),
96
102
  ...(defaultSearchMode ? { defaultSearchMode } : {}),
97
- ...(defaultContextStrategy ? { defaultContextStrategy } : {})
103
+ ...(defaultContextStrategy ? { defaultContextStrategy } : {}),
104
+ ...(defaultContextCacheTtlMs ? { defaultContextCacheTtlMs } : {})
98
105
  };
99
106
  return Object.keys(profile).length > 0 ? profile : null;
100
107
  };
@@ -177,6 +184,9 @@ const sanitizeConfig = (value) => ({
177
184
  ? value.defaultContextTokens
178
185
  : defaultBrainlinkConfig.defaultContextTokens,
179
186
  defaultContextStrategy: sanitizeContextStrategy(value.defaultContextStrategy, defaultBrainlinkConfig.defaultContextStrategy),
187
+ defaultContextCacheTtlMs: typeof value.defaultContextCacheTtlMs === 'number' && Number.isFinite(value.defaultContextCacheTtlMs) && value.defaultContextCacheTtlMs > 0
188
+ ? Math.floor(value.defaultContextCacheTtlMs)
189
+ : defaultBrainlinkConfig.defaultContextCacheTtlMs,
180
190
  allowedVaults: [...sanitizeAllowedVaults(value.allowedVaults), ...readAllowedVaultsFromEnv()],
181
191
  chunkSize: typeof value.chunkSize === 'number' && value.chunkSize > 0 ? value.chunkSize : defaultBrainlinkConfig.chunkSize,
182
192
  searchPack: sanitizeSearchPackConfig(value.searchPack),
@@ -191,7 +201,8 @@ export const resolveAgentRuntimeDefaults = (config, agent) => {
191
201
  defaultSearchLimit: profile?.defaultSearchLimit ?? config.defaultSearchLimit,
192
202
  defaultContextTokens: profile?.defaultContextTokens ?? config.defaultContextTokens,
193
203
  defaultSearchMode: profile?.defaultSearchMode ?? config.defaultSearchMode,
194
- defaultContextStrategy: profile?.defaultContextStrategy ?? config.defaultContextStrategy
204
+ defaultContextStrategy: profile?.defaultContextStrategy ?? config.defaultContextStrategy,
205
+ defaultContextCacheTtlMs: profile?.defaultContextCacheTtlMs ?? config.defaultContextCacheTtlMs
195
206
  };
196
207
  };
197
208
  const mergeConfigLayers = (layers) => layers.reduce((state, config) => ({
package/dist/mcp/tools.js CHANGED
@@ -399,7 +399,7 @@ export const contextTool = async (input) => {
399
399
  const strategy = sanitizeContextStrategy(input.strategy, context.defaults.defaultContextStrategy);
400
400
  const limit = input.limit ?? context.defaults.defaultSearchLimit;
401
401
  const tokens = input.tokens ?? context.defaults.defaultContextTokens;
402
- const contextPackage = await buildContextPackage(context.vault, input.query, limit, tokens, context.agent, mode, strategy);
402
+ const contextPackage = await buildContextPackage(context.vault, input.query, limit, tokens, context.agent, mode, strategy, context.defaults.defaultContextCacheTtlMs);
403
403
  const contextSession = await touchContextSession(context.vault, context.agent);
404
404
  return jsonResult({
405
405
  vault: context.vault,
@@ -718,7 +718,7 @@ export const syncTool = async (input) => {
718
718
  const strategy = sanitizeContextStrategy(input.strategy, context.defaults.defaultContextStrategy);
719
719
  const contextLimit = input.contextLimit ?? context.defaults.defaultSearchLimit;
720
720
  const contextTokens = input.contextTokens ?? context.defaults.defaultContextTokens;
721
- const contextPackage = await buildContextPackage(context.vault, input.contextQuery, contextLimit, contextTokens, context.agent, mode, strategy);
721
+ const contextPackage = await buildContextPackage(context.vault, input.contextQuery, contextLimit, contextTokens, context.agent, mode, strategy, context.defaults.defaultContextCacheTtlMs);
722
722
  const contextSession = await touchContextSession(context.vault, context.agent);
723
723
  return jsonResult({
724
724
  ...response,
@@ -740,7 +740,7 @@ export const bootstrapTool = async (input) => {
740
740
  const limit = input.limit ?? context.defaults.defaultSearchLimit;
741
741
  const tokens = input.tokens ?? context.defaults.defaultContextTokens;
742
742
  const contextPackage = input.query
743
- ? await buildContextPackage(context.vault, input.query, limit, tokens, context.agent, mode, strategy)
743
+ ? await buildContextPackage(context.vault, input.query, limit, tokens, context.agent, mode, strategy, context.defaults.defaultContextCacheTtlMs)
744
744
  : undefined;
745
745
  const contextSession = input.query ? await touchContextSession(context.vault, context.agent) : undefined;
746
746
  const guidance = stats.documentCount === 0
@@ -51,7 +51,7 @@ Set `BRAINLINK_HOME` when the whole Brainlink home directory should live somewhe
51
51
  Use `blink config where` and `blink config doctor` to inspect active paths and effective source.
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
- You can set `agentProfiles` to define per-agent defaults for `defaultSearchMode`, `defaultSearchLimit`, `defaultContextTokens` and `defaultContextStrategy`.
54
+ You can set `agentProfiles` to define per-agent defaults for `defaultSearchMode`, `defaultSearchLimit`, `defaultContextTokens`, `defaultContextStrategy` and `defaultContextCacheTtlMs`.
55
55
  You can tune search-pack compression with `searchPack.rowChunkSize`, `searchPack.compressionLevel` and `searchPack.useDictionary`.
56
56
  Guardrails for benchmark acceptance are configured with `searchPack.guardrailMinSavingsPercent` and `searchPack.guardrailMaxLatencyRegressionPercent`.
57
57
 
@@ -51,7 +51,8 @@ Optional per-agent retrieval defaults in `brainlink.config.json`:
51
51
  "defaultSearchMode": "semantic",
52
52
  "defaultSearchLimit": 8,
53
53
  "defaultContextTokens": 2400,
54
- "defaultContextStrategy": "auto"
54
+ "defaultContextStrategy": "auto",
55
+ "defaultContextCacheTtlMs": 120000
55
56
  }
56
57
  }
57
58
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "0.1.0-beta.165",
3
+ "version": "0.1.0-beta.166",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",