hippo-memory 1.61.0 → 1.63.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.
- package/README.md +37 -53
- package/dist/agent-memories/apply.d.ts +3 -1
- package/dist/agent-memories/apply.js +23 -1
- package/dist/agent-memories/claude-code.d.ts +4 -1
- package/dist/agent-memories/claude-code.js +53 -9
- package/dist/agent-memories/report.d.ts +1 -0
- package/dist/agent-memories/report.js +2 -2
- package/dist/agent-memories/sync.d.ts +3 -3
- package/dist/agent-memories/sync.js +46 -24
- package/dist/agent-memories/types.d.ts +0 -2
- package/dist/ambient-store.d.ts +4 -4
- package/dist/ambient-store.js +4 -3
- package/dist/api/assemble.d.ts +7 -10
- package/dist/api/assemble.js +62 -66
- package/dist/api/audit.d.ts +2 -2
- package/dist/api/audit.js +2 -2
- package/dist/api/auth.d.ts +6 -6
- package/dist/api/auth.js +5 -7
- package/dist/api/context-select.d.ts +50 -0
- package/dist/api/context-select.js +342 -0
- package/dist/api/context-types.d.ts +12 -17
- package/dist/api/context.d.ts +5 -4
- package/dist/api/context.js +208 -535
- package/dist/api/dormant.js +1 -1
- package/dist/api/drill-down.d.ts +4 -4
- package/dist/api/drill-down.js +56 -41
- package/dist/api/outcome.d.ts +8 -13
- package/dist/api/outcome.js +13 -17
- package/dist/api/promote.d.ts +5 -8
- package/dist/api/promote.js +55 -66
- package/dist/api/quarantine.js +3 -2
- package/dist/api/recall-types.d.ts +43 -55
- package/dist/api/recall.d.ts +3 -3
- package/dist/api/recall.js +310 -456
- package/dist/api/remember.d.ts +2 -2
- package/dist/api/sleep.d.ts +7 -35
- package/dist/api/sleep.js +206 -220
- package/dist/api/tokens.d.ts +2 -2
- package/dist/api/tokens.js +2 -2
- package/dist/api/types.d.ts +6 -10
- package/dist/api/types.js +2 -4
- package/dist/audit-prune.d.ts +4 -6
- package/dist/audit-prune.js +3 -5
- package/dist/audit.js +11 -33
- package/dist/auth.d.ts +8 -9
- package/dist/auth.js +5 -7
- package/dist/autolearn.d.ts +1 -1
- package/dist/autolearn.js +1 -1
- package/dist/availability.js +3 -5
- package/dist/capture/command.d.ts +5 -4
- package/dist/capture/command.js +8 -22
- package/dist/capture/compact.d.ts +2 -2
- package/dist/capture/compact.js +11 -15
- package/dist/capture/extract.js +37 -115
- package/dist/capture-error.d.ts +1 -1
- package/dist/capture-error.js +1 -1
- package/dist/churn-git.d.ts +1 -1
- package/dist/churn-git.js +1 -1
- package/dist/cli/audit.js +3 -4
- package/dist/cli/auth.js +3 -6
- package/dist/cli/briefs.js +324 -306
- package/dist/cli/context.js +44 -34
- package/dist/cli/continuity.js +283 -271
- package/dist/cli/curate.d.ts +1 -1
- package/dist/cli/curate.js +43 -58
- package/dist/cli/dag.js +5 -9
- package/dist/cli/decisions.js +334 -345
- package/dist/cli/explain.js +68 -61
- package/dist/cli/goals.js +1 -1
- package/dist/cli/init.js +1 -1
- package/dist/cli/maintenance.js +62 -51
- package/dist/cli/playbooks.js +391 -379
- package/dist/cli/projects.js +11 -6
- package/dist/cli/recall.js +30 -44
- package/dist/cli/remember.js +118 -87
- package/dist/cli/session-hooks.js +106 -115
- package/dist/cli/setup.d.ts +1 -1
- package/dist/cli/setup.js +267 -250
- package/dist/cli/shared.js +3 -3
- package/dist/cli/slack.js +1 -1
- package/dist/cli/sleep.js +27 -1
- package/dist/cli/status.d.ts +4 -4
- package/dist/cli/status.js +80 -76
- package/dist/cli/transfer.js +88 -107
- package/dist/cli/usage.js +9 -6
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +4 -9
- package/dist/compaction-record.d.ts +2 -2
- package/dist/compaction-record.js +89 -68
- package/dist/compare.d.ts +11 -16
- package/dist/compare.js +11 -16
- package/dist/config.d.ts +19 -18
- package/dist/config.js +90 -67
- package/dist/connectors/github/backfill.d.ts +2 -2
- package/dist/connectors/github/backfill.js +8 -15
- package/dist/connectors/github/cli-impl.js +3 -8
- package/dist/connectors/github/deletion.d.ts +5 -12
- package/dist/connectors/github/deletion.js +5 -12
- package/dist/connectors/github/dlq.d.ts +6 -9
- package/dist/connectors/github/dlq.js +2 -3
- package/dist/connectors/github/ingest.d.ts +5 -7
- package/dist/connectors/github/ingest.js +8 -12
- package/dist/connectors/github/octokit-client.d.ts +3 -5
- package/dist/connectors/github/octokit-client.js +5 -6
- package/dist/connectors/github/signature.d.ts +9 -39
- package/dist/connectors/github/signature.js +9 -39
- package/dist/connectors/github/tenant-routing.d.ts +1 -1
- package/dist/connectors/github/tenant-routing.js +1 -1
- package/dist/connectors/github/transform.js +2 -2
- package/dist/connectors/github/types.d.ts +2 -10
- package/dist/connectors/github/types.js +1 -3
- package/dist/connectors/slack/deletion.d.ts +3 -8
- package/dist/connectors/slack/deletion.js +3 -8
- package/dist/connectors/slack/dlq.d.ts +1 -1
- package/dist/connectors/slack/ingest.d.ts +1 -1
- package/dist/connectors/slack/ingest.js +7 -16
- package/dist/connectors/slack/signature.d.ts +1 -1
- package/dist/connectors/slack/tenant-routing.d.ts +3 -5
- package/dist/connectors/slack/tenant-routing.js +3 -5
- package/dist/connectors/slack/transform.d.ts +5 -6
- package/dist/connectors/slack/transform.js +5 -6
- package/dist/connectors/slack/types.d.ts +2 -6
- package/dist/connectors/slack/types.js +1 -3
- package/dist/connectors/slack/web-client.js +10 -3
- package/dist/connectors/slack/workspaces.d.ts +3 -5
- package/dist/connectors/slack/workspaces.js +3 -5
- package/dist/consolidate/conflicts.js +3 -14
- package/dist/consolidate/decay.js +9 -29
- package/dist/consolidate/llm-passes.js +4 -5
- package/dist/consolidate/merge.js +8 -23
- package/dist/consolidate/run.d.ts +1 -8
- package/dist/consolidate/run.js +3 -25
- package/dist/consolidate/sleep.js +5 -17
- package/dist/consolidate/traces.js +9 -21
- package/dist/customer-notes.d.ts +5 -7
- package/dist/customer-notes.js +82 -76
- package/dist/dag.d.ts +10 -21
- package/dist/dag.js +189 -203
- package/dist/db/continuity.js +2 -2
- package/dist/db/migrations/v14.js +1 -1
- package/dist/db/migrations/v15.js +1 -2
- package/dist/db/migrations/v16.js +3 -4
- package/dist/db/migrations/v17.js +2 -3
- package/dist/db/migrations/v19.js +1 -1
- package/dist/db/migrations/v20.js +1 -1
- package/dist/db/migrations/v21.js +2 -6
- package/dist/db/migrations/v22.js +2 -4
- package/dist/db/migrations/v23.js +1 -1
- package/dist/db/migrations/v24.js +4 -6
- package/dist/db/migrations/v25.js +2 -3
- package/dist/db/migrations/v26.js +3 -3
- package/dist/db/migrations/v27.js +2 -10
- package/dist/db/migrations/v28.js +5 -8
- package/dist/db/migrations/v29.js +3 -4
- package/dist/db/migrations/v30.js +2 -2
- package/dist/db/migrations/v31.js +1 -1
- package/dist/db/migrations/v32.js +1 -1
- package/dist/db/migrations/v33.js +3 -3
- package/dist/db/migrations/v34.js +1 -1
- package/dist/db/migrations/v35.js +3 -4
- package/dist/db/migrations/v36.js +3 -4
- package/dist/db/migrations/v37.js +5 -5
- package/dist/db/migrations/v38.js +7 -8
- package/dist/db/migrations/v39.js +1 -1
- package/dist/db/migrations/v40.js +4 -16
- package/dist/db/migrations/v41.js +3 -4
- package/dist/db/migrations/v42.js +3 -4
- package/dist/db/migrations/v45.js +1 -1
- package/dist/db/migrations/v46.js +1 -1
- package/dist/db/migrations/v47.js +1 -1
- package/dist/db/migrations/v48.js +1 -1
- package/dist/decisions.d.ts +2 -2
- package/dist/decisions.js +97 -80
- package/dist/dedupe.js +86 -61
- package/dist/delivery-recorder.js +154 -135
- package/dist/doctor.js +129 -110
- package/dist/dormant.js +1 -4
- package/dist/embedding-provider.d.ts +4 -8
- package/dist/embedding-provider.js +4 -8
- package/dist/embeddings.js +55 -47
- package/dist/env.d.ts +1 -1
- package/dist/env.js +12 -12
- package/dist/escape.d.ts +5 -0
- package/dist/escape.js +10 -0
- package/dist/eval-stats.d.ts +1 -2
- package/dist/eval-stats.js +1 -2
- package/dist/eval-suite.js +27 -21
- package/dist/extract.js +4 -9
- package/dist/failure-log.d.ts +3 -3
- package/dist/failure-log.js +1 -1
- package/dist/forward-claim-detector.d.ts +2 -4
- package/dist/forward-claim-detector.js +6 -11
- package/dist/goals.d.ts +3 -3
- package/dist/goals.js +103 -91
- package/dist/graph/read.d.ts +2 -2
- package/dist/graph/read.js +5 -6
- package/dist/graph/types.d.ts +8 -8
- package/dist/graph/write.d.ts +7 -14
- package/dist/graph/write.js +16 -23
- package/dist/graph-extract.d.ts +7 -8
- package/dist/graph-extract.js +62 -72
- package/dist/graph-recall.d.ts +2 -2
- package/dist/graph-recall.js +55 -49
- package/dist/graph-stream.d.ts +5 -6
- package/dist/graph-stream.js +66 -57
- package/dist/graph-view.d.ts +2 -2
- package/dist/graph-view.js +7 -7
- package/dist/half-life-migration.d.ts +1 -2
- package/dist/half-life-migration.js +2 -3
- package/dist/hooks/codex-session.js +1 -1
- package/dist/hooks/codex-wrapper.d.ts +1 -1
- package/dist/hooks/codex-wrapper.js +3 -2
- package/dist/hooks/json-hooks.d.ts +2 -2
- package/dist/hooks/json-hooks.js +5 -4
- package/dist/hooks/opencode.d.ts +1 -1
- package/dist/hooks/opencode.js +5 -4
- package/dist/hooks/shared.d.ts +3 -7
- package/dist/hooks/shared.js +1 -8
- package/dist/http-util.d.ts +2 -3
- package/dist/http-util.js +3 -0
- package/dist/importers/core.d.ts +2 -9
- package/dist/importers/core.js +15 -30
- package/dist/importers/sources.js +2 -1
- package/dist/importers/vault.js +2 -20
- package/dist/incidents.d.ts +1 -1
- package/dist/incidents.js +46 -39
- package/dist/instruction-detect.d.ts +1 -1
- package/dist/instruction-detect.js +1 -1
- package/dist/invalidation.d.ts +3 -0
- package/dist/invalidation.js +160 -114
- package/dist/json.d.ts +5 -0
- package/dist/json.js +4 -0
- package/dist/judgment.js +1 -2
- package/dist/local-embedding.js +1 -1
- package/dist/mcp/admin-tools.js +7 -17
- package/dist/mcp/format.js +1 -1
- package/dist/mcp/framing.js +3 -6
- package/dist/mcp/protocol.d.ts +2 -5
- package/dist/mcp/protocol.js +1 -3
- package/dist/mcp/recall-tools.js +12 -15
- package/dist/mcp/request.js +4 -3
- package/dist/mcp/session-state.js +2 -3
- package/dist/mcp/stdio.js +2 -1
- package/dist/mcp/tools.js +9 -6
- package/dist/memory-value-weights.d.ts +5 -8
- package/dist/memory-value-weights.js +5 -8
- package/dist/memory-value.d.ts +13 -13
- package/dist/memory-value.js +26 -37
- package/dist/memory.d.ts +20 -22
- package/dist/memory.js +24 -48
- package/dist/multihop.d.ts +1 -1
- package/dist/multihop.js +3 -2
- package/dist/owner-validation.d.ts +4 -5
- package/dist/owner-validation.js +4 -5
- package/dist/physics.d.ts +4 -4
- package/dist/physics.js +7 -9
- package/dist/policies.d.ts +9 -10
- package/dist/policies.js +96 -81
- package/dist/postinstall.js +3 -6
- package/dist/predictions/planning-fallacy.d.ts +9 -14
- package/dist/predictions/planning-fallacy.js +10 -16
- package/dist/predictions/store.d.ts +15 -23
- package/dist/predictions/store.js +36 -33
- package/dist/processes.d.ts +2 -7
- package/dist/processes.js +88 -72
- package/dist/project-briefs.d.ts +2 -3
- package/dist/project-briefs.js +141 -118
- package/dist/project-identity.d.ts +22 -9
- package/dist/project-identity.js +47 -12
- package/dist/project-merge.d.ts +28 -5
- package/dist/project-merge.js +213 -46
- package/dist/project-remote.d.ts +12 -0
- package/dist/project-remote.js +138 -0
- package/dist/prompt-recall.js +1 -2
- package/dist/rate-limit.d.ts +1 -1
- package/dist/rate-limit.js +1 -1
- package/dist/raw-archive.d.ts +9 -0
- package/dist/raw-archive.js +70 -53
- package/dist/recall-history.d.ts +19 -20
- package/dist/recall-history.js +24 -42
- package/dist/recall-pipeline.js +4 -28
- package/dist/recall-scope.d.ts +7 -8
- package/dist/recall-scope.js +7 -8
- package/dist/recall-trace.d.ts +5 -9
- package/dist/recall-trace.js +6 -10
- package/dist/refine-llm.d.ts +1 -1
- package/dist/refine-llm.js +2 -2
- package/dist/reject-flow.d.ts +3 -4
- package/dist/reject-flow.js +122 -117
- package/dist/rejection.d.ts +5 -6
- package/dist/rejection.js +7 -15
- package/dist/rerankers/clef.d.ts +1 -1
- package/dist/rerankers/jev.d.ts +1 -2
- package/dist/rerankers/jev.js +4 -5
- package/dist/rerankers/llm.d.ts +1 -2
- package/dist/rerankers/llm.js +1 -2
- package/dist/rerankers/types.d.ts +1 -2
- package/dist/rrf.d.ts +2 -2
- package/dist/rrf.js +2 -2
- package/dist/search/bm25-search.d.ts +1 -1
- package/dist/search/bm25-search.js +2 -1
- package/dist/search/boosts.js +2 -1
- package/dist/search/hybrid.d.ts +1 -1
- package/dist/search/hybrid.js +2 -1
- package/dist/search/physics-search.d.ts +1 -1
- package/dist/search/physics-search.js +2 -1
- package/dist/search/types.d.ts +2 -0
- package/dist/search/types.js +3 -1
- package/dist/secret-detect.d.ts +4 -5
- package/dist/secret-detect.js +6 -10
- package/dist/server/auth.js +5 -5
- package/dist/server/client-ip.js +1 -1
- package/dist/server/cursor.js +2 -1
- package/dist/server/mcp-http.js +4 -4
- package/dist/server/request.d.ts +3 -6
- package/dist/server/request.js +6 -7
- package/dist/server/routes/admin.js +5 -4
- package/dist/server/routes/customer-notes.js +6 -5
- package/dist/server/routes/decisions.js +4 -3
- package/dist/server/routes/incidents.js +7 -5
- package/dist/server/routes/memories.js +7 -7
- package/dist/server/routes/policies.js +3 -2
- package/dist/server/routes/predictions.js +12 -15
- package/dist/server/routes/processes.js +3 -2
- package/dist/server/routes/project-briefs.js +8 -7
- package/dist/server/routes/recall.js +95 -93
- package/dist/server/routes/skills.js +6 -5
- package/dist/server/types.d.ts +1 -1
- package/dist/server/validation.d.ts +1 -2
- package/dist/server/validation.js +7 -14
- package/dist/server-detect.js +72 -58
- package/dist/server.d.ts +2 -2
- package/dist/server.js +131 -117
- package/dist/shared.d.ts +26 -17
- package/dist/shared.js +102 -104
- package/dist/skills.d.ts +3 -3
- package/dist/skills.js +88 -72
- package/dist/store/audit-event.d.ts +2 -2
- package/dist/store/audit-event.js +1 -1
- package/dist/store/candidates.d.ts +2 -2
- package/dist/store/candidates.js +4 -3
- package/dist/store/conflicts.js +30 -22
- package/dist/store/delete-and-batch.d.ts +11 -14
- package/dist/store/delete-and-batch.js +40 -91
- package/dist/store/entry-reads.d.ts +17 -25
- package/dist/store/entry-reads.js +59 -40
- package/dist/store/entry-row.d.ts +6 -24
- package/dist/store/entry-row.js +6 -24
- package/dist/store/entry-writes.d.ts +6 -7
- package/dist/store/entry-writes.js +13 -11
- package/dist/store/handoffs.d.ts +1 -1
- package/dist/store/handoffs.js +7 -10
- package/dist/store/index-and-stats.d.ts +2 -6
- package/dist/store/index-and-stats.js +4 -10
- package/dist/store/mirrors.d.ts +6 -19
- package/dist/store/mirrors.js +14 -39
- package/dist/store/open.js +9 -31
- package/dist/store/rows.d.ts +5 -11
- package/dist/store/rows.js +6 -11
- package/dist/store/search-rows.d.ts +17 -34
- package/dist/store/search-rows.js +34 -56
- package/dist/store/sessions.d.ts +4 -5
- package/dist/store/sessions.js +5 -6
- package/dist/store/summaries.d.ts +13 -17
- package/dist/store/summaries.js +26 -70
- package/dist/support-bundle.js +4 -8
- package/dist/tenant.d.ts +1 -5
- package/dist/token-ledger.d.ts +1 -1
- package/dist/token-ledger.js +3 -5
- package/dist/trace.js +1 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/working-memory.d.ts +1 -1
- package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
- package/extensions/openclaw-plugin/package.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@ import { loadConfig } from './config.js';
|
|
|
7
7
|
import { closeHippoDb, isSqliteBusy, openHippoDb } from './db.js';
|
|
8
8
|
import { gatedWrite } from './gated-write.js';
|
|
9
9
|
import { COMPACTION_MEMORY_TAG, COMPACTION_SOURCE_PREFIX, Layer, createMemory, generateId } from './memory.js';
|
|
10
|
-
import {
|
|
10
|
+
import { isGlobalStoreRoot, originInSql, projectId, projectNames, resolveProjectIdentity } from './project-identity.js';
|
|
11
11
|
import { maskEmails, redactSecretsStrict } from './secret-detect.js';
|
|
12
12
|
import { strengthenRetrievedOn, writeEntryMirrors } from './store/entry-writes.js';
|
|
13
13
|
import { isRecallBoostAblated } from './ablation.js';
|
|
@@ -30,11 +30,19 @@ const CLAIMED_SUFFIX = '.claimed';
|
|
|
30
30
|
/** The marker redactSecretsStrict writes; an item holding it was a secret before it was stored. */
|
|
31
31
|
const REDACTED = '[REDACTED]';
|
|
32
32
|
/** Where the session ran: rows written through the global store keep the project the session was in. */
|
|
33
|
-
|
|
33
|
+
function compactionProject(hippoRoot, cwd) {
|
|
34
34
|
if (!isGlobalStoreRoot(hippoRoot))
|
|
35
|
-
return
|
|
35
|
+
return resolveProjectIdentity(path.dirname(hippoRoot));
|
|
36
36
|
// No cwd means user-global, as stampOriginProject gives the global store; undefined would fall back to the hook's own cwd.
|
|
37
|
-
return cwd === null ? '' :
|
|
37
|
+
return cwd === null ? '' : resolveProjectIdentity(cwd);
|
|
38
|
+
}
|
|
39
|
+
function compactionOrigin(hippoRoot, cwd) {
|
|
40
|
+
return projectId(compactionProject(hippoRoot, cwd));
|
|
41
|
+
}
|
|
42
|
+
/** The names a record's held rows may carry: its origin, plus the folder name rows saved before project ids used. */
|
|
43
|
+
function heldOrigins(hippoRoot, cwd, originProject) {
|
|
44
|
+
const names = projectNames(compactionProject(hippoRoot, cwd));
|
|
45
|
+
return names.includes(originProject) ? names : [originProject];
|
|
38
46
|
}
|
|
39
47
|
function scrub(text) {
|
|
40
48
|
return maskEmails(redactSecretsStrict(text));
|
|
@@ -158,12 +166,76 @@ function restates(item, held) {
|
|
|
158
166
|
return next === item.length;
|
|
159
167
|
}
|
|
160
168
|
/** Live rows of one tenant and origin that default recall shows: a compaction often restates what an earlier one, or the user, already saved. */
|
|
161
|
-
function heldRows(db, tenantId,
|
|
169
|
+
function heldRows(db, tenantId, origins) {
|
|
162
170
|
// SAFETY: the SELECT names the id, source_session_id and content columns.
|
|
163
|
-
const rows = db.prepare(`SELECT id, source_session_id, content FROM memories WHERE tenant_id = ? AND
|
|
164
|
-
AND (scope IS NULL OR (scope != 'unknown:legacy' AND scope NOT LIKE '%:private:%'))`).all(tenantId,
|
|
171
|
+
const rows = db.prepare(`SELECT id, source_session_id, content FROM memories WHERE tenant_id = ? AND ${originInSql(origins)} AND superseded_by IS NULL AND kind != 'raw'
|
|
172
|
+
AND (scope IS NULL OR (scope != 'unknown:legacy' AND scope NOT LIKE '%:private:%'))`).all(tenantId, ...origins);
|
|
165
173
|
return rows.map((r) => ({ id: r.id, sessionId: r.source_session_id, words: words(r.content) }));
|
|
166
174
|
}
|
|
175
|
+
function compactionEntry(text, ctx, baseHalfLifeDays) {
|
|
176
|
+
return {
|
|
177
|
+
...createMemory(text, {
|
|
178
|
+
layer: Layer.Episodic,
|
|
179
|
+
tags: [COMPACTION_MEMORY_TAG],
|
|
180
|
+
source: `${COMPACTION_SOURCE_PREFIX}${ctx.sessionId}`,
|
|
181
|
+
confidence: 'observed',
|
|
182
|
+
kind: 'distilled',
|
|
183
|
+
source_session_id: ctx.sessionId,
|
|
184
|
+
tenantId: ctx.tenantId,
|
|
185
|
+
baseHalfLifeDays,
|
|
186
|
+
}),
|
|
187
|
+
origin_project: ctx.originProject,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/** Runs inside saveItems' transaction: restatements of held rows are counted, the rest go through the write gate. */
|
|
191
|
+
function writeItemRows(db, hippoRoot, ctx, rows, baseHalfLifeDays) {
|
|
192
|
+
const out = { written: [], repeats: 0, refused: 0, restated: [] };
|
|
193
|
+
const held = heldRows(db, ctx.tenantId, heldOrigins(hippoRoot, ctx.cwd, ctx.originProject));
|
|
194
|
+
const restated = new Set();
|
|
195
|
+
for (const text of rows) {
|
|
196
|
+
const itemWords = words(text);
|
|
197
|
+
const match = held.find((h) => restates(itemWords, h.words));
|
|
198
|
+
if (match) {
|
|
199
|
+
out.repeats++;
|
|
200
|
+
if (match.id !== null && match.sessionId !== ctx.sessionId)
|
|
201
|
+
restated.add(match.id);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
// createMemory throws below 3 chars, which would sink the whole transaction.
|
|
205
|
+
if (text.trim().length < 3) {
|
|
206
|
+
out.refused++;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const entry = compactionEntry(text, ctx, baseHalfLifeDays);
|
|
210
|
+
if (gatedWrite(db, hippoRoot, entry, { actor: 'post-compact' }) === 'written') {
|
|
211
|
+
out.written.push(entry);
|
|
212
|
+
held.push({ id: null, sessionId: ctx.sessionId, words: itemWords });
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
out.refused++;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
out.restated = [...restated];
|
|
219
|
+
return out;
|
|
220
|
+
}
|
|
221
|
+
/** After commit: report skips, mirror the new rows, bump the counter. */
|
|
222
|
+
function finishItemWrites(hippoRoot, writes, log) {
|
|
223
|
+
if (writes.repeats > 0)
|
|
224
|
+
log(`skipped ${writes.repeats} item(s) the store already holds`);
|
|
225
|
+
if (writes.refused > 0)
|
|
226
|
+
log(`skipped ${writes.refused} item(s) the write gate refused`);
|
|
227
|
+
for (const entry of writes.written)
|
|
228
|
+
writeEntryMirrors(hippoRoot, entry);
|
|
229
|
+
if (writes.written.length > 0) {
|
|
230
|
+
try {
|
|
231
|
+
updateStats(hippoRoot, { remembered: writes.written.length });
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
// The rows are committed; a counter that could not be bumped must not turn that into a failed step.
|
|
235
|
+
log(`remembered counter not updated: ${errorMessage(err)}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
167
239
|
/** Items that become rows, then the record `done`, in one transaction so two sessions cannot both insert one text. Mirrors after commit. */
|
|
168
240
|
export function saveItems(db, hippoRoot, ctx, log) {
|
|
169
241
|
const usable = ctx.items.filter((item) => !item.includes(REDACTED));
|
|
@@ -175,9 +247,7 @@ export function saveItems(db, hippoRoot, ctx, log) {
|
|
|
175
247
|
if (capped > 0)
|
|
176
248
|
log(`capped: ${capped} more kept in the record only`);
|
|
177
249
|
const baseHalfLifeDays = loadConfig(hippoRoot).defaultHalfLifeDays;
|
|
178
|
-
|
|
179
|
-
let repeats = 0;
|
|
180
|
-
let refused = 0;
|
|
250
|
+
let writes;
|
|
181
251
|
db.exec('BEGIN IMMEDIATE');
|
|
182
252
|
try {
|
|
183
253
|
if (ctx.recordId !== null) {
|
|
@@ -190,47 +260,11 @@ export function saveItems(db, hippoRoot, ctx, log) {
|
|
|
190
260
|
return current?.items_written ?? 0;
|
|
191
261
|
}
|
|
192
262
|
}
|
|
193
|
-
|
|
194
|
-
const restated = new Set();
|
|
195
|
-
for (const text of rows) {
|
|
196
|
-
const itemWords = words(text);
|
|
197
|
-
const match = held.find((h) => restates(itemWords, h.words));
|
|
198
|
-
if (match) {
|
|
199
|
-
repeats++;
|
|
200
|
-
if (match.id !== null && match.sessionId !== ctx.sessionId)
|
|
201
|
-
restated.add(match.id);
|
|
202
|
-
continue;
|
|
203
|
-
}
|
|
204
|
-
// createMemory throws below 3 chars, which would sink the whole transaction.
|
|
205
|
-
if (text.trim().length < 3) {
|
|
206
|
-
refused++;
|
|
207
|
-
continue;
|
|
208
|
-
}
|
|
209
|
-
const entry = {
|
|
210
|
-
...createMemory(text, {
|
|
211
|
-
layer: Layer.Episodic,
|
|
212
|
-
tags: [COMPACTION_MEMORY_TAG],
|
|
213
|
-
source: `${COMPACTION_SOURCE_PREFIX}${ctx.sessionId}`,
|
|
214
|
-
confidence: 'observed',
|
|
215
|
-
kind: 'distilled',
|
|
216
|
-
source_session_id: ctx.sessionId,
|
|
217
|
-
tenantId: ctx.tenantId,
|
|
218
|
-
baseHalfLifeDays,
|
|
219
|
-
}),
|
|
220
|
-
origin_project: ctx.originProject,
|
|
221
|
-
};
|
|
222
|
-
if (gatedWrite(db, hippoRoot, entry, { actor: 'post-compact' }) === 'written') {
|
|
223
|
-
written.push(entry);
|
|
224
|
-
held.push({ id: null, sessionId: ctx.sessionId, words: itemWords });
|
|
225
|
-
}
|
|
226
|
-
else {
|
|
227
|
-
refused++;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
263
|
+
writes = writeItemRows(db, hippoRoot, ctx, rows, baseHalfLifeDays);
|
|
230
264
|
// Said again by another session is the same signal as being recalled; the same session carrying it forward is not.
|
|
231
|
-
strengthenRetrievedOn(db,
|
|
265
|
+
strengthenRetrievedOn(db, writes.restated, { tenantId: ctx.tenantId, recallBoostAblated: isRecallBoostAblated() });
|
|
232
266
|
if (ctx.recordId !== null) {
|
|
233
|
-
db.prepare(`UPDATE compactions SET items_written = ?, status = 'done' WHERE tenant_id = ? AND id = ?`).run(written.length, ctx.tenantId, ctx.recordId);
|
|
267
|
+
db.prepare(`UPDATE compactions SET items_written = ?, status = 'done' WHERE tenant_id = ? AND id = ?`).run(writes.written.length, ctx.tenantId, ctx.recordId);
|
|
234
268
|
}
|
|
235
269
|
db.exec('COMMIT');
|
|
236
270
|
}
|
|
@@ -241,22 +275,8 @@ export function saveItems(db, hippoRoot, ctx, log) {
|
|
|
241
275
|
catch { /* already rolled back; keep the original error */ }
|
|
242
276
|
throw err;
|
|
243
277
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (refused > 0)
|
|
247
|
-
log(`skipped ${refused} item(s) the write gate refused`);
|
|
248
|
-
for (const entry of written)
|
|
249
|
-
writeEntryMirrors(hippoRoot, entry);
|
|
250
|
-
if (written.length > 0) {
|
|
251
|
-
try {
|
|
252
|
-
updateStats(hippoRoot, { remembered: written.length });
|
|
253
|
-
}
|
|
254
|
-
catch (err) {
|
|
255
|
-
// The rows are committed; a counter that could not be bumped must not turn that into a failed step.
|
|
256
|
-
log(`remembered counter not updated: ${errorMessage(err)}`);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
return written.length;
|
|
278
|
+
finishItemWrites(hippoRoot, writes, log);
|
|
279
|
+
return writes.written.length;
|
|
260
280
|
}
|
|
261
281
|
/** null when the text is not a PostCompact payload naming a session. */
|
|
262
282
|
export function parsePostCompactPayload(stdinText) {
|
|
@@ -320,6 +340,7 @@ export function saveCompaction(hippoRoot, payload, log) {
|
|
|
320
340
|
recordId: record?.id ?? null,
|
|
321
341
|
sessionId: payload.sessionId,
|
|
322
342
|
originProject: record?.originProject ?? compactionOrigin(hippoRoot, payload.cwd),
|
|
343
|
+
cwd: payload.cwd,
|
|
323
344
|
items: text.items,
|
|
324
345
|
}, log);
|
|
325
346
|
}
|
|
@@ -494,7 +515,7 @@ function importSpool(db, hippoRoot, tenantId, log, deadline) {
|
|
|
494
515
|
// The record holds the items now, so the file is done even if the write below fails.
|
|
495
516
|
fs.rmSync(claimed, { force: true });
|
|
496
517
|
removed = true;
|
|
497
|
-
saveItems(db, hippoRoot, { tenantId: spooled.tenantId, recordId: record.id, sessionId: record.sessionId, originProject: record.originProject, items: record.items }, log);
|
|
518
|
+
saveItems(db, hippoRoot, { tenantId: spooled.tenantId, recordId: record.id, sessionId: record.sessionId, originProject: record.originProject, cwd: record.cwd, items: record.items }, log);
|
|
498
519
|
finished++;
|
|
499
520
|
}
|
|
500
521
|
catch (err) {
|
|
@@ -515,7 +536,7 @@ export function replayCompactions(db, hippoRoot, log, deadline = Number.POSITIVE
|
|
|
515
536
|
if (Date.now() > deadline)
|
|
516
537
|
break;
|
|
517
538
|
try {
|
|
518
|
-
saveItems(db, hippoRoot, { tenantId, recordId: record.id, sessionId: record.sessionId, originProject: record.originProject, items: record.items }, log);
|
|
539
|
+
saveItems(db, hippoRoot, { tenantId, recordId: record.id, sessionId: record.sessionId, originProject: record.originProject, cwd: record.cwd, items: record.items }, log);
|
|
519
540
|
finished++;
|
|
520
541
|
}
|
|
521
542
|
catch (err) {
|
|
@@ -549,7 +570,7 @@ export function replayCompactions(db, hippoRoot, log, deadline = Number.POSITIVE
|
|
|
549
570
|
log(`${record.id} was filled by another process`);
|
|
550
571
|
continue;
|
|
551
572
|
}
|
|
552
|
-
saveItems(db, hippoRoot, { tenantId, recordId: record.id, sessionId: record.sessionId, originProject: record.originProject, items: text.items }, log);
|
|
573
|
+
saveItems(db, hippoRoot, { tenantId, recordId: record.id, sessionId: record.sessionId, originProject: record.originProject, cwd: record.cwd, items: text.items }, log);
|
|
553
574
|
finished++;
|
|
554
575
|
}
|
|
555
576
|
catch (err) {
|
package/dist/compare.d.ts
CHANGED
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
* multihop.ts, rerankers/*). Every comparator here takes a structural param
|
|
7
7
|
* type instead of an imported one, on purpose — a type-only import back to
|
|
8
8
|
* search.ts would still create the search.ts <-> physics.ts ESM import
|
|
9
|
-
* cycle this module exists to avoid
|
|
10
|
-
* docs/plans/2026-07-09-recall-determinism.md T2).
|
|
9
|
+
* cycle this module exists to avoid.
|
|
11
10
|
*
|
|
12
11
|
* Mirrors the deliberate-determinism comment style already established in
|
|
13
12
|
* graph-stream.ts:88, :165-168, :233-236 — a sort with a documented,
|
|
@@ -32,11 +31,9 @@ export interface EntryIdentity {
|
|
|
32
31
|
* independently-created stores (different directory name, different insert
|
|
33
32
|
* order of everything else on disk) sorts identically. The metadata keys make
|
|
34
33
|
* byte-identical twins order by what they carry instead of by `id`
|
|
35
|
-
* (`crypto.randomUUID()`), which is per-instance random
|
|
36
|
-
* dedupe survivor
|
|
37
|
-
*
|
|
38
|
-
* episodic copy because `mem_` sorts before `sem_`. `id` stays the terminal
|
|
39
|
-
* key so the order is total within one store.
|
|
34
|
+
* (`crypto.randomUUID()`), which is per-instance random and would make the
|
|
35
|
+
* dedupe survivor arbitrary. `id` stays the terminal key so the order is
|
|
36
|
+
* total within one store.
|
|
40
37
|
*
|
|
41
38
|
* Layer rank puts semantic first: semantic rows are consolidation output, so
|
|
42
39
|
* keeping that copy preserves the promotion instead of demoting the memory.
|
|
@@ -47,8 +44,8 @@ export interface EntryIdentity {
|
|
|
47
44
|
* Plain `<`/`>`, NOT `localeCompare`: `localeCompare` is locale- and
|
|
48
45
|
* ICU-version-dependent (a determinism leak in its own right) and needlessly
|
|
49
46
|
* slow for a tiebreak that only needs a total order. The metadata keys are
|
|
50
|
-
* only computed on a content tie, which is rare
|
|
51
|
-
*
|
|
47
|
+
* only computed on a content tie, which is rare, so the per-compare Set/sort
|
|
48
|
+
* cost never lands on the hot path.
|
|
52
49
|
*/
|
|
53
50
|
export declare function compareEntryIdentity(a: EntryIdentity, b: EntryIdentity): number;
|
|
54
51
|
/** Minimal shape for score-primary sort sites (SearchResult and friends
|
|
@@ -72,13 +69,11 @@ export declare function compareScoredResults(a: ScoredEntryLike, b: ScoredEntryL
|
|
|
72
69
|
*
|
|
73
70
|
* `ScoredPhysicsResult` (physics.ts) carries `{ memoryId, baseScore,
|
|
74
71
|
* clusterAmplification, finalScore }` -- NO `entry`/`content` in scope at
|
|
75
|
-
* that layer, so `compareEntryIdentity` cannot apply directly
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* MUTATES scores before the downstream content-aware merge sort runs -- so
|
|
81
|
-
* the tie key must be content-stable at THIS layer, not just downstream).
|
|
72
|
+
* that layer, so `compareEntryIdentity` cannot apply directly. With only the
|
|
73
|
+
* default memoryId key this is PER-INSTANCE-ONLY determinism; callers that need
|
|
74
|
+
* CROSS-INGEST stability supply `tieKeyOf` mapping the result to its memory
|
|
75
|
+
* CONTENT, because the baseScore tie order selects the cluster_top_k
|
|
76
|
+
* amplification set, which MUTATES scores before the content-aware merge sort.
|
|
82
77
|
*
|
|
83
78
|
* A factory (not a fixed-field comparator) because physics.ts re-sorts the
|
|
84
79
|
* same result array by two different score fields in sequence (`baseScore`
|
package/dist/compare.js
CHANGED
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
* multihop.ts, rerankers/*). Every comparator here takes a structural param
|
|
7
7
|
* type instead of an imported one, on purpose — a type-only import back to
|
|
8
8
|
* search.ts would still create the search.ts <-> physics.ts ESM import
|
|
9
|
-
* cycle this module exists to avoid
|
|
10
|
-
* docs/plans/2026-07-09-recall-determinism.md T2).
|
|
9
|
+
* cycle this module exists to avoid.
|
|
11
10
|
*
|
|
12
11
|
* Mirrors the deliberate-determinism comment style already established in
|
|
13
12
|
* graph-stream.ts:88, :165-168, :233-236 — a sort with a documented,
|
|
@@ -21,11 +20,9 @@
|
|
|
21
20
|
* independently-created stores (different directory name, different insert
|
|
22
21
|
* order of everything else on disk) sorts identically. The metadata keys make
|
|
23
22
|
* byte-identical twins order by what they carry instead of by `id`
|
|
24
|
-
* (`crypto.randomUUID()`), which is per-instance random
|
|
25
|
-
* dedupe survivor
|
|
26
|
-
*
|
|
27
|
-
* episodic copy because `mem_` sorts before `sem_`. `id` stays the terminal
|
|
28
|
-
* key so the order is total within one store.
|
|
23
|
+
* (`crypto.randomUUID()`), which is per-instance random and would make the
|
|
24
|
+
* dedupe survivor arbitrary. `id` stays the terminal key so the order is
|
|
25
|
+
* total within one store.
|
|
29
26
|
*
|
|
30
27
|
* Layer rank puts semantic first: semantic rows are consolidation output, so
|
|
31
28
|
* keeping that copy preserves the promotion instead of demoting the memory.
|
|
@@ -36,8 +33,8 @@
|
|
|
36
33
|
* Plain `<`/`>`, NOT `localeCompare`: `localeCompare` is locale- and
|
|
37
34
|
* ICU-version-dependent (a determinism leak in its own right) and needlessly
|
|
38
35
|
* slow for a tiebreak that only needs a total order. The metadata keys are
|
|
39
|
-
* only computed on a content tie, which is rare
|
|
40
|
-
*
|
|
36
|
+
* only computed on a content tie, which is rare, so the per-compare Set/sort
|
|
37
|
+
* cost never lands on the hot path.
|
|
41
38
|
*/
|
|
42
39
|
export function compareEntryIdentity(a, b) {
|
|
43
40
|
return (compareStrings(a.content, b.content) ||
|
|
@@ -90,13 +87,11 @@ export function compareScoredResults(a, b) {
|
|
|
90
87
|
*
|
|
91
88
|
* `ScoredPhysicsResult` (physics.ts) carries `{ memoryId, baseScore,
|
|
92
89
|
* clusterAmplification, finalScore }` -- NO `entry`/`content` in scope at
|
|
93
|
-
* that layer, so `compareEntryIdentity` cannot apply directly
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* MUTATES scores before the downstream content-aware merge sort runs -- so
|
|
99
|
-
* the tie key must be content-stable at THIS layer, not just downstream).
|
|
90
|
+
* that layer, so `compareEntryIdentity` cannot apply directly. With only the
|
|
91
|
+
* default memoryId key this is PER-INSTANCE-ONLY determinism; callers that need
|
|
92
|
+
* CROSS-INGEST stability supply `tieKeyOf` mapping the result to its memory
|
|
93
|
+
* CONTENT, because the baseScore tie order selects the cluster_top_k
|
|
94
|
+
* amplification set, which MUTATES scores before the content-aware merge sort.
|
|
100
95
|
*
|
|
101
96
|
* A factory (not a fixed-field comparator) because physics.ts re-sorts the
|
|
102
97
|
* same result array by two different score fields in sequence (`baseScore`
|
package/dist/config.d.ts
CHANGED
|
@@ -64,34 +64,33 @@ export interface HippoConfig {
|
|
|
64
64
|
enabled: boolean;
|
|
65
65
|
budget: number;
|
|
66
66
|
/** Skip a block identical to the one already injected this session
|
|
67
|
-
*
|
|
67
|
+
* Default true. Needs a session id from the hook payload. */
|
|
68
68
|
skipUnchanged: boolean;
|
|
69
69
|
/** Resend an unchanged block after this many consecutive skips, so long
|
|
70
70
|
* sessions still see pinned rules near the latest turn. Default 10; 0
|
|
71
71
|
* never resends an unchanged block. */
|
|
72
72
|
refreshTurns: number;
|
|
73
|
-
/**
|
|
74
|
-
*
|
|
75
|
-
* tokens fell 847 to 533 (docs/evals/2026-09-26-z1-prompt-recall-result.md). */
|
|
73
|
+
/** Gate the hook's backfill on the prompt's own content instead of the five
|
|
74
|
+
* newest memories. Default true: the same overlap for fewer tokens. */
|
|
76
75
|
promptRecall: boolean;
|
|
77
|
-
/**
|
|
76
|
+
/** Overlap metric for the prompt-recall gate. Default 'jaccard' (tuned). */
|
|
78
77
|
promptRecallMetric: PromptRecallMetric;
|
|
79
|
-
/**
|
|
78
|
+
/** Minimum overlap score to admit a candidate. Default 0.04 (tuned). */
|
|
80
79
|
promptRecallThreshold: number;
|
|
81
|
-
/**
|
|
80
|
+
/** Minimum shared tokens to admit a candidate. Default 2. */
|
|
82
81
|
promptRecallMinShared: number;
|
|
83
|
-
/**
|
|
82
|
+
/** Max prompt-recall entries injected per prompt. Default 5 (tuned). */
|
|
84
83
|
promptRecallMaxItems: number;
|
|
85
|
-
/**
|
|
84
|
+
/** FTS candidate pool size per store before gating. Default 100. */
|
|
86
85
|
promptRecallCandidates: number;
|
|
87
86
|
};
|
|
88
|
-
/** Memory scope isolation
|
|
87
|
+
/** Memory scope isolation: when true (default), ambient context
|
|
89
88
|
* (`hippo context`, the UserPromptSubmit hook, /v1/context, MCP
|
|
90
89
|
* hippo_context) excludes memories owned by OTHER projects; explicit
|
|
91
90
|
* recall is unaffected. Set false to disable the ORIGIN PARTITION only -
|
|
92
91
|
* the secret veto is unconditional for ambient surfaces and no config or
|
|
93
92
|
* flag re-includes secret-flagged rows (explicit recall still returns
|
|
94
|
-
* them).
|
|
93
|
+
* them). */
|
|
95
94
|
contextProjectIsolation: boolean;
|
|
96
95
|
extraction: {
|
|
97
96
|
enabled: boolean | 'auto';
|
|
@@ -110,11 +109,8 @@ export interface HippoConfig {
|
|
|
110
109
|
ambient: {
|
|
111
110
|
enabled: boolean;
|
|
112
111
|
};
|
|
113
|
-
/**
|
|
114
|
-
* (
|
|
115
|
-
* E2 weights (src/memory-value-weights.ts) only run when explicitly
|
|
116
|
-
* enabled; no other knobs in v1 (the rescue budget is a code constant
|
|
117
|
-
* tied to E2 evidence, not user-tunable). */
|
|
112
|
+
/** Opt-in learned memory-value rescue veto on the sleep decay pass. Default OFF: the frozen weights
|
|
113
|
+
* (src/memory-value-weights.ts) only run when enabled; the rescue budget is a code constant, not user-tunable. */
|
|
118
114
|
memoryValue: {
|
|
119
115
|
enabled: boolean;
|
|
120
116
|
};
|
|
@@ -131,8 +127,8 @@ export interface HippoConfig {
|
|
|
131
127
|
* Default 180. 0 keeps dormant memories forever. */
|
|
132
128
|
retentionDays: number;
|
|
133
129
|
};
|
|
134
|
-
/**
|
|
135
|
-
* changed since storage. Default OFF
|
|
130
|
+
/** Tags a memory `churn-stale` when its named file/symbol/script
|
|
131
|
+
* changed since storage. Default OFF until a measurement justifies the flip. */
|
|
136
132
|
churnStaleness: {
|
|
137
133
|
enabled: boolean;
|
|
138
134
|
};
|
|
@@ -150,6 +146,11 @@ export interface HippoConfig {
|
|
|
150
146
|
pilot: {
|
|
151
147
|
holdoutRateBp: number;
|
|
152
148
|
};
|
|
149
|
+
/** How a folder's project is named. `remote: false` drops the origin-remote rule, leaving
|
|
150
|
+
* `.hippo-project.json` then the folder name. Read from the global store's config only, so every store agrees. */
|
|
151
|
+
projectIdentity: {
|
|
152
|
+
remote: boolean;
|
|
153
|
+
};
|
|
153
154
|
}
|
|
154
155
|
export declare function loadConfig(hippoRoot: string): HippoConfig;
|
|
155
156
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.js
CHANGED
|
@@ -5,10 +5,11 @@ import * as fs from 'fs';
|
|
|
5
5
|
import * as path from 'path';
|
|
6
6
|
import { DEFAULT_PHYSICS_CONFIG, mergePhysicsConfig } from './physics-config.js';
|
|
7
7
|
import { DEFAULT_HALF_LIFE_DAYS } from './memory.js';
|
|
8
|
+
import { DEFAULT_LOCAL_BUMP, DEFAULT_RECALL_BUDGET } from './search/types.js';
|
|
8
9
|
import { log } from './log.js';
|
|
9
10
|
const DEFAULT_CONFIG = {
|
|
10
11
|
defaultHalfLifeDays: DEFAULT_HALF_LIFE_DAYS,
|
|
11
|
-
defaultBudget:
|
|
12
|
+
defaultBudget: DEFAULT_RECALL_BUDGET,
|
|
12
13
|
defaultContextBudget: 3000,
|
|
13
14
|
decayBasis: 'adaptive',
|
|
14
15
|
autoLearnOnSleep: true,
|
|
@@ -36,7 +37,7 @@ const DEFAULT_CONFIG = {
|
|
|
36
37
|
lambda: 0.7,
|
|
37
38
|
},
|
|
38
39
|
search: {
|
|
39
|
-
localBump:
|
|
40
|
+
localBump: DEFAULT_LOCAL_BUMP,
|
|
40
41
|
},
|
|
41
42
|
replay: {
|
|
42
43
|
count: 5,
|
|
@@ -92,6 +93,9 @@ const DEFAULT_CONFIG = {
|
|
|
92
93
|
pilot: {
|
|
93
94
|
holdoutRateBp: 0,
|
|
94
95
|
},
|
|
96
|
+
projectIdentity: {
|
|
97
|
+
remote: true,
|
|
98
|
+
},
|
|
95
99
|
};
|
|
96
100
|
function isMemoryValueConfig(value) {
|
|
97
101
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -132,6 +136,17 @@ function pilotHoldoutRate(value) {
|
|
|
132
136
|
`(got ${JSON.stringify(value)}) - using 0 (pilot off).`);
|
|
133
137
|
return 0;
|
|
134
138
|
}
|
|
139
|
+
// Only a real false turns the remote rule off; anything else malformed warns and keeps it on.
|
|
140
|
+
function projectIdentityRemote(value) {
|
|
141
|
+
const remote = value?.remote;
|
|
142
|
+
if (remote === true || remote === false)
|
|
143
|
+
return remote;
|
|
144
|
+
if (value === undefined || (remote === undefined && value !== null && value.constructor === Object))
|
|
145
|
+
return true;
|
|
146
|
+
log.warn(`config.json's "projectIdentity" must be an object like {"remote": false} ` +
|
|
147
|
+
`(got ${JSON.stringify(value)}) - using true (projects named by their origin remote).`);
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
135
150
|
function agentMemoryTools(value) {
|
|
136
151
|
if (value === undefined || value === null)
|
|
137
152
|
return null;
|
|
@@ -141,6 +156,70 @@ function agentMemoryTools(value) {
|
|
|
141
156
|
`(got ${JSON.stringify(value)}) - importing none.`);
|
|
142
157
|
return [];
|
|
143
158
|
}
|
|
159
|
+
function memoryValueOverride(raw) {
|
|
160
|
+
// Spreading a non-object raw.memoryValue (e.g. {"memoryValue": true}) silently leaves `enabled` false;
|
|
161
|
+
// this feature must never be silently off, so warn loudly and fall back to defaults instead.
|
|
162
|
+
const memoryValueRaw = raw.memoryValue;
|
|
163
|
+
const validMemoryValueConfig = memoryValueRaw === undefined || isMemoryValueConfig(memoryValueRaw);
|
|
164
|
+
if (!validMemoryValueConfig) {
|
|
165
|
+
log.warn(`config.json's "memoryValue" must be an object like {"enabled": true} ` +
|
|
166
|
+
`(got ${JSON.stringify(memoryValueRaw)}) - using defaults.`);
|
|
167
|
+
}
|
|
168
|
+
return memoryValueRaw !== undefined && isMemoryValueConfig(memoryValueRaw) ? memoryValueRaw : {};
|
|
169
|
+
}
|
|
170
|
+
function dormantSettings(raw) {
|
|
171
|
+
// Same rule as memoryValue above: a malformed value never silently
|
|
172
|
+
// changes what sleep does. Anything but a real object / boolean / number
|
|
173
|
+
// warns and falls back to the default, which keeps faded memories.
|
|
174
|
+
const dormantRaw = raw.dormant;
|
|
175
|
+
if (dormantRaw !== undefined && !isDormantConfig(dormantRaw)) {
|
|
176
|
+
log.warn(`config.json's "dormant" must be an object like {"enabled": false} ` +
|
|
177
|
+
`(got ${JSON.stringify(dormantRaw)}) - using the default (faded memories kept dormant).`);
|
|
178
|
+
}
|
|
179
|
+
const dormantOverride = dormantRaw !== undefined && isDormantConfig(dormantRaw) ? dormantRaw : {};
|
|
180
|
+
// Only a real boolean counts: {"enabled": "false"} is a truthy string.
|
|
181
|
+
let dormantEnabled = dormantOverride.enabled ?? DEFAULT_CONFIG.dormant.enabled;
|
|
182
|
+
if (dormantEnabled !== true && dormantEnabled !== false) {
|
|
183
|
+
log.warn(`config.json's "dormant.enabled" must be true or false ` +
|
|
184
|
+
`(got ${JSON.stringify(dormantEnabled)}) - using the default (faded memories kept dormant).`);
|
|
185
|
+
dormantEnabled = DEFAULT_CONFIG.dormant.enabled;
|
|
186
|
+
}
|
|
187
|
+
let dormantRetentionDays = dormantOverride.retentionDays ?? DEFAULT_CONFIG.dormant.retentionDays;
|
|
188
|
+
if (!Number.isFinite(dormantRetentionDays) || dormantRetentionDays < 0) {
|
|
189
|
+
log.warn(`config.json's "dormant.retentionDays" must be a number of days, 0 or more ` +
|
|
190
|
+
`(got ${JSON.stringify(dormantRetentionDays)}) - using ${DEFAULT_CONFIG.dormant.retentionDays}.`);
|
|
191
|
+
dormantRetentionDays = DEFAULT_CONFIG.dormant.retentionDays;
|
|
192
|
+
}
|
|
193
|
+
return { enabled: dormantEnabled, retentionDays: dormantRetentionDays };
|
|
194
|
+
}
|
|
195
|
+
function churnStalenessEnabled(raw) {
|
|
196
|
+
// Same "never silently wrong" rule as memoryValue/dormant above.
|
|
197
|
+
const churnStalenessRaw = raw.churnStaleness;
|
|
198
|
+
const validChurnStalenessConfig = churnStalenessRaw === undefined || isChurnStalenessConfig(churnStalenessRaw);
|
|
199
|
+
if (!validChurnStalenessConfig) {
|
|
200
|
+
log.warn(`config.json's "churnStaleness" must be an object like {"enabled": true} ` +
|
|
201
|
+
`(got ${JSON.stringify(churnStalenessRaw)}) - using defaults.`);
|
|
202
|
+
}
|
|
203
|
+
const enabled = churnStalenessRaw !== undefined && isChurnStalenessConfig(churnStalenessRaw)
|
|
204
|
+
? churnStalenessRaw.enabled
|
|
205
|
+
: DEFAULT_CONFIG.churnStaleness.enabled;
|
|
206
|
+
if (enabled !== true && enabled !== false) {
|
|
207
|
+
log.warn(`config.json's "churnStaleness.enabled" must be true or false ` +
|
|
208
|
+
`(got ${JSON.stringify(enabled)}) - using false.`);
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
return enabled;
|
|
212
|
+
}
|
|
213
|
+
function defaultHalfLifeDays(raw) {
|
|
214
|
+
// Every writer starts a memory on this, and a zero or negative half-life scores zero strength, so sleep would retire it.
|
|
215
|
+
const days = raw.defaultHalfLifeDays ?? DEFAULT_CONFIG.defaultHalfLifeDays;
|
|
216
|
+
if (!Number.isFinite(days) || days <= 0) {
|
|
217
|
+
log.warn(`config.json's "defaultHalfLifeDays" must be a number of days above 0 ` +
|
|
218
|
+
`(got ${JSON.stringify(days)}) - using ${DEFAULT_CONFIG.defaultHalfLifeDays}.`);
|
|
219
|
+
return DEFAULT_CONFIG.defaultHalfLifeDays;
|
|
220
|
+
}
|
|
221
|
+
return days;
|
|
222
|
+
}
|
|
144
223
|
export function loadConfig(hippoRoot) {
|
|
145
224
|
const configPath = path.join(hippoRoot, 'config.json');
|
|
146
225
|
if (!fs.existsSync(configPath))
|
|
@@ -149,66 +228,12 @@ export function loadConfig(hippoRoot) {
|
|
|
149
228
|
const raw = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
150
229
|
const basis = raw.decayBasis;
|
|
151
230
|
const validBasis = basis === 'clock' || basis === 'session' || basis === 'adaptive';
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
// default `false` with zero indication anything was wrong. This
|
|
157
|
-
// feature's whole point is "never silently off": warn loudly and fall
|
|
158
|
-
// back to defaults instead of merging garbage.
|
|
159
|
-
const memoryValueRaw = raw.memoryValue;
|
|
160
|
-
const validMemoryValueConfig = memoryValueRaw === undefined || isMemoryValueConfig(memoryValueRaw);
|
|
161
|
-
if (!validMemoryValueConfig) {
|
|
162
|
-
log.warn(`config.json's "memoryValue" must be an object like {"enabled": true} ` +
|
|
163
|
-
`(got ${JSON.stringify(memoryValueRaw)}) - using defaults.`);
|
|
164
|
-
}
|
|
165
|
-
const memoryValueOverride = memoryValueRaw !== undefined && isMemoryValueConfig(memoryValueRaw) ? memoryValueRaw : {};
|
|
166
|
-
// Same rule as memoryValue above: a malformed value never silently
|
|
167
|
-
// changes what sleep does. Anything but a real object / boolean / number
|
|
168
|
-
// warns and falls back to the default, which keeps faded memories.
|
|
169
|
-
const dormantRaw = raw.dormant;
|
|
170
|
-
if (dormantRaw !== undefined && !isDormantConfig(dormantRaw)) {
|
|
171
|
-
log.warn(`config.json's "dormant" must be an object like {"enabled": false} ` +
|
|
172
|
-
`(got ${JSON.stringify(dormantRaw)}) - using the default (faded memories kept dormant).`);
|
|
173
|
-
}
|
|
174
|
-
const dormantOverride = dormantRaw !== undefined && isDormantConfig(dormantRaw) ? dormantRaw : {};
|
|
175
|
-
// Only a real boolean counts: {"enabled": "false"} is a truthy string.
|
|
176
|
-
let dormantEnabled = dormantOverride.enabled ?? DEFAULT_CONFIG.dormant.enabled;
|
|
177
|
-
if (dormantEnabled !== true && dormantEnabled !== false) {
|
|
178
|
-
log.warn(`config.json's "dormant.enabled" must be true or false ` +
|
|
179
|
-
`(got ${JSON.stringify(dormantEnabled)}) - using the default (faded memories kept dormant).`);
|
|
180
|
-
dormantEnabled = DEFAULT_CONFIG.dormant.enabled;
|
|
181
|
-
}
|
|
182
|
-
let dormantRetentionDays = dormantOverride.retentionDays ?? DEFAULT_CONFIG.dormant.retentionDays;
|
|
183
|
-
if (!Number.isFinite(dormantRetentionDays) || dormantRetentionDays < 0) {
|
|
184
|
-
log.warn(`config.json's "dormant.retentionDays" must be a number of days, 0 or more ` +
|
|
185
|
-
`(got ${JSON.stringify(dormantRetentionDays)}) - using ${DEFAULT_CONFIG.dormant.retentionDays}.`);
|
|
186
|
-
dormantRetentionDays = DEFAULT_CONFIG.dormant.retentionDays;
|
|
187
|
-
}
|
|
188
|
-
// Same "never silently wrong" rule as memoryValue/dormant above.
|
|
189
|
-
const churnStalenessRaw = raw.churnStaleness;
|
|
190
|
-
const validChurnStalenessConfig = churnStalenessRaw === undefined || isChurnStalenessConfig(churnStalenessRaw);
|
|
191
|
-
if (!validChurnStalenessConfig) {
|
|
192
|
-
log.warn(`config.json's "churnStaleness" must be an object like {"enabled": true} ` +
|
|
193
|
-
`(got ${JSON.stringify(churnStalenessRaw)}) - using defaults.`);
|
|
194
|
-
}
|
|
195
|
-
let churnStalenessEnabled = churnStalenessRaw !== undefined && isChurnStalenessConfig(churnStalenessRaw)
|
|
196
|
-
? churnStalenessRaw.enabled
|
|
197
|
-
: DEFAULT_CONFIG.churnStaleness.enabled;
|
|
198
|
-
if (churnStalenessEnabled !== true && churnStalenessEnabled !== false) {
|
|
199
|
-
log.warn(`config.json's "churnStaleness.enabled" must be true or false ` +
|
|
200
|
-
`(got ${JSON.stringify(churnStalenessEnabled)}) - using false.`);
|
|
201
|
-
churnStalenessEnabled = false;
|
|
202
|
-
}
|
|
203
|
-
// Every writer starts a memory on this, and a zero or negative half-life scores zero strength, so sleep would retire it.
|
|
204
|
-
let defaultHalfLifeDays = raw.defaultHalfLifeDays ?? DEFAULT_CONFIG.defaultHalfLifeDays;
|
|
205
|
-
if (!Number.isFinite(defaultHalfLifeDays) || defaultHalfLifeDays <= 0) {
|
|
206
|
-
log.warn(`config.json's "defaultHalfLifeDays" must be a number of days above 0 ` +
|
|
207
|
-
`(got ${JSON.stringify(defaultHalfLifeDays)}) - using ${DEFAULT_CONFIG.defaultHalfLifeDays}.`);
|
|
208
|
-
defaultHalfLifeDays = DEFAULT_CONFIG.defaultHalfLifeDays;
|
|
209
|
-
}
|
|
231
|
+
const memoryValue = memoryValueOverride(raw);
|
|
232
|
+
const dormant = dormantSettings(raw);
|
|
233
|
+
const churnEnabled = churnStalenessEnabled(raw);
|
|
234
|
+
const halfLifeDays = defaultHalfLifeDays(raw);
|
|
210
235
|
return {
|
|
211
|
-
defaultHalfLifeDays,
|
|
236
|
+
defaultHalfLifeDays: halfLifeDays,
|
|
212
237
|
defaultBudget: raw.defaultBudget ?? DEFAULT_CONFIG.defaultBudget,
|
|
213
238
|
defaultContextBudget: raw.defaultContextBudget ?? DEFAULT_CONFIG.defaultContextBudget,
|
|
214
239
|
decayBasis: validBasis ? basis : DEFAULT_CONFIG.decayBasis,
|
|
@@ -232,18 +257,16 @@ export function loadConfig(hippoRoot) {
|
|
|
232
257
|
ambient: { ...DEFAULT_CONFIG.ambient, ...(raw.ambient ?? {}) },
|
|
233
258
|
memoryValue: {
|
|
234
259
|
...DEFAULT_CONFIG.memoryValue,
|
|
235
|
-
...
|
|
236
|
-
},
|
|
237
|
-
dormant: {
|
|
238
|
-
enabled: dormantEnabled,
|
|
239
|
-
retentionDays: dormantRetentionDays,
|
|
260
|
+
...memoryValue,
|
|
240
261
|
},
|
|
262
|
+
dormant,
|
|
241
263
|
churnStaleness: {
|
|
242
|
-
enabled:
|
|
264
|
+
enabled: churnEnabled,
|
|
243
265
|
},
|
|
244
266
|
agentMemories: { tools: agentMemoryTools(raw.agentMemories?.tools) },
|
|
245
267
|
deliveryLedger: { enabled: deliveryLedgerEnabled(raw.deliveryLedger) },
|
|
246
268
|
pilot: { holdoutRateBp: pilotHoldoutRate(raw.pilot) },
|
|
269
|
+
projectIdentity: { remote: projectIdentityRemote(raw.projectIdentity) },
|
|
247
270
|
};
|
|
248
271
|
}
|
|
249
272
|
catch (err) {
|