@evo-dev/core 0.0.1-alpha.2 → 0.0.1-alpha.4

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 (54) hide show
  1. package/assets/team/agents/code-reviewer.md +48 -0
  2. package/assets/team/agents/docs-maintainer.md +51 -0
  3. package/assets/team/agents/implementation-engineer.md +51 -0
  4. package/assets/team/agents/product-scope-analyst.md +58 -0
  5. package/assets/team/agents/release-engineer.md +55 -0
  6. package/assets/team/agents/security-boundary-reviewer.md +50 -0
  7. package/assets/team/agents/solution-architect.md +51 -0
  8. package/assets/team/agents/verification-engineer.md +51 -0
  9. package/assets/team/team.md +102 -0
  10. package/dist/config/index.js +181 -125
  11. package/dist/index.js +10971 -9455
  12. package/package.json +1 -1
  13. package/src/agents/index.ts +1 -1
  14. package/src/code-agent-traces/index.ts +3 -10
  15. package/src/config/settings.ts +14 -0
  16. package/src/config/store.ts +1 -1
  17. package/src/daemon/index.ts +1 -1
  18. package/src/evolution/candidates/index.ts +235 -0
  19. package/src/evolution/control/index.ts +19 -0
  20. package/src/evolution/evidence/analysis.ts +529 -0
  21. package/src/evolution/evidence/index.ts +3 -0
  22. package/src/evolution/evidence/session-memory/constants.ts +12 -0
  23. package/src/evolution/evidence/session-memory/index.ts +4 -0
  24. package/src/evolution/evidence/session-memory/paths.ts +29 -0
  25. package/src/evolution/evidence/session-memory/policy.ts +39 -0
  26. package/src/evolution/evidence/session-memory/segment.ts +192 -0
  27. package/src/evolution/evidence/session-memory/state-machine.ts +249 -0
  28. package/src/evolution/evidence/session-memory/storage.ts +145 -0
  29. package/src/evolution/evidence/session-memory/types.ts +207 -0
  30. package/src/evolution/evidence/session-memory/updater.ts +184 -0
  31. package/src/evolution/formatters.ts +169 -0
  32. package/src/evolution/index.ts +12 -2827
  33. package/src/{knowledge → evolution/knowledge}/index.ts +7 -7
  34. package/src/evolution/paths.ts +41 -0
  35. package/src/evolution/processor/distillation.ts +436 -0
  36. package/src/evolution/processor/index.ts +3 -0
  37. package/src/evolution/processor/process.ts +306 -0
  38. package/src/evolution/schema.ts +522 -0
  39. package/src/evolution/shared.ts +677 -0
  40. package/src/evolution/triggers/classification.ts +102 -0
  41. package/src/evolution/triggers/index.ts +295 -0
  42. package/src/hooks/index.ts +56 -9
  43. package/src/index.ts +10 -2
  44. package/src/runtime-logs/index.ts +4 -12
  45. package/src/team/index.ts +576 -3
  46. package/src/utils/errors.ts +13 -0
  47. package/src/utils/fs.ts +40 -0
  48. package/src/utils/hash.ts +9 -0
  49. package/src/utils/ids.ts +12 -0
  50. package/src/utils/index.ts +7 -0
  51. package/src/utils/parsing.ts +11 -0
  52. package/src/utils/text.ts +18 -0
  53. package/src/utils/time.ts +5 -0
  54. /package/src/{learning → evolution/review}/index.ts +0 -0
