memorix 1.5.1 → 1.6.1

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 (52) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +3 -3
  3. package/README.zh-CN.md +3 -3
  4. package/dist/cli/index.js +558 -102
  5. package/dist/cli/memcode.js +12 -1
  6. package/dist/index.js +221 -27
  7. package/dist/maintenance-runner.js +59 -11
  8. package/dist/media-audio-runner.js +59 -11
  9. package/dist/media-video-runner.js +59 -11
  10. package/dist/memcode-runtime/CHANGELOG.md +47 -0
  11. package/dist/sdk.js +220 -26
  12. package/dist/vector-backfill-runner.js +59 -11
  13. package/docs/1.2.4-PERSISTENT-MEMORY-DELIVERY.md +1 -1
  14. package/docs/AGENT_OPERATOR_PLAYBOOK.md +11 -3
  15. package/docs/API_REFERENCE.md +2 -2
  16. package/docs/DEVELOPMENT.md +5 -3
  17. package/docs/DOCKER.md +5 -1
  18. package/docs/PERFORMANCE.md +2 -2
  19. package/docs/README.md +3 -1
  20. package/docs/SETUP.md +8 -2
  21. package/package.json +1 -1
  22. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  23. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  24. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  25. package/plugins/copilot/memorix/plugin.json +1 -1
  26. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  27. package/plugins/omp/memorix/package.json +1 -1
  28. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  29. package/plugins/pi/memorix/package.json +1 -1
  30. package/server.json +2 -2
  31. package/src/cli/commands/agent-integrations.ts +9 -3
  32. package/src/cli/commands/codegraph.ts +48 -5
  33. package/src/cli/commands/context.ts +20 -4
  34. package/src/cli/commands/doctor.ts +46 -15
  35. package/src/cli/commands/hook.ts +13 -1
  36. package/src/cli/commands/resume.ts +4 -0
  37. package/src/cli/commands/serve-http.ts +68 -7
  38. package/src/cli/heap.ts +76 -0
  39. package/src/cli/invocation.ts +7 -4
  40. package/src/codegraph/auto-context.ts +4 -1
  41. package/src/codegraph/context-pack.ts +3 -1
  42. package/src/codegraph/external-provider.ts +93 -0
  43. package/src/embedding/provider.ts +76 -6
  44. package/src/hooks/handler.ts +9 -18
  45. package/src/hooks/installers/index.ts +1 -1
  46. package/src/hooks/lifecycle.ts +173 -0
  47. package/src/hooks/official-skills.ts +3 -3
  48. package/src/hooks/rules/memorix-agent-rules.md +1 -1
  49. package/src/knowledge/context-receipt.ts +41 -0
  50. package/src/knowledge/workflows.ts +6 -3
  51. package/src/knowledge/workset.ts +56 -2
  52. package/src/server.ts +15 -3
