memorix 1.2.1 → 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.
Files changed (199) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +14 -2
  3. package/README.zh-CN.md +14 -2
  4. package/TEAM.md +86 -86
  5. package/dist/cli/index.js +15407 -13779
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/index.js +1321 -529
  8. package/dist/index.js.map +1 -1
  9. package/dist/maintenance-runner.d.ts +1 -1
  10. package/dist/maintenance-runner.js +8458 -8087
  11. package/dist/maintenance-runner.js.map +1 -1
  12. package/dist/memcode-runtime/CHANGELOG.md +16 -0
  13. package/dist/sdk.d.ts +7 -2
  14. package/dist/sdk.js +1349 -535
  15. package/dist/sdk.js.map +1 -1
  16. package/dist/types.d.ts +49 -1
  17. package/dist/types.js.map +1 -1
  18. package/docs/1.2.2-MEMORY-CONTROL-PLANE.md +434 -0
  19. package/docs/AGENT_OPERATOR_PLAYBOOK.md +4 -0
  20. package/docs/API_REFERENCE.md +24 -4
  21. package/docs/DESIGN_DECISIONS.md +357 -357
  22. package/docs/README.md +1 -1
  23. package/docs/dev-log/progress.txt +91 -11
  24. package/package.json +1 -1
  25. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  26. package/src/audit/index.ts +156 -156
  27. package/src/cli/command-guide.ts +192 -0
  28. package/src/cli/commands/audit-list.ts +89 -89
  29. package/src/cli/commands/audit.ts +9 -4
  30. package/src/cli/commands/background.ts +659 -659
  31. package/src/cli/commands/cleanup.ts +5 -1
  32. package/src/cli/commands/codegraph.ts +15 -5
  33. package/src/cli/commands/context.ts +3 -2
  34. package/src/cli/commands/doctor.ts +4 -2
  35. package/src/cli/commands/explain.ts +9 -3
  36. package/src/cli/commands/formation.ts +48 -48
  37. package/src/cli/commands/git-hook-install.ts +111 -111
  38. package/src/cli/commands/handoff.ts +75 -61
  39. package/src/cli/commands/hooks-status.ts +63 -63
  40. package/src/cli/commands/identity.ts +116 -0
  41. package/src/cli/commands/ingest-commit.ts +153 -153
  42. package/src/cli/commands/ingest-image.ts +71 -69
  43. package/src/cli/commands/ingest-log.ts +180 -180
  44. package/src/cli/commands/ingest.ts +44 -44
  45. package/src/cli/commands/integrate-shared.ts +15 -15
  46. package/src/cli/commands/lock.ts +93 -92
  47. package/src/cli/commands/memory.ts +58 -21
  48. package/src/cli/commands/message.ts +123 -118
  49. package/src/cli/commands/operator-shared.ts +98 -3
  50. package/src/cli/commands/poll.ts +74 -64
  51. package/src/cli/commands/purge-all-memory.ts +85 -85
  52. package/src/cli/commands/purge-project-memory.ts +83 -83
  53. package/src/cli/commands/reasoning.ts +135 -121
  54. package/src/cli/commands/retention.ts +9 -4
  55. package/src/cli/commands/serve-http.ts +8 -2
  56. package/src/cli/commands/serve-shared.ts +118 -118
  57. package/src/cli/commands/session.ts +29 -3
  58. package/src/cli/commands/skills.ts +124 -119
  59. package/src/cli/commands/status.ts +4 -3
  60. package/src/cli/commands/task.ts +193 -184
  61. package/src/cli/commands/team.ts +14 -10
  62. package/src/cli/commands/transfer.ts +108 -55
  63. package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
  64. package/src/cli/identity.ts +89 -0
  65. package/src/cli/index.ts +96 -19
  66. package/src/cli/invocation.ts +115 -0
  67. package/src/cli/tui/ChatView.tsx +234 -234
  68. package/src/cli/tui/CommandBar.tsx +312 -312
  69. package/src/cli/tui/ContextRail.tsx +118 -118
  70. package/src/cli/tui/HeaderBar.tsx +72 -72
  71. package/src/cli/tui/LogoBanner.tsx +51 -51
  72. package/src/cli/tui/Sidebar.tsx +179 -179
  73. package/src/cli/tui/chat-service.ts +41 -18
  74. package/src/cli/tui/data.ts +23 -44
  75. package/src/cli/tui/index.ts +41 -41
  76. package/src/cli/tui/markdown-render.tsx +371 -371
  77. package/src/cli/tui/operator-context.ts +60 -0
  78. package/src/cli/tui/use-mouse.ts +157 -157
  79. package/src/cli/tui/useNavigation.ts +56 -56
  80. package/src/cli/tui/views/MemoryView.tsx +10 -8
  81. package/src/cli/update-checker.ts +211 -211
  82. package/src/cli/version.ts +7 -7
  83. package/src/cli/workbench.ts +1 -1
  84. package/src/codegraph/auto-context.ts +31 -2
  85. package/src/codegraph/context-pack.ts +1 -0
  86. package/src/codegraph/project-context.ts +2 -0
  87. package/src/compact/engine.ts +26 -10
  88. package/src/compact/index-format.ts +25 -2
  89. package/src/compact/token-budget.ts +74 -74
  90. package/src/dashboard/project-classification.ts +64 -64
  91. package/src/dashboard/server.ts +46 -9
  92. package/src/embedding/fastembed-provider.ts +142 -142
  93. package/src/embedding/transformers-provider.ts +111 -111
  94. package/src/git/extractor.ts +209 -209
  95. package/src/git/hooks-path.ts +85 -85
  96. package/src/hooks/admission.ts +117 -0
  97. package/src/hooks/handler.ts +98 -91
  98. package/src/hooks/pattern-detector.ts +173 -173
  99. package/src/hooks/significance-filter.ts +250 -250
  100. package/src/knowledge/context-assembly.ts +97 -0
  101. package/src/knowledge/workset.ts +179 -10
  102. package/src/llm/memory-manager.ts +328 -328
  103. package/src/llm/provider.ts +885 -885
  104. package/src/llm/quality.ts +248 -248
  105. package/src/memory/admission.ts +57 -0
  106. package/src/memory/attribution-guard.ts +249 -249
  107. package/src/memory/consolidation.ts +13 -2
  108. package/src/memory/disclosure-policy.ts +140 -135
  109. package/src/memory/entity-extractor.ts +197 -197
  110. package/src/memory/export-import.ts +11 -3
  111. package/src/memory/formation/evaluate.ts +217 -217
  112. package/src/memory/formation/extract.ts +361 -361
  113. package/src/memory/formation/index.ts +417 -417
  114. package/src/memory/formation/resolve.ts +344 -344
  115. package/src/memory/formation/types.ts +315 -315
  116. package/src/memory/freshness.ts +122 -122
  117. package/src/memory/graph-context.ts +8 -2
  118. package/src/memory/graph.ts +197 -197
  119. package/src/memory/observations.ts +162 -4
  120. package/src/memory/quality-audit.ts +2 -0
  121. package/src/memory/refs.ts +94 -94
  122. package/src/memory/retention.ts +22 -2
  123. package/src/memory/secret-filter.ts +79 -79
  124. package/src/memory/session.ts +5 -2
  125. package/src/memory/visibility.ts +80 -0
  126. package/src/multimodal/image-loader.ts +143 -143
  127. package/src/orchestrate/adapters/claude-stream.ts +192 -192
  128. package/src/orchestrate/adapters/claude.ts +111 -111
  129. package/src/orchestrate/adapters/codex-stream.ts +134 -134
  130. package/src/orchestrate/adapters/codex.ts +41 -41
  131. package/src/orchestrate/adapters/gemini-stream.ts +166 -166
  132. package/src/orchestrate/adapters/gemini.ts +42 -42
  133. package/src/orchestrate/adapters/index.ts +73 -73
  134. package/src/orchestrate/adapters/opencode-stream.ts +143 -143
  135. package/src/orchestrate/adapters/opencode.ts +47 -47
  136. package/src/orchestrate/adapters/spawn-helper.ts +286 -286
  137. package/src/orchestrate/adapters/types.ts +77 -77
  138. package/src/orchestrate/capability-router.ts +284 -284
  139. package/src/orchestrate/context-compact.ts +188 -188
  140. package/src/orchestrate/cost-tracker.ts +219 -219
  141. package/src/orchestrate/error-recovery.ts +191 -191
  142. package/src/orchestrate/evidence.ts +140 -140
  143. package/src/orchestrate/ledger.ts +110 -110
  144. package/src/orchestrate/memorix-bridge.ts +378 -340
  145. package/src/orchestrate/output-budget.ts +80 -80
  146. package/src/orchestrate/permission.ts +152 -152
  147. package/src/orchestrate/pipeline-trace.ts +131 -131
  148. package/src/orchestrate/prompt-builder.ts +155 -155
  149. package/src/orchestrate/ring-buffer.ts +37 -37
  150. package/src/orchestrate/task-graph.ts +389 -389
  151. package/src/orchestrate/worktree.ts +232 -232
  152. package/src/project/aliases.ts +374 -374
  153. package/src/project/detector.ts +268 -268
  154. package/src/rules/adapters/claude-code.ts +99 -99
  155. package/src/rules/adapters/codex.ts +97 -97
  156. package/src/rules/adapters/copilot.ts +124 -124
  157. package/src/rules/adapters/cursor.ts +114 -114
  158. package/src/rules/adapters/kiro.ts +126 -126
  159. package/src/rules/adapters/trae.ts +56 -56
  160. package/src/rules/adapters/windsurf.ts +83 -83
  161. package/src/rules/syncer.ts +235 -235
  162. package/src/runtime/control-plane-maintenance.ts +1 -0
  163. package/src/runtime/isolated-maintenance.ts +1 -0
  164. package/src/runtime/lifecycle.ts +18 -0
  165. package/src/runtime/maintenance-jobs.ts +1 -0
  166. package/src/runtime/maintenance-runner.ts +2 -0
  167. package/src/runtime/project-maintenance.ts +89 -0
  168. package/src/sdk.ts +334 -304
  169. package/src/search/intent-detector.ts +289 -289
  170. package/src/search/query-expansion.ts +52 -52
  171. package/src/server/formation-timeout.ts +27 -27
  172. package/src/server.ts +260 -81
  173. package/src/skills/mini-skills.ts +386 -386
  174. package/src/store/chat-store.ts +119 -119
  175. package/src/store/graph-store.ts +249 -249
  176. package/src/store/mini-skill-store.ts +349 -349
  177. package/src/store/orama-store.ts +61 -6
  178. package/src/store/persistence-json.ts +212 -212
  179. package/src/store/persistence.ts +291 -291
  180. package/src/store/project-affinity.ts +195 -195
  181. package/src/store/sqlite-db.ts +23 -1
  182. package/src/store/sqlite-store.ts +12 -2
  183. package/src/team/event-bus.ts +76 -76
  184. package/src/team/file-locks.ts +173 -173
  185. package/src/team/handoff.ts +168 -161
  186. package/src/team/messages.ts +203 -203
  187. package/src/team/poll.ts +132 -132
  188. package/src/team/tasks.ts +211 -211
  189. package/src/types.ts +51 -0
  190. package/src/wiki/generator.ts +2 -0
  191. package/src/workspace/mcp-adapters/codex.ts +191 -191
  192. package/src/workspace/mcp-adapters/copilot.ts +105 -105
  193. package/src/workspace/mcp-adapters/cursor.ts +53 -53
  194. package/src/workspace/mcp-adapters/kiro.ts +64 -64
  195. package/src/workspace/mcp-adapters/opencode.ts +123 -123
  196. package/src/workspace/mcp-adapters/trae.ts +134 -134
  197. package/src/workspace/mcp-adapters/windsurf.ts +91 -91
  198. package/src/workspace/sanitizer.ts +60 -60
  199. package/src/workspace/workflow-sync.ts +131 -131
