@gamaze/hicortex 0.15.1 → 0.15.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -56,9 +56,9 @@ The plugin connects to `http://127.0.0.1:8787` by default. For a remote server,
56
56
 
57
57
  ## Requirements
58
58
 
59
- - Node.js 18+
59
+ - Node.js 20+
60
60
  - **Server mode:** LLM required — Ollama 9b+ (recommended), Claude CLI, or API key (Anthropic, OpenAI, etc.). ~500MB disk for database + embedding model.
61
- - **Client mode:** No local LLM needed. Node.js 18+ and network access to the server are sufficient.
61
+ - **Client mode:** No local LLM needed. Node.js 20+ and network access to the server are sufficient.
62
62
  - **OC plugin:** Requires a running Hicortex server. No local LLM, database, or embedder in the plugin itself.
63
63
 
64
64
  ## What Happens Automatically
@@ -210,6 +210,13 @@ Config at `~/.hicortex/config.json`. Created by `init`. Key options:
210
210
  | `contextAgents` | Per-agent context modes (0.13): `{ "<id>": "override" \| "global" \| "off" }`. Absent + no `agents/<id>/` dir → every agent gets the global set. Boot-time (restart to apply) — see [Per-agent context](#per-agent-context-013) |
211
211
  | `agentName` | This install's per-agent context id sent as `?agent=`. **Unset by default** (CC shares the global context — no `?agent=` sent). Explicit opt-in via `init --agent-name <name>`; `init --agent-name ""` clears it. An empty/whitespace value equals unset |
212
212
  | `nightlyHour` | Local hour (0–23) for the nightly job installed by `init` (defaults: client 2, server 3). Applied on fresh installs; existing schedules are never overwritten |
213
+ | `scoreSimilarityWeight` | Weight of semantic similarity in the ranking score (default: 0.50) |
214
+ | `scoreStrengthWeight` | Weight of effective strength — importance/use/recency of access (default: 0.20) |
215
+ | `scoreConnectionsWeight` | Weight of graph centrality (default: 0.15) |
216
+ | `scoreRecencyWeight` | Weight of the slow recency curve (default: 0.15) |
217
+ | `freshnessBoostDays` | Fresh-memory window: new memories rank higher for this many days (default: 7) |
218
+ | `freshnessBoostWeight` | Size of the fresh-memory bonus at age 0, fading linearly to 0 at the window edge (default: 0.15; set 0 to disable) |
219
+ | `supersededDemotion` | Score multiplier for a memory a later decision reversed (default: 0.50) |
213
220
  | `decayHalfLifeDays` | Memory decay half-life in days at reference importance (default: 365). Larger = slower forgetting; importance, access, and links slow it further |
214
221
  | `searchLimit` / `recentLimit` | Default result counts for search (8) and recent (12) |
215
222
  | `recentWindowDays` | Candidate window for recent recall (default: 180) |
@@ -218,6 +225,7 @@ Config at `~/.hicortex/config.json`. Created by `init`. Key options:
218
225
  | `recallMinSimilarity` | Relevance floor for index entries (default: 0.55; text-search matches always pass) |
219
226
  | `recallReshowTurns` | Turns before an already-shown memory may reappear in the same session (default: 30) |
220
227
  | `recallMinPromptChars` | Prompts shorter than this skip the recall index (default: 20) |
228
+ | `sessionIntentWeight` | Blend weight of the session-intent rolling centroid in the recall search vector: `query = (1-w)·prompt + w·centroid` (default: 0.33; set 0 to disable — pure-prompt recall, the kill-switch). The first turn of a session searches with pure prompt and seeds the centroid; subsequent turns blend so recall follows the session's intent instead of being query-literal. The EMA rate (0.4) is a shipped constant, not configurable |
221
229
  | `dedupMergeThreshold` | Minimum cosine similarity for `hicortex dedup` to cluster memories as near-duplicates (default: 0.92) |
222
230
  | `supersessionMinSimilarity` | Minimum cosine similarity for a nightly supersession candidate pair (default: 0.80) |
223
231
  | `supersessionMaxCalls` | Max classify-tier LLM calls the nightly's supersession stage spends per run (default: 30) |
@@ -135,12 +135,9 @@ export declare function classifyRelationship(source: Memory, target: Memory, sim
135
135
  export declare const DEFAULT_SUPERSESSION_MIN_SIMILARITY = 0.8;
136
136
  /** Default max classify-tier LLM calls (pairs evaluated) spent per nightly run. */
137
137
  export declare const DEFAULT_SUPERSESSION_MAX_CALLS = 30;
138
- /** Default multiplier applied to a superseded memory's base_strength. */
139
- export declare const DEFAULT_SUPERSESSION_PENALTY = 0.5;
140
138
  export interface SupersessionOptions {
141
139
  minSimilarity?: number;
142
140
  maxCalls?: number;
143
- penalty?: number;
144
141
  }
145
142
  export interface SupersessionStageResult {
146
143
  scanned: number;
@@ -38,7 +38,7 @@ var __importStar = (this && this.__importStar) || (function () {
38
38
  };
39
39
  })();
40
40
  Object.defineProperty(exports, "__esModule", { value: true });
41
- exports.DEFAULT_SUPERSESSION_PENALTY = exports.DEFAULT_SUPERSESSION_MAX_CALLS = exports.DEFAULT_SUPERSESSION_MIN_SIMILARITY = exports.BudgetTracker = exports.REFLECTION_CONTRADICTION_MIN_COSINE = exports.l2ToCosine = exports.CROSS_PROJECT_LINK_THRESHOLD = exports.CONSOLIDATE_LINK_TOP_K = exports.CONSOLIDATE_LINK_THRESHOLD = void 0;
41
+ exports.DEFAULT_SUPERSESSION_MAX_CALLS = exports.DEFAULT_SUPERSESSION_MIN_SIMILARITY = exports.BudgetTracker = exports.REFLECTION_CONTRADICTION_MIN_COSINE = exports.l2ToCosine = exports.CROSS_PROJECT_LINK_THRESHOLD = exports.CONSOLIDATE_LINK_TOP_K = exports.CONSOLIDATE_LINK_THRESHOLD = void 0;
42
42
  exports.isContradictionCandidate = isContradictionCandidate;
43
43
  exports.parseJsonLenient = parseJsonLenient;
44
44
  exports.rebuildContentModuleIndex = rebuildContentModuleIndex;
@@ -850,9 +850,7 @@ exports.DEFAULT_SUPERSESSION_MIN_SIMILARITY = 0.8;
850
850
  /** Default max classify-tier LLM calls (pairs evaluated) spent per nightly run. */
851
851
  exports.DEFAULT_SUPERSESSION_MAX_CALLS = 30;
852
852
  /** Default multiplier applied to a superseded memory's base_strength. */
853
- exports.DEFAULT_SUPERSESSION_PENALTY = 0.5;
854
853
  /** Floor under which a superseded memory's base_strength never drops. */
855
- const SUPERSESSION_STRENGTH_FLOOR = 0.1;
856
854
  /** Neighbor pool size before shape/older/similarity filtering narrows to top 5. */
857
855
  const SUPERSESSION_NEIGHBOR_POOL = 15;
858
856
  /** Older-neighbor pairs kept per candidate after filtering. */
@@ -965,7 +963,6 @@ async function stageSupersession(db, llm, budget, embedFn, dryRun, stateDir, opt
965
963
  };
966
964
  const minSimilarity = validNumber(options.minSimilarity, exports.DEFAULT_SUPERSESSION_MIN_SIMILARITY, (n) => n > 0 && n <= 1);
967
965
  const maxCalls = validNumber(options.maxCalls, exports.DEFAULT_SUPERSESSION_MAX_CALLS, (n) => n >= 0);
968
- const penalty = validNumber(options.penalty, exports.DEFAULT_SUPERSESSION_PENALTY, (n) => n > 0 && n <= 1);
969
966
  const startCursor = (0, state_js_1.loadState)(stateDir).supersessionCursor ?? 0;
970
967
  const rows = db
971
968
  .prepare(`SELECT rowid AS __rowid, * FROM memories
@@ -1011,9 +1008,13 @@ async function stageSupersession(db, llm, budget, embedFn, dryRun, stateDir, opt
1011
1008
  }
1012
1009
  if (verdict) {
1013
1010
  const cosine = (0, retrieval_js_1.l2ToCosine)(neighbor.distance);
1011
+ // The link IS the signal (0.15.2): retrieval demotes superseded
1012
+ // memories via an explicit scoring multiplier (supersededDemotion,
1013
+ // retrieval.ts). The old base_strength penalty was retired because it
1014
+ // (a) fought the config-tunable strength weight and (b) leaked into
1015
+ // prune eligibility — a reversed decision must rank lower, not edge
1016
+ // toward deletion.
1014
1017
  storage.addLink(db, neighbor.id, candidate.id, "superseded_by", cosine);
1015
- const newStrength = Math.max(SUPERSESSION_STRENGTH_FLOOR, (neighbor.base_strength ?? 0.5) * penalty);
1016
- storage.updateMemory(db, neighbor.id, { base_strength: newStrength });
1017
1018
  superseded++;
1018
1019
  console.log(`[hicortex] Supersession: ${neighbor.id.slice(0, 8)} superseded_by ${candidate.id.slice(0, 8)} (cosine ${cosine.toFixed(3)})`);
1019
1020
  }
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Recall@k sweep eval for session-intent keying (#192, commit 0867d6c).
4
+ *
5
+ * SHIP GATE: does blending the prompt with the per-session EMA centroid
6
+ * (retrieval.blendQueryVector) improve recall across rephrased turns, AND
7
+ * what does it COST when a session shifts topics mid-stream? Picks the
8
+ * default `sessionIntentWeight`.
9
+ *
10
+ * Two scenario families, same corpus:
11
+ *
12
+ * FOCUSED — 6 sessions (one per topic), 4 turns each of GENUINE REPHRASES
13
+ * of one intent. Tests the UPSIDE: does the centroid pull drifting
14
+ * rephrases back on-topic? (recall@5 / p@5 should rise with w.)
15
+ *
16
+ * SHIFT — 5 sessions, each 2 turns on topic A then 2 turns on topic B (a
17
+ * clean mid-session topic change). Tests the DOWNSIDE that focused-only
18
+ * can't see: a higher weight leaves the centroid partly A right after the
19
+ * shift, so the first turn(s) on B may LAG (lower recall@5 for B). The
20
+ * load-bearing read-out is post-shift recovery at turn-3 (first B turn)
21
+ * and turn-4, plus turns-to-recover.
22
+ *
23
+ * Method (both families):
24
+ * - Synthetic corpus of 30 memories across 6 well-separated topics (5 each),
25
+ * embedded with the REAL bge-small-en-v1.5 model (the production embedder).
26
+ * - For each weight w in {0.0, 0.2, 0.4, 0.5, 0.6, 0.8}: fresh
27
+ * SessionRecallRegistry per session; per turn, embed the prompt ONCE,
28
+ * blend with the live centroid, pass the blended vector to retrieve() via
29
+ * queryEmbedding, record recall@5 + p@5 for the turn's CURRENT topic,
30
+ * then fold the prompt into the centroid (EMA α=0.4).
31
+ *
32
+ * Fairness controls (load-bearing — the eval is useless if these slip):
33
+ * - `noStrengthen: true` on every retrieve(): the DB stays STATIC across all
34
+ * retrieve calls. Strengthening would mutate effective_strength/access_count
35
+ * between runs and contaminate cross-weight comparisons.
36
+ * - Uniform memory metadata: every memory has base_strength=0.5, created_at
37
+ * ≈ now, access_count=0, and NO links. So effective_strength, recency,
38
+ * connections, and the freshness boost are all uniform → the ONLY
39
+ * discriminator is vector cosine + RRF rank. That isolates the blend.
40
+ * - Cold-exposure slots are a no-op here (all candidates equally cold),
41
+ * so the top-k is the plain score order.
42
+ * - The centroid update is weight-INDEPENDENT (EMA of prompts at α=0.4), so
43
+ * the per-turn centroid is identical across all weights for a given
44
+ * session; only the blend differs. Embeddings are precomputed once.
45
+ *
46
+ * Metrics (per turn, for the turn's CURRENT topic):
47
+ * - recall@5 — did >=1 same-topic memory surface in the top-5? (coarse; the
48
+ * requested ship-gate metric — saturates on a small corpus.)
49
+ * - p@5 — on-topic count in the top-5 (0..5, finer).
50
+ * - recall@1 — was the SINGLE top result on-topic? (finest; focused only.)
51
+ *
52
+ * Honest by construction: it prints whatever the numbers are, including
53
+ * w=0 winning, the blend hurting a focused scenario, or high weights lagging
54
+ * badly post-shift. Report written to data/eval-recall-sweep/report.md and
55
+ * printed to stdout.
56
+ *
57
+ * Run: npm run eval:recall-sweep (== node dist/eval/recall-sweep.js)
58
+ */
59
+ export {};