memorix 1.2.1 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +14 -2
- package/README.zh-CN.md +14 -2
- package/dist/cli/index.js +15424 -13780
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +1337 -536
- package/dist/index.js.map +1 -1
- package/dist/maintenance-runner.d.ts +1 -1
- package/dist/maintenance-runner.js +8458 -8087
- package/dist/maintenance-runner.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +23 -0
- package/dist/sdk.d.ts +7 -2
- package/dist/sdk.js +1365 -542
- 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 +24 -4
- package/docs/README.md +1 -1
- package/docs/dev-log/progress.txt +101 -11
- package/package.json +1 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/src/cli/command-guide.ts +192 -0
- package/src/cli/commands/audit.ts +9 -4
- package/src/cli/commands/cleanup.ts +5 -1
- package/src/cli/commands/codegraph.ts +15 -5
- 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/handoff.ts +21 -7
- package/src/cli/commands/identity.ts +116 -0
- package/src/cli/commands/ingest-image.ts +5 -3
- package/src/cli/commands/lock.ts +11 -10
- package/src/cli/commands/memory.ts +58 -21
- package/src/cli/commands/message.ts +19 -14
- package/src/cli/commands/operator-shared.ts +98 -3
- package/src/cli/commands/poll.ts +16 -6
- package/src/cli/commands/reasoning.ts +17 -3
- package/src/cli/commands/retention.ts +9 -4
- package/src/cli/commands/serve-http.ts +8 -2
- package/src/cli/commands/session.ts +44 -10
- package/src/cli/commands/skills.ts +10 -5
- package/src/cli/commands/status.ts +4 -3
- package/src/cli/commands/task.ts +26 -17
- package/src/cli/commands/team.ts +14 -10
- package/src/cli/commands/transfer.ts +63 -10
- 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/chat-service.ts +41 -18
- package/src/cli/tui/data.ts +23 -44
- package/src/cli/tui/operator-context.ts +60 -0
- package/src/cli/tui/session-service.ts +3 -2
- package/src/cli/tui/views/MemoryView.tsx +10 -8
- package/src/codegraph/auto-context.ts +31 -2
- package/src/codegraph/context-pack.ts +1 -0
- package/src/codegraph/project-context.ts +2 -0
- package/src/compact/engine.ts +26 -10
- package/src/compact/index-format.ts +25 -2
- package/src/dashboard/server.ts +46 -9
- package/src/hooks/admission.ts +117 -0
- package/src/hooks/handler.ts +98 -91
- package/src/knowledge/context-assembly.ts +97 -0
- package/src/knowledge/workset.ts +179 -10
- package/src/memory/admission.ts +57 -0
- package/src/memory/consolidation.ts +13 -2
- package/src/memory/disclosure-policy.ts +6 -1
- package/src/memory/export-import.ts +11 -3
- package/src/memory/graph-context.ts +8 -2
- package/src/memory/observations.ts +162 -4
- package/src/memory/quality-audit.ts +2 -0
- package/src/memory/retention.ts +22 -2
- package/src/memory/session.ts +29 -11
- package/src/memory/visibility.ts +80 -0
- package/src/orchestrate/memorix-bridge.ts +38 -0
- 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 +35 -5
- package/src/server.ts +267 -83
- package/src/store/orama-store.ts +61 -6
- package/src/store/sqlite-db.ts +23 -1
- package/src/store/sqlite-store.ts +12 -2
- package/src/team/handoff.ts +7 -0
- package/src/types.ts +51 -0
- package/src/wiki/generator.ts +2 -0
package/src/store/orama-store.ts
CHANGED
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { create, insert, search, remove, update, count, getByID, type AnyOrama } from '@orama/orama';
|
|
12
|
-
import type { MemorixDocument, SearchOptions, IndexEntry, KnowledgeLayer } from '../types.js';
|
|
12
|
+
import type { MemorixDocument, SearchOptions, IndexEntry, KnowledgeLayer, ObservationReader } from '../types.js';
|
|
13
13
|
import { OBSERVATION_ICONS, type ObservationType } from '../types.js';
|
|
14
14
|
import { resolveKnowledgeLayer } from '../skills/mini-skills.js';
|
|
15
|
+
import { canReadObservation } from '../memory/visibility.js';
|
|
15
16
|
import { getEmbeddingProvider, type EmbeddingProvider } from '../embedding/provider.js';
|
|
16
17
|
import { calculateProjectAffinity, extractProjectKeywords, type AffinityContext, type MemoryContent } from './project-affinity.js';
|
|
17
18
|
import { detectQueryIntent, applyIntentBoost } from '../search/intent-detector.js';
|
|
@@ -175,6 +176,11 @@ async function initializeDb(
|
|
|
175
176
|
source: 'string' as const,
|
|
176
177
|
sourceDetail: 'string' as const,
|
|
177
178
|
valueCategory: 'string' as const,
|
|
179
|
+
admissionState: 'string' as const,
|
|
180
|
+
admissionReason: 'string' as const,
|
|
181
|
+
visibility: 'string' as const,
|
|
182
|
+
createdByAgentId: 'string' as const,
|
|
183
|
+
sharedWithAgentIds: 'string' as const,
|
|
178
184
|
documentType: 'string' as const,
|
|
179
185
|
knowledgeLayer: 'string' as const,
|
|
180
186
|
};
|
|
@@ -390,6 +396,13 @@ export async function hydrateIndex(
|
|
|
390
396
|
lastAccessedAt: obs.lastAccessedAt || '',
|
|
391
397
|
status: obs.status ?? 'active',
|
|
392
398
|
source: obs.source || 'agent',
|
|
399
|
+
sourceDetail: obs.sourceDetail ?? '',
|
|
400
|
+
valueCategory: obs.valueCategory ?? '',
|
|
401
|
+
admissionState: obs.admissionState ?? '',
|
|
402
|
+
admissionReason: obs.admissionReason ?? '',
|
|
403
|
+
visibility: obs.visibility ?? 'project',
|
|
404
|
+
createdByAgentId: obs.createdByAgentId ?? '',
|
|
405
|
+
sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? []),
|
|
393
406
|
documentType: 'observation',
|
|
394
407
|
knowledgeLayer: resolveKnowledgeLayer('observation', obs.sourceDetail, obs.source),
|
|
395
408
|
...(compatibleVector ? { embedding: compatibleVector } : {}),
|
|
@@ -443,6 +456,26 @@ export async function insertObservation(doc: MemorixDocument): Promise<void> {
|
|
|
443
456
|
rememberObservationDoc(doc);
|
|
444
457
|
}
|
|
445
458
|
|
|
459
|
+
/**
|
|
460
|
+
* Update retrieval metadata without rebuilding an embedding. Admission is
|
|
461
|
+
* deliberately metadata-only: promotion must not create another embedding
|
|
462
|
+
* request or delay a background qualification job.
|
|
463
|
+
*/
|
|
464
|
+
export async function updateObservationMetadata(
|
|
465
|
+
projectId: string,
|
|
466
|
+
observationId: number,
|
|
467
|
+
patch: Partial<Pick<MemorixDocument, 'admissionState' | 'admissionReason' | 'visibility' | 'createdByAgentId' | 'sharedWithAgentIds'>>,
|
|
468
|
+
): Promise<boolean> {
|
|
469
|
+
const database = await getDb();
|
|
470
|
+
const id = makeOramaObservationId(projectId, observationId);
|
|
471
|
+
const existing = getByID(database, id) as MemorixDocument | undefined;
|
|
472
|
+
if (!existing) return false;
|
|
473
|
+
const next = { ...existing, ...patch };
|
|
474
|
+
await update(database, id, next);
|
|
475
|
+
rememberObservationDoc(next);
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
|
|
446
479
|
/**
|
|
447
480
|
* Remove an observation document by its Orama internal ID.
|
|
448
481
|
*/
|
|
@@ -643,6 +676,9 @@ export async function searchObservations(options: SearchOptions): Promise<IndexE
|
|
|
643
676
|
const doc = hit.document as unknown as MemorixDocument;
|
|
644
677
|
return projectIds.includes(doc.projectId);
|
|
645
678
|
})
|
|
679
|
+
// Visibility is enforced before scoring/ranking so private records cannot
|
|
680
|
+
// affect result order, fallback behavior, or downstream detail reads.
|
|
681
|
+
.filter((hit) => canReadObservation(hit.document as unknown as MemorixDocument, options.reader))
|
|
646
682
|
// Post-filter by status (active/resolved/archived)
|
|
647
683
|
.filter((hit) => {
|
|
648
684
|
if (statusFilter === 'all') return true;
|
|
@@ -682,6 +718,8 @@ export async function searchObservations(options: SearchOptions): Promise<IndexE
|
|
|
682
718
|
source: (doc.source || 'agent') as 'agent' | 'git' | 'manual',
|
|
683
719
|
sourceDetail: (doc.sourceDetail || undefined) as 'explicit' | 'hook' | 'git-ingest' | undefined,
|
|
684
720
|
valueCategory: (doc.valueCategory || undefined) as 'core' | 'contextual' | 'ephemeral' | undefined,
|
|
721
|
+
admissionState: (doc.admissionState || undefined) as IndexEntry['admissionState'],
|
|
722
|
+
visibility: (doc.visibility || undefined) as IndexEntry['visibility'],
|
|
685
723
|
entityName: doc.entityName || undefined,
|
|
686
724
|
documentType: (doc.documentType || 'observation') as 'observation' | 'mini-skill',
|
|
687
725
|
knowledgeLayer: (doc.knowledgeLayer || 'project-truth') as KnowledgeLayer,
|
|
@@ -752,6 +790,16 @@ export async function searchObservations(options: SearchOptions): Promise<IndexE
|
|
|
752
790
|
}));
|
|
753
791
|
}
|
|
754
792
|
|
|
793
|
+
// Automatic capture is useful as an audit trail, but unqualified evidence
|
|
794
|
+
// should not crowd out durable project knowledge in ordinary retrieval.
|
|
795
|
+
// Keep it available as a fallback when it is all the project has.
|
|
796
|
+
if (hasQuery) {
|
|
797
|
+
const qualifiedEntries = intermediate.filter(
|
|
798
|
+
(entry) => entry.admissionState !== 'candidate' && entry.admissionState !== 'ephemeral',
|
|
799
|
+
);
|
|
800
|
+
if (qualifiedEntries.length > 0) intermediate = qualifiedEntries;
|
|
801
|
+
}
|
|
802
|
+
|
|
755
803
|
// Re-sort: chronological for WHEN queries, relevance for others
|
|
756
804
|
if (intentResult?.preferChronological) {
|
|
757
805
|
intermediate.sort((a, b) => new Date(b.rawTime).getTime() - new Date(a.rawTime).getTime());
|
|
@@ -960,7 +1008,8 @@ export async function searchObservations(options: SearchOptions): Promise<IndexE
|
|
|
960
1008
|
let entries: IndexEntry[] = intermediate.map(({ rawTime: _, _isCommandLog: _c, ...rest }: any) => rest);
|
|
961
1009
|
|
|
962
1010
|
for (const hit of results.hits) {
|
|
963
|
-
|
|
1011
|
+
const doc = hit.document as unknown as MemorixDocument;
|
|
1012
|
+
if (canReadObservation(doc, options.reader)) rememberObservationDoc(doc);
|
|
964
1013
|
}
|
|
965
1014
|
|
|
966
1015
|
// Explainable recall: annotate entries with match reasons (O(1) lookup via Map)
|
|
@@ -1013,7 +1062,10 @@ export async function searchObservations(options: SearchOptions): Promise<IndexE
|
|
|
1013
1062
|
|
|
1014
1063
|
// Record access for returned results (fire-and-forget, non-blocking).
|
|
1015
1064
|
if (options.trackAccess !== false) {
|
|
1016
|
-
const
|
|
1065
|
+
const returnedKeys = new Set(entries.map((entry) => makeEntryKey(entry.projectId, entry.id)));
|
|
1066
|
+
const hitDocs = results.hits
|
|
1067
|
+
.map((hit) => ({ id: hit.id, doc: hit.document as unknown as MemorixDocument }))
|
|
1068
|
+
.filter(({ doc }) => returnedKeys.has(makeEntryKey(doc.projectId, doc.observationId)));
|
|
1017
1069
|
recordAccessBatch(hitDocs).catch(() => {});
|
|
1018
1070
|
}
|
|
1019
1071
|
|
|
@@ -1071,6 +1123,7 @@ export async function getTimeline(
|
|
|
1071
1123
|
projectId?: string,
|
|
1072
1124
|
depthBefore = 3,
|
|
1073
1125
|
depthAfter = 3,
|
|
1126
|
+
reader?: ObservationReader,
|
|
1074
1127
|
): Promise<{ before: IndexEntry[]; anchor: IndexEntry | null; after: IndexEntry[] }> {
|
|
1075
1128
|
// Use in-memory observations for reliable lookup
|
|
1076
1129
|
// (Orama search with empty term is unreliable — same fix as compactDetail)
|
|
@@ -1079,9 +1132,9 @@ export async function getTimeline(
|
|
|
1079
1132
|
const rawObs = await withFreshIndex(() => getAllObservations());
|
|
1080
1133
|
|
|
1081
1134
|
// Filter by project if specified — prevents cross-project context leaking
|
|
1082
|
-
const allObs = projectId
|
|
1135
|
+
const allObs = (projectId
|
|
1083
1136
|
? rawObs.filter((o) => o.projectId === projectId)
|
|
1084
|
-
: rawObs;
|
|
1137
|
+
: rawObs).filter((observation) => canReadObservation(observation, reader));
|
|
1085
1138
|
|
|
1086
1139
|
// Sort by creation time
|
|
1087
1140
|
const sorted = allObs.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
@@ -1093,7 +1146,7 @@ export async function getTimeline(
|
|
|
1093
1146
|
|
|
1094
1147
|
const toIndexEntry = (obs: {
|
|
1095
1148
|
id: number; type: string; title: string; tokens: number; createdAt: string;
|
|
1096
|
-
source?: string; sourceDetail?: string; valueCategory?: string;
|
|
1149
|
+
source?: string; sourceDetail?: string; valueCategory?: string; admissionState?: string; visibility?: string;
|
|
1097
1150
|
}): IndexEntry => {
|
|
1098
1151
|
const obsType = obs.type as ObservationType;
|
|
1099
1152
|
return {
|
|
@@ -1106,6 +1159,8 @@ export async function getTimeline(
|
|
|
1106
1159
|
source: (obs.source as IndexEntry['source']) || undefined,
|
|
1107
1160
|
sourceDetail: (obs.sourceDetail as IndexEntry['sourceDetail']) || undefined,
|
|
1108
1161
|
valueCategory: (obs.valueCategory as IndexEntry['valueCategory']) || undefined,
|
|
1162
|
+
admissionState: (obs.admissionState as IndexEntry['admissionState']) || undefined,
|
|
1163
|
+
visibility: (obs.visibility as IndexEntry['visibility']) || undefined,
|
|
1109
1164
|
};
|
|
1110
1165
|
};
|
|
1111
1166
|
|
package/src/store/sqlite-db.ts
CHANGED
|
@@ -57,7 +57,11 @@ CREATE TABLE IF NOT EXISTS observations (
|
|
|
57
57
|
relatedCommits TEXT,
|
|
58
58
|
relatedEntities TEXT,
|
|
59
59
|
sourceDetail TEXT,
|
|
60
|
-
valueCategory TEXT
|
|
60
|
+
valueCategory TEXT,
|
|
61
|
+
admissionState TEXT,
|
|
62
|
+
admissionReason TEXT,
|
|
63
|
+
visibility TEXT,
|
|
64
|
+
sharedWithAgentIds TEXT
|
|
61
65
|
);
|
|
62
66
|
`;
|
|
63
67
|
|
|
@@ -529,6 +533,8 @@ CREATE INDEX IF NOT EXISTS idx_observations_projectId ON observations(projectId)
|
|
|
529
533
|
CREATE INDEX IF NOT EXISTS idx_observations_topicKey ON observations(projectId, topicKey);
|
|
530
534
|
CREATE INDEX IF NOT EXISTS idx_observations_status ON observations(status);
|
|
531
535
|
CREATE INDEX IF NOT EXISTS idx_observations_project_status_id ON observations(projectId, status, id);
|
|
536
|
+
CREATE INDEX IF NOT EXISTS idx_observations_project_admission ON observations(projectId, status, admissionState, id);
|
|
537
|
+
CREATE INDEX IF NOT EXISTS idx_observations_project_visibility ON observations(projectId, status, visibility, id);
|
|
532
538
|
CREATE INDEX IF NOT EXISTS idx_mini_skills_projectId ON mini_skills(projectId);
|
|
533
539
|
CREATE INDEX IF NOT EXISTS idx_sessions_projectId ON sessions(projectId);
|
|
534
540
|
CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(projectId, status);
|
|
@@ -590,6 +596,22 @@ function addColumnIfMissing(db: any, table: string, column: string, definition:
|
|
|
590
596
|
}
|
|
591
597
|
|
|
592
598
|
const SCHEMA_MIGRATIONS: SchemaMigration[] = [
|
|
599
|
+
{
|
|
600
|
+
id: '1.2.2-observation-admission',
|
|
601
|
+
apply: (db) => {
|
|
602
|
+
addColumnIfMissing(db, 'observations', 'admissionState', 'admissionState TEXT');
|
|
603
|
+
addColumnIfMissing(db, 'observations', 'admissionReason', 'admissionReason TEXT');
|
|
604
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_observations_project_admission ON observations(projectId, status, admissionState, id)');
|
|
605
|
+
},
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
id: '1.2.2-observation-visibility',
|
|
609
|
+
apply: (db) => {
|
|
610
|
+
addColumnIfMissing(db, 'observations', 'visibility', 'visibility TEXT');
|
|
611
|
+
addColumnIfMissing(db, 'observations', 'sharedWithAgentIds', 'sharedWithAgentIds TEXT');
|
|
612
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_observations_project_visibility ON observations(projectId, status, visibility, id)');
|
|
613
|
+
},
|
|
614
|
+
},
|
|
593
615
|
{
|
|
594
616
|
id: '1.2-code-state-snapshots',
|
|
595
617
|
apply: (db) => {
|
|
@@ -49,6 +49,10 @@ function obsToRow(obs: Observation): Record<string, unknown> {
|
|
|
49
49
|
relatedEntities: obs.relatedEntities ? JSON.stringify(obs.relatedEntities) : null,
|
|
50
50
|
sourceDetail: obs.sourceDetail ?? null,
|
|
51
51
|
valueCategory: obs.valueCategory ?? null,
|
|
52
|
+
admissionState: obs.admissionState ?? null,
|
|
53
|
+
admissionReason: obs.admissionReason ?? null,
|
|
54
|
+
visibility: obs.visibility ?? null,
|
|
55
|
+
sharedWithAgentIds: obs.sharedWithAgentIds ? JSON.stringify(obs.sharedWithAgentIds) : null,
|
|
52
56
|
createdByAgentId: obs.createdByAgentId ?? null,
|
|
53
57
|
writeGeneration: obs.writeGeneration ?? 0,
|
|
54
58
|
};
|
|
@@ -80,6 +84,10 @@ function rowToObs(row: any): Observation {
|
|
|
80
84
|
...(row.relatedEntities ? { relatedEntities: safeJsonParse(row.relatedEntities, []) } : {}),
|
|
81
85
|
...(row.sourceDetail ? { sourceDetail: row.sourceDetail } : {}),
|
|
82
86
|
...(row.valueCategory ? { valueCategory: row.valueCategory } : {}),
|
|
87
|
+
...(row.admissionState ? { admissionState: row.admissionState } : {}),
|
|
88
|
+
...(row.admissionReason ? { admissionReason: row.admissionReason } : {}),
|
|
89
|
+
...(row.visibility ? { visibility: row.visibility } : {}),
|
|
90
|
+
...(row.sharedWithAgentIds ? { sharedWithAgentIds: safeJsonParse(row.sharedWithAgentIds, []) } : {}),
|
|
83
91
|
...(row.createdByAgentId ? { createdByAgentId: row.createdByAgentId } : {}),
|
|
84
92
|
...(row.writeGeneration ? { writeGeneration: row.writeGeneration } : {}),
|
|
85
93
|
} as Observation;
|
|
@@ -132,12 +140,14 @@ export class SqliteBackend implements ObservationStore {
|
|
|
132
140
|
(id, entityName, type, title, narrative, facts, filesModified, concepts, tokens,
|
|
133
141
|
createdAt, updatedAt, projectId, hasCausalLanguage, topicKey, revisionCount,
|
|
134
142
|
sessionId, status, progress, source, commitHash, relatedCommits, relatedEntities,
|
|
135
|
-
sourceDetail, valueCategory,
|
|
143
|
+
sourceDetail, valueCategory, admissionState, admissionReason, visibility, sharedWithAgentIds,
|
|
144
|
+
createdByAgentId, writeGeneration)
|
|
136
145
|
VALUES
|
|
137
146
|
(@id, @entityName, @type, @title, @narrative, @facts, @filesModified, @concepts, @tokens,
|
|
138
147
|
@createdAt, @updatedAt, @projectId, @hasCausalLanguage, @topicKey, @revisionCount,
|
|
139
148
|
@sessionId, @status, @progress, @source, @commitHash, @relatedCommits, @relatedEntities,
|
|
140
|
-
@sourceDetail, @valueCategory, @
|
|
149
|
+
@sourceDetail, @valueCategory, @admissionState, @admissionReason, @visibility, @sharedWithAgentIds,
|
|
150
|
+
@createdByAgentId, @writeGeneration)
|
|
141
151
|
`);
|
|
142
152
|
this.stmtUpdate = this.stmtInsert; // INSERT OR REPLACE works for both
|
|
143
153
|
this.stmtSetStatus = this.db.prepare(`UPDATE observations SET status = ? WHERE id = ?`);
|
package/src/team/handoff.ts
CHANGED
|
@@ -57,6 +57,8 @@ export async function createHandoffArtifact(
|
|
|
57
57
|
topicKey?: string;
|
|
58
58
|
sourceDetail?: 'explicit' | 'hook' | 'git-ingest';
|
|
59
59
|
valueCategory?: 'core' | 'contextual' | 'ephemeral';
|
|
60
|
+
visibility?: 'personal' | 'project' | 'team';
|
|
61
|
+
sharedWithAgentIds?: string[];
|
|
60
62
|
createdByAgentId?: string;
|
|
61
63
|
}) => Promise<{ observation: { id: number }; upserted: boolean }>,
|
|
62
64
|
teamStore: TeamStore,
|
|
@@ -82,6 +84,11 @@ export async function createHandoffArtifact(
|
|
|
82
84
|
topicKey: input.taskId ? `handoff:${input.taskId}:${input.fromAgentId}` : undefined,
|
|
83
85
|
sourceDetail: 'explicit',
|
|
84
86
|
valueCategory: 'core', // immune to retention archival
|
|
87
|
+
// A direct handoff is not a project-wide memo. The sender retains access
|
|
88
|
+
// and the explicit recipient receives a read grant. Broadcast handoffs
|
|
89
|
+
// are deliberately visible only to active team members.
|
|
90
|
+
visibility: input.toAgentId ? 'personal' : 'team',
|
|
91
|
+
...(input.toAgentId ? { sharedWithAgentIds: [input.toAgentId] } : {}),
|
|
85
92
|
createdByAgentId: input.fromAgentId,
|
|
86
93
|
});
|
|
87
94
|
|
package/src/types.ts
CHANGED
|
@@ -85,6 +85,33 @@ export const OBSERVATION_ICONS: Record<ObservationType, string> = {
|
|
|
85
85
|
/** Observation lifecycle status */
|
|
86
86
|
export type ObservationStatus = 'active' | 'resolved' | 'archived';
|
|
87
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Control-plane admission state for automatically captured observations.
|
|
90
|
+
* Candidate and ephemeral records remain inspectable, but only qualified
|
|
91
|
+
* records are eligible for automatic context delivery. Missing state preserves
|
|
92
|
+
* legacy behavior for observations written before this policy existed.
|
|
93
|
+
*/
|
|
94
|
+
export type ObservationAdmissionState = 'ephemeral' | 'candidate' | 'qualified';
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Who may retrieve an observation through agent-facing memory surfaces.
|
|
98
|
+
* Missing visibility is a legacy record and resolves to `project`.
|
|
99
|
+
*/
|
|
100
|
+
export type ObservationVisibility = 'personal' | 'project' | 'team';
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Identity available to an agent-facing retrieval call. Internal maintenance
|
|
104
|
+
* deliberately omits this and may inspect all project records for lifecycle work.
|
|
105
|
+
*/
|
|
106
|
+
export interface ObservationReader {
|
|
107
|
+
/** Bound project for a normal project-scoped call. Omit only for explicit global search. */
|
|
108
|
+
projectId?: string;
|
|
109
|
+
/** Stable coordination identity, available after an explicit team join. */
|
|
110
|
+
agentId?: string;
|
|
111
|
+
/** True only when agentId is an active member of the bound project team. */
|
|
112
|
+
isTeamMember?: boolean;
|
|
113
|
+
}
|
|
114
|
+
|
|
88
115
|
/** Progress tracking for task/feature observations */
|
|
89
116
|
export interface ProgressInfo {
|
|
90
117
|
feature: string;
|
|
@@ -132,6 +159,14 @@ export interface Observation {
|
|
|
132
159
|
sourceDetail?: 'explicit' | 'hook' | 'git-ingest';
|
|
133
160
|
/** Value category from formation pipeline evaluation */
|
|
134
161
|
valueCategory?: 'core' | 'contextual' | 'ephemeral';
|
|
162
|
+
/** Control-plane admission state for automatic capture and delivery. */
|
|
163
|
+
admissionState?: ObservationAdmissionState;
|
|
164
|
+
/** Short, sanitized explanation for the latest admission decision. */
|
|
165
|
+
admissionReason?: string;
|
|
166
|
+
/** Retrieval scope. Legacy records without a value remain project-visible. */
|
|
167
|
+
visibility?: ObservationVisibility;
|
|
168
|
+
/** Explicit additional readers for a personal record, used by targeted handoffs. */
|
|
169
|
+
sharedWithAgentIds?: string[];
|
|
135
170
|
/** Phase 4a: Agent ID that created this observation (team attribution) */
|
|
136
171
|
createdByAgentId?: string;
|
|
137
172
|
/** Phase 4a: Monotonic write generation — snapshot of storage_generation at write time (watermark coherence) */
|
|
@@ -176,6 +211,10 @@ export interface IndexEntry {
|
|
|
176
211
|
sourceDetail?: 'explicit' | 'hook' | 'git-ingest';
|
|
177
212
|
/** Value category for source-aware ranking */
|
|
178
213
|
valueCategory?: 'core' | 'contextual' | 'ephemeral';
|
|
214
|
+
/** Control-plane admission state when known. */
|
|
215
|
+
admissionState?: ObservationAdmissionState;
|
|
216
|
+
/** Retrieval scope when known. */
|
|
217
|
+
visibility?: ObservationVisibility;
|
|
179
218
|
/** Explainable recall: why this result matched. */
|
|
180
219
|
matchedFields?: string[];
|
|
181
220
|
/** Entity name — used for entity-affinity scoring and workstream deduplication. */
|
|
@@ -216,6 +255,8 @@ export interface SearchOptions {
|
|
|
216
255
|
source?: 'agent' | 'git' | 'manual';
|
|
217
256
|
/** Internal observability probes can disable access tracking without affecting normal search behavior. */
|
|
218
257
|
trackAccess?: boolean;
|
|
258
|
+
/** Internal reader context for agent-facing visibility filtering. */
|
|
259
|
+
reader?: ObservationReader;
|
|
219
260
|
}
|
|
220
261
|
|
|
221
262
|
/** Topic key family heuristics for suggesting stable topic keys */
|
|
@@ -258,6 +299,16 @@ export interface MemorixDocument {
|
|
|
258
299
|
sourceDetail?: string;
|
|
259
300
|
/** Value category from formation evaluation */
|
|
260
301
|
valueCategory?: string;
|
|
302
|
+
/** Control-plane admission state for automatic capture and delivery. */
|
|
303
|
+
admissionState?: string;
|
|
304
|
+
/** Short, sanitized explanation for the latest admission decision. */
|
|
305
|
+
admissionReason?: string;
|
|
306
|
+
/** Retrieval scope for agent-facing filtering. */
|
|
307
|
+
visibility?: string;
|
|
308
|
+
/** Agent that owns a personal record, or authored a shared record. */
|
|
309
|
+
createdByAgentId?: string;
|
|
310
|
+
/** Explicit readers for a targeted personal record. */
|
|
311
|
+
sharedWithAgentIds?: string;
|
|
261
312
|
/** Optional vector embedding for semantic/hybrid retrieval */
|
|
262
313
|
embedding?: number[];
|
|
263
314
|
/** Document type: observation or mini-skill (Phase 3a) */
|
package/src/wiki/generator.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Observation, MiniSkill } from '../types.js';
|
|
2
|
+
import { isEligibleForKnowledgePromotion } from '../memory/admission.js';
|
|
2
3
|
import type { KnowledgeSourceRef, KnowledgeItem, KnowledgeSection, ProjectKnowledgeOverview } from './types.js';
|
|
3
4
|
|
|
4
5
|
const COMMAND_LOG_TITLE = /^(Ran:|Command:|Executed:)\s/i;
|
|
@@ -63,6 +64,7 @@ function isEligible(o: Observation, projectId: string): boolean {
|
|
|
63
64
|
if (isCommandLog(o)) return false;
|
|
64
65
|
if (isInactive(o)) return false;
|
|
65
66
|
if (isOtherProject(o, projectId)) return false;
|
|
67
|
+
if (!isEligibleForKnowledgePromotion(o)) return false;
|
|
66
68
|
if (o.valueCategory === 'ephemeral') return false;
|
|
67
69
|
if (!contextualHasSubstance(o)) return false;
|
|
68
70
|
return true;
|