@@ -0,0 +1,102 @@
1
+ import type {
2
+ EvolutionEvidenceEvent,
3
+ EvolutionEvidenceEventKind,
4
+ EvolutionEvidenceWindow,
5
+ EvolutionEvosCase,
6
+ EvolutionNormalizedEventType,
7
+ EvolutionTriggerPolicy,
8
+ EvolutionTriggerReason,
9
+ EvolutionTriggerStrength,
10
+ } from "../schema.ts";
11
+ import { NORMALIZED_EVENT_TYPES, sanitizeText } from "../shared.ts";
12
+
13
+ export function normalizeEvolutionEventType(
14
+ value: string | null | undefined,
15
+ ): EvolutionNormalizedEventType {
16
+ if (value === "AgentStop") return "SubagentStop";
17
+ if (typeof value === "string" && (NORMALIZED_EVENT_TYPES as readonly string[]).includes(value)) {
18
+ return value as EvolutionNormalizedEventType;
19
+ }
20
+ return "unknown";
21
+ }
22
+
23
+ export function decideEvolutionTrigger(input: {
24
+ eventType: EvolutionNormalizedEventType;
25
+ summary: string;
26
+ kind: EvolutionEvidenceEventKind;
27
+ }): { strength: EvolutionTriggerStrength; reason: EvolutionTriggerReason } {
28
+ if (input.eventType === "TaskCompleted") return { strength: "strong", reason: "task-completed" };
29
+ if (input.eventType === "StopFailure") return { strength: "strong", reason: "stop-failure" };
30
+ if (input.eventType === "PostToolUseFailure")
31
+ return { strength: "strong", reason: "tool-failure" };
32
+ if (input.eventType === "PermissionDenied")
33
+ return { strength: "strong", reason: "permission-denied" };
34
+ if (input.eventType === "Stop") return { strength: "conditional", reason: "turn-completed" };
35
+ if (input.eventType === "SessionEnd") return { strength: "conditional", reason: "session-ended" };
36
+ if (input.eventType === "SubagentStop")
37
+ return { strength: "conditional", reason: "role-completed" };
38
+ if (input.kind === "error" || /fail|failed|failure|error|issue/i.test(input.summary)) {
39
+ return { strength: "strong", reason: "failure-signal" };
40
+ }
41
+ return { strength: "none", reason: "none" };
42
+ }
43
+
44
+ export function createTriggerPolicy(events: EvolutionEvidenceEvent[]): EvolutionTriggerPolicy {
45
+ const failures = events.filter((event) => event.kind === "error").length;
46
+ const verifications = events.filter((event) => event.kind === "verification").length;
47
+ const roleLifecycle = events.filter((event) => event.kind === "subagent").length;
48
+ const skillCalls = events.filter((event) => event.kind === "skill-call").length;
49
+ const strongest = strongestTrigger(events);
50
+ const reasons = uniqueTriggerReasons(
51
+ events.flatMap((event) => (event.triggerReason === "none" ? [] : [event.triggerReason])),
52
+ );
53
+ const hasSignal = failures > 0 || verifications > 0 || roleLifecycle > 0 || skillCalls > 0;
54
+ return {
55
+ strongest: strongest.strength,
56
+ reasons,
57
+ distillRecommended:
58
+ strongest.strength === "strong" || (strongest.strength === "conditional" && hasSignal),
59
+ evidenceSignals: { failures, verifications, roleLifecycle, skillCalls },
60
+ };
61
+ }
62
+
63
+ export function strongestTrigger(events: EvolutionEvidenceEvent[]): {
64
+ strength: EvolutionTriggerStrength;
65
+ reason: EvolutionTriggerReason;
66
+ } {
67
+ const strong = events.find((event) => event.triggerStrength === "strong");
68
+ if (strong !== undefined) return { strength: "strong", reason: strong.triggerReason };
69
+ const conditional = events.find((event) => event.triggerStrength === "conditional");
70
+ if (conditional !== undefined) {
71
+ return { strength: "conditional", reason: conditional.triggerReason };
72
+ }
73
+ return { strength: "none", reason: "none" };
74
+ }
75
+
76
+ export function uniqueTriggerReasons(values: EvolutionTriggerReason[]): EvolutionTriggerReason[] {
77
+ return [...new Set(values)].filter((reason) => reason !== "none");
78
+ }
79
+
80
+ export function chooseEvosTriggerKind(
81
+ evidenceWindow: EvolutionEvidenceWindow,
82
+ ): EvolutionEvosCase["trigger"]["kind"] {
83
+ if (evidenceWindow.triggerPolicy.reasons.includes("task-completed")) return "task-completion";
84
+ if (evidenceWindow.triggerPolicy.reasons.includes("turn-completed")) return "session-completion";
85
+ if (evidenceWindow.triggerPolicy.reasons.includes("session-ended")) return "session-completion";
86
+ return "manual";
87
+ }
88
+
89
+ export function createTriggerSummary(evidenceWindow: EvolutionEvidenceWindow): string {
90
+ const reasons = evidenceWindow.triggerPolicy.reasons.join(", ") || "manual";
91
+ return sanitizeText(
92
+ `Evolution distillation used ${evidenceWindow.triggerPolicy.strongest} trigger evidence: ${reasons}.`,
93
+ );
94
+ }
95
+
96
+ export function createTriggerDecisionSummary(
97
+ eventType: EvolutionNormalizedEventType,
98
+ strength: EvolutionTriggerStrength,
99
+ reason: EvolutionTriggerReason,
100
+ ): string {
101
+ return `Hook event ${eventType} classified as ${strength} evolution trigger (${reason}).`;
102
+ }
@@ -0,0 +1,295 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { resolveEvoDevPaths } from "../../config/paths.ts";
4
+ import {
5
+ createStableId,
6
+ isFileExistsError,
7
+ normalizeTimestamp,
8
+ readJsonFiles,
9
+ writeJsonFile as writeJson,
10
+ } from "../../utils/index.ts";
11
+ import { resolveEvolutionPaths } from "../paths.ts";
12
+ import type {
13
+ EvolutionTriggerDecision,
14
+ EvolutionTriggerInput,
15
+ EvolutionTriggerListInput,
16
+ EvolutionTriggerRecord,
17
+ EvolutionTriggerStatus,
18
+ SegmentEvolutionTriggerInput,
19
+ SegmentEvolutionTriggerListInput,
20
+ SegmentEvolutionTriggerRecord,
21
+ } from "../schema.ts";
22
+ import {
23
+ createPrivacyFields,
24
+ listDirectoryNames,
25
+ parseSegmentTrigger,
26
+ parseTrigger,
27
+ sanitizeId,
28
+ sanitizeStorageId,
29
+ sanitizeText,
30
+ validateEvolutionTriggerRecord,
31
+ validateSegmentEvolutionTriggerRecord,
32
+ } from "../shared.ts";
33
+ import {
34
+ createTriggerDecisionSummary,
35
+ decideEvolutionTrigger,
36
+ normalizeEvolutionEventType,
37
+ } from "./classification.ts";
38
+
39
+ export function resolveEvolutionTriggerDecision(input: {
40
+ eventType: string;
41
+ summary?: string | null;
42
+ }): EvolutionTriggerDecision {
43
+ const eventType = normalizeEvolutionEventType(input.eventType);
44
+ const trigger = decideEvolutionTrigger({
45
+ eventType,
46
+ summary: input.summary ?? "",
47
+ kind: "trace",
48
+ });
49
+ return {
50
+ eventType,
51
+ strength: trigger.strength,
52
+ reason: trigger.reason,
53
+ shouldQueue: trigger.strength !== "none",
54
+ summary: createTriggerDecisionSummary(eventType, trigger.strength, trigger.reason),
55
+ };
56
+ }
57
+
58
+ export async function enqueueEvolutionTrigger(
59
+ input: EvolutionTriggerInput,
60
+ ): Promise<EvolutionTriggerRecord | null> {
61
+ const decision = resolveEvolutionTriggerDecision({
62
+ eventType: input.eventType,
63
+ summary: input.summary,
64
+ });
65
+ if (!decision.shouldQueue) return null;
66
+
67
+ const now = normalizeTimestamp(input.now);
68
+ const projectKey = sanitizeId(input.projectKey);
69
+ const runId = sanitizeId(input.runId);
70
+ const id = createStableId("trigger", [
71
+ projectKey,
72
+ runId,
73
+ input.roleId ?? "",
74
+ input.eventId ?? "",
75
+ decision.eventType,
76
+ now,
77
+ ]);
78
+ const trigger: EvolutionTriggerRecord = {
79
+ schemaVersion: 1,
80
+ id,
81
+ kind: "evolution-trigger",
82
+ projectKey,
83
+ runId,
84
+ roleId: input.roleId === undefined || input.roleId === null ? null : sanitizeId(input.roleId),
85
+ taskId: input.taskId === undefined || input.taskId === null ? null : sanitizeId(input.taskId),
86
+ eventType: decision.eventType,
87
+ eventId:
88
+ input.eventId === undefined || input.eventId === null ? null : sanitizeId(input.eventId),
89
+ evidenceRef:
90
+ input.evidenceRef === undefined || input.evidenceRef === null
91
+ ? null
92
+ : sanitizeText(input.evidenceRef),
93
+ summary: sanitizeText(input.summary ?? decision.summary),
94
+ triggerStrength: decision.strength,
95
+ triggerReason: decision.reason,
96
+ status: "pending",
97
+ attempts: 0,
98
+ createdAt: now,
99
+ updatedAt: now,
100
+ processedBatchId: null,
101
+ lastError: null,
102
+ rawContentStored: false,
103
+ privacy: createPrivacyFields(),
104
+ };
105
+ validateEvolutionTriggerRecord(trigger);
106
+ const paths = resolveEvolutionPaths({ homeDir: input.homeDir, projectKey, runId });
107
+ await writeJson(join(paths.triggersDir, `${trigger.id}.json`), trigger, { overwrite: false });
108
+ return trigger;
109
+ }
110
+
111
+ export async function listEvolutionTriggers(
112
+ input: EvolutionTriggerListInput,
113
+ ): Promise<EvolutionTriggerRecord[]> {
114
+ const paths = resolveEvoDevPaths(input.homeDir);
115
+ const evolutionStateDir = join(paths.stateDir, "evolution");
116
+ const projectKeys =
117
+ input.projectKey === undefined
118
+ ? await listDirectoryNames(evolutionStateDir)
119
+ : [sanitizeStorageId("projectKey", input.projectKey)];
120
+ const triggers: EvolutionTriggerRecord[] = [];
121
+ for (const projectKey of projectKeys) {
122
+ const projectStateDir = join(evolutionStateDir, projectKey);
123
+ const runIds =
124
+ input.runId === undefined
125
+ ? await listDirectoryNames(projectStateDir)
126
+ : [sanitizeStorageId("runId", input.runId)];
127
+ for (const runId of runIds) {
128
+ const runTriggers = await readJsonFiles(
129
+ join(projectStateDir, runId, "triggers"),
130
+ parseTrigger,
131
+ );
132
+ triggers.push(
133
+ ...runTriggers.filter(
134
+ (trigger) => input.status === undefined || trigger.status === input.status,
135
+ ),
136
+ );
137
+ }
138
+ }
139
+ return triggers.sort((left, right) => left.createdAt.localeCompare(right.createdAt));
140
+ }
141
+
142
+ export async function enqueueSegmentEvolutionTrigger(
143
+ input: SegmentEvolutionTriggerInput,
144
+ ): Promise<SegmentEvolutionTriggerRecord> {
145
+ const now = normalizeTimestamp(input.now);
146
+ const projectKey = sanitizeId(input.projectKey);
147
+ const sessionKey = sanitizeId(input.sessionKey);
148
+ const segmentId = sanitizeId(input.segmentId);
149
+ const id = createStableId("segment-trigger", [projectKey, sessionKey, segmentId]);
150
+ const trigger: SegmentEvolutionTriggerRecord = {
151
+ schemaVersion: 1,
152
+ id,
153
+ kind: "segment-evolution-trigger",
154
+ projectKey,
155
+ sessionKey,
156
+ runId: input.runId === undefined || input.runId === null ? null : sanitizeId(input.runId),
157
+ roleId: input.roleId === undefined || input.roleId === null ? null : sanitizeId(input.roleId),
158
+ segmentId,
159
+ segmentPath: sanitizeText(input.segmentPath),
160
+ strength: input.strength,
161
+ reason: input.reason,
162
+ summary: sanitizeText(input.summary),
163
+ status: "pending",
164
+ attempts: 0,
165
+ createdAt: now,
166
+ updatedAt: now,
167
+ processedBatchId: null,
168
+ lastError: null,
169
+ rawContentStored: false,
170
+ };
171
+ validateSegmentEvolutionTriggerRecord(trigger);
172
+ const path = resolveSegmentEvolutionTriggerPath(input.homeDir, trigger);
173
+ try {
174
+ await writeJson(path, trigger, { overwrite: false });
175
+ return trigger;
176
+ } catch (error) {
177
+ if (!isFileExistsError(error)) throw error;
178
+ return parseSegmentTrigger(JSON.parse(await readFile(path, "utf8")) as unknown);
179
+ }
180
+ }
181
+
182
+ export async function listSegmentEvolutionTriggers(
183
+ input: SegmentEvolutionTriggerListInput,
184
+ ): Promise<SegmentEvolutionTriggerRecord[]> {
185
+ const paths = resolveEvoDevPaths(input.homeDir);
186
+ const evolutionStateDir = join(paths.stateDir, "evolution");
187
+ const projectKeys =
188
+ input.projectKey === undefined
189
+ ? await listDirectoryNames(evolutionStateDir)
190
+ : [sanitizeStorageId("projectKey", input.projectKey)];
191
+ const triggers: SegmentEvolutionTriggerRecord[] = [];
192
+ for (const projectKey of projectKeys) {
193
+ const projectSegmentsDir = join(evolutionStateDir, projectKey, "segments");
194
+ const projectTriggers = await readJsonFiles(projectSegmentsDir, parseSegmentTrigger);
195
+ triggers.push(
196
+ ...projectTriggers.filter((trigger) => {
197
+ if (input.status !== undefined && trigger.status !== input.status) return false;
198
+ if (
199
+ input.runId !== undefined &&
200
+ trigger.runId !== sanitizeStorageId("runId", input.runId)
201
+ ) {
202
+ return false;
203
+ }
204
+ return true;
205
+ }),
206
+ );
207
+ }
208
+ return triggers.sort((left, right) => left.createdAt.localeCompare(right.createdAt));
209
+ }
210
+
211
+ export function groupTriggersByRun(triggers: EvolutionTriggerRecord[]): EvolutionTriggerRecord[][] {
212
+ const groups = new Map<string, EvolutionTriggerRecord[]>();
213
+ for (const trigger of triggers) {
214
+ const key = `${trigger.projectKey}\0${trigger.runId}`;
215
+ groups.set(key, [...(groups.get(key) ?? []), trigger]);
216
+ }
217
+ return [...groups.values()];
218
+ }
219
+
220
+ export async function updateTriggers(
221
+ homeDir: string,
222
+ triggers: EvolutionTriggerRecord[],
223
+ patch: {
224
+ status: EvolutionTriggerStatus;
225
+ updatedAt: string;
226
+ attempts?: ((trigger: EvolutionTriggerRecord) => number) | number;
227
+ processedBatchId?: string | null;
228
+ lastError?: string | null;
229
+ },
230
+ ): Promise<void> {
231
+ for (const trigger of triggers) {
232
+ const next: EvolutionTriggerRecord = {
233
+ ...trigger,
234
+ status: patch.status,
235
+ updatedAt: patch.updatedAt,
236
+ attempts:
237
+ typeof patch.attempts === "function"
238
+ ? patch.attempts(trigger)
239
+ : (patch.attempts ?? trigger.attempts),
240
+ processedBatchId:
241
+ patch.processedBatchId === undefined ? trigger.processedBatchId : patch.processedBatchId,
242
+ lastError: patch.lastError === undefined ? trigger.lastError : patch.lastError,
243
+ };
244
+ validateEvolutionTriggerRecord(next);
245
+ const paths = resolveEvolutionPaths({
246
+ homeDir,
247
+ projectKey: next.projectKey,
248
+ runId: next.runId,
249
+ });
250
+ await writeJson(join(paths.triggersDir, `${next.id}.json`), next, { overwrite: true });
251
+ }
252
+ }
253
+
254
+ export async function updateSegmentTriggers(
255
+ homeDir: string,
256
+ triggers: SegmentEvolutionTriggerRecord[],
257
+ patch: {
258
+ status: EvolutionTriggerStatus;
259
+ updatedAt: string;
260
+ attempts?: ((trigger: SegmentEvolutionTriggerRecord) => number) | number;
261
+ processedBatchId?: string | null;
262
+ lastError?: string | null;
263
+ },
264
+ ): Promise<void> {
265
+ for (const trigger of triggers) {
266
+ const next: SegmentEvolutionTriggerRecord = {
267
+ ...trigger,
268
+ status: patch.status,
269
+ updatedAt: patch.updatedAt,
270
+ attempts:
271
+ typeof patch.attempts === "function"
272
+ ? patch.attempts(trigger)
273
+ : (patch.attempts ?? trigger.attempts),
274
+ processedBatchId:
275
+ patch.processedBatchId === undefined ? trigger.processedBatchId : patch.processedBatchId,
276
+ lastError: patch.lastError === undefined ? trigger.lastError : patch.lastError,
277
+ };
278
+ validateSegmentEvolutionTriggerRecord(next);
279
+ await writeJson(resolveSegmentEvolutionTriggerPath(homeDir, next), next, { overwrite: true });
280
+ }
281
+ }
282
+
283
+ export function resolveSegmentEvolutionTriggerPath(
284
+ homeDir: string,
285
+ trigger: Pick<SegmentEvolutionTriggerRecord, "projectKey" | "id">,
286
+ ): string {
287
+ const paths = resolveEvoDevPaths(homeDir);
288
+ return join(
289
+ paths.stateDir,
290
+ "evolution",
291
+ sanitizeStorageId("projectKey", trigger.projectKey),
292
+ "segments",
293
+ `${sanitizeStorageId("segmentTrigger", trigger.id)}.json`,
294
+ );
295
+ }
@@ -1,13 +1,19 @@
1
- import { createHash } from "node:crypto";
2
1
  import { mkdir, readFile, writeFile } from "node:fs/promises";
