hippo-memory 1.62.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 +9 -0
- package/dist/agent-memories/apply.d.ts +2 -0
- package/dist/agent-memories/apply.js +23 -1
- package/dist/agent-memories/claude-code.d.ts +2 -1
- package/dist/agent-memories/claude-code.js +4 -4
- package/dist/agent-memories/report.d.ts +1 -0
- package/dist/agent-memories/report.js +2 -2
- package/dist/agent-memories/sync.js +37 -23
- 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 +7 -12
- 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 +4 -3
- package/dist/api/context-select.js +9 -11
- package/dist/api/context-types.d.ts +12 -17
- package/dist/api/context.d.ts +3 -2
- package/dist/api/context.js +20 -20
- package/dist/api/dormant.js +1 -1
- package/dist/api/drill-down.d.ts +4 -4
- package/dist/api/drill-down.js +22 -10
- 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/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 +7 -18
- package/dist/api/remember.d.ts +2 -2
- package/dist/api/sleep.d.ts +7 -35
- package/dist/api/sleep.js +3 -2
- 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 +1 -1
- package/dist/capture/compact.js +9 -13
- 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/curate.d.ts +1 -1
- package/dist/cli/curate.js +8 -24
- package/dist/cli/dag.js +5 -9
- package/dist/cli/decisions.js +10 -21
- package/dist/cli/explain.js +2 -1
- package/dist/cli/goals.js +1 -1
- package/dist/cli/init.js +1 -1
- package/dist/cli/playbooks.js +4 -9
- package/dist/cli/projects.js +3 -1
- package/dist/cli/recall.js +2 -1
- package/dist/cli/remember.js +6 -18
- package/dist/cli/session-hooks.js +21 -40
- package/dist/cli/setup.d.ts +1 -1
- package/dist/cli/setup.js +2 -6
- 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 +7 -12
- package/dist/cli/transfer.js +8 -13
- 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 -0
- 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 +6 -9
- package/dist/dag.d.ts +10 -21
- package/dist/dag.js +23 -73
- 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 +6 -6
- package/dist/dedupe.js +86 -61
- package/dist/delivery-recorder.js +154 -135
- package/dist/doctor.js +119 -104
- 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 +5 -6
- 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 +1 -1
- 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 +12 -14
- 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 +3 -3
- package/dist/project-briefs.d.ts +2 -3
- package/dist/project-briefs.js +9 -13
- package/dist/project-identity.d.ts +22 -9
- package/dist/project-identity.js +47 -12
- package/dist/project-merge.d.ts +17 -2
- package/dist/project-merge.js +109 -27
- 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 +17 -17
- package/dist/shared.js +93 -97
- package/dist/skills.d.ts +3 -3
- package/dist/skills.js +8 -8
- 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 +31 -59
- 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
|
@@ -72,9 +72,9 @@ async function dagBuildPass(run, { apiKey, llmError, llmOpts }) {
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
// -------------------------------------------------------------------------
|
|
75
|
-
// 1.8. DAG summary rebuild — drain dirty queue from
|
|
75
|
+
// 1.8. DAG summary rebuild — drain dirty queue from the child-write hooks
|
|
76
76
|
// -------------------------------------------------------------------------
|
|
77
|
-
// Consumer of
|
|
77
|
+
// Consumer of the summary_dirty flag. Walks dirty L2 summaries, regenerates
|
|
78
78
|
// each via generateDagSummary, atomically refreshes content + 6 metadata
|
|
79
79
|
// columns + clears summary_dirty (with FTS sync). Same apiKey/dryRun gate
|
|
80
80
|
// as buildDag above. Cap HIPPO_DAG_REBUILD_CAP (default 20, hard ceiling
|
|
@@ -84,8 +84,7 @@ async function dagRebuildPass(run, { llmError, llmOpts }) {
|
|
|
84
84
|
try {
|
|
85
85
|
const { rebuildDirtySummaries } = await import('../dag.js');
|
|
86
86
|
const rawCap = envDagRebuildCap();
|
|
87
|
-
//
|
|
88
|
-
// unbounded LLM cost.
|
|
87
|
+
// Hard ceiling so a misconfigured env can't burn unbounded LLM cost.
|
|
89
88
|
const cap = rawCap !== undefined ? Math.min(rawCap, 1000) : 20;
|
|
90
89
|
const rebuildResult = await rebuildDirtySummaries(run.hippoRoot, { ...llmOpts, onError: llmError('dag rebuild'), cap });
|
|
91
90
|
result.summariesRebuilt = rebuildResult.rebuilt;
|
|
@@ -115,7 +114,7 @@ async function dagRebuildPass(run, { llmError, llmOpts }) {
|
|
|
115
114
|
// -------------------------------------------------------------------------
|
|
116
115
|
// 1.9. DAG entity profiles — cluster L2 topic summaries into L3 profiles
|
|
117
116
|
// -------------------------------------------------------------------------
|
|
118
|
-
//
|
|
117
|
+
// Aggregate per-entity L2 summaries (e.g. all the speaker:Alice
|
|
119
118
|
// topic summaries) into a single L3 entity profile. Runs even when phase
|
|
120
119
|
// 1.7 buildDag was skipped (re-clusters existing L2s every sleep).
|
|
121
120
|
//
|
|
@@ -54,13 +54,8 @@ export function mergePass(run) {
|
|
|
54
54
|
&& !e.pinned // a pin merged with a look-alike would read as one of two values
|
|
55
55
|
&& tokenize(e.content).length > 0);
|
|
56
56
|
const used = new Set();
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
// clustered across the whole host-wide `survivors` list with no tenant
|
|
60
|
-
// boundary, and mergeContents concatenated cross-tenant content into one
|
|
61
|
-
// row. Map preserves insertion order, so single-tenant stores (every row
|
|
62
|
-
// 'default') get exactly one partition and iterate in the same order as
|
|
63
|
-
// before this fix — byte-identical behavior there.
|
|
57
|
+
// Partition BEFORE the overlap loop so a cluster can never span tenants and merge cross-tenant content.
|
|
58
|
+
// Map keeps insertion order, so a single-tenant store gets one partition in the original order.
|
|
64
59
|
const mergeCandidatesByTenant = new Map();
|
|
65
60
|
for (const entry of mergeCandidates) {
|
|
66
61
|
const key = derivationPartitionKey(entry.tenantId, entry.scope, entry.origin_project);
|
|
@@ -70,12 +65,8 @@ export function mergePass(run) {
|
|
|
70
65
|
else
|
|
71
66
|
mergeCandidatesByTenant.set(key, [entry]);
|
|
72
67
|
}
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
// for the whole non-dry-run consolidate — see that
|
|
76
|
-
// declaration's comment. Only needed for real writes — a dry-run preview
|
|
77
|
-
// never reaches batchWriteAndDelete's guard bypass, so there is nothing
|
|
78
|
-
// here for it to protect against.
|
|
68
|
+
// The rejection guard reuses the one consolidateDb handle opened lazily in consolidate(); a dry-run
|
|
69
|
+
// never reaches batchWriteAndDelete's guard bypass, so it has nothing to protect there.
|
|
79
70
|
let mergesSkippedRejected = 0;
|
|
80
71
|
for (const [, tenantCandidates] of mergeCandidatesByTenant) {
|
|
81
72
|
const partition = {
|
|
@@ -115,13 +106,8 @@ function mergeCluster(run, partition, cluster, related, used) {
|
|
|
115
106
|
const mergedContent = mergeContents(cluster);
|
|
116
107
|
const allTags = Array.from(new Set(cluster.flatMap((e) => e.tags))).sort();
|
|
117
108
|
const maxValence = pickStrongestValence(cluster);
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
// row will ACTUALLY land in.
|
|
121
|
-
// T1 fix: createMemory now receives tenantId: mergeTenant (the
|
|
122
|
-
// partition's tenant — every member of `cluster` shares it by
|
|
123
|
-
// construction), so the row lands in its source tenant instead of
|
|
124
|
-
// always 'default'.
|
|
109
|
+
// Build the semantic entry FIRST (createMemory is cheap and pure) so the tombstone check below runs
|
|
110
|
+
// under the partition's tenant, the one every cluster member shares and the row will land in.
|
|
125
111
|
let semantic = null;
|
|
126
112
|
if (!dryRun) {
|
|
127
113
|
semantic = {
|
|
@@ -152,9 +138,8 @@ function mergeCluster(run, partition, cluster, related, used) {
|
|
|
152
138
|
result.semanticCreated++;
|
|
153
139
|
// Demote source episodics (they've been compressed into neocortex):
|
|
154
140
|
// scale half_life_days so they decay sooner while staying recoverable.
|
|
155
|
-
// Immediate ranking is deliberately unchanged:
|
|
156
|
-
//
|
|
157
|
-
// summary regresses budget-bounded QA (docs/evals/). The stored
|
|
141
|
+
// Immediate ranking is deliberately unchanged: dropping children below a
|
|
142
|
+
// worse-retrieving summary regresses budget-bounded QA. The stored
|
|
158
143
|
// strength is refreshed to the live value so inspect, replay sampling,
|
|
159
144
|
// and strength-sorted assembly see the truth instead of a fake 0.3.
|
|
160
145
|
// Mutate in place (not a copy): `cluster` holds the same object
|
|
@@ -15,7 +15,7 @@ export interface ConsolidationResult {
|
|
|
15
15
|
semanticCreated: number;
|
|
16
16
|
replayed: number;
|
|
17
17
|
promotedTraces: number;
|
|
18
|
-
/**
|
|
18
|
+
/** Sessions skipped because their events span two derivation scopes. */
|
|
19
19
|
tracesSkippedMixedScope: number;
|
|
20
20
|
extractionCandidates: number;
|
|
21
21
|
extracted: number;
|
|
@@ -35,13 +35,6 @@ export interface ConsolidationResult {
|
|
|
35
35
|
}
|
|
36
36
|
export declare const REPLAY_COUNT_DEFAULT = 5;
|
|
37
37
|
export declare function keptAsWritten(entry: MemoryEntry): boolean;
|
|
38
|
-
/** JSON value shape for a session event's free-form metadata field, cast to
|
|
39
|
-
* once at its `Record<string, unknown>` origin so it can be narrowed via
|
|
40
|
-
* isJsonString below rather than left as unparsed `unknown`. */
|
|
41
|
-
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
42
|
-
[key: string]: JsonValue;
|
|
43
|
-
};
|
|
44
|
-
export declare function isJsonString(value: JsonValue): value is string;
|
|
45
38
|
/** The sleep's one tombstone-check handle: opened on first use, never under dryRun, closed once. */
|
|
46
39
|
interface LazyDb {
|
|
47
40
|
get: () => DatabaseSyncLike | null;
|
package/dist/consolidate/run.js
CHANGED
|
@@ -4,31 +4,9 @@ export const REPLAY_COUNT_DEFAULT = 5;
|
|
|
4
4
|
export function keptAsWritten(entry) {
|
|
5
5
|
return entry.tags.some((tag) => NO_MERGE_TAGS.has(tag));
|
|
6
6
|
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
// AT1 rejection-guard db handle (docs/plans/2026-08-15-at1-rejected-value-tombstone.md):
|
|
11
|
-
// covers BOTH the auto-promote pass (1.4) and the merge
|
|
12
|
-
// pass (3) — both build deterministic content that
|
|
13
|
-
// batchWriteAndDelete writes through the guard's bypass, so both need a
|
|
14
|
-
// producer-side tombstone check before pushing to pendingWrites.
|
|
15
|
-
//
|
|
16
|
-
// T3 fix (2026-08-15 hardening pass, perf hygiene): memoized lazy getter,
|
|
17
|
-
// not an eager open. The handle only serves these two tombstone checks —
|
|
18
|
-
// a sleep with zero promotable sessions and zero merge clusters never
|
|
19
|
-
// reaches either use site, so opening it unconditionally on every
|
|
20
|
-
// non-dry-run sleep paid a db-open cost for nothing. dryRun still never
|
|
21
|
-
// opens (getConsolidateDb short-circuits before touching the handle).
|
|
22
|
-
// consolidateDbOpened (not just a truthy handle check) is the
|
|
23
|
-
// single source of truth for "was this ever opened", so the finally
|
|
24
|
-
// closes it exactly once and never double-opens.
|
|
25
|
-
//
|
|
26
|
-
// AT1 P2 fix (codex, handle-leak restructure): the getter's lifetime must
|
|
27
|
-
// start IMMEDIATELY before the try whose finally closes it, covering every
|
|
28
|
-
// phase that can touch it — not just the merge pass. An exception thrown by
|
|
29
|
-
// auto-promote (1.4), replay (1.5), batch extraction (1.6), the DAG
|
|
30
|
-
// passes (1.7-1.9), or physics (2) would otherwise propagate past an open handle
|
|
31
|
-
// with nothing to close it.
|
|
7
|
+
// Auto-promote (1.4) and merge (3) write deterministic content through the guard's bypass, so both
|
|
8
|
+
// need this tombstone check. Lazy so a sleep reaching neither never opens it; call it IMMEDIATELY
|
|
9
|
+
// before the try whose finally closes it, so a throw in any phase cannot leak the handle.
|
|
32
10
|
export function lazyConsolidateDb(hippoRoot, dryRun) {
|
|
33
11
|
let consolidateDbHandle = null;
|
|
34
12
|
let consolidateDbOpened = false;
|
|
@@ -33,10 +33,8 @@ export async function consolidate(hippoRoot, options = {}) {
|
|
|
33
33
|
const dryRun = options.dryRun ?? false;
|
|
34
34
|
const result = newConsolidationResult(dryRun);
|
|
35
35
|
const halfLife = migrateHalfLives(hippoRoot, dryRun, result);
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
// with no cross-tenant dedup. The api.sleep audit row tags this with the
|
|
39
|
-
// admin synthetic actor; see api.ts:2050 for the rationale.
|
|
36
|
+
// Host-wide by design: per-tenant filtering would mean N runs per host and no cross-tenant dedup.
|
|
37
|
+
// The api.sleep audit row tags this with the admin synthetic actor.
|
|
40
38
|
const all = loadAllEntries(hippoRoot);
|
|
41
39
|
if (dryRun)
|
|
42
40
|
for (const e of all)
|
|
@@ -153,24 +151,14 @@ function logRun(run, decay) {
|
|
|
153
151
|
if (decay.rescuedEntries.length > 0)
|
|
154
152
|
auditRescues(run, decay);
|
|
155
153
|
}
|
|
156
|
-
// One audit row per rescue
|
|
157
|
-
//
|
|
158
|
-
// (and after conflict detection + run logging), not inline in the decay
|
|
159
|
-
// pass — same durability posture as api.ts's top-level 'consolidate'
|
|
160
|
-
// summary audit row (written only once the whole sleep has completed).
|
|
161
|
-
// Writing it earlier would assert rescues for a cycle whose effects
|
|
162
|
-
// never landed if a later phase threw. Real writes only — dry-run
|
|
163
|
-
// previews the decision (details line above) but persists nothing.
|
|
154
|
+
// One audit row per rescue, written only after batchWriteAndDelete commits: writing earlier
|
|
155
|
+
// would assert rescues for a cycle whose effects never landed if a later phase threw.
|
|
164
156
|
function auditRescues(run, { rescuedEntries, rankById }) {
|
|
165
157
|
const { result } = run;
|
|
166
158
|
try {
|
|
167
159
|
const auditDb = openHippoDb(run.hippoRoot);
|
|
168
160
|
try {
|
|
169
|
-
//
|
|
170
|
-
// whole loop — a single failed appendAuditEvent must not silently
|
|
171
|
-
// drop every remaining row. Mirrors the physics pass's
|
|
172
|
-
// skipped-warning precedent: count losses, keep
|
|
173
|
-
// the overall fail-soft posture, tell the operator via details.
|
|
161
|
+
// Per-row try/catch: one failed appendAuditEvent must not silently drop every remaining row.
|
|
174
162
|
let auditFailures = 0;
|
|
175
163
|
for (const entry of rescuedEntries) {
|
|
176
164
|
try {
|
|
@@ -8,7 +8,8 @@ import { resolveTenantId } from '../tenant.js';
|
|
|
8
8
|
import { appendAuditEvent, reportAuditWriteFailure } from '../audit.js';
|
|
9
9
|
import { commonDerivationScope } from '../recall-scope.js';
|
|
10
10
|
import { log } from '../log.js';
|
|
11
|
-
import { REPLAY_COUNT_DEFAULT
|
|
11
|
+
import { REPLAY_COUNT_DEFAULT } from './run.js';
|
|
12
|
+
import { isJsonString } from '../json.js';
|
|
12
13
|
// -------------------------------------------------------------------------
|
|
13
14
|
// 1.4. Auto-promote complete sessions to traces
|
|
14
15
|
// -------------------------------------------------------------------------
|
|
@@ -24,10 +25,8 @@ export function promoteSessionTraces(run) {
|
|
|
24
25
|
let tracesSkippedRejected = 0;
|
|
25
26
|
const windowDays = run.config.autoTraceWindowDays ?? 7;
|
|
26
27
|
const sinceMs = run.now.getTime() - windowDays * 24 * 60 * 60 * 1000;
|
|
27
|
-
// Auto-trace
|
|
28
|
-
// tenant
|
|
29
|
-
// consolidation across all tenants need a per-tenant loop layered on top
|
|
30
|
-
// of this — tracked in docs/plans/2026-05-02-continuity-tables-tenant-scope.md.
|
|
28
|
+
// Auto-trace runs single-tenant (the env-resolved tenant); consolidating
|
|
29
|
+
// every tenant needs a per-tenant loop layered on top of this.
|
|
31
30
|
const consolidationTenant = resolveTenantId({});
|
|
32
31
|
const promotable = findPromotableSessions(run.hippoRoot, consolidationTenant, sinceMs);
|
|
33
32
|
for (const session of promotable) {
|
|
@@ -60,7 +59,7 @@ function sessionTrace(run, consolidationTenant, sessionId) {
|
|
|
60
59
|
session_id: sessionId,
|
|
61
60
|
limit: 1000,
|
|
62
61
|
});
|
|
63
|
-
//
|
|
62
|
+
// A mixed-scope session would otherwise leak into one trace.
|
|
64
63
|
const sessionScope = commonDerivationScope(events.map((e) => e.scope));
|
|
65
64
|
if (!sessionScope.ok) {
|
|
66
65
|
run.result.tracesSkippedMixedScope++;
|
|
@@ -91,26 +90,15 @@ function sessionTrace(run, consolidationTenant, sessionId) {
|
|
|
91
90
|
tags: ['auto-promoted'],
|
|
92
91
|
source: 'auto-promote',
|
|
93
92
|
scope: sessionScope.scope,
|
|
94
|
-
//
|
|
95
|
-
// tenant
|
|
96
|
-
// under. Before
|
|
97
|
-
// this, createMemory omitted tenantId and the trace always landed
|
|
98
|
-
// 'default' (memory.ts:535) while the idempotency check ran under
|
|
99
|
-
// consolidationTenant — for any non-default tenant that check never
|
|
100
|
-
// hit, and the trace regenerated every sleep.
|
|
93
|
+
// Same tenant as the traceExistsForSession check above, or a
|
|
94
|
+
// non-default tenant's trace regenerates every sleep.
|
|
101
95
|
tenantId: consolidationTenant,
|
|
102
96
|
baseHalfLifeDays: run.config.defaultHalfLifeDays,
|
|
103
97
|
});
|
|
104
98
|
return { trace, outcome };
|
|
105
99
|
}
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
// removed, that idempotency check no longer blocks regeneration, and
|
|
109
|
-
// this write would otherwise reach batchWriteAndDelete's guard bypass
|
|
110
|
-
// unchecked, resurrecting it every sleep. Check under THE ENTRY'S OWN
|
|
111
|
-
// stamped tenantId (read off `trace` after createMemory — never guess
|
|
112
|
-
// the tenant) + the built content's digest. A hit skips the push
|
|
113
|
-
// entirely: not counted as promoted, not added to survivors.
|
|
100
|
+
// traceExistsForSession only sees live rows, so a removed rejected trace would regenerate every
|
|
101
|
+
// sleep; check the tombstone under the entry's own stamped tenantId, never a guessed one.
|
|
114
102
|
function traceRejected(run, trace, sessionId) {
|
|
115
103
|
const consolidateDb = run.getConsolidateDb();
|
|
116
104
|
if (!consolidateDb)
|
package/dist/customer-notes.d.ts
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* (docs/plans/2026-06-01-e2-customer-note-object.md).
|
|
2
|
+
* customer_note first-class object.
|
|
4
3
|
*
|
|
5
4
|
* A `customer_note` is a discrete note recorded against an account/customer entity:
|
|
6
5
|
* a `note` body scoped to a `customer`, evolving via the supersede delta lifecycle.
|
|
7
|
-
* Entity-scoping is a free-form `customer` column (the `entities` table is unbuilt
|
|
8
|
-
*
|
|
6
|
+
* Entity-scoping is a free-form `customer` column (the `entities` table is unbuilt,
|
|
7
|
+
* so an FK is deferred). Unlike project_brief's one-summary-per-repo,
|
|
9
8
|
* a customer accrues MANY discrete notes over time, each with its own supersede chain
|
|
10
9
|
* (correct a note -> a new version preserving history; close retires it).
|
|
11
10
|
*
|
|
12
11
|
* Reuses the project_brief/skill supersede machinery verbatim (superseded_by self-FK
|
|
13
12
|
* + CAS + INSERT-preflight + server-derived version + change_summary + supersede
|
|
14
|
-
* tenant-match trigger). It has NO assembler/renderer (the simplest
|
|
13
|
+
* tenant-match trigger). It has NO assembler/renderer (the simplest first-class object): the
|
|
15
14
|
* contribution is purely the entity-scoping dimension.
|
|
16
15
|
*
|
|
17
16
|
* The `customer_notes` table is the source of truth (survives memory decay); the
|
|
@@ -73,8 +72,7 @@ export interface ListCustomerNotesOpts {
|
|
|
73
72
|
* the new version = predecessor.version + 1 (server-derived).
|
|
74
73
|
*
|
|
75
74
|
* The memory mirror carries a `customer:<lc>` tag (in addition to ['customer_note']
|
|
76
|
-
* + caller extraTags) so scope-aware recall treats the note as entity-local
|
|
77
|
-
* project_brief codex-P2 recall-locality lesson applied to entity scoping. There is
|
|
75
|
+
* + caller extraTags) so scope-aware recall treats the note as entity-local. There is
|
|
78
76
|
* no self-recursion path (customer_note has no receipt-query/refresh).
|
|
79
77
|
*/
|
|
80
78
|
export declare function saveCustomerNote(hippoRoot: string, tenantId: string, opts: SaveCustomerNoteOpts, actor?: string): CustomerNote;
|
package/dist/customer-notes.js
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* (docs/plans/2026-06-01-e2-customer-note-object.md).
|
|
2
|
+
* customer_note first-class object.
|
|
4
3
|
*
|
|
5
4
|
* A `customer_note` is a discrete note recorded against an account/customer entity:
|
|
6
5
|
* a `note` body scoped to a `customer`, evolving via the supersede delta lifecycle.
|
|
7
|
-
* Entity-scoping is a free-form `customer` column (the `entities` table is unbuilt
|
|
8
|
-
*
|
|
6
|
+
* Entity-scoping is a free-form `customer` column (the `entities` table is unbuilt,
|
|
7
|
+
* so an FK is deferred). Unlike project_brief's one-summary-per-repo,
|
|
9
8
|
* a customer accrues MANY discrete notes over time, each with its own supersede chain
|
|
10
9
|
* (correct a note -> a new version preserving history; close retires it).
|
|
11
10
|
*
|
|
12
11
|
* Reuses the project_brief/skill supersede machinery verbatim (superseded_by self-FK
|
|
13
12
|
* + CAS + INSERT-preflight + server-derived version + change_summary + supersede
|
|
14
|
-
* tenant-match trigger). It has NO assembler/renderer (the simplest
|
|
13
|
+
* tenant-match trigger). It has NO assembler/renderer (the simplest first-class object): the
|
|
15
14
|
* contribution is purely the entity-scoping dimension.
|
|
16
15
|
*
|
|
17
16
|
* The `customer_notes` table is the source of truth (survives memory decay); the
|
|
@@ -97,7 +96,6 @@ function buildNoteContent(customer, note) {
|
|
|
97
96
|
// Preflight the supersede target BEFORE inserting the new row (so the new
|
|
98
97
|
// autoincrement id can never be its own supersede target); read the
|
|
99
98
|
// predecessor version in the same SELECT for server-derived versioning.
|
|
100
|
-
// Mirrors saveProjectBrief / saveSkill (codex P1 2026-05-28).
|
|
101
99
|
function preflightNoteSupersede(db, tenantId, supersedesId) {
|
|
102
100
|
// SAFETY: SELECT projects exactly status, version; .get() returns that
|
|
103
101
|
// shape for the matching row, or undefined when no note/tenant pair matches.
|
|
@@ -175,8 +173,7 @@ function writeNoteRow(db, memoryId, w) {
|
|
|
175
173
|
* the new version = predecessor.version + 1 (server-derived).
|
|
176
174
|
*
|
|
177
175
|
* The memory mirror carries a `customer:<lc>` tag (in addition to ['customer_note']
|
|
178
|
-
* + caller extraTags) so scope-aware recall treats the note as entity-local
|
|
179
|
-
* project_brief codex-P2 recall-locality lesson applied to entity scoping. There is
|
|
176
|
+
* + caller extraTags) so scope-aware recall treats the note as entity-local. There is
|
|
180
177
|
* no self-recursion path (customer_note has no receipt-query/refresh).
|
|
181
178
|
*/
|
|
182
179
|
export function saveCustomerNote(hippoRoot, tenantId, opts, actor = 'cli') {
|
|
@@ -255,7 +252,7 @@ export function closeCustomerNote(hippoRoot, tenantId, id, actor = 'cli') {
|
|
|
255
252
|
// Closing removes the object from the graph. Remove its rows DIRECTLY (deterministic),
|
|
256
253
|
// not only via an enqueued rebuild whose queue item is lost if the mirror is later
|
|
257
254
|
// forgotten (the queue row cascade-deletes with the memory), which would leave the closed
|
|
258
|
-
// object stale and could block that forget
|
|
255
|
+
// object stale and could block that forget. Still enqueue when a mirror exists
|
|
259
256
|
// so a concurrent rebuild re-derives consistently (harmless if it also runs).
|
|
260
257
|
removeGraphEntitiesForObject(hippoRoot, tenantId, 'customer', closed.id);
|
|
261
258
|
if (closed.memoryId) {
|
package/dist/dag.d.ts
CHANGED
|
@@ -16,11 +16,8 @@ export interface DagBuildResult {
|
|
|
16
16
|
candidateClusters: number;
|
|
17
17
|
summariesCreated: number;
|
|
18
18
|
factsLinked: number;
|
|
19
|
-
/**
|
|
20
|
-
*
|
|
21
|
-
* phase abort). Member re-parenting writes are unaffected by construction
|
|
22
|
-
* (same id + same content = guard-exempt), so this only ever counts
|
|
23
|
-
* summary-creation refusals. */
|
|
19
|
+
/** Clusters skipped because the summary matched a rejected value. Re-parenting writes are
|
|
20
|
+
* guard-exempt (same id + content), so only summary-creation refusals count. */
|
|
24
21
|
rejected: number;
|
|
25
22
|
}
|
|
26
23
|
export declare function buildDag(hippoRoot: string, facts: MemoryEntry[], opts: DagSummaryOptions): Promise<DagBuildResult>;
|
|
@@ -33,21 +30,17 @@ export interface DagRebuildResult {
|
|
|
33
30
|
capped: boolean;
|
|
34
31
|
}
|
|
35
32
|
/**
|
|
36
|
-
*
|
|
33
|
+
* Sleep-cycle phase that drains the dirty L2 summary queue.
|
|
37
34
|
* Thin orchestrator; the heavy lifting lives in store.ts (load + apply)
|
|
38
35
|
* and dag.ts:generateDagSummary (LLM call).
|
|
39
36
|
*
|
|
40
|
-
* Per-summary try/catch
|
|
41
|
-
* throwing rebuild does NOT abort the rest of the queue.
|
|
37
|
+
* Per-summary try/catch: one throwing rebuild does NOT abort the rest of the queue.
|
|
42
38
|
*
|
|
43
39
|
* Race-loser handling: applyRebuildResult's UPDATE WHERE includes
|
|
44
40
|
* AND summary_dirty=1, so concurrent sleep's second writer returns
|
|
45
41
|
* changed=false. Silent skip (neither rebuilt++ nor refused++ nor failed++).
|
|
46
42
|
*
|
|
47
|
-
*
|
|
48
|
-
* (rebuild content matches a previously-rejected value) increments
|
|
49
|
-
* `refused`, not `rebuilt`. Dirty still clears either way; only the stat
|
|
50
|
-
* split changed (docs/plans/2026-08-15-hardening-at1-followups.md T4).
|
|
43
|
+
* A tombstone hit counts as `refused`, not `rebuilt`; dirty clears either way.
|
|
51
44
|
*/
|
|
52
45
|
export declare function rebuildDirtySummaries(hippoRoot: string, opts: DagSummaryOptions & {
|
|
53
46
|
cap?: number;
|
|
@@ -57,21 +50,17 @@ export interface EntityProfilesBuildResult {
|
|
|
57
50
|
profilesCreated: number;
|
|
58
51
|
l2sLinked: number;
|
|
59
52
|
failed: number;
|
|
60
|
-
/**
|
|
61
|
-
*
|
|
62
|
-
* rate-limit) — a tombstone hit is a deliberate refusal, not an error. */
|
|
53
|
+
/** Clusters skipped because the profile matched a rejected value. Kept apart from
|
|
54
|
+
* `failed`: a tombstone hit is a deliberate refusal, not an error. */
|
|
63
55
|
rejected: number;
|
|
64
56
|
}
|
|
65
57
|
/**
|
|
66
|
-
*
|
|
58
|
+
* Build L3 entity profiles by clustering L2 summaries with
|
|
67
59
|
* shared entity tags. Threshold 2+ L2s per entity. Mirrors buildDag L1->L2
|
|
68
60
|
* pattern, one level up.
|
|
69
61
|
*
|
|
70
|
-
* Born-dirty cancellation
|
|
71
|
-
*
|
|
72
|
-
* call clearSummaryDirtyAfterBuild with source='buildEntityProfiles-clean'
|
|
73
|
-
* so E3 sleep-cycle rebuild doesn't re-rebuild the freshly-built L3 this
|
|
74
|
-
* same cycle.
|
|
62
|
+
* Born-dirty cancellation: each L2 link write marks the new L3 dirty, so clear it
|
|
63
|
+
* or the same sleep cycle's rebuild re-rebuilds the freshly built L3.
|
|
75
64
|
*/
|
|
76
65
|
export declare function buildEntityProfiles(hippoRoot: string, l2Summaries: MemoryEntry[], opts: DagSummaryOptions): Promise<EntityProfilesBuildResult>;
|
|
77
66
|
//# sourceMappingURL=dag.d.ts.map
|
package/dist/dag.js
CHANGED
|
@@ -87,13 +87,9 @@ export async function generateDagSummary(label, factContents, opts) {
|
|
|
87
87
|
return null;
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
// LLM-synthesized summary. Map preserves insertion order, so
|
|
94
|
-
// single-tenant stores (every row 'default') get exactly one partition
|
|
95
|
-
// and iterate in the same order as before this fix — byte-identical
|
|
96
|
-
// behavior there.
|
|
90
|
+
// Partition BEFORE clustering so one LLM summary never mixes facts from different tenants.
|
|
91
|
+
// Map keeps insertion order, so a single-tenant store iterates in its original order.
|
|
92
|
+
// buildEntityProfiles partitions its L2s with this same key.
|
|
97
93
|
function partitionFactsByTenant(unparented) {
|
|
98
94
|
const unparentedByTenant = new Map();
|
|
99
95
|
for (const fact of unparented) {
|
|
@@ -123,9 +119,8 @@ function createClusterSummaryEntry(summary, cluster, home) {
|
|
|
123
119
|
baseHalfLifeDays: home.baseHalfLifeDays,
|
|
124
120
|
});
|
|
125
121
|
summaryEntry.origin_project = home.originProject;
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
// can reason about scope without walking the children.
|
|
122
|
+
// Cache descendant_count + earliest/latest_at on the summary row so
|
|
123
|
+
// DAG-aware recall can reason about scope without walking the children.
|
|
129
124
|
summaryEntry.descendant_count = cluster.members.length;
|
|
130
125
|
summaryEntry.earliest_at = memberCreatedAts[0];
|
|
131
126
|
summaryEntry.latest_at = memberCreatedAts[memberCreatedAts.length - 1];
|
|
@@ -137,21 +132,8 @@ async function summarizeCluster(hippoRoot, cluster, home, opts, result) {
|
|
|
137
132
|
if (!summary)
|
|
138
133
|
return;
|
|
139
134
|
const summaryEntry = createClusterSummaryEntry(summary, cluster, home);
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
// member re-parenting writes below never run for a skipped cluster
|
|
143
|
-
// (there is no summary id to parent them under).
|
|
144
|
-
//
|
|
145
|
-
// The tombstone check itself is tenant-scoped for free: writeEntry ->
|
|
146
|
-
// writeEntryDbOnly -> upsertEntryRow calls
|
|
147
|
-
// checkRejectionGuard(db, entry.tenantId ?? 'default', ...)
|
|
148
|
-
// (store/entry-writes.ts), reading tenantId off the entry being written. Now
|
|
149
|
-
// that summaryEntry carries home.tenantId instead of the implicit
|
|
150
|
-
// 'default', the guard consults that tenant's tombstones — no
|
|
151
|
-
// separate check needed here (unlike consolidate.ts's merge pass,
|
|
152
|
-
// which pre-checks via findRejectedValue because it writes through
|
|
153
|
-
// batchWriteAndDelete's bypassRejectionGuard path instead of
|
|
154
|
-
// writeEntry).
|
|
135
|
+
// A refused summary skips only this cluster. writeEntry's guard reads tenantId off the entry
|
|
136
|
+
// (home.tenantId), so it already checks this tenant's tombstones: no separate check here.
|
|
155
137
|
try {
|
|
156
138
|
writeEntry(hippoRoot, summaryEntry);
|
|
157
139
|
}
|
|
@@ -169,11 +151,8 @@ async function summarizeCluster(hippoRoot, cluster, home, opts, result) {
|
|
|
169
151
|
writeEntry(hippoRoot, updated);
|
|
170
152
|
result.factsLinked++;
|
|
171
153
|
}
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
// The summary we just built IS fresh, no rebuild needed. Without this,
|
|
175
|
-
// E3 in the SAME sleep cycle would re-rebuild every new summary
|
|
176
|
-
// (2x LLM cost). plan-eng-r1 HIGH must-fix.
|
|
154
|
+
// Member writes just marked this fresh summary dirty; clear it, or the same sleep cycle's
|
|
155
|
+
// rebuild pass would re-rebuild every new summary at twice the LLM cost.
|
|
177
156
|
clearSummaryDirtyAfterBuild(hippoRoot, summaryEntry.id, summaryEntry.tenantId, 'buildDag');
|
|
178
157
|
}
|
|
179
158
|
export async function buildDag(hippoRoot, facts, opts) {
|
|
@@ -248,21 +227,17 @@ async function rebuildOneSummary(hippoRoot, summary, opts, result) {
|
|
|
248
227
|
// success, silently skip
|
|
249
228
|
}
|
|
250
229
|
/**
|
|
251
|
-
*
|
|
230
|
+
* Sleep-cycle phase that drains the dirty L2 summary queue.
|
|
252
231
|
* Thin orchestrator; the heavy lifting lives in store.ts (load + apply)
|
|
253
232
|
* and dag.ts:generateDagSummary (LLM call).
|
|
254
233
|
*
|
|
255
|
-
* Per-summary try/catch
|
|
256
|
-
* throwing rebuild does NOT abort the rest of the queue.
|
|
234
|
+
* Per-summary try/catch: one throwing rebuild does NOT abort the rest of the queue.
|
|
257
235
|
*
|
|
258
236
|
* Race-loser handling: applyRebuildResult's UPDATE WHERE includes
|
|
259
237
|
* AND summary_dirty=1, so concurrent sleep's second writer returns
|
|
260
238
|
* changed=false. Silent skip (neither rebuilt++ nor refused++ nor failed++).
|
|
261
239
|
*
|
|
262
|
-
*
|
|
263
|
-
* (rebuild content matches a previously-rejected value) increments
|
|
264
|
-
* `refused`, not `rebuilt`. Dirty still clears either way; only the stat
|
|
265
|
-
* split changed (docs/plans/2026-08-15-hardening-at1-followups.md T4).
|
|
240
|
+
* A tombstone hit counts as `refused`, not `rebuilt`; dirty clears either way.
|
|
266
241
|
*/
|
|
267
242
|
export async function rebuildDirtySummaries(hippoRoot, opts) {
|
|
268
243
|
const cap = opts.cap ?? 20;
|
|
@@ -291,33 +266,14 @@ export async function rebuildDirtySummaries(hippoRoot, opts) {
|
|
|
291
266
|
await rebuildOneSummary(hippoRoot, summary, opts, result);
|
|
292
267
|
}
|
|
293
268
|
catch (err) {
|
|
294
|
-
//
|
|
295
|
-
//
|
|
296
|
-
// (audit() wraps its own writes try/catch per store/audit-event.ts, so a
|
|
297
|
-
// throw here is exotic: SQLite I/O error, prepare failure, etc).
|
|
269
|
+
// One throw doesn't abort the queue; log enough to triage an exotic
|
|
270
|
+
// failure (SQLite I/O, prepare), since audit() already catches its own.
|
|
298
271
|
result.failed++;
|
|
299
272
|
log.error(`rebuildDirtySummaries: summary ${summary.id} (tenant ${summary.tenantId}) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
300
273
|
}
|
|
301
274
|
}
|
|
302
275
|
return result;
|
|
303
276
|
}
|
|
304
|
-
// independent-review HIGH #1 fold: cluster ONLY within-tenant.
|
|
305
|
-
// clusterFacts has no tenant awareness; without this partition step a
|
|
306
|
-
// multi-tenant host could form a cluster spanning tenants and produce
|
|
307
|
-
// a single L3 with tenantId='default' that doesn't belong to either
|
|
308
|
-
// child tenant. Fix: bucket by tenantId, run clusterFacts per-tenant,
|
|
309
|
-
// pass tenantId to createMemory.
|
|
310
|
-
function partitionL2sByTenant(unparented) {
|
|
311
|
-
const byTenant = new Map();
|
|
312
|
-
for (const l2 of unparented) {
|
|
313
|
-
const tid = l2.tenantId ?? 'default';
|
|
314
|
-
const key = derivationPartitionKey(tid, l2.scope, l2.origin_project);
|
|
315
|
-
const list = byTenant.get(key) ?? [];
|
|
316
|
-
list.push(l2);
|
|
317
|
-
byTenant.set(key, list);
|
|
318
|
-
}
|
|
319
|
-
return byTenant;
|
|
320
|
-
}
|
|
321
277
|
/** The L3 profile entry for one cluster of L2 summaries. */
|
|
322
278
|
function createProfileEntry(summary, cluster, home) {
|
|
323
279
|
const memberCreatedAts = cluster.members.map((m) => m.created).sort();
|
|
@@ -327,7 +283,7 @@ function createProfileEntry(summary, cluster, home) {
|
|
|
327
283
|
tags: [...cluster.entityTags, ...neverAutoShareTags(cluster.members), 'dag-entity-profile'],
|
|
328
284
|
confidence: 'inferred',
|
|
329
285
|
dag_level: 3,
|
|
330
|
-
tenantId: home.tenantId,
|
|
286
|
+
tenantId: home.tenantId,
|
|
331
287
|
scope: home.scope,
|
|
332
288
|
baseHalfLifeDays: home.baseHalfLifeDays,
|
|
333
289
|
});
|
|
@@ -346,9 +302,7 @@ async function profileCluster(hippoRoot, cluster, home, opts, result) {
|
|
|
346
302
|
return;
|
|
347
303
|
}
|
|
348
304
|
const profileEntry = createProfileEntry(summary, cluster, home);
|
|
349
|
-
//
|
|
350
|
-
// count, log once. The member re-linking writes below never run for a
|
|
351
|
-
// skipped cluster (mirrors buildDag above).
|
|
305
|
+
// A refused profile skips only this cluster; the re-linking below never runs for it.
|
|
352
306
|
try {
|
|
353
307
|
writeEntry(hippoRoot, profileEntry);
|
|
354
308
|
}
|
|
@@ -366,21 +320,17 @@ async function profileCluster(hippoRoot, cluster, home, opts, result) {
|
|
|
366
320
|
writeEntry(hippoRoot, updated);
|
|
367
321
|
result.l2sLinked++;
|
|
368
322
|
}
|
|
369
|
-
//
|
|
370
|
-
//
|
|
371
|
-
// Args: (root, id, tenantId, actor, source).
|
|
323
|
+
// Re-linking just marked the fresh L3 dirty; clear it so this cycle's rebuild skips it.
|
|
324
|
+
// The distinct source tags the audit row.
|
|
372
325
|
clearSummaryDirtyAfterBuild(hippoRoot, profileEntry.id, home.tenantId, 'buildEntityProfiles', 'buildEntityProfiles-clean');
|
|
373
326
|
}
|
|
374
327
|
/**
|
|
375
|
-
*
|
|
328
|
+
* Build L3 entity profiles by clustering L2 summaries with
|
|
376
329
|
* shared entity tags. Threshold 2+ L2s per entity. Mirrors buildDag L1->L2
|
|
377
330
|
* pattern, one level up.
|
|
378
331
|
*
|
|
379
|
-
* Born-dirty cancellation
|
|
380
|
-
*
|
|
381
|
-
* call clearSummaryDirtyAfterBuild with source='buildEntityProfiles-clean'
|
|
382
|
-
* so E3 sleep-cycle rebuild doesn't re-rebuild the freshly-built L3 this
|
|
383
|
-
* same cycle.
|
|
332
|
+
* Born-dirty cancellation: each L2 link write marks the new L3 dirty, so clear it
|
|
333
|
+
* or the same sleep cycle's rebuild re-rebuilds the freshly built L3.
|
|
384
334
|
*/
|
|
385
335
|
export async function buildEntityProfiles(hippoRoot, l2Summaries, opts) {
|
|
386
336
|
const result = {
|
|
@@ -393,9 +343,9 @@ export async function buildEntityProfiles(hippoRoot, l2Summaries, opts) {
|
|
|
393
343
|
const baseHalfLifeDays = loadConfig(hippoRoot).defaultHalfLifeDays;
|
|
394
344
|
// Only L2 with no L3 parent yet (avoid re-clustering already-profiled L2s).
|
|
395
345
|
const unparented = l2Summaries.filter((s) => s.dag_level === 2 && !s.dag_parent_id);
|
|
396
|
-
for (const [, tenantL2s] of
|
|
346
|
+
for (const [, tenantL2s] of partitionFactsByTenant(unparented)) {
|
|
397
347
|
const home = {
|
|
398
|
-
tenantId: tenantL2s[0].tenantId
|
|
348
|
+
tenantId: tenantL2s[0].tenantId,
|
|
399
349
|
scope: derivationScope(tenantL2s[0].scope),
|
|
400
350
|
originProject: tenantL2s[0].origin_project,
|
|
401
351
|
baseHalfLifeDays,
|
package/dist/db/continuity.js
CHANGED
|
@@ -112,8 +112,8 @@ const CARD_COMMENTS_DDL = `
|
|
|
112
112
|
tenant_id TEXT NOT NULL DEFAULT 'default'
|
|
113
113
|
)
|
|
114
114
|
`;
|
|
115
|
-
// Before the loop on stamped stores: a table lost after its migration stamped
|
|
116
|
-
//
|
|
115
|
+
// Before the loop on stamped stores: a table lost after its migration stamped
|
|
116
|
+
// is never re-migrated, and v4/v16/v22 ALTER or read it. Fresh stores use the chain.
|
|
117
117
|
export function ensureContinuityTables(db) {
|
|
118
118
|
db.exec(TASK_SNAPSHOTS_DDL);
|
|
119
119
|
db.exec(SESSION_EVENTS_DDL);
|
|
@@ -2,7 +2,7 @@ import { tableHasColumn } from '../tables.js';
|
|
|
2
2
|
export const v14 = {
|
|
3
3
|
version: 14,
|
|
4
4
|
up: (db) => {
|
|
5
|
-
//
|
|
5
|
+
// Provenance envelope: kind, scope, owner, artifact_ref.
|
|
6
6
|
// SQLite ALTER TABLE ADD COLUMN cannot add CHECK; CHECK enforcement lives
|
|
7
7
|
// in INSERT/UPDATE triggers added later in this migration.
|
|
8
8
|
if (!tableHasColumn(db, 'memories', 'kind')) {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
export const v15 = {
|
|
2
2
|
version: 15,
|
|
3
3
|
up: (db) => {
|
|
4
|
-
//
|
|
5
|
-
// dedup safety. Both findings landed in /review on commits 41b1f4d..6456e7d.
|
|
4
|
+
// Close the NULL-kind bypass and add raw_archive dedup safety.
|
|
6
5
|
//
|
|
7
6
|
// (1) Original v14 triggers used `WHEN NEW.kind IS NOT NULL AND NEW.kind NOT IN (...)`.
|
|
8
7
|
// A direct INSERT/UPDATE setting kind=NULL bypassed the CHECK substitute. Replace
|