memorix 1.5.0 → 1.6.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +3 -3
  3. package/README.zh-CN.md +3 -3
  4. package/dist/cli/index.js +452 -109
  5. package/dist/index.js +376 -87
  6. package/dist/maintenance-runner.js +174 -41
  7. package/dist/media-audio-runner.js +174 -41
  8. package/dist/media-video-runner.js +174 -41
  9. package/dist/memcode-runtime/CHANGELOG.md +37 -0
  10. package/dist/sdk.js +375 -86
  11. package/dist/types.d.ts +2 -0
  12. package/dist/vector-backfill-runner.js +174 -41
  13. package/docs/1.2.4-PERSISTENT-MEMORY-DELIVERY.md +1 -1
  14. package/docs/API_REFERENCE.md +1 -1
  15. package/docs/DEVELOPMENT.md +5 -3
  16. package/package.json +1 -1
  17. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  18. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  19. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  20. package/plugins/copilot/memorix/plugin.json +1 -1
  21. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  22. package/plugins/omp/memorix/package.json +1 -1
  23. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  24. package/plugins/pi/memorix/package.json +1 -1
  25. package/server.json +2 -2
  26. package/src/cli/commands/codegraph.ts +48 -5
  27. package/src/cli/commands/context.ts +20 -4
  28. package/src/cli/commands/resume.ts +4 -0
  29. package/src/codegraph/auto-context.ts +4 -1
  30. package/src/codegraph/context-pack.ts +3 -1
  31. package/src/codegraph/external-provider.ts +93 -0
  32. package/src/embedding/api-provider.ts +105 -22
  33. package/src/embedding/provider.ts +2 -0
  34. package/src/hooks/installers/index.ts +1 -1
  35. package/src/hooks/official-skills.ts +3 -3
  36. package/src/hooks/rules/memorix-agent-rules.md +1 -1
  37. package/src/knowledge/context-receipt.ts +41 -0
  38. package/src/knowledge/workflows.ts +6 -3
  39. package/src/knowledge/workset.ts +56 -2
  40. package/src/llm/provider.ts +17 -8
  41. package/src/memory/formation/extract.ts +6 -3
  42. package/src/memory/formation/index.ts +2 -1
  43. package/src/memory/formation/resolve.ts +10 -6
  44. package/src/memory/formation/types.ts +3 -1
  45. package/src/memory/long-term.ts +7 -1
  46. package/src/server.ts +29 -13
  47. package/src/store/orama-store.ts +6 -4
  48. package/src/timeout.ts +67 -0
  49. package/src/types.ts +2 -0
@@ -2755,6 +2755,8 @@ function getPreferredBatchSize(config) {
2755
2755
  }