package/src/team/tasks.ts CHANGED
@@ -1,211 +1,211 @@
1
- /**
2
- * Task Manager — Simple task DAG with dependencies
3
- *
4
- * Agents can create tasks, claim them, complete them, and query available work.
5
- * Dependencies are validated: a task can only be claimed when all deps are completed.
6
- * Simple JSON format — no complex graph structures.
7
- */
8
-
9
- import { randomUUID } from 'node:crypto';
10
-
11
- // ─── Types ───────────────────────────────────────────────────────────
12
-
13
- export type TaskStatus = 'pending' | 'in_progress' | 'completed' | 'failed';
14
-
15
- export interface TaskCreateInput {
16
- description: string;
17
- deps?: string[];
18
- metadata?: Record<string, unknown>;
19
- }
20
-
21
- export interface Task {
22
- id: string;
23
- description: string;
24
- status: TaskStatus;
25
- deps: string[];
26
- assignee?: string;
27
- result?: string;
28
- metadata?: Record<string, unknown>;
29
- createdAt: Date;
30
- updatedAt: Date;
31
- }
32
-
33
- export interface TaskListFilter {
34
- status?: TaskStatus;
35
- assignee?: string;
36
- }
37
-
38
- // ─── Manager ─────────────────────────────────────────────────────────
39
-
40
- export class TaskManager {
41
- private tasks = new Map<string, Task>();
42
-
43
- /**
44
- * Create a new task. Validates that all dependency IDs exist.
45
- */
46
- create(input: TaskCreateInput): Task {
47
- const deps = input.deps ?? [];
48
-
49
- // Validate deps exist
50
- for (const dep of deps) {
51
- if (!this.tasks.has(dep)) {
52
- throw new Error(`Unknown dependency task: ${dep}`);
53
- }
54
- }
55
-
56
- const now = new Date();
57
- const task: Task = {
58
- id: randomUUID(),
59
- description: input.description,
60
- status: 'pending',
61
- deps,
62
- metadata: input.metadata,
63
- createdAt: now,
64
- updatedAt: now,
65
- };
66
-
67
- this.tasks.set(task.id, task);
68
- return { ...task };
69
- }
70
-
71
- /**
72
- * Claim a pending task for an agent.
73
- * Validates: task exists, not claimed by another, all deps completed.
74
- */
75
- claim(taskId: string, agentId: string): Task {
76
- const task = this.tasks.get(taskId);
77
- if (!task) throw new Error(`Task not found: ${taskId}`);
78
-
79
- // Allow same agent to re-claim
80
- if (task.assignee && task.assignee !== agentId) {
81
- throw new Error(`Task already claimed by ${task.assignee}`);
82
- }
83
-
84
- // Check all dependencies are completed
85
- for (const depId of task.deps) {
86
- const dep = this.tasks.get(depId);
87
- if (!dep || dep.status !== 'completed') {
88
- throw new Error(`Cannot claim: dependencies not completed (${depId})`);
89
- }
90
- }
91
-
92
- task.assignee = agentId;
93
- task.status = 'in_progress';
94
- task.updatedAt = new Date();
95
- return { ...task };
96
- }
97
-
98
- /**
99
- * Complete a task with a result. Only the assignee can complete,
100
- * unless allowRescue is true (used when original assignee left).
101
- */
102
- complete(taskId: string, agentId: string, result: string, allowRescue = false): Task {
103
- const task = this.tasks.get(taskId);
104
- if (!task) throw new Error(`Task not found: ${taskId}`);
105
- if (!task.assignee) throw new Error(`Task not claimed: ${taskId}`);
106
- if (task.assignee !== agentId && !allowRescue) {
107
- throw new Error(`Only assignee ${task.assignee} can complete this task`);
108
- }
109
-
110
- task.assignee = agentId;
111
- task.status = 'completed';
112
- task.result = result;
113
- task.updatedAt = new Date();
114
- return { ...task };
115
- }
116
-
117
- /**
118
- * Release all in_progress tasks assigned to a specific agent.
119
- * Returns them to 'pending' so other agents can claim them.
120
- */
121
- releaseByAgent(agentId: string): number {
122
- let count = 0;
123
- for (const task of this.tasks.values()) {
124
- if (task.assignee === agentId && task.status === 'in_progress') {
125
- task.status = 'pending';
126
- task.assignee = undefined;
127
- task.updatedAt = new Date();
128
- count++;
129
- }
130
- }
131
- return count;
132
- }
133
-
134
- /**
135
- * List tasks with optional filters.
136
- */
137
- list(filter?: TaskListFilter): Task[] {
138
- let all = [...this.tasks.values()];
139
- if (filter?.status) {
140
- all = all.filter(t => t.status === filter.status);
141
- }
142
- if (filter?.assignee) {
143
- all = all.filter(t => t.assignee === filter.assignee);
144
- }
145
- return all.map(t => ({ ...t }));
146
- }
147
-
148
- /**
149
- * Get tasks that are available to be claimed:
150
- * - status is 'pending'
151
- * - all dependencies are 'completed'
152
- */
153
- getAvailable(): Task[] {
154
- const result: Task[] = [];
155
- for (const task of this.tasks.values()) {
156
- if (task.status !== 'pending') continue;
157
-
158
- const depsReady = task.deps.every(depId => {
159
- const dep = this.tasks.get(depId);
160
- return dep && dep.status === 'completed';
161
- });
162
-
163
- if (depsReady) {
164
- result.push({ ...task });
165
- }
166
- }
167
- return result;
168
- }
169
-
170
- /**
171
- * Get a single task by ID.
172
- */
173
- getTask(taskId: string): Task | null {
174
- const task = this.tasks.get(taskId);
175
- return task ? { ...task } : null;
176
- }
177
-
178
- /**
179
- * Check if a task's assignee matches the given agentId.
180
- */
181
- isAssignee(taskId: string, agentId: string): boolean {
182
- const task = this.tasks.get(taskId);
183
- return task?.assignee === agentId;
184
- }
185
-
186
- /** Serialize state for file persistence */
187
- serialize(): { tasks: Record<string, unknown> } {
188
- const tasks: Record<string, unknown> = {};
189
- for (const [id, t] of this.tasks) {
190
- tasks[id] = {
191
- ...t,
192
- createdAt: t.createdAt.toISOString(),
193
- updatedAt: t.updatedAt.toISOString(),
194
- };
195
- }
196
- return { tasks };
197
- }
198
-
199
- /** Hydrate state from file persistence */
200
- hydrate(data: { tasks?: Record<string, any> }): void {
201
- this.tasks.clear();
202
- if (!data?.tasks) return;
203
- for (const [id, raw] of Object.entries(data.tasks)) {
204
- this.tasks.set(id, {
205
- ...(raw as any),
206
- createdAt: new Date((raw as any).createdAt),
207
- updatedAt: new Date((raw as any).updatedAt),
208
- });
209
- }
210
- }
211
- }
1
+ /**
2
+ * Task Manager — Simple task DAG with dependencies
3
+ *
4
+ * Agents can create tasks, claim them, complete them, and query available work.
5
+ * Dependencies are validated: a task can only be claimed when all deps are completed.
6
+ * Simple JSON format — no complex graph structures.
7
+ */
8
+
9
+ import { randomUUID } from 'node:crypto';
10
+
11
+ // ─── Types ───────────────────────────────────────────────────────────
12
+
13
+ export type TaskStatus = 'pending' | 'in_progress' | 'completed' | 'failed';
14
+
15
+ export interface TaskCreateInput {
16
+ description: string;
17
+ deps?: string[];
18
+ metadata?: Record<string, unknown>;
19
+ }
20
+
21
+ export interface Task {
22
+ id: string;
23
+ description: string;
24
+ status: TaskStatus;
25
+ deps: string[];
26
+ assignee?: string;
27
+ result?: string;
28
+ metadata?: Record<string, unknown>;
29
+ createdAt: Date;
30
+ updatedAt: Date;
31
+ }
32
+
33
+ export interface TaskListFilter {
34
+ status?: TaskStatus;
35
+ assignee?: string;
36
+ }
37
+
38
+ // ─── Manager ─────────────────────────────────────────────────────────
39
+
40
+ export class TaskManager {
41
+ private tasks = new Map<string, Task>();
42
+
43
+ /**
44
+ * Create a new task. Validates that all dependency IDs exist.
45
+ */
46
+ create(input: TaskCreateInput): Task {
47
+ const deps = input.deps ?? [];
48
+
49
+ // Validate deps exist
50
+ for (const dep of deps) {
51
+ if (!this.tasks.has(dep)) {
52
+ throw new Error(`Unknown dependency task: ${dep}`);
53
+ }
54
+ }
55
+
56
+ const now = new Date();
57
+ const task: Task = {
58
+ id: randomUUID(),
59
+ description: input.description,
60
+ status: 'pending',
61
+ deps,
62
+ metadata: input.metadata,
63
+ createdAt: now,
64
+ updatedAt: now,
65
+ };
66
+
67
+ this.tasks.set(task.id, task);
68
+ return { ...task };
69
+ }
70
+
71
+ /**
72
+ * Claim a pending task for an agent.
73
+ * Validates: task exists, not claimed by another, all deps completed.
74
+ */
75
+ claim(taskId: string, agentId: string): Task {
76
+ const task = this.tasks.get(taskId);
77
+ if (!task) throw new Error(`Task not found: ${taskId}`);
78
+
79
+ // Allow same agent to re-claim
80
+ if (task.assignee && task.assignee !== agentId) {
81
+ throw new Error(`Task already claimed by ${task.assignee}`);
82
+ }
83
+
84
+ // Check all dependencies are completed
85
+ for (const depId of task.deps) {
86
+ const dep = this.tasks.get(depId);
87
+ if (!dep || dep.status !== 'completed') {
88
+ throw new Error(`Cannot claim: dependencies not completed (${depId})`);
89
+ }
90
+ }
91
+
92
+ task.assignee = agentId;
93
+ task.status = 'in_progress';
94
+ task.updatedAt = new Date();
95
+ return { ...task };
96
+ }
97
+
98
+ /**
99
+ * Complete a task with a result. Only the assignee can complete,
100
+ * unless allowRescue is true (used when original assignee left).
101
+ */
102
+ complete(taskId: string, agentId: string, result: string, allowRescue = false): Task {
103
+ const task = this.tasks.get(taskId);
104
+ if (!task) throw new Error(`Task not found: ${taskId}`);
105
+ if (!task.assignee) throw new Error(`Task not claimed: ${taskId}`);
106
+ if (task.assignee !== agentId && !allowRescue) {
107
+ throw new Error(`Only assignee ${task.assignee} can complete this task`);
108
+ }
109
+
110
+ task.assignee = agentId;
111
+ task.status = 'completed';
112
+ task.result = result;
113
+ task.updatedAt = new Date();
114
+ return { ...task };
115
+ }
116
+
117
+ /**
118
+ * Release all in_progress tasks assigned to a specific agent.
119
+ * Returns them to 'pending' so other agents can claim them.
120
+ */
121
+ releaseByAgent(agentId: string): number {
122
+ let count = 0;
123
+ for (const task of this.tasks.values()) {
124
+ if (task.assignee === agentId && task.status === 'in_progress') {
125
+ task.status = 'pending';
126
+ task.assignee = undefined;
127
+ task.updatedAt = new Date();
128
+ count++;
129
+ }
130
+ }
131
+ return count;
132
+ }
133
+
134
+ /**
135
+ * List tasks with optional filters.
136
+ */
137
+ list(filter?: TaskListFilter): Task[] {
138
+ let all = [...this.tasks.values()];
139
+ if (filter?.status) {
140
+ all = all.filter(t => t.status === filter.status);
141
+ }
142
+ if (filter?.assignee) {
143
+ all = all.filter(t => t.assignee === filter.assignee);
144
+ }
145
+ return all.map(t => ({ ...t }));
146
+ }
147
+
148
+ /**
149
+ * Get tasks that are available to be claimed:
150
+ * - status is 'pending'
151
+ * - all dependencies are 'completed'
152
+ */
153
+ getAvailable(): Task[] {
154
+ const result: Task[] = [];
155
+ for (const task of this.tasks.values()) {
156
+ if (task.status !== 'pending') continue;
157
+
158
+ const depsReady = task.deps.every(depId => {
159
+ const dep = this.tasks.get(depId);
160
+ return dep && dep.status === 'completed';
161
+ });
162
+
163
+ if (depsReady) {
164
+ result.push({ ...task });
165
+ }
166
+ }
167
+ return result;
168
+ }
169
+
170
+ /**
171
+ * Get a single task by ID.
172
+ */
173
+ getTask(taskId: string): Task | null {
174
+ const task = this.tasks.get(taskId);
175
+ return task ? { ...task } : null;
176
+ }
177
+
178
+ /**
179
+ * Check if a task's assignee matches the given agentId.
180
+ */
181
+ isAssignee(taskId: string, agentId: string): boolean {
182
+ const task = this.tasks.get(taskId);
183
+ return task?.assignee === agentId;
184
+ }
185
+
186
+ /** Serialize state for file persistence */
187
+ serialize(): { tasks: Record<string, unknown> } {
188
+ const tasks: Record<string, unknown> = {};
189
+ for (const [id, t] of this.tasks) {
190
+ tasks[id] = {
191
+ ...t,
192
+ createdAt: t.createdAt.toISOString(),
193
+ updatedAt: t.updatedAt.toISOString(),
194
+ };
195
+ }
196
+ return { tasks };
197
+ }
198
+
199
+ /** Hydrate state from file persistence */
200
+ hydrate(data: { tasks?: Record<string, any> }): void {
201
+ this.tasks.clear();
202
+ if (!data?.tasks) return;
203
+ for (const [id, raw] of Object.entries(data.tasks)) {
204
+ this.tasks.set(id, {
205
+ ...(raw as any),
206
+ createdAt: new Date((raw as any).createdAt),
207
+ updatedAt: new Date((raw as any).updatedAt),
208
+ });
209
+ }
210
+ }
211
+ }
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) */
@@ -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;