3
2
  import { dirname, join } from "node:path";
4
- import { enqueueEvolutionTrigger, resolveEvolutionTriggerDecision } from "../evolution/index.ts";
3
+ import {
4
+ type SessionMemoryPolicySnapshot,
5
+ updateSessionMemoryFromHook,
6
+ } from "../evolution/evidence/session-memory/index.ts";
5
7
  import {
6
8
  createScopedKnowledgeContextPack,
7
9
  formatScopedKnowledgePromptBlock,
8
10
  hasContextInjectionReceipt,
9
11
  writeContextInjectionReceipt,
10
- } from "../knowledge/index.ts";
12
+ } from "../evolution/knowledge/index.ts";
13
+ import {
14
+ enqueueEvolutionTrigger,
15
+ resolveEvolutionTriggerDecision,
16
+ } from "../evolution/triggers/index.ts";
11
17
  import { resolveTraceTeamContext } from "../runtime-logs/index.ts";
12
18
  import {
13
19
  type TaskContract,
@@ -24,6 +30,7 @@ import {
24
30
  resolveTeamRunPaths,
25
31
  updateTeamAgentHookState,
26
32
  } from "../team/index.ts";
33
+ import { sha256Short } from "../utils/index.ts";
27
34
 
28
35
  export const CANONICAL_HOOK_EVENT_TYPES = [
29
36
  "SessionStart",
@@ -180,6 +187,7 @@ export interface HandleHookRuntimeInput {
180
187
  receivedAt?: string;
181
188
  environment?: Record<string, string | undefined>;
182
189
  runtimeInjectionEnabled?: boolean;
190
+ sessionMemoryPolicy?: Partial<SessionMemoryPolicySnapshot>;
183
191
  }
184
192
 
185
193
  const DEFAULT_EVENT_SETTINGS: Record<CanonicalHookEventType, boolean> = {
@@ -468,7 +476,12 @@ export async function handleHookRuntime(input: HandleHookRuntimeInput): Promise<
468
476
  } else if (result === undefined) {
469
477
  result = await handleAdditionalContext(input, input.event.type);
470
478
  }
471
- const evolutionDiagnostics = await recordEvolutionTriggerFromHook(input);
479
+ const sessionMemoryDiagnostics = await recordSessionMemoryFromHook(input);
480
+ const evolutionDiagnostics =
481
+ sessionMemoryDiagnostics.queuedSegmentTriggerId !== null ||
482
+ shouldSuppressLegacyEvolutionTrigger(input.event.type)
483
+ ? { stateWrites: [], warnings: [] }
484
+ : await recordEvolutionTriggerFromHook(input);
472
485
  const messageDiagnostics = await deliverPendingTeamMessagesFromHook(input, result);
473
486
  const scopedContextDiagnostics = await injectScopedKnowledgeContextFromHook(input, result);
474
487
  const teamStateDiagnostics = await recordTeamAgentHookStateFromHook(input);
@@ -476,7 +489,10 @@ export async function handleHookRuntime(input: HandleHookRuntimeInput): Promise<
476
489
  appendRuntimeDiagnostics(
477
490
  appendRuntimeDiagnostics(
478
491
  appendRuntimeDiagnostics(
479
- appendRuntimeDiagnostics(result, diagnostics),
492
+ appendRuntimeDiagnostics(
493
+ appendRuntimeDiagnostics(result, diagnostics),
494
+ sessionMemoryDiagnostics,
495
+ ),
480
496
  evolutionDiagnostics,
481
497
  ),
482
498
  messageDiagnostics,
@@ -611,6 +627,37 @@ async function recordEvolutionTriggerFromHook(
611
627
  }
612
628
  }
613
629
 
630
+ async function recordSessionMemoryFromHook(
631
+ input: HandleHookRuntimeInput,
632
+ ): Promise<HookRuntimeDiagnostics & { queuedSegmentTriggerId: string | null }> {
633
+ try {
634
+ const result = await updateSessionMemoryFromHook({
635
+ homeDir: input.homeDir,
636
+ target: input.target,
637
+ event: input.event,
638
+ rawPayload: input.rawPayload,
639
+ environment: input.environment,
640
+ receivedAt: input.receivedAt,
641
+ policy: input.sessionMemoryPolicy,
642
+ });
643
+ return {
644
+ stateWrites: result.stateWrites,
645
+ warnings: result.warnings,
646
+ queuedSegmentTriggerId: result.queuedSegmentTriggerId,
647
+ };
648
+ } catch {
649
+ return {
650
+ stateWrites: [],
651
+ warnings: ["Session Memory state could not be updated at this hook safe point."],
652
+ queuedSegmentTriggerId: null,
653
+ };
654
+ }
655
+ }
656
+
657
+ function shouldSuppressLegacyEvolutionTrigger(eventType: CanonicalHookEventType): boolean {
658
+ return eventType === "Stop" || eventType === "SubagentStop" || eventType === "SessionEnd";
659
+ }
660
+
614
661
  async function deliverPendingTeamMessagesFromHook(
615
662
  input: HandleHookRuntimeInput,
616
663
  result: HookRuntimeResult | undefined,
@@ -790,7 +837,7 @@ async function handleUserPromptSubmit(input: HandleHookRuntimeInput): Promise<Ho
790
837
  : null;
791
838
  const shouldShowDiagnostics = input.teamRuntimeDisplayMode === "development";
792
839
  const teamRuntimeContextDeliveredAt =
793
- teamRuntimeContext !== null && shouldShowDiagnostics
840
+ teamRuntimeContext !== null
794
841
  ? (input.receivedAt ?? new Date().toISOString())
795
842
  : (previousBinding?.teamRuntimeContextDeliveredAt ?? null);
796
843
  const binding: HookRuntimeSessionBinding = {
@@ -819,7 +866,7 @@ async function handleUserPromptSubmit(input: HandleHookRuntimeInput): Promise<Ho
819
866
  : hookOutput(input.event.type, {
820
867
  additionalContext: visibleContext,
821
868
  });
822
- if (teamRuntimeContext !== null && shouldShowDiagnostics) {
869
+ if (teamRuntimeContext !== null) {
823
870
  output = appendAdditionalContext(output, input.event.type, teamRuntimeContext);
824
871
  }
825
872
 
@@ -1139,7 +1186,7 @@ function resolveHookSessionKey(payload: Record<string, unknown>): string {
1139
1186
  const sessionId = optionalPayloadString(payload.session_id ?? payload.sessionId);
1140
1187
  const cwd = optionalPayloadString(payload.cwd);
1141
1188
  const source = sessionId ?? cwd ?? "local";
1142
- return `session-${createHash("sha256").update(source).digest("hex").slice(0, 16)}`;
1189
+ return `session-${sha256Short(source)}`;
1143
1190
  }
1144
1191
 
1145
1192
  async function writeJsonFile(path: string, value: unknown): Promise<void> {
@@ -1297,7 +1344,7 @@ function stableEventId(
1297
1344
 
1298
1345
  function hashOptionalIdentifier(value: unknown): string | null {
1299
1346
  if (typeof value !== "string" || value.length === 0) return null;
1300
- return `sha256-${createHash("sha256").update(value).digest("hex").slice(0, 16)}`;
1347
+ return `sha256-${sha256Short(value)}`;
1301
1348
  }
1302
1349
 
1303
1350
  function optionalBoolean(value: unknown, fallback: boolean, path: string): boolean {
package/src/index.ts CHANGED
@@ -4,9 +4,17 @@ export * from "./code-agent-traces/index.ts";
4
4
  export * from "./config/index.ts";
5
5
  export * from "./daemon/index.ts";
6
6
  export * from "./evolution/index.ts";
7
+ export * as evolutionCandidates from "./evolution/candidates/index.ts";
8
+ export * as evolutionControl from "./evolution/control/index.ts";
9
+ export * as evolutionEvidence from "./evolution/evidence/index.ts";
10
+ export * from "./evolution/evidence/session-memory/index.ts";
11
+ export * from "./evolution/knowledge/index.ts";
12
+ export * as evolutionKnowledge from "./evolution/knowledge/index.ts";
13
+ export * as evolutionProcessor from "./evolution/processor/index.ts";
14
+ export * from "./evolution/review/index.ts";
15
+ export * as evolutionReview from "./evolution/review/index.ts";
16
+ export * as evolutionTriggers from "./evolution/triggers/index.ts";
7
17
  export * from "./hooks/index.ts";
8
- export * from "./knowledge/index.ts";
9
- export * from "./learning/index.ts";
10
18
  export * from "./observability/index.ts";
11
19
  export * from "./pack/index.ts";
12
20
  export * from "./plugins/index.ts";
@@ -1,7 +1,7 @@
1
- import { createHash } from "node:crypto";
2
1
  import { appendFile, mkdir } from "node:fs/promises";
3
2
  import { dirname, isAbsolute, join, relative } from "node:path";
4
3
  import { resolveEvoDevPaths } from "../config/paths.ts";
4
+ import { normalizeTimestamp, sha256Short } from "../utils/index.ts";
5
5
 
6
6
  export type EvoDevLogPhase = "started" | "completed" | "failed";
7
7
  export type EvoDevDebugLogLevel = "verbose" | "debug" | "info" | "warn" | "error";
@@ -559,7 +559,7 @@ export function resolveTraceSessionKey(payload: unknown): string {
559
559
  const sessionId = optionalString(record.session_id ?? record.sessionId);
560
560
  const cwd = optionalString(record.cwd);
561
561
  const source = sessionId ?? cwd ?? "local";
562
- return `session-${createHash("sha256").update(source).digest("hex").slice(0, 16)}`;
562
+ return `session-${sha256Short(source)}`;
563
563
  }
564
564
 
565
565
  function sanitizeTraceLogEntryForAppend(entry: TraceLogEntryV1): TraceLogEntryV1 {
@@ -644,12 +644,6 @@ async function appendJsonLine(path: string, value: unknown): Promise<void> {
644
644
  await appendFile(path, `${JSON.stringify(value)}\n`, "utf8");
645
645
  }
646
646
 
647
- function normalizeTimestamp(value?: Date | string): string {
648
- if (value instanceof Date) return value.toISOString();
649
- if (typeof value === "string" && value.trim() !== "") return new Date(value).toISOString();
650
- return new Date().toISOString();
651
- }
652
-
653
647
  function formatLocalDateKey(timestamp: string): string {
654
648
  const date = new Date(timestamp);
655
649
  const year = date.getFullYear();
@@ -727,8 +721,7 @@ function createExecutionEventId(input: {
727
721
  summary: string;
728
722
  metadata: Record<string, EvoDevExecutionEventMetadataValue>;
729
723
  }): string {
730
- const hash = createHash("sha256").update(JSON.stringify(input)).digest("hex").slice(0, 16);
731
- return `exec-${hash}`;
724
+ return `exec-${sha256Short(JSON.stringify(input))}`;
732
725
  }
733
726
 
734
727
  function sanitizeDebugValue(value: unknown, depth = 0): unknown {
@@ -775,8 +768,7 @@ function sanitizePersistentIdentifier(value: string, prefix: string): string {
775
768
  if (!SENSITIVE_EVENT_TEXT_PATTERN.test(value) && !SENSITIVE_EVENT_TEXT_PATTERN.test(pathSafe)) {
776
769
  return pathSafe;
777
770
  }
778
- const hash = createHash("sha256").update(value).digest("hex").slice(0, 16);
779
- return `${prefix}-${hash}`;
771
+ return `${prefix}-${sha256Short(value)}`;
780
772
  }
781
773
 
782
774
  function stripTrailingSlash(path: string): string {