2756
2756
  function parseBatchLimit(error) {
2757
2757
  if (!(error instanceof Error)) return null;
2758
+ const status = error instanceof EmbeddingAPIError ? error.status : Number(error.message.match(/Embedding API error \((\d{3})\)/i)?.[1] ?? 0);
2759
+ if (status > 0 && status !== 400) return null;
2758
2760
  const explicit = error.message.match(/should not be larger than\s+(\d+)/i);
2759
2761
  if (explicit) return parseInt(explicit[1], 10);
2760
2762
  if (/batch size/i.test(error.message)) {
@@ -2767,37 +2769,99 @@ async function fetchWithRetry(url, apiKey, body, options = {}, attempt = 0, nati
2767
2769
  const controller = new AbortController();
2768
2770
  const timeoutMs = typeof options.timeoutMs === "number" && Number.isFinite(options.timeoutMs) ? Math.max(100, Math.min(Math.floor(options.timeoutMs), 6e4)) : 1e4;
2769
2771
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
2772
+ const signal = options.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal;
2770
2773
  let response;
2771
2774
  try {
2772
2775
  response = await fetch(url, {
2773
2776
  method: "POST",
2774
2777
  headers: nativeGemini ? { "Content-Type": "application/json", "x-goog-api-key": apiKey } : { "Content-Type": "application/json", "Authorization": `Bearer ${apiKey}` },
2775
2778
  body: JSON.stringify(body),
2776
- signal: controller.signal
2779
+ signal
2777
2780
  });
2778
2781
  } catch (err) {
2779
2782
  clearTimeout(timeout);
2780
- if (err instanceof Error && err.name === "AbortError") {
2783
+ if (err instanceof Error && (err.name === "AbortError" || controller.signal.aborted || options.signal?.aborted)) {
2781
2784
  throw new Error(`Embedding API timeout after ${timeoutMs}ms: ${url}`);
2782
2785
  }
2783
2786
  throw err;
2784
2787
  }
2785
- clearTimeout(timeout);
2786
- if (response.ok) {
2787
- return response.json();
2788
+ try {
2789
+ if (response.ok) {
2790
+ return await raceWithAbort(
2791
+ response.json(),
2792
+ signal
2793
+ );
2794
+ }
2795
+ if (options.retry !== false && (response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES2) {
2796
+ const delay2 = BASE_DELAY_MS * Math.pow(2, attempt);
2797
+ const retryAfter = response.headers.get("retry-after");
2798
+ const parsedRetryAfter = retryAfter ? parseInt(retryAfter, 10) * 1e3 : NaN;
2799
+ const waitMs = Number.isFinite(parsedRetryAfter) ? parsedRetryAfter : delay2;
2800
+ console.error(`[memorix] Embedding API ${response.status}, retry ${attempt + 1}/${MAX_RETRIES2} in ${waitMs}ms`);
2801
+ await waitForRetry(waitMs, options.signal);
2802
+ return fetchWithRetry(url, apiKey, body, options, attempt + 1, nativeGemini);
2803
+ }
2804
+ const errorText2 = await raceWithAbort(
2805
+ response.text().catch(() => "unknown error"),
2806
+ signal
2807
+ );
2808
+ throw new EmbeddingAPIError(response.status, errorText2);
2809
+ } catch (err) {
2810
+ if (err instanceof Error && (err.name === "AbortError" || controller.signal.aborted || options.signal?.aborted)) {
2811
+ throw new Error(`Embedding API timeout after ${timeoutMs}ms: ${url}`);
2812
+ }
2813
+ throw err;
2814
+ } finally {
2815
+ clearTimeout(timeout);
2788
2816
  }
2789
- if (options.retry !== false && (response.status === 429 || response.status >= 500) && attempt < MAX_RETRIES2) {
2790
- const delay2 = BASE_DELAY_MS * Math.pow(2, attempt);
2791
- const retryAfter = response.headers.get("retry-after");
2792
- const waitMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : delay2;
2793
- console.error(`[memorix] Embedding API ${response.status}, retry ${attempt + 1}/${MAX_RETRIES2} in ${waitMs}ms`);
2817
+ }
2818
+ function abortError() {
2819
+ const error = new Error("Embedding API request aborted");
2820
+ error.name = "AbortError";
2821
+ return error;
2822
+ }
2823
+ function raceWithAbort(promise, signal) {
2824
+ if (signal.aborted) return Promise.reject(abortError());
2825
+ return new Promise((resolve, reject) => {
2826
+ const onAbort = () => {
2827
+ signal.removeEventListener("abort", onAbort);
2828
+ reject(abortError());
2829
+ };
2830
+ signal.addEventListener("abort", onAbort, { once: true });
2831
+ promise.then(
2832
+ (value) => {
2833
+ signal.removeEventListener("abort", onAbort);
2834
+ resolve(value);
2835
+ },
2836
+ (error) => {
2837
+ signal.removeEventListener("abort", onAbort);
2838
+ reject(error);
2839
+ }
2840
+ );
2841
+ });
2842
+ }
2843
+ async function waitForRetry(waitMs, signal) {
2844
+ if (!signal) {
2794
2845
  await new Promise((resolve) => setTimeout(resolve, waitMs));
2795
- return fetchWithRetry(url, apiKey, body, options, attempt + 1, nativeGemini);
2846
+ return;
2796
2847
  }
2797
- const errorText2 = await response.text().catch(() => "unknown error");
2798
- throw new Error(`Embedding API error (${response.status}): ${errorText2}`);
2848
+ const abortSignal = signal;
2849
+ if (abortSignal.aborted) throw new Error("Embedding API retry aborted");
2850
+ await new Promise((resolve, reject) => {
2851
+ const timer = setTimeout(done, waitMs);
2852
+ const onAbort = () => {
2853
+ clearTimeout(timer);
2854
+ abortSignal.removeEventListener("abort", onAbort);
2855
+ reject(new Error("Embedding API retry aborted"));
2856
+ };
2857
+ function done() {
2858
+ abortSignal.removeEventListener("abort", onAbort);
2859
+ resolve();
2860
+ }
2861
+ abortSignal.addEventListener("abort", onAbort, { once: true });
2862
+ });
2799
2863
  }
2800
- var cache3, MAX_CACHE_SIZE3, diskCacheDirty2, diskSaveTimer, diskCacheLoaded, diskCacheLoadPromise, MAX_INPUT_CHARS, MAX_CONCURRENCY, DISK_SAVE_DEBOUNCE_MS, DEFAULT_MAX_BATCH_SIZE, DASHSCOPE_MAX_BATCH_SIZE, MAX_RETRIES2, BASE_DELAY_MS, APIEmbeddingProvider;
2864
+ var cache3, MAX_CACHE_SIZE3, diskCacheDirty2, diskSaveTimer, diskCacheLoaded, diskCacheLoadPromise, MAX_INPUT_CHARS, MAX_CONCURRENCY, DISK_SAVE_DEBOUNCE_MS, DEFAULT_MAX_BATCH_SIZE, DASHSCOPE_MAX_BATCH_SIZE, MAX_RETRIES2, BASE_DELAY_MS, EmbeddingAPIError, APIEmbeddingProvider;
2801
2865
  var init_api_provider = __esm({
2802
2866
  "src/embedding/api-provider.ts"() {
2803
2867
  "use strict";
@@ -2816,6 +2880,14 @@ var init_api_provider = __esm({
2816
2880
  DASHSCOPE_MAX_BATCH_SIZE = 10;
2817
2881
  MAX_RETRIES2 = 3;
2818
2882
  BASE_DELAY_MS = 500;
2883
+ EmbeddingAPIError = class extends Error {
2884
+ status;
2885
+ constructor(status, detail) {
2886
+ super(`Embedding API error (${status}): ${detail}`);
2887
+ this.name = "EmbeddingAPIError";
2888
+ this.status = status;
2889
+ }
2890
+ };
2819
2891
  APIEmbeddingProvider = class _APIEmbeddingProvider {
2820
2892
  name;
2821
2893
  dimensions;
@@ -3027,15 +3099,14 @@ var init_api_provider = __esm({
3027
3099
  }
3028
3100
  } catch (error) {
3029
3101
  const providerLimit = parseBatchLimit(error);
3030
- const fallbackSize = providerLimit ?? Math.ceil(chunkTexts.length / 2);
3031
- if (chunkTexts.length > 1 && fallbackSize < chunkTexts.length) {
3102
+ if (providerLimit !== null && chunkTexts.length > 1 && providerLimit < chunkTexts.length) {
3032
3103
  console.error(
3033
- `[memorix] Embedding batch too large for provider, retrying in chunks of ${fallbackSize}`
3104
+ `[memorix] Embedding batch too large for provider, retrying in chunks of ${providerLimit}`
3034
3105
  );
3035
- for (let start = 0; start < chunkTexts.length; start += fallbackSize) {
3106
+ for (let start = 0; start < chunkTexts.length; start += providerLimit) {
3036
3107
  await processChunk(
3037
- chunkTexts.slice(start, start + fallbackSize),
3038
- chunkIndices.slice(start, start + fallbackSize)
3108
+ chunkTexts.slice(start, start + providerLimit),
3109
+ chunkIndices.slice(start, start + providerLimit)
3039
3110
  );
3040
3111
  }
3041
3112
  return;
@@ -3899,22 +3970,23 @@ function getLLMConfig() {
3899
3970
  function setLLMConfig(config) {
3900
3971
  currentConfig = config;
3901
3972
  }
3902
- async function callLLM(systemPrompt, userMessage) {
3973
+ async function callLLM(systemPrompt, userMessage, signal) {
3903
3974
  if (!currentConfig) {
3904
3975
  throw new Error("LLM not configured. Set MEMORIX_LLM_API_KEY or OPENAI_API_KEY.");
3905
3976
  }
3906
3977
  if (currentConfig.provider === "anthropic") {
3907
- return callAnthropic(systemPrompt, userMessage);
3978
+ return callAnthropic(systemPrompt, userMessage, signal);
3908
3979
  }
3909
- return callOpenAICompatible(systemPrompt, userMessage);
3980
+ return callOpenAICompatible(systemPrompt, userMessage, signal);
3910
3981
  }
3911
- async function callOpenAICompatible(systemPrompt, userMessage) {
3982
+ async function callOpenAICompatible(systemPrompt, userMessage, signal) {
3912
3983
  const config = currentConfig;
3913
3984
  let base = config.baseUrl.replace(/\/+$/, "");
3914
3985
  if (!base.endsWith("/v1")) base += "/v1";
3915
3986
  const url = `${base}/chat/completions`;
3987
+ const fetchSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(LLM_CALL_TIMEOUT_MS)]) : AbortSignal.timeout(LLM_CALL_TIMEOUT_MS);
3916
3988
  const response = await fetch(url, {
3917
- signal: AbortSignal.timeout(LLM_CALL_TIMEOUT_MS),
3989
+ signal: fetchSignal,
3918
3990
  method: "POST",
3919
3991
  headers: {
3920
3992
  "Content-Type": "application/json",
@@ -3931,10 +4003,10 @@ async function callOpenAICompatible(systemPrompt, userMessage) {
3931
4003
  })
3932
4004
  });
3933
4005
  if (!response.ok) {
3934
- const error = await readResponseText(response, void 0, 64 * 1024).catch(() => "unknown error");
4006
+ const error = await readResponseText(response, signal, 64 * 1024).catch(() => "unknown error");
3935
4007
  throw new Error(`LLM API error (${response.status}): ${error}`);
3936
4008
  }
3937
- const data = await readResponseJson(response);
4009
+ const data = await readResponseJson(response, signal);
3938
4010
  return {
3939
4011
  content: data.choices[0]?.message?.content ?? "",
3940
4012
  usage: data.usage ? {
@@ -3943,11 +4015,12 @@ async function callOpenAICompatible(systemPrompt, userMessage) {
3943
4015
  } : void 0
3944
4016
  };
3945
4017
  }
3946
- async function callAnthropic(systemPrompt, userMessage) {
4018
+ async function callAnthropic(systemPrompt, userMessage, signal) {
3947
4019
  const config = currentConfig;
3948
4020
  const url = `${config.baseUrl}/messages`;
4021
+ const fetchSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(LLM_CALL_TIMEOUT_MS)]) : AbortSignal.timeout(LLM_CALL_TIMEOUT_MS);
3949
4022
  const response = await fetch(url, {
3950
- signal: AbortSignal.timeout(LLM_CALL_TIMEOUT_MS),
4023
+ signal: fetchSignal,
3951
4024
  method: "POST",
3952
4025
  headers: {
3953
4026
  "Content-Type": "application/json",
@@ -3965,10 +4038,10 @@ async function callAnthropic(systemPrompt, userMessage) {
3965
4038
  })
3966
4039
  });
3967
4040
  if (!response.ok) {
3968
- const error = await readResponseText(response, void 0, 64 * 1024).catch(() => "unknown error");
4041
+ const error = await readResponseText(response, signal, 64 * 1024).catch(() => "unknown error");
3969
4042
  throw new Error(`Anthropic API error (${response.status}): ${error}`);
3970
4043
  }
3971
- const data = await readResponseJson(response);
4044
+ const data = await readResponseJson(response, signal);
3972
4045
  return {
3973
4046
  content: data.content[0]?.text ?? "",
3974
4047
  usage: data.usage ? {
@@ -4473,6 +4546,57 @@ function withTimeout(promise, ms, label) {
4473
4546
  );
4474
4547
  });
4475
4548
  }
4549
+ function withTimeoutSignal(operation, ms, label, parentSignal) {
4550
+ const controller = new AbortController();
4551
+ const signal = parentSignal ? AbortSignal.any([parentSignal, controller.signal]) : controller.signal;
4552
+ return new Promise((resolve, reject) => {
4553
+ let settled = false;
4554
+ let timedOut = false;
4555
+ let timer;
4556
+ const cleanup = () => {
4557
+ if (timer !== void 0) clearTimeout(timer);
4558
+ parentSignal?.removeEventListener("abort", onParentAbort);
4559
+ };
4560
+ const onParentAbort = () => {
4561
+ if (settled || timedOut) return;
4562
+ settled = true;
4563
+ cleanup();
4564
+ reject(parentSignal?.reason ?? new Error(`${label} aborted`));
4565
+ };
4566
+ parentSignal?.addEventListener("abort", onParentAbort, { once: true });
4567
+ timer = setTimeout(() => {
4568
+ if (settled) return;
4569
+ timedOut = true;
4570
+ controller.abort(new Error(`${label} timed out after ${ms}ms`));
4571
+ settled = true;
4572
+ cleanup();
4573
+ reject(new Error(`${label} timed out after ${ms}ms`));
4574
+ }, ms);
4575
+ let pending;
4576
+ try {
4577
+ pending = operation(signal);
4578
+ } catch (error) {
4579
+ settled = true;
4580
+ cleanup();
4581
+ reject(error);
4582
+ return;
4583
+ }
4584
+ pending.then(
4585
+ (value) => {
4586
+ if (settled) return;
4587
+ settled = true;
4588
+ cleanup();
4589
+ resolve(value);
4590
+ },
4591
+ (error) => {
4592
+ if (settled) return;
4593
+ settled = true;
4594
+ cleanup();
4595
+ reject(timedOut ? new Error(`${label} timed out after ${ms}ms`) : error);
4596
+ }
4597
+ );
4598
+ });
4599
+ }
4476
4600
  var init_timeout = __esm({
4477
4601
  "src/timeout.ts"() {
4478
4602
  "use strict";
@@ -5337,16 +5461,18 @@ async function searchObservations(options) {
5337
5461
  );
5338
5462
  } else {
5339
5463
  const EMBEDDING_TIMEOUT_MS = 15e3;
5340
- queryVector = await withTimeout(
5341
- provider2.embedInput ? provider2.embedInput(
5464
+ queryVector = await withTimeoutSignal(
5465
+ (signal) => provider2.embedInput ? provider2.embedInput(
5342
5466
  { modality: "text", text: expandedEmbeddingQuery },
5343
- { intent: "query", timeoutMs: EMBEDDING_TIMEOUT_MS, retry: false }
5467
+ { intent: "query", timeoutMs: EMBEDDING_TIMEOUT_MS, retry: false, signal }
5344
5468
  ) : provider2.embed(expandedEmbeddingQuery, {
5345
5469
  timeoutMs: EMBEDDING_TIMEOUT_MS,
5346
- retry: false
5470
+ retry: false,
5471
+ signal
5347
5472
  }),
5348
5473
  EMBEDDING_TIMEOUT_MS,
5349
- "Embedding"
5474
+ "Embedding",
5475
+ options.signal
5350
5476
  );
5351
5477
  mark("embedding");
5352
5478
  const cjkRatio = (originalQuery.match(/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g) || []).length / originalQuery.length;
@@ -143266,7 +143392,13 @@ async function maintainLongTermMemories(input) {
143266
143392
  }
143267
143393
  for (const group of groups.values()) {
143268
143394
  if (group.length < 2) continue;
143269
- group.sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt));
143395
+ group.sort((a, b) => {
143396
+ const byCreatedAt = Date.parse(b.createdAt) - Date.parse(a.createdAt);
143397
+ if (byCreatedAt !== 0) return byCreatedAt;
143398
+ const byUpdatedAt = Date.parse(b.updatedAt) - Date.parse(a.updatedAt);
143399
+ if (byUpdatedAt !== 0) return byUpdatedAt;
143400
+ return b.id.localeCompare(a.id);
143401
+ });
143270
143402
  const newest = group[0];
143271
143403
  for (const older of group.slice(1)) {
143272
143404
  try {
@@ -146658,6 +146790,7 @@ var init_task_lens = __esm({
146658
146790
  var workflows_exports = {};
146659
146791
  __export(workflows_exports, {
146660
146792
  WORKFLOW_ADAPTER_TARGETS: () => WORKFLOW_ADAPTER_TARGETS,
146793
+ WORKFLOW_AGENT_TARGETS: () => WORKFLOW_AGENT_TARGETS,
146661
146794
  applyWorkflowAdapter: () => applyWorkflowAdapter,
146662
146795
  importWindsurfWorkflows: () => importWindsurfWorkflows,
146663
146796
  parseWorkflowMarkdown: () => parseWorkflowMarkdown,
@@ -146723,7 +146856,7 @@ function optionalStatus(data) {
146723
146856
  return value;
146724
146857
  }
146725
146858
  function normalizeAgents(values) {
146726
- const unknown3 = values.filter((value) => !KNOWN_AGENTS.includes(value));
146859
+ const unknown3 = values.filter((value) => !WORKFLOW_AGENT_TARGETS.includes(value));
146727
146860
  if (unknown3.length > 0) {
146728
146861
  throw new Error("workflow frontmatter has unsupported agent: " + unknown3[0]);
146729
146862
  }
@@ -146999,7 +147132,7 @@ function taskScore(workflow, task) {
146999
147132
  return { score, reasons: [...new Set(reasons)] };
147000
147133
  }
147001
147134
  function selectWorkflows(input) {
147002
- const selected = input.workflows.filter((workflow) => workflow.status === "active").map((workflow) => {
147135
+ const selected = input.workflows.filter((workflow) => workflow.status === "active" && (!input.agent || workflow.compatibleAgents.includes(input.agent))).map((workflow) => {
147003
147136
  const match2 = taskScore(workflow, input.task);
147004
147137
  const cautions = input.store && input.projectId ? input.store.recentFailureCautions(input.projectId, workflow.id) : [];
147005
147138
  return {
@@ -147279,7 +147412,7 @@ async function selectWorkspaceWorkflows(input) {
147279
147412
  errors: synced.errors
147280
147413
  };
147281
147414
  }
147282
- var KNOWN_AGENTS, WORKFLOW_ADAPTER_TARGETS, STATUS_VALUES, LENS_TERMS, GENERIC_WORKFLOW_LENSES, GENERIC_WORKFLOW_TERMS;
147415
+ var WORKFLOW_AGENT_TARGETS, WORKFLOW_ADAPTER_TARGETS, STATUS_VALUES, LENS_TERMS, GENERIC_WORKFLOW_LENSES, GENERIC_WORKFLOW_TERMS;
147283
147416
  var init_workflows2 = __esm({
147284
147417
  "src/knowledge/workflows.ts"() {
147285
147418
  "use strict";
@@ -147291,7 +147424,7 @@ var init_workflows2 = __esm({
147291
147424
  init_workflow_store();
147292
147425
  init_outcome_store();
147293
147426
  init_task_lens();
147294
- KNOWN_AGENTS = [
147427
+ WORKFLOW_AGENT_TARGETS = [
147295
147428
  "windsurf",
147296
147429
  "cursor",
147297
147430
  "claude-code",
@@ -2,6 +2,43 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [Unreleased]
6
+
7
+ ## [1.6.0] - 2026-08-17
8
+
9
+ ### Added
10
+ - **Bounded context receipts** -- `memorix context|resume --brief-json`,
11
+ `memorix codegraph context-pack --brief-json`, and MCP project-context
12
+ `format: "receipt"` return only the agent-ready brief, selection receipt,
13
+ CodeGraph quality, and optional Agent loadout. The detailed `--json` and
14
+ MCP `format: "json"` forms remain available for diagnostics.
15
+ - **Task-scoped Agent loadouts** -- `--agent <target>` selects only compatible
16
+ project workflows and reports their required context and workflow tools.
17
+ This is guidance, not a replacement for the host agent's authorization.
18
+ - **Explicit external CodeGraph lifecycle** -- `memorix codegraph init|sync`
19
+ manages an already installed local CodeGraph index. It never invokes
20
+ CodeGraph's agent installer or writes Agent configuration.
21
+
22
+ ### Changed
23
+ - **Honest CodeGraph status** -- Status now distinguishes the persistent Lite
24
+ Code State index from the provider actually usable for the current task.
25
+ A stale external graph falls back to Lite instead of being presented as fresh.
26
+ - **Bounded fallback guidance** -- Newly generated Agent rules teach
27
+ `--brief-json` for the one CLI fallback; `--json` is reserved for deliberate
28
+ detailed diagnostics.
29
+
30
+ ## [1.5.1] - 2026-08-16
31
+
32
+ ### Fixed
33
+ - **Bounded formation cancellation** -- Formation deadlines now abort the
34
+ LLM and vector-search work they started, including response-body reads and
35
+ retry waits. A provider that ignores cancellation still cannot hold the
36
+ caller past its deadline.
37
+ - **Embedding error classification** -- Only an explicit HTTP 400 batch-shape
38
+ error may trigger batch splitting. Authentication, billing, quota, and
39
+ upstream errors fail once instead of multiplying latency through recursive
40
+ retries.
41
+
5
42
  ## [1.5.0] - 2026-08-14
6
43
 
7
44
  ### Added