@@ -3206,6 +3206,7 @@ __export(provider_exports, {
3206
3206
  MAX_INLINE_EMBEDDING_BYTES: () => MAX_INLINE_EMBEDDING_BYTES,
3207
3207
  UnsupportedEmbeddingModalityError: () => UnsupportedEmbeddingModalityError,
3208
3208
  getEmbeddingProvider: () => getEmbeddingProvider,
3209
+ getEmbeddingRuntimeHealth: () => getEmbeddingRuntimeHealth,
3209
3210
  isEmbeddingExplicitlyDisabled: () => isEmbeddingExplicitlyDisabled,
3210
3211
  isVectorSearchAvailable: () => isVectorSearchAvailable,
3211
3212
  resetProvider: () => resetProvider,
@@ -3313,13 +3314,36 @@ function isTemporaryEmbeddingFailure(error) {
3313
3314
  if (!(error instanceof Error)) return false;
3314
3315
  return /embedding api error \(401\)/i.test(error.message) || /invalid_api_key/i.test(error.message) || /incorrect api key/i.test(error.message) || /unauthorized/i.test(error.message) || /embedding api timeout/i.test(error.message) || /embedding api error \((402|429|5\d\d)\)/i.test(error.message) || /quota exceeded/i.test(error.message) || /account balance/i.test(error.message) || /fetch failed/i.test(error.message) || /econnreset/i.test(error.message) || /econnrefused/i.test(error.message) || /temporarily unavailable/i.test(error.message);
3315
3316
  }
3317
+ function classifyEmbeddingFailure(error) {
3318
+ const message = error instanceof Error ? error.message : String(error);
3319
+ if (/embedding api error \(401\)|invalid_api_key|incorrect api key|unauthorized/i.test(message)) {
3320
+ return "authorization";
3321
+ }
3322
+ if (/embedding api error \(402\)|quota exceeded|account balance/i.test(message)) {
3323
+ return "billing";
3324
+ }
3325
+ if (/embedding api timeout|abort/i.test(message)) {
3326
+ return "timeout";
3327
+ }
3328
+ return "upstream";
3329
+ }
3316
3330
  function markTemporaryFailure(reason) {
3317
3331
  provider = null;
3318
3332
  initPromise = null;
3319
3333
  lastInitWasTemporaryFailure = true;
3320
3334
  lastFailureTimestamp = Date.now();
3335
+ lastEmbeddingFailure = {
3336
+ kind: classifyEmbeddingFailure(reason),
3337
+ occurredAt: lastFailureTimestamp
3338
+ };
3321
3339
  const message = reason instanceof Error ? reason.message : String(reason);
3322
- warnOnce(`[memorix] Embedding provider temporarily unavailable at runtime: ${message}`);
3340
+ warnOnce(`[memorix] Embedding provider unavailable at runtime: ${message}`);
3341
+ }
3342
+ function markProviderReady(candidate) {
3343
+ provider = wrapProvider(candidate);
3344
+ lastInitWasTemporaryFailure = false;
3345
+ lastEmbeddingFailure = null;
3346
+ return provider;
3323
3347
  }
3324
3348
  async function createFastEmbedProvider() {
3325
3349
  try {
@@ -3435,35 +3459,35 @@ async function getEmbeddingProvider(options = {}) {
3435
3459
  if (mode === "fastembed") {
3436
3460
  const initialized = await createFastEmbedProvider();
3437
3461
  if (!initialized) return null;
3438
- provider = wrapProvider(initialized);
3462
+ provider = markProviderReady(initialized);
3439
3463
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
3440
3464
  return provider;
3441
3465
  }
3442
3466
  if (mode === "transformers") {
3443
3467
  const initialized = await createTransformersProvider();
3444
3468
  if (!initialized) return null;
3445
- provider = wrapProvider(initialized);
3469
+ provider = markProviderReady(initialized);
3446
3470
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
3447
3471
  return provider;
3448
3472
  }
3449
3473
  if (mode === "api") {
3450
3474
  const initialized = await createAPIProvider(options);
3451
3475
  if (!initialized) return null;
3452
- provider = wrapProvider(initialized);
3476
+ provider = markProviderReady(initialized);
3453
3477
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
3454
3478
  return provider;
3455
3479
  }
3456
3480
  if (hasAPIEmbeddingConfig()) {
3457
3481
  const initialized = await createAPIProvider(options);
3458
3482
  if (initialized) {
3459
- provider = wrapProvider(initialized);
3483
+ provider = markProviderReady(initialized);
3460
3484
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
3461
3485
  return provider;
3462
3486
  }
3463
3487
  }
3464
3488
  const localFallback = await createLocalFallbackProvider();
3465
3489
  if (localFallback) {
3466
- provider = wrapProvider(localFallback);
3490
+ provider = markProviderReady(localFallback);
3467
3491
  warnOnce(`[memorix] Embedding provider: ${provider.name} (${provider.dimensions}d)`);
3468
3492
  return provider;
3469
3493
  }
@@ -3485,6 +3509,27 @@ async function isVectorSearchAvailable() {
3485
3509
  const p = await getEmbeddingProvider();
3486
3510
  return p !== null;
3487
3511
  }
3512
+ function getEmbeddingRuntimeHealth() {
3513
+ const mode = getEmbeddingMode2();
3514
+ if (mode === "off") return { status: "disabled" };
3515
+ if (lastInitWasTemporaryFailure && lastEmbeddingFailure) {
3516
+ return {
3517
+ status: "degraded",
3518
+ failure: {
3519
+ kind: lastEmbeddingFailure.kind,
3520
+ occurredAt: new Date(lastEmbeddingFailure.occurredAt).toISOString(),
3521
+ retryAfterMs: Math.max(0, RETRY_COOLDOWN_MS - (Date.now() - lastEmbeddingFailure.occurredAt))
3522
+ }
3523
+ };
3524
+ }
3525
+ if (provider) {
3526
+ return {
3527
+ status: "ready",
3528
+ provider: { name: provider.name, dimensions: provider.dimensions }
3529
+ };
3530
+ }
3531
+ return { status: "uninitialized" };
3532
+ }
3488
3533
  function isEmbeddingExplicitlyDisabled() {
3489
3534
  return getEmbeddingMode2() === "off";
3490
3535
  }
@@ -3493,10 +3538,11 @@ function resetProvider() {
3493
3538
  initPromise = null;
3494
3539
  lastInitWasTemporaryFailure = false;
3495
3540
  lastFailureTimestamp = 0;
3541
+ lastEmbeddingFailure = null;
3496
3542
  lastEmbeddingWarning = "";
3497
3543
  lastEmbeddingWarningAt = 0;
3498
3544
  }
3499
- var EmbeddingInputError, UnsupportedEmbeddingModalityError, MAX_INLINE_EMBEDDING_BYTES, provider, initPromise, lastInitWasTemporaryFailure, lastEmbeddingWarning, lastEmbeddingWarningAt, WARNING_COOLDOWN_MS, RETRY_COOLDOWN_MS, lastFailureTimestamp;
3545
+ var EmbeddingInputError, UnsupportedEmbeddingModalityError, MAX_INLINE_EMBEDDING_BYTES, provider, initPromise, lastInitWasTemporaryFailure, lastEmbeddingFailure, lastEmbeddingWarning, lastEmbeddingWarningAt, WARNING_COOLDOWN_MS, RETRY_COOLDOWN_MS, lastFailureTimestamp;
3500
3546
  var init_provider = __esm({
3501
3547
  "src/embedding/provider.ts"() {
3502
3548
  "use strict";
@@ -3521,6 +3567,7 @@ var init_provider = __esm({
3521
3567
  provider = null;
3522
3568
  initPromise = null;
3523
3569
  lastInitWasTemporaryFailure = false;
3570
+ lastEmbeddingFailure = null;
3524
3571
  lastEmbeddingWarning = "";
3525
3572
  lastEmbeddingWarningAt = 0;
3526
3573
  WARNING_COOLDOWN_MS = 3e4;
@@ -147780,6 +147827,7 @@ var init_task_lens = __esm({
147780
147827
  var workflows_exports = {};
147781
147828
  __export(workflows_exports, {
147782
147829
  WORKFLOW_ADAPTER_TARGETS: () => WORKFLOW_ADAPTER_TARGETS,
147830
+ WORKFLOW_AGENT_TARGETS: () => WORKFLOW_AGENT_TARGETS,
147783
147831
  applyWorkflowAdapter: () => applyWorkflowAdapter,
147784
147832
  importWindsurfWorkflows: () => importWindsurfWorkflows,
147785
147833
  parseWorkflowMarkdown: () => parseWorkflowMarkdown,
@@ -147845,7 +147893,7 @@ function optionalStatus(data) {
147845
147893
  return value;
147846
147894
  }
147847
147895
  function normalizeAgents(values) {
147848
- const unknown3 = values.filter((value) => !KNOWN_AGENTS.includes(value));
147896
+ const unknown3 = values.filter((value) => !WORKFLOW_AGENT_TARGETS.includes(value));
147849
147897
  if (unknown3.length > 0) {
147850
147898
  throw new Error("workflow frontmatter has unsupported agent: " + unknown3[0]);
147851
147899
  }
@@ -148121,7 +148169,7 @@ function taskScore(workflow, task) {
148121
148169
  return { score, reasons: [...new Set(reasons)] };
148122
148170
  }
148123
148171
  function selectWorkflows(input) {
148124
- const selected = input.workflows.filter((workflow) => workflow.status === "active").map((workflow) => {
148172
+ const selected = input.workflows.filter((workflow) => workflow.status === "active" && (!input.agent || workflow.compatibleAgents.includes(input.agent))).map((workflow) => {
148125
148173
  const match2 = taskScore(workflow, input.task);
148126
148174
  const cautions = input.store && input.projectId ? input.store.recentFailureCautions(input.projectId, workflow.id) : [];
148127
148175
  return {
@@ -148401,7 +148449,7 @@ async function selectWorkspaceWorkflows(input) {
148401
148449
  errors: synced.errors
148402
148450
  };
148403
148451
  }
148404
- var KNOWN_AGENTS, WORKFLOW_ADAPTER_TARGETS, STATUS_VALUES, LENS_TERMS, GENERIC_WORKFLOW_LENSES, GENERIC_WORKFLOW_TERMS;
148452
+ var WORKFLOW_AGENT_TARGETS, WORKFLOW_ADAPTER_TARGETS, STATUS_VALUES, LENS_TERMS, GENERIC_WORKFLOW_LENSES, GENERIC_WORKFLOW_TERMS;
148405
148453
  var init_workflows2 = __esm({
148406
148454
  "src/knowledge/workflows.ts"() {
148407
148455
  "use strict";
@@ -148413,7 +148461,7 @@ var init_workflows2 = __esm({
148413
148461
  init_workflow_store();
148414
148462
  init_outcome_store();
148415
148463
  init_task_lens();
148416
- KNOWN_AGENTS = [
148464
+ WORKFLOW_AGENT_TARGETS = [
148417
148465
  "windsurf",
148418
148466
  "cursor",
148419
148467
  "claude-code",
@@ -26,7 +26,7 @@ database, a transcript archive, or another default MCP tool.
26
26
  | Start a new, unrelated task | A bounded task Workset. No automatic prior-session text. |
27
27
  | Continue or take over prior work | The same Workset plus a bounded prior-work projection. |
28
28
  | MCP is available | Use the existing `memorix_project_context` tool with the real task. |
29
- | MCP is unavailable | Make one CLI call: `memorix resume "<task>" --json` for continuation or `memorix context "<task>" --json` for a new task. |
29
+ | MCP is unavailable | Make one CLI call: `memorix resume "<task>" --brief-json` for continuation or `memorix context "<task>" --brief-json` for a new task. `--json` remains the detailed diagnostic form. |
30
30
  | Claude Code hook is installed | An explicit continuation prompt receives the same bounded prior-work Workset through the official `UserPromptSubmit` context channel, unless `memory.inject = "silent"`. |
31
31
  | Read-only assessment | The brief may be read, but Memorix does not persist a new observation unless the user explicitly asks to save one. |
32
32
  | Need a deliberate session handoff | Keep using the explicit session/handoff surfaces; this projection does not replace them. |
@@ -58,7 +58,7 @@ An unbound terminal is intentionally project-scoped: it reads and writes project
58
58
 
59
59
  Long-term memory is a reviewed layer, not a generic global note store. `memorix memory long-term add` creates a candidate; `qualify` and `approve` are explicit audit transitions before it can appear in a bounded task Workset. A project-derived observation, code fact, Git fact, test, session, Claim, or workflow stays project-bound. Only manual or user-confirmed `user + portable` records can cross local projects for the same local installation identity. When one is task-matched in a Workset, it is intentionally usable background even if its origin differs; use its `durable:<id>` reference with `memorix_detail` only when the full record is needed. Use `archive` or `supersede` when a durable item is no longer current.
60
60
 
61
- `--cwd <git-project>` is the canonical way to run direct CLI work from outside a repository. It changes the command's project anchor only; Git remains the source of truth for final project identity.
61
+ `--cwd <git-project>` is the canonical way to run direct CLI work from outside a repository. It changes the command's project anchor only; Git remains the source of truth for final project identity. Existing directories are canonicalized to their real filesystem paths before binding, which prevents macOS aliases such as `/var` and `/private/var` from splitting one project or hiding a Claude local MCP entry.
62
62
 
63
63
  Use MCP when:
64
64
 
@@ -164,7 +164,12 @@ Do not assume the HTTP connection alone tells Memorix which project the user mea
164
164
 
165
165
  The HTTP service is normally started with `memorix background start`; the same project-binding rules apply when you run `memorix serve-http --port 3211` in the foreground.
166
166
 
167
- HTTP MCP sessions idle out after 30 minutes by default. If the user's HTTP MCP client is sensitive to stale session IDs after long idle periods, set `MEMORIX_SESSION_TIMEOUT_MS` before starting or restarting the service. Example: `MEMORIX_SESSION_TIMEOUT_MS=86400000` keeps sessions alive for 24 hours.
167
+ HTTP MCP sessions idle out after 12 hours by default. An expired session now
168
+ receives a fast `404` reinitialize hint instead of a generic transport failure.
169
+ If the user's HTTP MCP client needs a longer idle period, set
170
+ `MEMORIX_SESSION_TIMEOUT_MS` before starting or restarting the service.
171
+ Example: `MEMORIX_SESSION_TIMEOUT_MS=86400000` keeps sessions alive for 24
172
+ hours. Set it to `0` only when the host has its own reliable session cleanup.
168
173
 
169
174
  ### Do not confuse project config and global config
170
175
 
@@ -702,7 +707,10 @@ If it shows "Not running" or "dead":
702
707
  memorix background ensure
703
708
  ```
704
709
 
705
- If the client is connected but starts failing after roughly 30 minutes of no Memorix tool use, check for stale HTTP session expiry rather than treating it as project binding failure. Restart the service with a longer idle timeout:
710
+ If the client is connected but starts failing after a long inactive period,
711
+ check for the explicit stale-session `404` reinitialize hint rather than
712
+ treating it as a project-binding failure. The default idle window is 12 hours;
713
+ restart with a longer window only when the client genuinely needs it:
706
714
 
707
715
  ```powershell
708
716
  $env:MEMORIX_SESSION_TIMEOUT_MS = "86400000"
@@ -115,7 +115,7 @@ MCP:
115
115
  - `memorix_context_pack` builds a task-specific packet with reliable current memories, lower-trust unbound memories, current code facts, freshness warnings, suggested reads, and suggested verification.
116
116
  - `memorix_graph_context` is an advanced compact graph overview. It starts from a small task-matched baseline and can add only directly related, evidence-governed memories. Its `Graph evidence` section includes relation provenance rather than recursively dumping graph neighbors.
117
117
 
118
- `memorix context` defaults to `--refresh auto`, so first use can seed Code State without a separate manual `memorix codegraph refresh`. Its brief puts live package/changelog/Git facts before memory hints and flags an old `ACTIVE_WORK.md`, legacy `progress.txt`, or legacy dev-log note as historical when it predates the latest changelog, so agents should treat current facts as the source of truth when files disagree. Task lenses keep the packet shaped to the work: bugfix briefs prefer failing tests and repros, release briefs prefer metadata/changelog/package checks, and onboarding briefs prefer docs and entry points while hiding unrelated suspect details. Continuation delivery is separate from the task lens: continuation language in `memorix_project_context` enables a bounded prior-work projection, and `memorix resume "..."` makes that choice explicit. It includes only the latest useful session summary, up to three readable durable memories, and at most one recent source-labelled compact checkpoint. Checkpoints are host lifecycle evidence, not durable memory or transcript backups; ordinary new tasks do not receive historical-session context. A completed MCP brief is the default retrieval boundary: search, detail, or Context Pack should expand it only for a named missing fact or an explicit request for deeper history. Use `--refresh never` for read-only inspection and `--refresh always` when you want to force a fresh scan.
118
+ `memorix context` defaults to `--refresh auto`, so first use can seed Code State without a separate manual `memorix codegraph refresh`. Its brief puts live package/changelog/Git facts before memory hints and flags an old `ACTIVE_WORK.md`, legacy `progress.txt`, or legacy dev-log note as historical when it predates the latest changelog, so agents should treat current facts as the source of truth when files disagree. Use `--brief-json` for the same bounded brief plus a receipt of selected and omitted evidence; `--json` remains the detailed legacy diagnostic form. Task lenses keep the packet shaped to the work: bugfix briefs prefer failing tests and repros, release briefs prefer metadata/changelog/package checks, and onboarding briefs prefer docs and entry points while hiding unrelated suspect details. Continuation delivery is separate from the task lens: continuation language in `memorix_project_context` enables a bounded prior-work projection, and `memorix resume "..."` makes that choice explicit. It includes only the latest useful session summary, up to three readable durable memories, and at most one recent source-labelled compact checkpoint. Checkpoints are host lifecycle evidence, not durable memory or transcript backups; ordinary new tasks do not receive historical-session context. A completed MCP brief is the default retrieval boundary: search, detail, or Context Pack should expand it only for a named missing fact or an explicit request for deeper history. Use `--refresh never` for read-only inspection and `--refresh always` when you want to force a fresh scan.
119
119
 
120
120
  Project-specific generated, vendored, or cache paths can be excluded from Code State with `[codegraph].exclude_patterns` in `memorix.toml` or `~/.memorix/config.toml` (`codegraph.excludePatterns` in legacy YAML). User patterns extend the built-in excludes and are applied to indexing, Project Context suggested reads, and Context Pack suggested reads.
121
121
 
@@ -652,7 +652,7 @@ Coordination state is opt-in project state for tasks, handoff messages, locks, a
652
652
 
653
653
  Runtime environment:
654
654
 
655
- - `MEMORIX_SESSION_TIMEOUT_MS` — HTTP MCP session idle timeout in milliseconds. Default: `1800000` (30 minutes). Increase this for clients that do not transparently reinitialize after stale HTTP session IDs, for example `86400000` for 24 hours.
655
+ - `MEMORIX_SESSION_TIMEOUT_MS` — HTTP MCP session idle timeout in milliseconds. Default: `43200000` (12 hours). Expired session IDs receive a fast `404` with a reinitialize hint. Increase this for long-lived clients, for example `86400000` for 24 hours; set `0` only when the host provides reliable lifecycle cleanup.
656
656
 
657
657
  ### `team_manage`
658
658
 
@@ -326,9 +326,11 @@ Notes:
326
326
  - `server.json` is the official MCP Registry record. Keep it synchronized with
327
327
  `package.json` by running `npm run sync:mcp-registry`; verify it with
328
328
  `npm run check:mcp-registry` before a release.
329
- - The GitHub publish workflow publishes npm first, then uses GitHub OIDC to
330
- publish the same verified version to the official MCP Registry. It does not
331
- require a Registry token or private key in repository secrets.
329
+ - The GitHub publish workflow can publish npm first, then uses GitHub OIDC to
330
+ publish the same verified version to the official MCP Registry. When npm was
331
+ already published manually, run the workflow with `publish_npm=false` to
332
+ publish only the Registry record. It does not require a Registry token or
333
+ private key in repository secrets.
332
334
  - `@memorix/ai`, `@memorix/agent-core`, `@memorix/tui`, and `@memorix/memcode` are internal workspaces. Their code is bundled into the root `memorix` distribution and they must stay `private` unless the project deliberately establishes an owned npm scope and a separate package-support contract.
333
335
  - npm publish is usually manual, especially when 2FA is enabled
334
336
  - GitHub release automation should not be treated as a substitute for manual runtime validation
package/docs/DOCKER.md CHANGED
@@ -128,7 +128,11 @@ docker compose ps
128
128
 
129
129
  ### HTTP session idle timeout
130
130
 
131
- The HTTP MCP transport defaults to a 30-minute idle timeout. The example `compose.yaml` raises this to 24 hours for IDEs that keep one HTTP MCP session open while the user reads diffs, runs tests, or switches focus.
131
+ The HTTP MCP transport defaults to a 12-hour idle timeout. The example
132
+ `compose.yaml` raises this to 24 hours for IDEs that keep one HTTP MCP session
133
+ open while the user reads diffs, runs tests, or switches focus. When a session
134
+ does expire, the service returns a fast `404` so the client can initialize a
135
+ fresh MCP session.
132
136
 
133
137
  To change it:
134
138
 
@@ -71,7 +71,7 @@ On the release development machine used for this check, the healthy HTTP service
71
71
 
72
72
  | Knob | Default | Use When |
73
73
  | --- | --- | --- |
74
- | `MEMORIX_SESSION_TIMEOUT_MS` | `1800000` (30 min) | Increase for HTTP MCP clients that do not recover from stale session IDs after idle time |
74
+ | `MEMORIX_SESSION_TIMEOUT_MS` | `43200000` (12 h) | Set a shorter GC window for supervised clients, or `0` to disable idle session GC |
75
75
  | `MEMORIX_FORMATION_TIMEOUT_MS` | `12000` (12 s) | Raise when LLM-backed formation should outlive slow proxy/provider hops |
76
76
  | `MEMORIX_LLM_API_KEY` / `OPENAI_API_KEY` | unset | Enable LLM-backed enrichment, extraction, rerank, or skill generation |
77
77
  | `MEMORIX_LLM_TIMEOUT_MS` | `30000` (30 s) | Bound a single LLM-backed extraction/resolve call |
@@ -86,7 +86,7 @@ On the release development machine used for this check, the healthy HTTP service
86
86
  ## Operator Guidance
87
87
 
88
88
  - For memory-only use, prefer stdio MCP or a lightweight `memorix_session_start`; do not join orchestration coordination state by default.
89
- - For long-lived IDE sessions over HTTP, set `MEMORIX_SESSION_TIMEOUT_MS=86400000` before `memorix background start` if your client is stale-session-sensitive.
89
+ - HTTP sessions now default to 12 hours and return a fast `404` with a reinitialize hint after expiry. Set `MEMORIX_SESSION_TIMEOUT_MS=0` only when the host has its own reliable lifecycle cleanup; otherwise retain a bounded GC window.
90
90
  - If LLM-backed formation is timing out against a slow proxy/provider, raise `MEMORIX_FORMATION_TIMEOUT_MS` and keep it higher than `MEMORIX_LLM_TIMEOUT_MS`, because the full pipeline can include multiple LLM-backed stages.
91
91
  - For Docker, use it when you want a managed HTTP service. Do not use image size alone as the runtime memory estimate.
92
92
  - For orchestrated subagent work, expect CPU and disk activity proportional to the spawned agents and verification commands.
package/docs/README.md CHANGED
@@ -117,11 +117,13 @@ Historical/deep-reference documents may describe older designs. If they conflict
117
117
 
118
118
  ## Current Product Line
119
119
 
120
- The released product is on the **1.4 line**. The current baseline has:
120
+ The released product is on the **1.6 line**. The current baseline has:
121
121
 
122
122
  - `memorix setup --agent <agent> --global` is the default agent integration command
123
123
  - `memorix serve` is the manual stdio MCP server for external agents
124
124
  - `memorix background start` runs the shared HTTP MCP service and dashboard
125
+ - context and CodeGraph commands return bounded task receipts before detailed diagnostics
126
+ - HTTP MCP sessions have a bounded, explicit lifecycle and fail with a reinitialize hint after expiry
125
127
  - `memorix integrate --agent <agent>` and `memorix hooks install --agent <agent>` remain manual/fallback generation commands
126
128
  - `memorix` / `memcode` open memcode, the bundled terminal agent that uses the same Memorix memory pool
127
129
  - `~/.memorix/config.toml` and project `memorix.toml` are the user-facing configuration model
package/docs/SETUP.md CHANGED
@@ -42,7 +42,7 @@ Memorix project identity comes from Git. Open or initialize a real Git repositor
42
42
  git init
43
43
  ```
44
44
 
45
- `projectRoot` and current working directory are detection anchors. The final project identity is derived from the real Git root and remote metadata.
45
+ `projectRoot` and current working directory are detection anchors. The final project identity is derived from the real Git root and remote metadata. Memorix resolves an existing project directory to its real filesystem path before binding it, so equivalent macOS paths such as `/var/...` and `/private/var/...` do not create separate project or Claude local-MCP entries.
46
46
 
47
47
  ---
48
48
 
@@ -247,13 +247,19 @@ Generic HTTP MCP config:
247
247
 
248
248
  For multi-project HTTP usage, agents should call `memorix_session_start(projectRoot=...)` with the absolute workspace path when available. This prevents cross-project drift when the background service is shared.
249
249
 
250
- HTTP sessions idle out after 30 minutes by default. For clients that do not recover gracefully from stale HTTP session IDs:
250
+ HTTP sessions idle out after 12 hours by default. An expired session receives a
251
+ fast `404` that tells the client to initialize again, rather than being treated
252
+ as a project-binding failure. For clients that intentionally need a longer
253
+ window:
251
254
 
252
255
  ```powershell
253
256
  $env:MEMORIX_SESSION_TIMEOUT_MS = "86400000"
254
257
  memorix background restart
255
258
  ```
256
259
 
260
+ Set `MEMORIX_SESSION_TIMEOUT_MS=0` only when the host has its own reliable
261
+ session cleanup; otherwise keep a bounded idle window.
262
+
257
263
  ### Option E: Docker HTTP service
258
264
 
259
265
  ```bash
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "memorix",
3
3
  "mcpName": "io.github.AVIDS2/memorix",
4
- "version": "1.5.1",
4
+ "version": "1.6.1",
5
5
  "description": "Local-first shared memory layer for AI coding agents across MCP clients, Git history, reasoning context, and project sessions.",
6
6
  "type": "module",
7
7
  "sideEffects": false,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
3
  "name": "memorix",
4
- "version": "1.5.1",
4
+ "version": "1.6.1",
5
5
  "description": "Shared workspace memory for Claude Code and other AI coding agents.",
6
6
  "author": {
7
7
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.5.1",
3
+ "version": "1.6.1",
4
4
  "description": "Local-first project memory for CodeBuddy Code and other coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.5.1",
3
+ "version": "1.6.1",
4
4
  "description": "Shared workspace memory for Codex and other AI coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.5.1",
3
+ "version": "1.6.1",
4
4
  "description": "Shared local workspace memory for GitHub Copilot CLI and other AI coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.5.1",
3
+ "version": "1.6.1",
4
4
  "mcpServers": {
5
5
  "memorix": {
6
6
  "command": "memorix",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix-omp-package",
3
- "version": "1.5.1",
3
+ "version": "1.6.1",
4
4
  "description": "Memorix shared workspace memory package for Oh-my-Pi.",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.5.1",
3
+ "version": "1.6.1",
4
4
  "description": "Shared workspace memory for OpenClaw and other AI coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix-pi-package",
3
- "version": "1.5.1",
3
+ "version": "1.6.1",
4
4
  "description": "Memorix shared workspace memory package for Pi coding agent.",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
package/server.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "url": "https://github.com/AVIDS2/memorix",
8
8
  "source": "github"
9
9
  },
10
- "version": "1.5.1",
10
+ "version": "1.6.1",
11
11
  "websiteUrl": "https://github.com/AVIDS2/memorix#readme",
12
12
  "icons": [
13
13
  {
@@ -22,7 +22,7 @@
22
22
  {
23
23
  "registryType": "npm",
24
24
  "identifier": "memorix",
25
- "version": "1.5.1",
25
+ "version": "1.6.1",
26
26
  "runtimeHint": "npx",
27
27
  "packageArguments": [
28
28
  {
@@ -1,5 +1,5 @@
1
1
  import { spawnSync } from 'node:child_process';
2
- import { existsSync } from 'node:fs';
2
+ import { existsSync, realpathSync } from 'node:fs';
3
3
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
4
4
  import { basename, dirname, resolve } from 'node:path';
5
5
  import { homedir } from 'node:os';
@@ -580,11 +580,17 @@ function sanitizeServer(server: MCPServerEntry): AgentMcpCheck['server'] {
580
580
  }
581
581
 
582
582
  function normalizeProjectKey(projectRoot: string): string {
583
- return resolve(projectRoot).replace(/\\/g, '/').toLowerCase();
583
+ return canonicalProjectPath(projectRoot).toLowerCase();
584
584
  }
585
585
 
586
586
  function defaultClaudeProjectKey(projectRoot: string): string {
587
- return resolve(projectRoot).replace(/\\/g, '/');
587
+ return canonicalProjectPath(projectRoot);
588
+ }
589
+
590
+ function canonicalProjectPath(projectRoot: string): string {
591
+ const resolved = resolve(projectRoot);
592
+ const canonical = existsSync(resolved) ? realpathSync(resolved) : resolved;
593
+ return canonical.replace(/\\/g, '/');
588
594
  }
589
595
 
590
596
  function getClaudeLocalConfigPath(): string {
@@ -5,11 +5,14 @@ import { assembleContextPackForTask, attachTaskWorkset, buildContextPackPrompt }
5
5
  import { backfillMissingObservationCodeRefs } from '../../codegraph/binder.js';
6
6
  import { collectCurrentProjectFacts, formatGitFact } from '../../codegraph/current-facts.js';
7
7
  import { resolveTaskLens } from '../../codegraph/task-lens.js';
8
- import { getExternalCodeGraphContext, inspectExternalCodeGraph } from '../../codegraph/external-provider.js';
8
+ import { getExternalCodeGraphContext, inspectExternalCodeGraph, runExternalCodeGraphLifecycle } from '../../codegraph/external-provider.js';
9
9
  import type { CodeGraphProviderQuality } from '../../codegraph/types.js';
10
10
  import { getResolvedConfig } from '../../config/resolved-config.js';
11
+ import { buildBoundedContextReceipt } from '../../knowledge/context-receipt.js';
12
+ import { WORKFLOW_AGENT_TARGETS } from '../../knowledge/workflows.js';
11
13
  import { getAllObservations } from '../../memory/observations.js';
12
14
  import { filterReadableObservations } from '../../memory/visibility.js';
15
+ import type { AgentTarget } from '../../types.js';
13
16
  import { emitError, emitResult, getCliProjectContext, parsePositiveInt } from './operator-shared.js';
14
17
 
15
18
  function formatSnapshotStatus(status: ReturnType<CodeGraphStore['status']>): string[] {
@@ -48,6 +51,7 @@ function formatStatus(status: ReturnType<CodeGraphStore['status']>, quality?: Co
48
51
  `- Persistent provider: ${status.provider} (heuristic local index)`,
49
52
  `- External semantic CodeGraph: ${quality.external.state}`
50
53
  + (quality.external.reason ? ` (${quality.external.reason})` : ''),
54
+ `- Task-scoped provider: ${quality.selected} (${quality.selectedQuality})`,
51
55
  ]
52
56
  : []),
53
57
  ].join('\n');
@@ -57,6 +61,8 @@ function formatUsageHint(): string {
57
61
  return [
58
62
  'Usage:',
59
63
  ' memorix codegraph refresh',
64
+ ' memorix codegraph init # explicit local CodeGraph initialization',
65
+ ' memorix codegraph sync # explicit local CodeGraph synchronization',
60
66
  ' memorix codegraph status --json',
61
67
  ' memorix codegraph diff [--from <snapshot>] [--to <snapshot>]',
62
68
  ' memorix codegraph impact --path <relative-source-path>',
@@ -116,25 +122,34 @@ function compactFacts(project: { rootPath: string }): { facts: string[]; dirty:
116
122
  return { facts, dirty: current.git.dirty };
117
123
  }
118
124
 
125
+ function coerceAgentTarget(input?: string): AgentTarget | undefined {
126
+ const value = input?.trim().toLowerCase();
127
+ if (!value) return undefined;
128
+ if (WORKFLOW_AGENT_TARGETS.includes(value as AgentTarget)) return value as AgentTarget;
129
+ throw new Error('agent must be one of: ' + WORKFLOW_AGENT_TARGETS.join(', '));
130
+ }
131
+
119
132
  export default defineCommand({
120
133
  meta: {
121
134
  name: 'codegraph',
122
135
  description: 'Inspect and refresh CodeGraph Memory for the current project',
123
136
  },
124
137
  args: {
125
- action: { type: 'string', description: 'Action: status, refresh, diff, impact, or context-pack' },
138
+ action: { type: 'string', description: 'Action: status, refresh, diff, impact, init, sync, or context-pack' },
126
139
  task: { type: 'string', description: 'Task text for context-pack' },
127
140
  path: { type: 'string', description: 'Indexed relative source path for impact' },
128
141
  limit: { type: 'string', description: 'Max active memories to inspect for context-pack' },
129
142
  from: { type: 'string', description: 'Earlier CodeGraph snapshot id for diff' },
130
143
  to: { type: 'string', description: 'Later CodeGraph snapshot id for diff' },
131
144
  impactLimit: { type: 'string', description: 'Maximum graph relations to inspect for diff impact' },
145
+ agent: { type: 'string', description: 'Optional target agent for compatible workflow selection' },
132
146
  json: { type: 'boolean', description: 'Emit machine-readable JSON output' },
147
+ briefJson: { type: 'boolean', description: 'Emit only the bounded agent brief and receipt JSON for context-pack' },
133
148
  },
134
149
  run: async ({ args }) => {
135
150
  const positional = (args._ as string[]) ?? [];
136
151
  const action = (positional[0] || (args.action as string | undefined) || 'status').toLowerCase();
137
- const asJson = !!args.json;
152
+ const asJson = !!args.json || !!args.briefJson;
138
153
 
139
154
  try {
140
155
  const { project, dataDir, reader } = await getCliProjectContext();
@@ -206,6 +221,27 @@ export default defineCommand({
206
221
  return;
207
222
  }
208
223
 
224
+ case 'init':
225
+ case 'sync': {
226
+ const lifecycle = await runExternalCodeGraphLifecycle({
227
+ action,
228
+ projectRoot: project.rootPath,
229
+ mode: codegraphConfig.externalContext,
230
+ command: codegraphConfig.externalCommand,
231
+ timeoutMs: codegraphConfig.externalTimeoutMs,
232
+ });
233
+ emitResult(
234
+ { project, lifecycle, providerQuality: lifecycle.quality },
235
+ [
236
+ lifecycle.message,
237
+ `- External semantic CodeGraph: ${lifecycle.health.state}`,
238
+ `- Task-scoped provider: ${lifecycle.quality.selected} (${lifecycle.quality.selectedQuality})`,
239
+ ].join('\n'),
240
+ asJson,
241
+ );
242
+ return;
243
+ }
244
+
209
245
  case 'diff': {
210
246
  const snapshots = store.listSnapshots(project.id, 2);
211
247
  const fromSnapshotId = (args.from as string | undefined)?.trim() || snapshots[1]?.id;
@@ -299,13 +335,20 @@ export default defineCommand({
299
335
  ? { runtimeCautions: [{ kind: 'external-codegraph-fallback' as const, message: external.caution }] }
300
336
  : {}),
301
337
  reader,
338
+ agent: coerceAgentTarget(args.agent as string | undefined),
302
339
  });
303
- emitResult({ project, pack, providerQuality: external.quality }, buildContextPackPrompt(pack), asJson);
340
+ emitResult(
341
+ args.briefJson && pack.workset
342
+ ? buildBoundedContextReceipt({ workset: pack.workset, providerQuality: external.quality })
343
+ : { project, pack, providerQuality: external.quality },
344
+ buildContextPackPrompt(pack),
345
+ asJson,
346
+ );
304
347
  return;
305
348
  }
306
349
 
307
350
  default:
308
- emitError(`unknown codegraph action "${action}". Use "status", "refresh", "diff", "impact", or "context-pack".`, asJson);
351
+ emitError(`unknown codegraph action "${action}". Use "status", "refresh", "init", "sync", "diff", "impact", or "context-pack".`, asJson);
309
352
  }
310
353
  } catch (error) {
311
354
  emitError(error instanceof Error ? error.message : String(error), asJson);