memorix 1.2.0 → 1.2.2
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/CHANGELOG.md +30 -1
- package/README.md +18 -4
- package/README.zh-CN.md +18 -4
- package/TEAM.md +86 -86
- package/dist/cli/index.js +15919 -14055
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +1997 -1021
- package/dist/index.js.map +1 -1
- package/dist/maintenance-runner.d.ts +1 -1
- package/dist/maintenance-runner.js +8481 -8005
- package/dist/maintenance-runner.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +30 -1
- package/dist/sdk.d.ts +7 -2
- package/dist/sdk.js +2022 -1024
- package/dist/sdk.js.map +1 -1
- package/dist/types.d.ts +49 -1
- package/dist/types.js.map +1 -1
- package/docs/1.2.2-MEMORY-CONTROL-PLANE.md +434 -0
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +4 -0
- package/docs/API_REFERENCE.md +27 -5
- package/docs/DESIGN_DECISIONS.md +357 -357
- package/docs/DEVELOPMENT.md +4 -0
- package/docs/README.md +1 -1
- package/docs/SETUP.md +7 -1
- package/docs/dev-log/progress.txt +91 -11
- package/docs/knowledge/workflows/memorix-release.md +57 -0
- package/package.json +1 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/src/audit/index.ts +156 -156
- package/src/cli/command-guide.ts +192 -0
- package/src/cli/commands/audit-list.ts +89 -89
- package/src/cli/commands/audit.ts +9 -4
- package/src/cli/commands/background.ts +659 -659
- package/src/cli/commands/cleanup.ts +5 -1
- package/src/cli/commands/codegraph.ts +17 -8
- package/src/cli/commands/context.ts +3 -2
- package/src/cli/commands/doctor.ts +4 -2
- package/src/cli/commands/explain.ts +9 -3
- package/src/cli/commands/formation.ts +48 -48
- package/src/cli/commands/git-hook-install.ts +111 -111
- package/src/cli/commands/handoff.ts +75 -61
- package/src/cli/commands/hooks-status.ts +63 -63
- package/src/cli/commands/identity.ts +116 -0
- package/src/cli/commands/ingest-commit.ts +153 -153
- package/src/cli/commands/ingest-image.ts +71 -69
- package/src/cli/commands/ingest-log.ts +180 -180
- package/src/cli/commands/ingest.ts +44 -44
- package/src/cli/commands/integrate-shared.ts +15 -15
- package/src/cli/commands/knowledge.ts +40 -0
- package/src/cli/commands/lock.ts +93 -92
- package/src/cli/commands/memory.ts +58 -21
- package/src/cli/commands/message.ts +123 -118
- package/src/cli/commands/operator-shared.ts +98 -3
- package/src/cli/commands/poll.ts +74 -64
- package/src/cli/commands/purge-all-memory.ts +85 -85
- package/src/cli/commands/purge-project-memory.ts +83 -83
- package/src/cli/commands/reasoning.ts +135 -121
- package/src/cli/commands/retention.ts +9 -4
- package/src/cli/commands/serve-http.ts +22 -43
- package/src/cli/commands/serve-shared.ts +118 -118
- package/src/cli/commands/session.ts +29 -3
- package/src/cli/commands/setup.ts +9 -3
- package/src/cli/commands/skills.ts +124 -119
- package/src/cli/commands/status.ts +4 -3
- package/src/cli/commands/task.ts +193 -184
- package/src/cli/commands/team.ts +14 -10
- package/src/cli/commands/transfer.ts +108 -55
- package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
- package/src/cli/identity.ts +89 -0
- package/src/cli/index.ts +96 -19
- package/src/cli/invocation.ts +115 -0
- package/src/cli/tui/ChatView.tsx +234 -234
- package/src/cli/tui/CommandBar.tsx +312 -312
- package/src/cli/tui/ContextRail.tsx +118 -118
- package/src/cli/tui/HeaderBar.tsx +72 -72
- package/src/cli/tui/LogoBanner.tsx +51 -51
- package/src/cli/tui/Sidebar.tsx +179 -179
- package/src/cli/tui/chat-service.ts +41 -18
- package/src/cli/tui/data.ts +23 -44
- package/src/cli/tui/index.ts +41 -41
- package/src/cli/tui/markdown-render.tsx +371 -371
- package/src/cli/tui/operator-context.ts +60 -0
- package/src/cli/tui/use-mouse.ts +157 -157
- package/src/cli/tui/useNavigation.ts +56 -56
- package/src/cli/tui/views/MemoryView.tsx +10 -8
- package/src/cli/update-checker.ts +211 -211
- package/src/cli/version.ts +7 -7
- package/src/cli/workbench.ts +1 -1
- package/src/codegraph/auto-context.ts +34 -17
- package/src/codegraph/context-pack.ts +1 -0
- package/src/codegraph/current-facts.ts +19 -1
- package/src/codegraph/project-context.ts +2 -0
- package/src/codegraph/task-lens.ts +49 -5
- package/src/compact/engine.ts +26 -10
- package/src/compact/index-format.ts +25 -2
- package/src/compact/token-budget.ts +74 -74
- package/src/dashboard/project-classification.ts +64 -64
- package/src/dashboard/server.ts +58 -52
- package/src/embedding/fastembed-provider.ts +142 -142
- package/src/embedding/transformers-provider.ts +111 -111
- package/src/git/extractor.ts +209 -209
- package/src/git/hooks-path.ts +85 -85
- package/src/hooks/admission.ts +117 -0
- package/src/hooks/handler.ts +98 -91
- package/src/hooks/pattern-detector.ts +173 -173
- package/src/hooks/significance-filter.ts +250 -250
- package/src/knowledge/claims.ts +51 -1
- package/src/knowledge/context-assembly.ts +97 -0
- package/src/knowledge/types.ts +1 -0
- package/src/knowledge/workflows.ts +34 -3
- package/src/knowledge/workset.ts +179 -10
- package/src/llm/memory-manager.ts +328 -328
- package/src/llm/provider.ts +885 -885
- package/src/llm/quality.ts +248 -248
- package/src/memory/admission.ts +57 -0
- package/src/memory/attribution-guard.ts +249 -249
- package/src/memory/auto-relations.ts +21 -0
- package/src/memory/consolidation.ts +13 -2
- package/src/memory/disclosure-policy.ts +140 -135
- package/src/memory/entity-extractor.ts +197 -197
- package/src/memory/export-import.ts +11 -3
- package/src/memory/formation/evaluate.ts +217 -217
- package/src/memory/formation/extract.ts +361 -361
- package/src/memory/formation/index.ts +417 -417
- package/src/memory/formation/resolve.ts +344 -344
- package/src/memory/formation/types.ts +315 -315
- package/src/memory/freshness.ts +122 -122
- package/src/memory/graph-context.ts +8 -2
- package/src/memory/graph-scope.ts +46 -0
- package/src/memory/graph.ts +197 -197
- package/src/memory/observations.ts +162 -4
- package/src/memory/quality-audit.ts +2 -0
- package/src/memory/refs.ts +94 -94
- package/src/memory/retention.ts +22 -2
- package/src/memory/secret-filter.ts +79 -79
- package/src/memory/session.ts +5 -2
- package/src/memory/visibility.ts +80 -0
- package/src/multimodal/image-loader.ts +143 -143
- package/src/orchestrate/adapters/claude-stream.ts +192 -192
- package/src/orchestrate/adapters/claude.ts +111 -111
- package/src/orchestrate/adapters/codex-stream.ts +134 -134
- package/src/orchestrate/adapters/codex.ts +41 -41
- package/src/orchestrate/adapters/gemini-stream.ts +166 -166
- package/src/orchestrate/adapters/gemini.ts +42 -42
- package/src/orchestrate/adapters/index.ts +73 -73
- package/src/orchestrate/adapters/opencode-stream.ts +143 -143
- package/src/orchestrate/adapters/opencode.ts +47 -47
- package/src/orchestrate/adapters/spawn-helper.ts +286 -286
- package/src/orchestrate/adapters/types.ts +77 -77
- package/src/orchestrate/capability-router.ts +284 -284
- package/src/orchestrate/context-compact.ts +188 -188
- package/src/orchestrate/cost-tracker.ts +219 -219
- package/src/orchestrate/error-recovery.ts +191 -191
- package/src/orchestrate/evidence.ts +140 -140
- package/src/orchestrate/ledger.ts +110 -110
- package/src/orchestrate/memorix-bridge.ts +378 -340
- package/src/orchestrate/output-budget.ts +80 -80
- package/src/orchestrate/permission.ts +152 -152
- package/src/orchestrate/pipeline-trace.ts +131 -131
- package/src/orchestrate/prompt-builder.ts +155 -155
- package/src/orchestrate/ring-buffer.ts +37 -37
- package/src/orchestrate/task-graph.ts +389 -389
- package/src/orchestrate/verify-gate.ts +33 -10
- package/src/orchestrate/worktree.ts +232 -232
- package/src/project/aliases.ts +374 -374
- package/src/project/detector.ts +268 -268
- package/src/rules/adapters/claude-code.ts +99 -99
- package/src/rules/adapters/codex.ts +97 -97
- package/src/rules/adapters/copilot.ts +124 -124
- package/src/rules/adapters/cursor.ts +114 -114
- package/src/rules/adapters/kiro.ts +126 -126
- package/src/rules/adapters/trae.ts +56 -56
- package/src/rules/adapters/windsurf.ts +83 -83
- package/src/rules/syncer.ts +235 -235
- package/src/runtime/control-plane-maintenance.ts +1 -0
- package/src/runtime/isolated-maintenance.ts +1 -0
- package/src/runtime/lifecycle.ts +18 -0
- package/src/runtime/maintenance-jobs.ts +1 -0
- package/src/runtime/maintenance-runner.ts +2 -0
- package/src/runtime/project-maintenance.ts +89 -0
- package/src/sdk.ts +334 -304
- package/src/search/intent-detector.ts +289 -289
- package/src/search/query-expansion.ts +52 -52
- package/src/server/formation-timeout.ts +27 -27
- package/src/server.ts +334 -93
- package/src/skills/mini-skills.ts +386 -386
- package/src/store/chat-store.ts +119 -119
- package/src/store/graph-store.ts +249 -249
- package/src/store/mini-skill-store.ts +349 -349
- package/src/store/orama-store.ts +61 -6
- package/src/store/persistence-json.ts +212 -212
- package/src/store/persistence.ts +291 -291
- package/src/store/project-affinity.ts +195 -195
- package/src/store/sqlite-db.ts +23 -1
- package/src/store/sqlite-store.ts +12 -2
- package/src/team/event-bus.ts +76 -76
- package/src/team/file-locks.ts +173 -173
- package/src/team/handoff.ts +168 -161
- package/src/team/messages.ts +203 -203
- package/src/team/poll.ts +132 -132
- package/src/team/tasks.ts +211 -211
- package/src/types.ts +51 -0
- package/src/wiki/generator.ts +2 -0
- package/src/workspace/mcp-adapters/codex.ts +191 -191
- package/src/workspace/mcp-adapters/copilot.ts +105 -105
- package/src/workspace/mcp-adapters/cursor.ts +53 -53
- package/src/workspace/mcp-adapters/kiro.ts +64 -64
- package/src/workspace/mcp-adapters/opencode.ts +123 -123
- package/src/workspace/mcp-adapters/trae.ts +134 -134
- package/src/workspace/mcp-adapters/windsurf.ts +91 -91
- package/src/workspace/sanitizer.ts +60 -60
- package/src/workspace/workflow-sync.ts +131 -131
|
@@ -8,7 +8,16 @@
|
|
|
8
8
|
* and in the Orama search index (for full-text + vector search).
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type {
|
|
11
|
+
import type {
|
|
12
|
+
Observation,
|
|
13
|
+
ObservationAdmissionState,
|
|
14
|
+
ObservationReader,
|
|
15
|
+
ObservationVisibility,
|
|
16
|
+
ObservationType,
|
|
17
|
+
ObservationStatus,
|
|
18
|
+
MemorixDocument,
|
|
19
|
+
ProgressInfo,
|
|
20
|
+
} from '../types.js';
|
|
12
21
|
import { TOPIC_KEY_FAMILIES } from '../types.js';
|
|
13
22
|
import {
|
|
14
23
|
insertObservation,
|
|
@@ -25,13 +34,15 @@ import {
|
|
|
25
34
|
makeOramaObservationId,
|
|
26
35
|
getLastSearchMode,
|
|
27
36
|
searchObservations,
|
|
37
|
+
updateObservationMetadata,
|
|
28
38
|
} from '../store/orama-store.js';
|
|
29
39
|
import { getObservationStore, initObservationStore } from '../store/obs-store.js';
|
|
30
40
|
import { countTextTokens } from '../compact/token-budget.js';
|
|
31
41
|
import { extractEntities, enrichConcepts } from './entity-extractor.js';
|
|
32
42
|
import { getEmbeddingProvider, isEmbeddingExplicitlyDisabled } from '../embedding/provider.js';
|
|
33
43
|
import { sanitizeCredentials } from './secret-filter.js';
|
|
34
|
-
import { enqueueClaimDerivation } from '../runtime/lifecycle.js';
|
|
44
|
+
import { enqueueClaimDerivation, enqueueObservationQualification } from '../runtime/lifecycle.js';
|
|
45
|
+
import { canManageObservation, resolveObservationVisibility } from './visibility.js';
|
|
35
46
|
|
|
36
47
|
/** In-memory observation list (loaded from persistence on init) */
|
|
37
48
|
let observations: Observation[] = [];
|
|
@@ -137,6 +148,20 @@ function queueClaimDerivation(observation: Observation): void {
|
|
|
137
148
|
}
|
|
138
149
|
}
|
|
139
150
|
|
|
151
|
+
function queueObservationQualification(observation: Observation): void {
|
|
152
|
+
const dataDir = projectDir;
|
|
153
|
+
if (!dataDir || observation.admissionState !== 'candidate') return;
|
|
154
|
+
try {
|
|
155
|
+
enqueueObservationQualification({
|
|
156
|
+
dataDir,
|
|
157
|
+
projectId: observation.projectId,
|
|
158
|
+
source: 'automatic-capture:' + observation.id,
|
|
159
|
+
});
|
|
160
|
+
} catch {
|
|
161
|
+
// Candidate evidence remains available for a later qualification scan.
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
140
165
|
function isVectorCompatibleWithCurrentIndex(embedding: number[] | null): boolean {
|
|
141
166
|
if (!embedding) return false;
|
|
142
167
|
const vectorDimensions = getVectorDimensions();
|
|
@@ -253,7 +278,13 @@ export async function storeObservation(input: {
|
|
|
253
278
|
relatedEntities?: string[];
|
|
254
279
|
sourceDetail?: 'explicit' | 'hook' | 'git-ingest';
|
|
255
280
|
valueCategory?: 'core' | 'contextual' | 'ephemeral';
|
|
281
|
+
admissionState?: ObservationAdmissionState;
|
|
282
|
+
admissionReason?: string;
|
|
283
|
+
visibility?: ObservationVisibility;
|
|
284
|
+
sharedWithAgentIds?: string[];
|
|
256
285
|
createdByAgentId?: string;
|
|
286
|
+
/** Optional caller-bound write scope for topic-key upserts. */
|
|
287
|
+
visibilityReader?: ObservationReader;
|
|
257
288
|
}): Promise<{ observation: Observation; upserted: boolean }> {
|
|
258
289
|
const now = new Date().toISOString();
|
|
259
290
|
|
|
@@ -273,6 +304,9 @@ export async function storeObservation(input: {
|
|
|
273
304
|
o => o.topicKey === input.topicKey && o.projectId === input.projectId,
|
|
274
305
|
);
|
|
275
306
|
if (existing) {
|
|
307
|
+
if (input.visibilityReader && !canManageObservation(existing, input.visibilityReader)) {
|
|
308
|
+
throw new Error('Cannot update a memory outside this session\'s write scope.');
|
|
309
|
+
}
|
|
276
310
|
return { observation: await upsertObservation(existing, input, now), upserted: true };
|
|
277
311
|
}
|
|
278
312
|
}
|
|
@@ -320,6 +354,9 @@ export async function storeObservation(input: {
|
|
|
320
354
|
if (input.topicKey) {
|
|
321
355
|
const diskExisting = await tx.findByTopicKey(input.projectId, input.topicKey);
|
|
322
356
|
if (diskExisting) {
|
|
357
|
+
if (input.visibilityReader && !canManageObservation(diskExisting, input.visibilityReader)) {
|
|
358
|
+
throw new Error('Cannot update a memory outside this session\'s write scope.');
|
|
359
|
+
}
|
|
323
360
|
// Switch to upsert path — update the existing observation after this
|
|
324
361
|
// short transaction has released its lock.
|
|
325
362
|
upsertedInsideLock = true;
|
|
@@ -355,6 +392,10 @@ export async function storeObservation(input: {
|
|
|
355
392
|
relatedEntities: input.relatedEntities,
|
|
356
393
|
sourceDetail: input.sourceDetail,
|
|
357
394
|
valueCategory: input.valueCategory,
|
|
395
|
+
admissionState: input.admissionState,
|
|
396
|
+
admissionReason: input.admissionReason ? sanitizeCredentials(input.admissionReason) : undefined,
|
|
397
|
+
visibility: input.visibility ?? 'project',
|
|
398
|
+
sharedWithAgentIds: input.sharedWithAgentIds,
|
|
358
399
|
createdByAgentId: input.createdByAgentId,
|
|
359
400
|
// Predict the generation that atomic() will commit after this callback.
|
|
360
401
|
// bumpGeneration() runs after fn(tx) returns, incrementing by 1.
|
|
@@ -409,6 +450,10 @@ export async function storeObservation(input: {
|
|
|
409
450
|
relatedEntities: input.relatedEntities,
|
|
410
451
|
sourceDetail: input.sourceDetail,
|
|
411
452
|
valueCategory: input.valueCategory,
|
|
453
|
+
admissionState: input.admissionState,
|
|
454
|
+
admissionReason: input.admissionReason ? sanitizeCredentials(input.admissionReason) : undefined,
|
|
455
|
+
visibility: input.visibility ?? 'project',
|
|
456
|
+
sharedWithAgentIds: input.sharedWithAgentIds,
|
|
412
457
|
createdByAgentId: input.createdByAgentId,
|
|
413
458
|
writeGeneration: 0,
|
|
414
459
|
};
|
|
@@ -435,6 +480,11 @@ export async function storeObservation(input: {
|
|
|
435
480
|
source: input.source ?? 'agent',
|
|
436
481
|
sourceDetail: input.sourceDetail ?? '',
|
|
437
482
|
valueCategory: input.valueCategory ?? '',
|
|
483
|
+
admissionState: input.admissionState ?? '',
|
|
484
|
+
admissionReason: input.admissionReason ? sanitizeCredentials(input.admissionReason) : '',
|
|
485
|
+
visibility: input.visibility ?? 'project',
|
|
486
|
+
createdByAgentId: input.createdByAgentId ?? '',
|
|
487
|
+
sharedWithAgentIds: JSON.stringify(input.sharedWithAgentIds ?? []),
|
|
438
488
|
};
|
|
439
489
|
|
|
440
490
|
await insertObservation(doc);
|
|
@@ -448,7 +498,10 @@ export async function storeObservation(input: {
|
|
|
448
498
|
}
|
|
449
499
|
|
|
450
500
|
await bindObservationCodeRefsBestEffort(observation);
|
|
451
|
-
|
|
501
|
+
queueObservationQualification(observation);
|
|
502
|
+
if (resolveObservationVisibility(observation) === 'project') {
|
|
503
|
+
queueClaimDerivation(observation);
|
|
504
|
+
}
|
|
452
505
|
|
|
453
506
|
// Generate embedding async (fire-and-forget) — never blocks MCP response
|
|
454
507
|
// Track in vectorMissingIds until embedding is successfully written.
|
|
@@ -518,6 +571,10 @@ async function upsertObservation(
|
|
|
518
571
|
progress?: ProgressInfo;
|
|
519
572
|
sourceDetail?: 'explicit' | 'hook' | 'git-ingest';
|
|
520
573
|
valueCategory?: 'core' | 'contextual' | 'ephemeral';
|
|
574
|
+
admissionState?: ObservationAdmissionState;
|
|
575
|
+
admissionReason?: string;
|
|
576
|
+
visibility?: ObservationVisibility;
|
|
577
|
+
sharedWithAgentIds?: string[];
|
|
521
578
|
},
|
|
522
579
|
now: string,
|
|
523
580
|
): Promise<Observation> {
|
|
@@ -556,6 +613,10 @@ async function upsertObservation(
|
|
|
556
613
|
if (input.progress) existing.progress = input.progress;
|
|
557
614
|
if (input.sourceDetail !== undefined) existing.sourceDetail = input.sourceDetail;
|
|
558
615
|
if (input.valueCategory !== undefined) existing.valueCategory = input.valueCategory;
|
|
616
|
+
if (input.admissionState !== undefined) existing.admissionState = input.admissionState;
|
|
617
|
+
if (input.admissionReason !== undefined) existing.admissionReason = sanitizeCredentials(input.admissionReason);
|
|
618
|
+
if (input.visibility !== undefined) existing.visibility = input.visibility;
|
|
619
|
+
if (input.sharedWithAgentIds !== undefined) existing.sharedWithAgentIds = input.sharedWithAgentIds;
|
|
559
620
|
|
|
560
621
|
// Re-index in Orama WITHOUT embedding first (non-blocking)
|
|
561
622
|
const doc: MemorixDocument = {
|
|
@@ -577,6 +638,11 @@ async function upsertObservation(
|
|
|
577
638
|
source: existing.source ?? 'agent',
|
|
578
639
|
sourceDetail: existing.sourceDetail ?? '',
|
|
579
640
|
valueCategory: existing.valueCategory ?? '',
|
|
641
|
+
admissionState: existing.admissionState ?? '',
|
|
642
|
+
admissionReason: existing.admissionReason ?? '',
|
|
643
|
+
visibility: existing.visibility ?? 'project',
|
|
644
|
+
createdByAgentId: existing.createdByAgentId ?? '',
|
|
645
|
+
sharedWithAgentIds: JSON.stringify(existing.sharedWithAgentIds ?? []),
|
|
580
646
|
};
|
|
581
647
|
|
|
582
648
|
// Remove old doc and insert updated one (with retry for concurrent upsert race)
|
|
@@ -603,7 +669,10 @@ async function upsertObservation(
|
|
|
603
669
|
}
|
|
604
670
|
|
|
605
671
|
await bindObservationCodeRefsBestEffort(existing);
|
|
606
|
-
|
|
672
|
+
queueObservationQualification(existing);
|
|
673
|
+
if (resolveObservationVisibility(existing) === 'project') {
|
|
674
|
+
queueClaimDerivation(existing);
|
|
675
|
+
}
|
|
607
676
|
|
|
608
677
|
// Generate embedding async (fire-and-forget) — never blocks MCP response
|
|
609
678
|
const searchableText = [input.title, input.narrative, ...(input.facts ?? [])].join(' ');
|
|
@@ -651,6 +720,79 @@ export function getObservation(id: number, projectId?: string): Observation | un
|
|
|
651
720
|
return observations.find((o) => o.id === id && (projectId ? o.projectId === projectId : true));
|
|
652
721
|
}
|
|
653
722
|
|
|
723
|
+
/**
|
|
724
|
+
* Promote or demote an automatic observation without changing its content.
|
|
725
|
+
* The expected-state guard keeps concurrent maintenance workers from
|
|
726
|
+
* resurrecting an observation that another writer has already changed.
|
|
727
|
+
*/
|
|
728
|
+
export async function updateObservationAdmission(input: {
|
|
729
|
+
observationId: number;
|
|
730
|
+
projectId?: string;
|
|
731
|
+
expectedState?: ObservationAdmissionState;
|
|
732
|
+
admissionState: ObservationAdmissionState;
|
|
733
|
+
admissionReason: string;
|
|
734
|
+
visibility?: ObservationVisibility;
|
|
735
|
+
}): Promise<Observation | undefined> {
|
|
736
|
+
await ensureFreshObservations();
|
|
737
|
+
const cached = observations.find((observation) =>
|
|
738
|
+
observation.id === input.observationId &&
|
|
739
|
+
(!input.projectId || observation.projectId === input.projectId),
|
|
740
|
+
);
|
|
741
|
+
if (!cached) return undefined;
|
|
742
|
+
|
|
743
|
+
const now = new Date().toISOString();
|
|
744
|
+
const admissionReason = sanitizeCredentials(input.admissionReason).slice(0, 240);
|
|
745
|
+
const apply = (current: Observation): Observation | undefined => {
|
|
746
|
+
if (input.projectId && current.projectId !== input.projectId) return undefined;
|
|
747
|
+
if (input.expectedState && current.admissionState !== input.expectedState) return undefined;
|
|
748
|
+
return {
|
|
749
|
+
...current,
|
|
750
|
+
admissionState: input.admissionState,
|
|
751
|
+
admissionReason,
|
|
752
|
+
...(input.visibility !== undefined ? { visibility: input.visibility } : {}),
|
|
753
|
+
updatedAt: now,
|
|
754
|
+
};
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
let updated: Observation | undefined;
|
|
758
|
+
if (projectDir) {
|
|
759
|
+
const store = getObservationStore();
|
|
760
|
+
await store.atomic(async (tx) => {
|
|
761
|
+
const current = await tx.getById(input.observationId);
|
|
762
|
+
if (!current) return;
|
|
763
|
+
const next = apply(current);
|
|
764
|
+
if (!next) return;
|
|
765
|
+
await tx.update(next);
|
|
766
|
+
updated = next;
|
|
767
|
+
});
|
|
768
|
+
} else {
|
|
769
|
+
updated = apply(cached);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
if (!updated) return undefined;
|
|
773
|
+
observations = observations.map((observation) =>
|
|
774
|
+
observation.id === updated!.id && observation.projectId === updated!.projectId
|
|
775
|
+
? updated!
|
|
776
|
+
: observation,
|
|
777
|
+
);
|
|
778
|
+
|
|
779
|
+
try {
|
|
780
|
+
await updateObservationMetadata(updated.projectId, updated.id, {
|
|
781
|
+
admissionState: updated.admissionState,
|
|
782
|
+
admissionReason: updated.admissionReason,
|
|
783
|
+
...(updated.visibility !== undefined ? { visibility: updated.visibility } : {}),
|
|
784
|
+
});
|
|
785
|
+
} catch {
|
|
786
|
+
// SQLite is canonical. The next index hydration repairs a missed update.
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
if (updated.admissionState === 'qualified' && resolveObservationVisibility(updated) === 'project') {
|
|
790
|
+
queueClaimDerivation(updated);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
return updated;
|
|
794
|
+
}
|
|
795
|
+
|
|
654
796
|
/**
|
|
655
797
|
* Resolve observations — mark them as resolved (completed/no longer active).
|
|
656
798
|
* This prevents resolved memories from appearing in default search results.
|
|
@@ -701,6 +843,11 @@ export async function resolveObservations(
|
|
|
701
843
|
source: obs.source ?? 'agent',
|
|
702
844
|
sourceDetail: obs.sourceDetail ?? '',
|
|
703
845
|
valueCategory: obs.valueCategory ?? '',
|
|
846
|
+
admissionState: obs.admissionState ?? '',
|
|
847
|
+
admissionReason: obs.admissionReason ?? '',
|
|
848
|
+
visibility: obs.visibility ?? 'project',
|
|
849
|
+
createdByAgentId: obs.createdByAgentId ?? '',
|
|
850
|
+
sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? []),
|
|
704
851
|
};
|
|
705
852
|
await insertObservation(doc);
|
|
706
853
|
// Async embedding update (fire-and-forget)
|
|
@@ -904,6 +1051,11 @@ export async function reindexObservations(): Promise<number> {
|
|
|
904
1051
|
source: obs.source ?? 'agent',
|
|
905
1052
|
sourceDetail: obs.sourceDetail ?? '',
|
|
906
1053
|
valueCategory: obs.valueCategory ?? '',
|
|
1054
|
+
admissionState: obs.admissionState ?? '',
|
|
1055
|
+
admissionReason: obs.admissionReason ?? '',
|
|
1056
|
+
visibility: obs.visibility ?? 'project',
|
|
1057
|
+
createdByAgentId: obs.createdByAgentId ?? '',
|
|
1058
|
+
sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? []),
|
|
907
1059
|
...(compatibleEmbedding ? { embedding: compatibleEmbedding } : {}),
|
|
908
1060
|
};
|
|
909
1061
|
await insertObservation(doc);
|
|
@@ -1028,6 +1180,7 @@ export async function probeSearchIndex(projectId: string): Promise<string> {
|
|
|
1028
1180
|
await searchObservations({
|
|
1029
1181
|
query: 'semantic memory retrieval status',
|
|
1030
1182
|
projectId,
|
|
1183
|
+
reader: { projectId },
|
|
1031
1184
|
limit: 1,
|
|
1032
1185
|
status: 'all',
|
|
1033
1186
|
trackAccess: false,
|
|
@@ -1114,6 +1267,11 @@ export async function backfillVectorEmbeddings(options: {
|
|
|
1114
1267
|
source: obs.source ?? 'agent',
|
|
1115
1268
|
sourceDetail: obs.sourceDetail ?? '',
|
|
1116
1269
|
valueCategory: obs.valueCategory ?? '',
|
|
1270
|
+
admissionState: obs.admissionState ?? '',
|
|
1271
|
+
admissionReason: obs.admissionReason ?? '',
|
|
1272
|
+
visibility: obs.visibility ?? 'project',
|
|
1273
|
+
createdByAgentId: obs.createdByAgentId ?? '',
|
|
1274
|
+
sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? []),
|
|
1117
1275
|
embedding,
|
|
1118
1276
|
};
|
|
1119
1277
|
await insertObservation(doc);
|
|
@@ -137,6 +137,8 @@ export function auditMemoryQuality(
|
|
|
137
137
|
source: obs.source ?? 'agent',
|
|
138
138
|
sourceDetail: obs.sourceDetail ?? '',
|
|
139
139
|
valueCategory: obs.valueCategory ?? '',
|
|
140
|
+
admissionState: obs.admissionState ?? '',
|
|
141
|
+
admissionReason: obs.admissionReason ?? '',
|
|
140
142
|
}, options.referenceTime) !== 'active')
|
|
141
143
|
.map((obs) => makeEntry(obs, 'Outside active retention zone'));
|
|
142
144
|
|
package/src/memory/refs.ts
CHANGED
|
@@ -1,94 +1,94 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Typed Memory Reference Protocol (Phase 3a)
|
|
3
|
-
*
|
|
4
|
-
* Provides a formal, unambiguous way to reference memory objects
|
|
5
|
-
* (observations and mini-skills) across internal code and the MCP API.
|
|
6
|
-
*
|
|
7
|
-
* String format:
|
|
8
|
-
* obs:42 — observation #42
|
|
9
|
-
* skill:3 — mini-skill #3
|
|
10
|
-
* obs:42@org/proj — observation #42 in project org/proj
|
|
11
|
-
*
|
|
12
|
-
* Legacy support:
|
|
13
|
-
* 42 (bare number) → obs:42
|
|
14
|
-
* "42" (bare string) → obs:42
|
|
15
|
-
*
|
|
16
|
-
* Display short forms (presentation only):
|
|
17
|
-
* #42 — observation
|
|
18
|
-
* S3 — mini-skill
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import type { MemoryRef } from '../types.js';
|
|
22
|
-
|
|
23
|
-
// ── Parsing ──────────────────────────────────────────────────────
|
|
24
|
-
|
|
25
|
-
const TYPED_REF_RE = /^(obs|skill):(\d+)(?:@(.+))?$/;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Parse a typed memory reference from a string or number.
|
|
29
|
-
*
|
|
30
|
-
* Accepts:
|
|
31
|
-
* - "obs:42", "skill:3", "obs:42@org/proj"
|
|
32
|
-
* - 42 (bare number → obs:42)
|
|
33
|
-
* - "42" (bare numeric string → obs:42)
|
|
34
|
-
*
|
|
35
|
-
* Throws on invalid input.
|
|
36
|
-
*/
|
|
37
|
-
export function parseMemoryRef(input: string | number): MemoryRef {
|
|
38
|
-
// Bare number → legacy observation ref
|
|
39
|
-
if (typeof input === 'number') {
|
|
40
|
-
if (!Number.isInteger(input) || input < 0) {
|
|
41
|
-
throw new Error(`Invalid memory ref: ${input} (must be a non-negative integer)`);
|
|
42
|
-
}
|
|
43
|
-
return { kind: 'obs', id: input };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const trimmed = input.trim();
|
|
47
|
-
|
|
48
|
-
// Bare numeric string → legacy observation ref
|
|
49
|
-
if (/^\d+$/.test(trimmed)) {
|
|
50
|
-
return { kind: 'obs', id: parseInt(trimmed, 10) };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Typed ref: obs:42 or skill:3 or obs:42@org/proj
|
|
54
|
-
const match = trimmed.match(TYPED_REF_RE);
|
|
55
|
-
if (!match) {
|
|
56
|
-
throw new Error(
|
|
57
|
-
`Invalid memory ref: "${input}". Expected format: obs:<id>, skill:<id>, obs:<id>@<projectId>, or a bare number.`,
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const kind = match[1] as 'obs' | 'skill';
|
|
62
|
-
const id = parseInt(match[2], 10);
|
|
63
|
-
const projectId = match[3] || undefined;
|
|
64
|
-
|
|
65
|
-
return { kind, id, projectId };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// ── Serialization ────────────────────────────────────────────────
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Serialize a MemoryRef to its canonical string form.
|
|
72
|
-
*
|
|
73
|
-
* Examples:
|
|
74
|
-
* { kind: 'obs', id: 42 } → "obs:42"
|
|
75
|
-
* { kind: 'skill', id: 3 } → "skill:3"
|
|
76
|
-
* { kind: 'obs', id: 42, projectId: 'o/p' } → "obs:42@o/p"
|
|
77
|
-
*/
|
|
78
|
-
export function serializeMemoryRef(ref: MemoryRef): string {
|
|
79
|
-
const base = `${ref.kind}:${ref.id}`;
|
|
80
|
-
return ref.projectId ? `${base}@${ref.projectId}` : base;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// ── Display ──────────────────────────────────────────────────────
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Format a MemoryRef for human-readable display.
|
|
87
|
-
*
|
|
88
|
-
* Short forms:
|
|
89
|
-
* obs:42 → "#42"
|
|
90
|
-
* skill:3 → "S3"
|
|
91
|
-
*/
|
|
92
|
-
export function displayRef(ref: MemoryRef): string {
|
|
93
|
-
return ref.kind === 'obs' ? `#${ref.id}` : `S${ref.id}`;
|
|
94
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Typed Memory Reference Protocol (Phase 3a)
|
|
3
|
+
*
|
|
4
|
+
* Provides a formal, unambiguous way to reference memory objects
|
|
5
|
+
* (observations and mini-skills) across internal code and the MCP API.
|
|
6
|
+
*
|
|
7
|
+
* String format:
|
|
8
|
+
* obs:42 — observation #42
|
|
9
|
+
* skill:3 — mini-skill #3
|
|
10
|
+
* obs:42@org/proj — observation #42 in project org/proj
|
|
11
|
+
*
|
|
12
|
+
* Legacy support:
|
|
13
|
+
* 42 (bare number) → obs:42
|
|
14
|
+
* "42" (bare string) → obs:42
|
|
15
|
+
*
|
|
16
|
+
* Display short forms (presentation only):
|
|
17
|
+
* #42 — observation
|
|
18
|
+
* S3 — mini-skill
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { MemoryRef } from '../types.js';
|
|
22
|
+
|
|
23
|
+
// ── Parsing ──────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
const TYPED_REF_RE = /^(obs|skill):(\d+)(?:@(.+))?$/;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Parse a typed memory reference from a string or number.
|
|
29
|
+
*
|
|
30
|
+
* Accepts:
|
|
31
|
+
* - "obs:42", "skill:3", "obs:42@org/proj"
|
|
32
|
+
* - 42 (bare number → obs:42)
|
|
33
|
+
* - "42" (bare numeric string → obs:42)
|
|
34
|
+
*
|
|
35
|
+
* Throws on invalid input.
|
|
36
|
+
*/
|
|
37
|
+
export function parseMemoryRef(input: string | number): MemoryRef {
|
|
38
|
+
// Bare number → legacy observation ref
|
|
39
|
+
if (typeof input === 'number') {
|
|
40
|
+
if (!Number.isInteger(input) || input < 0) {
|
|
41
|
+
throw new Error(`Invalid memory ref: ${input} (must be a non-negative integer)`);
|
|
42
|
+
}
|
|
43
|
+
return { kind: 'obs', id: input };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const trimmed = input.trim();
|
|
47
|
+
|
|
48
|
+
// Bare numeric string → legacy observation ref
|
|
49
|
+
if (/^\d+$/.test(trimmed)) {
|
|
50
|
+
return { kind: 'obs', id: parseInt(trimmed, 10) };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Typed ref: obs:42 or skill:3 or obs:42@org/proj
|
|
54
|
+
const match = trimmed.match(TYPED_REF_RE);
|
|
55
|
+
if (!match) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Invalid memory ref: "${input}". Expected format: obs:<id>, skill:<id>, obs:<id>@<projectId>, or a bare number.`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const kind = match[1] as 'obs' | 'skill';
|
|
62
|
+
const id = parseInt(match[2], 10);
|
|
63
|
+
const projectId = match[3] || undefined;
|
|
64
|
+
|
|
65
|
+
return { kind, id, projectId };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ── Serialization ────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Serialize a MemoryRef to its canonical string form.
|
|
72
|
+
*
|
|
73
|
+
* Examples:
|
|
74
|
+
* { kind: 'obs', id: 42 } → "obs:42"
|
|
75
|
+
* { kind: 'skill', id: 3 } → "skill:3"
|
|
76
|
+
* { kind: 'obs', id: 42, projectId: 'o/p' } → "obs:42@o/p"
|
|
77
|
+
*/
|
|
78
|
+
export function serializeMemoryRef(ref: MemoryRef): string {
|
|
79
|
+
const base = `${ref.kind}:${ref.id}`;
|
|
80
|
+
return ref.projectId ? `${base}@${ref.projectId}` : base;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ── Display ──────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Format a MemoryRef for human-readable display.
|
|
87
|
+
*
|
|
88
|
+
* Short forms:
|
|
89
|
+
* obs:42 → "#42"
|
|
90
|
+
* skill:3 → "S3"
|
|
91
|
+
*/
|
|
92
|
+
export function displayRef(ref: MemoryRef): string {
|
|
93
|
+
return ref.kind === 'obs' ? `#${ref.id}` : `S${ref.id}`;
|
|
94
|
+
}
|
package/src/memory/retention.ts
CHANGED
|
@@ -15,8 +15,9 @@
|
|
|
15
15
|
* retention period but are no longer permanently immune — they decay normally.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import type { MemorixDocument, Observation } from '../types.js';
|
|
18
|
+
import type { MemorixDocument, Observation, ObservationReader } from '../types.js';
|
|
19
19
|
import { getObservationStore } from '../store/obs-store.js';
|
|
20
|
+
import { canManageObservation } from './visibility.js';
|
|
20
21
|
|
|
21
22
|
// ── Importance → Retention Period mapping ────────────────────────────
|
|
22
23
|
|
|
@@ -113,6 +114,10 @@ export function isImmune(doc: MemorixDocument): boolean {
|
|
|
113
114
|
// Probe observations are operational heartbeats -- never immune, regardless of valueCategory or access.
|
|
114
115
|
if (doc.type === 'probe') return false;
|
|
115
116
|
|
|
117
|
+
// A candidate has not earned durable status. Never let an automatic core
|
|
118
|
+
// classification turn unqualified capture into permanently retained memory.
|
|
119
|
+
if (doc.admissionState === 'candidate' || doc.admissionState === 'ephemeral') return false;
|
|
120
|
+
|
|
116
121
|
// formation-classified core memories are immune regardless of type
|
|
117
122
|
if (doc.valueCategory === 'core') return true;
|
|
118
123
|
|
|
@@ -135,6 +140,11 @@ export function getImmunityReason(doc: MemorixDocument): string | null {
|
|
|
135
140
|
// Probe observations are never immune
|
|
136
141
|
if (doc.type === 'probe') return null;
|
|
137
142
|
|
|
143
|
+
// Automatic traces and candidates have not earned durable status. Keep the
|
|
144
|
+
// explanation aligned with isImmune() even when an earlier classifier gave
|
|
145
|
+
// a candidate the core value category.
|
|
146
|
+
if (doc.admissionState === 'candidate' || doc.admissionState === 'ephemeral') return null;
|
|
147
|
+
|
|
138
148
|
if (doc.valueCategory === 'core') return 'core valueCategory (formation-classified)';
|
|
139
149
|
const importance = getImportanceLevel(doc);
|
|
140
150
|
if (importance === 'critical') return 'critical importance';
|
|
@@ -369,6 +379,8 @@ export interface ArchiveExpiredBatchOptions {
|
|
|
369
379
|
limit?: number;
|
|
370
380
|
referenceTime?: Date;
|
|
371
381
|
accessMap?: Map<number, { accessCount: number; lastAccessedAt: string }>;
|
|
382
|
+
/** Omit only for trusted background maintenance. */
|
|
383
|
+
reader?: ObservationReader;
|
|
372
384
|
}
|
|
373
385
|
|
|
374
386
|
export interface ArchiveExpiredBatchResult {
|
|
@@ -401,6 +413,8 @@ function toRetentionDocument(
|
|
|
401
413
|
source: obs.source ?? 'agent',
|
|
402
414
|
sourceDetail: obs.sourceDetail ?? '',
|
|
403
415
|
valueCategory: obs.valueCategory ?? '',
|
|
416
|
+
admissionState: obs.admissionState ?? '',
|
|
417
|
+
admissionReason: obs.admissionReason ?? '',
|
|
404
418
|
};
|
|
405
419
|
}
|
|
406
420
|
|
|
@@ -423,6 +437,7 @@ export async function archiveExpiredBatch(
|
|
|
423
437
|
const hasMore = page.length > limit;
|
|
424
438
|
const scanned = hasMore ? page.slice(0, limit) : page;
|
|
425
439
|
const candidateIds = scanned
|
|
440
|
+
.filter((observation) => !options.reader || canManageObservation(observation, options.reader))
|
|
426
441
|
.filter((observation) => getRetentionZone(
|
|
427
442
|
toRetentionDocument(observation, options.accessMap),
|
|
428
443
|
options.referenceTime,
|
|
@@ -461,6 +476,7 @@ export async function archiveExpired(
|
|
|
461
476
|
referenceTime?: Date,
|
|
462
477
|
accessMap?: Map<number, { accessCount: number; lastAccessedAt: string }>,
|
|
463
478
|
projectId?: string,
|
|
479
|
+
reader?: ObservationReader,
|
|
464
480
|
): Promise<{ archived: number; remaining: number }> {
|
|
465
481
|
const store = getObservationStore();
|
|
466
482
|
if (projectId) {
|
|
@@ -472,12 +488,16 @@ export async function archiveExpired(
|
|
|
472
488
|
afterId,
|
|
473
489
|
referenceTime,
|
|
474
490
|
accessMap,
|
|
491
|
+
reader,
|
|
475
492
|
});
|
|
476
493
|
archived += batch.archived;
|
|
477
494
|
afterId = batch.nextCursor;
|
|
478
495
|
} while (afterId !== undefined);
|
|
479
496
|
|
|
480
|
-
const
|
|
497
|
+
const remainingObservations = await store.loadByProject(projectId, { status: 'active' });
|
|
498
|
+
const remaining = reader
|
|
499
|
+
? remainingObservations.filter((observation) => canManageObservation(observation, reader)).length
|
|
500
|
+
: remainingObservations.length;
|
|
481
501
|
return { archived, remaining };
|
|
482
502
|
}
|
|
483
503
|
|