iranti 0.3.0 → 0.3.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/README.md +48 -44
- package/dist/scripts/claude-code-memory-hook.js +42 -153
- package/dist/scripts/codex-setup.js +1 -1
- package/dist/scripts/iranti-cli.js +161 -17
- package/dist/scripts/iranti-mcp.js +80 -8
- package/dist/scripts/seed.js +1 -1
- package/dist/src/api/middleware/validation.d.ts.map +1 -1
- package/dist/src/api/middleware/validation.js +13 -1
- package/dist/src/api/middleware/validation.js.map +1 -1
- package/dist/src/api/routes/knowledge.d.ts.map +1 -1
- package/dist/src/api/routes/knowledge.js +3 -0
- package/dist/src/api/routes/knowledge.js.map +1 -1
- package/dist/src/api/routes/memory.d.ts.map +1 -1
- package/dist/src/api/routes/memory.js +3 -0
- package/dist/src/api/routes/memory.js.map +1 -1
- package/dist/src/api/server.js +1 -1
- package/dist/src/attendant/AttendantInstance.d.ts +44 -1
- package/dist/src/attendant/AttendantInstance.d.ts.map +1 -1
- package/dist/src/attendant/AttendantInstance.js +475 -41
- package/dist/src/attendant/AttendantInstance.js.map +1 -1
- package/dist/src/attendant/index.d.ts +1 -1
- package/dist/src/attendant/index.d.ts.map +1 -1
- package/dist/src/attendant/index.js.map +1 -1
- package/dist/src/chat/index.d.ts +2 -0
- package/dist/src/chat/index.d.ts.map +1 -1
- package/dist/src/chat/index.js +56 -22
- package/dist/src/chat/index.js.map +1 -1
- package/dist/src/lib/assistantCheckpoint.d.ts +21 -0
- package/dist/src/lib/assistantCheckpoint.d.ts.map +1 -0
- package/dist/src/lib/assistantCheckpoint.js +143 -0
- package/dist/src/lib/assistantCheckpoint.js.map +1 -0
- package/dist/src/lib/cliHelpCatalog.d.ts.map +1 -1
- package/dist/src/lib/cliHelpCatalog.js +6 -5
- package/dist/src/lib/cliHelpCatalog.js.map +1 -1
- package/dist/src/lib/hostMemoryFormatting.d.ts +25 -0
- package/dist/src/lib/hostMemoryFormatting.d.ts.map +1 -0
- package/dist/src/lib/hostMemoryFormatting.js +56 -0
- package/dist/src/lib/hostMemoryFormatting.js.map +1 -0
- package/dist/src/lib/llm.d.ts.map +1 -1
- package/dist/src/lib/llm.js +3 -1
- package/dist/src/lib/llm.js.map +1 -1
- package/dist/src/lib/projectLearning.d.ts +21 -0
- package/dist/src/lib/projectLearning.d.ts.map +1 -0
- package/dist/src/lib/projectLearning.js +357 -0
- package/dist/src/lib/projectLearning.js.map +1 -0
- package/dist/src/lib/protocolEnforcement.d.ts +3 -1
- package/dist/src/lib/protocolEnforcement.d.ts.map +1 -1
- package/dist/src/lib/protocolEnforcement.js +28 -2
- package/dist/src/lib/protocolEnforcement.js.map +1 -1
- package/dist/src/lib/sessionLedger.d.ts +18 -0
- package/dist/src/lib/sessionLedger.d.ts.map +1 -1
- package/dist/src/lib/sessionLedger.js +78 -0
- package/dist/src/lib/sessionLedger.js.map +1 -1
- package/dist/src/librarian/index.d.ts.map +1 -1
- package/dist/src/librarian/index.js +51 -0
- package/dist/src/librarian/index.js.map +1 -1
- package/dist/src/library/client.d.ts.map +1 -1
- package/dist/src/library/client.js +0 -1
- package/dist/src/library/client.js.map +1 -1
- package/dist/src/sdk/index.d.ts +2 -0
- package/dist/src/sdk/index.d.ts.map +1 -1
- package/dist/src/sdk/index.js +39 -2
- package/dist/src/sdk/index.js.map +1 -1
- package/package.json +9 -5
|
@@ -5,6 +5,7 @@ exports.normalizeExplicitTask = normalizeExplicitTask;
|
|
|
5
5
|
exports.formatOperatingRulesText = formatOperatingRulesText;
|
|
6
6
|
exports.readPersistedSessionState = readPersistedSessionState;
|
|
7
7
|
exports.summarizeSessionState = summarizeSessionState;
|
|
8
|
+
const crypto_1 = require("crypto");
|
|
8
9
|
const router_1 = require("../lib/router");
|
|
9
10
|
const staffEventRegistry_1 = require("../lib/staffEventRegistry");
|
|
10
11
|
const queries_1 = require("../library/queries");
|
|
@@ -19,6 +20,7 @@ const autoRemember_1 = require("../lib/autoRemember");
|
|
|
19
20
|
const semanticFactTags_1 = require("../lib/semanticFactTags");
|
|
20
21
|
const sessionLedger_1 = require("../lib/sessionLedger");
|
|
21
22
|
const sharedStateInvalidation_1 = require("../lib/sharedStateInvalidation");
|
|
23
|
+
const hostMemoryFormatting_1 = require("../lib/hostMemoryFormatting");
|
|
22
24
|
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
23
25
|
const ATTENDANT_RULES_QUERY = {
|
|
24
26
|
entityType: 'system',
|
|
@@ -55,6 +57,11 @@ const ENTITY_DETECTION_WINDOW_CHARS = 1500;
|
|
|
55
57
|
const MIN_ENTITY_CONFIDENCE = 0.75;
|
|
56
58
|
const MEMORY_DECISION_CONTEXT_WINDOW_CHARS = 2000;
|
|
57
59
|
const LEDGER_WORKING_MEMORY_PREFIX = 'system/session_ledger/recent_learning_';
|
|
60
|
+
const LEGACY_CONTINUITY_KEY_MAP = {
|
|
61
|
+
checkpoint_current_step: 'current_step',
|
|
62
|
+
checkpoint_next_step: 'next_step',
|
|
63
|
+
checkpoint_open_risks: 'open_risks',
|
|
64
|
+
};
|
|
58
65
|
const ATTEND_EXPECTED_CALL_SEQUENCE = [
|
|
59
66
|
'Call iranti_handshake at session start and again after context compaction.',
|
|
60
67
|
"Call iranti_attend(phase='pre-response') before replying to the user.",
|
|
@@ -65,8 +72,22 @@ const ATTEND_EXPECTED_CALL_SEQUENCE = [
|
|
|
65
72
|
"Call iranti_attend(phase='post-response') after every response without exception — even short replies may contain durable findings. Omitting this call is a compliance violation.",
|
|
66
73
|
'Call iranti_attend again when the new knowledge should change what is loaded next.',
|
|
67
74
|
];
|
|
68
|
-
const ATTEND_USAGE_REMINDER = 'Iranti is a hive mind.
|
|
75
|
+
const ATTEND_USAGE_REMINDER = 'Iranti is a hive mind. MANDATORY: call iranti_attend before every reply and around knowledge discovery. MANDATORY: call iranti_write after every file edit, confirmed finding, environment state change, and subagent completion — write what changed, why, and what it means. Skipping writes means the next session starts blind and must rediscover everything from scratch.';
|
|
69
76
|
const OBSERVE_USAGE_NOTE = 'observe() is retrieval-only. It surfaces candidate facts for context and warm-up, but it does not persist memory, replace iranti_attend, or count as a checkpoint/write.';
|
|
77
|
+
function normalizeContinuityKey(key) {
|
|
78
|
+
return LEGACY_CONTINUITY_KEY_MAP[key] ?? key;
|
|
79
|
+
}
|
|
80
|
+
function expandContinuityPriorityKeys(keys) {
|
|
81
|
+
const expanded = new Set();
|
|
82
|
+
for (const rawKey of keys) {
|
|
83
|
+
const key = rawKey.trim();
|
|
84
|
+
if (!key)
|
|
85
|
+
continue;
|
|
86
|
+
expanded.add(key);
|
|
87
|
+
expanded.add(normalizeContinuityKey(key));
|
|
88
|
+
}
|
|
89
|
+
return Array.from(expanded);
|
|
90
|
+
}
|
|
70
91
|
const MEMORY_NEED_POSITIVE_PATTERNS = [
|
|
71
92
|
/\bwhat(?:'s| is| was)?\s+my\b/i,
|
|
72
93
|
/\bdo you remember\b/i,
|
|
@@ -213,6 +234,83 @@ function mergeWorkingMemoryWithLedger(entries, learnings) {
|
|
|
213
234
|
? [...retained, ...toLedgerWorkingMemoryEntries(learnings)]
|
|
214
235
|
: retained;
|
|
215
236
|
}
|
|
237
|
+
function normalizeProjectPolicyRuleLines(value, fallbackSummary) {
|
|
238
|
+
const rules = [];
|
|
239
|
+
if (typeof value === 'string' && value.trim()) {
|
|
240
|
+
rules.push(value.trim());
|
|
241
|
+
}
|
|
242
|
+
else if (value && typeof value === 'object') {
|
|
243
|
+
const record = value;
|
|
244
|
+
if (typeof record.rule === 'string' && record.rule.trim()) {
|
|
245
|
+
rules.push(record.rule.trim());
|
|
246
|
+
}
|
|
247
|
+
if (typeof record.text === 'string' && record.text.trim()) {
|
|
248
|
+
rules.push(record.text.trim());
|
|
249
|
+
}
|
|
250
|
+
if (typeof record.instruction === 'string' && record.instruction.trim()) {
|
|
251
|
+
rules.push(record.instruction.trim());
|
|
252
|
+
}
|
|
253
|
+
if (Array.isArray(record.rules)) {
|
|
254
|
+
for (const rule of record.rules) {
|
|
255
|
+
if (typeof rule === 'string' && rule.trim()) {
|
|
256
|
+
rules.push(rule.trim());
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (Array.isArray(record.preferences)) {
|
|
261
|
+
for (const preference of record.preferences) {
|
|
262
|
+
if (typeof preference === 'string' && preference.trim()) {
|
|
263
|
+
rules.push(preference.trim());
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (rules.length === 0 && fallbackSummary?.trim()) {
|
|
269
|
+
rules.push(fallbackSummary.trim());
|
|
270
|
+
}
|
|
271
|
+
return Array.from(new Set(rules.map((rule) => rule.trim()).filter(Boolean)));
|
|
272
|
+
}
|
|
273
|
+
function isProjectPolicyKey(key) {
|
|
274
|
+
return /(?:^agent_(?:operating_)?(?:rule|rules|preference|preferences)$|(?:_rule|_rules|_preference|_preferences)$)/i.test(key.trim());
|
|
275
|
+
}
|
|
276
|
+
function isProjectPolicyEntry(entry) {
|
|
277
|
+
if (isProjectPolicyKey(entry.key))
|
|
278
|
+
return true;
|
|
279
|
+
const durableClass = typeof entry.properties?.durableClass === 'string'
|
|
280
|
+
? entry.properties.durableClass.trim().toLowerCase()
|
|
281
|
+
: '';
|
|
282
|
+
const semanticIntent = typeof entry.properties?.semanticIntent === 'string'
|
|
283
|
+
? entry.properties.semanticIntent.trim().toLowerCase()
|
|
284
|
+
: '';
|
|
285
|
+
return durableClass === 'preference' || semanticIntent === 'preference_capture';
|
|
286
|
+
}
|
|
287
|
+
function toProjectPolicyWorkingMemoryEntries(entries) {
|
|
288
|
+
return entries.map((entry) => ({
|
|
289
|
+
entityKey: entry.entityKey,
|
|
290
|
+
summary: `Project policy: ${entry.summary}`,
|
|
291
|
+
confidence: 100,
|
|
292
|
+
source: entry.source,
|
|
293
|
+
lastUpdated: entry.lastUpdated,
|
|
294
|
+
}));
|
|
295
|
+
}
|
|
296
|
+
function mergeWorkingMemoryWithProjectPolicies(entries, policies) {
|
|
297
|
+
const retained = entries.filter((entry) => !policies.some((policy) => policy.entityKey === entry.entityKey));
|
|
298
|
+
return policies.length > 0
|
|
299
|
+
? [...toProjectPolicyWorkingMemoryEntries(policies), ...retained]
|
|
300
|
+
: retained;
|
|
301
|
+
}
|
|
302
|
+
function applyProjectPolicyOperatingRules(operatingRules, projectPolicies) {
|
|
303
|
+
let nextRules = operatingRules;
|
|
304
|
+
for (const policy of projectPolicies) {
|
|
305
|
+
for (const rule of policy.rules) {
|
|
306
|
+
const renderedRule = `PROJECT POLICY (${policy.key}): ${rule}`;
|
|
307
|
+
if (!nextRules.includes(renderedRule)) {
|
|
308
|
+
nextRules = `${nextRules}\n- ${renderedRule}`;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return nextRules;
|
|
313
|
+
}
|
|
216
314
|
function formatMissingWriteCategories(categories) {
|
|
217
315
|
const labelMap = {
|
|
218
316
|
findings: 'what you found',
|
|
@@ -254,10 +352,20 @@ function advisoryTaskTokens(taskType) {
|
|
|
254
352
|
.map((token) => token.trim())
|
|
255
353
|
.filter((token) => token.length >= 4)));
|
|
256
354
|
}
|
|
257
|
-
function buildUsageGuidance(tool) {
|
|
355
|
+
function buildUsageGuidance(tool, turnsWithoutWrite = 0) {
|
|
356
|
+
let reminder = ATTEND_USAGE_REMINDER;
|
|
357
|
+
if (turnsWithoutWrite >= 3) {
|
|
358
|
+
reminder += ` NON-COMPLIANT: ${turnsWithoutWrite} turns have completed without a single iranti_write call. You are losing knowledge. Call iranti_write NOW for any findings, file changes, or decisions from recent turns.`;
|
|
359
|
+
}
|
|
360
|
+
else if (turnsWithoutWrite >= 2) {
|
|
361
|
+
reminder += ` WARNING: ${turnsWithoutWrite} turns without an iranti_write call. If you discovered, changed, or confirmed anything, write it now before it is lost.`;
|
|
362
|
+
}
|
|
363
|
+
else if (turnsWithoutWrite === 1) {
|
|
364
|
+
reminder += ' Reminder: if the previous turn produced durable findings, call iranti_write before continuing.';
|
|
365
|
+
}
|
|
258
366
|
return {
|
|
259
367
|
tool,
|
|
260
|
-
reminder
|
|
368
|
+
reminder,
|
|
261
369
|
expectedCallSequence: ATTEND_EXPECTED_CALL_SEQUENCE,
|
|
262
370
|
note: tool === 'observe'
|
|
263
371
|
? OBSERVE_USAGE_NOTE
|
|
@@ -489,6 +597,28 @@ function buildSessionComplianceState(input) {
|
|
|
489
597
|
requiredAction: 'Persist durable findings with iranti_write or iranti_checkpoint before the next turn if new knowledge, validation, or file changes occurred.',
|
|
490
598
|
});
|
|
491
599
|
}
|
|
600
|
+
if (input.turnsWithoutWrite >= 2) {
|
|
601
|
+
const severity = input.turnsWithoutWrite >= 3 ? 'error' : 'warn';
|
|
602
|
+
issues.push({
|
|
603
|
+
code: 'missing_writes_across_turns',
|
|
604
|
+
severity,
|
|
605
|
+
count: input.turnsWithoutWrite,
|
|
606
|
+
message: `${input.turnsWithoutWrite} completed turns without a single iranti_write or iranti_checkpoint call. Knowledge discovered during these turns is not being persisted.`,
|
|
607
|
+
requiredAction: 'Call iranti_write for each durable finding — file edits, confirmed facts, environment state, subagent results. Every turn that discovers something should write it.',
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
if (input.consecutiveUnusedMemoryInjections > 0) {
|
|
611
|
+
const severity = input.consecutiveUnusedMemoryInjections >= 2 ? 'error' : 'warn';
|
|
612
|
+
issues.push({
|
|
613
|
+
code: 'ignored_injected_memory',
|
|
614
|
+
severity,
|
|
615
|
+
count: input.consecutiveUnusedMemoryInjections,
|
|
616
|
+
message: input.consecutiveUnusedMemoryInjections >= 2
|
|
617
|
+
? `Injected memory has been surfaced and then ignored across ${input.consecutiveUnusedMemoryInjections} consecutive turns.`
|
|
618
|
+
: 'Injected memory was surfaced but the response did not use it in the previous turn.',
|
|
619
|
+
requiredAction: 'On the next turn, either answer from the injected facts directly or persist why the injected memory was insufficient before rediscovering the same state manually.',
|
|
620
|
+
});
|
|
621
|
+
}
|
|
492
622
|
let status = 'healthy';
|
|
493
623
|
if (issues.some((issue) => issue.severity === 'error')) {
|
|
494
624
|
status = 'non_compliant';
|
|
@@ -501,10 +631,14 @@ function buildSessionComplianceState(input) {
|
|
|
501
631
|
? 'Lifecycle is currently in progress and waiting for a post-response attend.'
|
|
502
632
|
: 'Lifecycle is currently compliant.'
|
|
503
633
|
: status === 'degraded'
|
|
504
|
-
?
|
|
634
|
+
? input.consecutiveUnusedMemoryInjections > 0
|
|
635
|
+
? 'Lifecycle is degraded: injected memory was surfaced but not used.'
|
|
636
|
+
: 'Lifecycle is degraded: iranti_write has not been called after recent knowledge-changing actions.'
|
|
505
637
|
: input.consecutivePreResponseWithoutPost > 0
|
|
506
638
|
? 'Lifecycle is non-compliant: the previous turn is still missing a post-response attend.'
|
|
507
|
-
:
|
|
639
|
+
: input.consecutiveUnusedMemoryInjections > 0
|
|
640
|
+
? 'Lifecycle is non-compliant: injected memory is being ignored instead of used or explicitly challenged.'
|
|
641
|
+
: 'Lifecycle is non-compliant: iranti_write calls are missing — durable findings are not being persisted.';
|
|
508
642
|
return {
|
|
509
643
|
status,
|
|
510
644
|
summary,
|
|
@@ -512,7 +646,9 @@ function buildSessionComplianceState(input) {
|
|
|
512
646
|
lastUpdated: input.lastUpdated ?? new Date().toISOString(),
|
|
513
647
|
counters: {
|
|
514
648
|
attendsWithoutPersist: input.attendsWithoutPersist,
|
|
649
|
+
turnsWithoutWrite: input.turnsWithoutWrite,
|
|
515
650
|
consecutivePreResponseWithoutPost: input.consecutivePreResponseWithoutPost,
|
|
651
|
+
consecutiveUnusedMemoryInjections: input.consecutiveUnusedMemoryInjections,
|
|
516
652
|
pendingPostResponse,
|
|
517
653
|
lastAttendPhase: input.lastAttendPhase ?? null,
|
|
518
654
|
},
|
|
@@ -970,13 +1106,13 @@ async function persistSharedCheckpointBreadcrumbs(params) {
|
|
|
970
1106
|
if (checkpoint.currentStep) {
|
|
971
1107
|
await (0, librarian_1.librarianWrite)({
|
|
972
1108
|
...common,
|
|
973
|
-
key: '
|
|
1109
|
+
key: 'current_step',
|
|
974
1110
|
valueRaw: { text: checkpoint.currentStep },
|
|
975
|
-
valueSummary: truncate(`
|
|
1111
|
+
valueSummary: truncate(`current step is ${checkpoint.currentStep}`, 220),
|
|
976
1112
|
properties: {
|
|
977
1113
|
...checkpointBaseProperties,
|
|
978
1114
|
durableClass: 'current_step',
|
|
979
|
-
canonicalKey: '
|
|
1115
|
+
canonicalKey: 'current_step',
|
|
980
1116
|
mergeStrategy: 'replace',
|
|
981
1117
|
...(0, semanticFactTags_1.buildSemanticFactTags)({
|
|
982
1118
|
memoryScope: 'project',
|
|
@@ -989,19 +1125,32 @@ async function persistSharedCheckpointBreadcrumbs(params) {
|
|
|
989
1125
|
expectedKeys.push({
|
|
990
1126
|
entityType: resolved.entityType,
|
|
991
1127
|
entityId: resolved.entityId,
|
|
992
|
-
key: '
|
|
1128
|
+
key: 'current_step',
|
|
993
1129
|
});
|
|
994
1130
|
}
|
|
995
1131
|
if (checkpoint.nextStep) {
|
|
1132
|
+
const existingNextStep = await (0, queries_2.findEntry)({
|
|
1133
|
+
entityType: resolved.entityType,
|
|
1134
|
+
entityId: resolved.entityId,
|
|
1135
|
+
key: 'next_step',
|
|
1136
|
+
});
|
|
1137
|
+
const priorInstruction = existingNextStep?.valueRaw && typeof existingNextStep.valueRaw === 'object'
|
|
1138
|
+
? existingNextStep.valueRaw.instruction
|
|
1139
|
+
: null;
|
|
1140
|
+
const mergedNextStep = typeof priorInstruction === 'string'
|
|
1141
|
+
&& priorInstruction.trim().length > 0
|
|
1142
|
+
&& priorInstruction.trim() !== checkpoint.nextStep.trim()
|
|
1143
|
+
? `${checkpoint.nextStep}. Prior task step: ${priorInstruction.trim()}`
|
|
1144
|
+
: checkpoint.nextStep;
|
|
996
1145
|
await (0, librarian_1.librarianWrite)({
|
|
997
1146
|
...common,
|
|
998
|
-
key: '
|
|
999
|
-
valueRaw: { instruction:
|
|
1000
|
-
valueSummary: truncate(`
|
|
1147
|
+
key: 'next_step',
|
|
1148
|
+
valueRaw: { instruction: mergedNextStep },
|
|
1149
|
+
valueSummary: truncate(`next step is ${mergedNextStep}`, 220),
|
|
1001
1150
|
properties: {
|
|
1002
1151
|
...checkpointBaseProperties,
|
|
1003
1152
|
durableClass: 'next_step',
|
|
1004
|
-
canonicalKey: '
|
|
1153
|
+
canonicalKey: 'next_step',
|
|
1005
1154
|
mergeStrategy: 'replace',
|
|
1006
1155
|
...(0, semanticFactTags_1.buildSemanticFactTags)({
|
|
1007
1156
|
memoryScope: 'project',
|
|
@@ -1014,7 +1163,7 @@ async function persistSharedCheckpointBreadcrumbs(params) {
|
|
|
1014
1163
|
expectedKeys.push({
|
|
1015
1164
|
entityType: resolved.entityType,
|
|
1016
1165
|
entityId: resolved.entityId,
|
|
1017
|
-
key: '
|
|
1166
|
+
key: 'next_step',
|
|
1018
1167
|
});
|
|
1019
1168
|
}
|
|
1020
1169
|
if (Array.isArray(checkpoint.fileChanges) && checkpoint.fileChanges.length > 0) {
|
|
@@ -1082,13 +1231,13 @@ async function persistSharedCheckpointBreadcrumbs(params) {
|
|
|
1082
1231
|
if (checkpoint.openRisks && checkpoint.openRisks.length > 0) {
|
|
1083
1232
|
await (0, librarian_1.librarianWrite)({
|
|
1084
1233
|
...common,
|
|
1085
|
-
key: '
|
|
1234
|
+
key: 'open_risks',
|
|
1086
1235
|
valueRaw: { items: checkpoint.openRisks },
|
|
1087
|
-
valueSummary: truncate(`
|
|
1236
|
+
valueSummary: truncate(`open risks include ${checkpoint.openRisks.join('; ')}`, 220),
|
|
1088
1237
|
properties: {
|
|
1089
1238
|
...checkpointBaseProperties,
|
|
1090
1239
|
durableClass: 'open_risks',
|
|
1091
|
-
canonicalKey: '
|
|
1240
|
+
canonicalKey: 'open_risks',
|
|
1092
1241
|
mergeStrategy: 'replace',
|
|
1093
1242
|
...(0, semanticFactTags_1.buildSemanticFactTags)({
|
|
1094
1243
|
memoryScope: 'project',
|
|
@@ -1101,7 +1250,7 @@ async function persistSharedCheckpointBreadcrumbs(params) {
|
|
|
1101
1250
|
expectedKeys.push({
|
|
1102
1251
|
entityType: resolved.entityType,
|
|
1103
1252
|
entityId: resolved.entityId,
|
|
1104
|
-
key: '
|
|
1253
|
+
key: 'open_risks',
|
|
1105
1254
|
});
|
|
1106
1255
|
}
|
|
1107
1256
|
}
|
|
@@ -1199,7 +1348,9 @@ class AttendantInstance {
|
|
|
1199
1348
|
this.advisoryLearningProfile = null;
|
|
1200
1349
|
this.contextCallCount = 0;
|
|
1201
1350
|
this.attendsWithoutPersist = 0;
|
|
1351
|
+
this.turnsWithoutWrite = 0;
|
|
1202
1352
|
this.consecutivePreResponseWithoutPost = 0;
|
|
1353
|
+
this.consecutiveUnusedMemoryInjections = 0;
|
|
1203
1354
|
this.lastAttendPhase = undefined;
|
|
1204
1355
|
this.complianceUpdatedAt = new Date().toISOString();
|
|
1205
1356
|
this.sessionStarted = new Date().toISOString();
|
|
@@ -1208,6 +1359,7 @@ class AttendantInstance {
|
|
|
1208
1359
|
this.eventHost = null;
|
|
1209
1360
|
this.sharedStateObservedAt = null;
|
|
1210
1361
|
this.pendingSharedStateInvalidations = new Map();
|
|
1362
|
+
this.pendingMemoryAttributions = [];
|
|
1211
1363
|
this.agentId = agentId;
|
|
1212
1364
|
(0, sharedStateInvalidation_1.registerSharedStateInvalidationObserver)(agentId, this);
|
|
1213
1365
|
}
|
|
@@ -1232,6 +1384,156 @@ class AttendantInstance {
|
|
|
1232
1384
|
...(this.eventHost ? { host: this.eventHost } : {}),
|
|
1233
1385
|
};
|
|
1234
1386
|
}
|
|
1387
|
+
updateBriefPendingMemoryAttributions() {
|
|
1388
|
+
if (!this.brief)
|
|
1389
|
+
return;
|
|
1390
|
+
this.brief = {
|
|
1391
|
+
...this.brief,
|
|
1392
|
+
pendingMemoryAttributions: this.pendingMemoryAttributions.map((entry) => ({ ...entry })),
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
addPendingMemoryAttribution(input) {
|
|
1396
|
+
const attribution = {
|
|
1397
|
+
injectionId: (0, crypto_1.randomUUID)(),
|
|
1398
|
+
surfaced: true,
|
|
1399
|
+
used: false,
|
|
1400
|
+
helpful: false,
|
|
1401
|
+
status: 'pending',
|
|
1402
|
+
phase: input.phase,
|
|
1403
|
+
surfacedAt: new Date().toISOString(),
|
|
1404
|
+
reason: 'awaiting_post_response_evaluation',
|
|
1405
|
+
injectedKeys: [...input.injectedKeys],
|
|
1406
|
+
injectedEntryIds: [...input.injectedEntryIds],
|
|
1407
|
+
evidenceKinds: [],
|
|
1408
|
+
};
|
|
1409
|
+
this.pendingMemoryAttributions.push(attribution);
|
|
1410
|
+
this.updateBriefPendingMemoryAttributions();
|
|
1411
|
+
return attribution;
|
|
1412
|
+
}
|
|
1413
|
+
recordMemoryEvidence(kind) {
|
|
1414
|
+
if (this.pendingMemoryAttributions.length === 0) {
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
for (const attribution of this.pendingMemoryAttributions) {
|
|
1418
|
+
if (attribution.status !== 'pending')
|
|
1419
|
+
continue;
|
|
1420
|
+
if (!attribution.evidenceKinds.includes(kind)) {
|
|
1421
|
+
attribution.evidenceKinds = [...attribution.evidenceKinds, kind];
|
|
1422
|
+
}
|
|
1423
|
+
(0, staffEventRegistry_1.getStaffEventEmitter)().emit({
|
|
1424
|
+
staffComponent: 'Attendant',
|
|
1425
|
+
actionType: 'memory_evidence_observed',
|
|
1426
|
+
agentId: this.agentId,
|
|
1427
|
+
source: this.eventSource,
|
|
1428
|
+
reason: kind,
|
|
1429
|
+
level: 'audit',
|
|
1430
|
+
metadata: this.buildEventMetadata({
|
|
1431
|
+
injectionId: attribution.injectionId,
|
|
1432
|
+
injectedKeys: attribution.injectedKeys,
|
|
1433
|
+
injectedEntryIds: attribution.injectedEntryIds,
|
|
1434
|
+
evidenceKind: kind,
|
|
1435
|
+
}),
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
this.updateBriefPendingMemoryAttributions();
|
|
1439
|
+
}
|
|
1440
|
+
responseMentionsInjectedMemory(response, attribution) {
|
|
1441
|
+
const responseTokens = new Set(tokenize(response));
|
|
1442
|
+
if (responseTokens.size === 0)
|
|
1443
|
+
return false;
|
|
1444
|
+
for (const entityKey of attribution.injectedKeys) {
|
|
1445
|
+
const key = entityKey.split('/').slice(2).join('/');
|
|
1446
|
+
for (const token of tokenize(key.replace(/[_/.-]+/g, ' '))) {
|
|
1447
|
+
if (responseTokens.has(token)) {
|
|
1448
|
+
return true;
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
return false;
|
|
1453
|
+
}
|
|
1454
|
+
responseShowsRecoveryValue(response, attribution) {
|
|
1455
|
+
const normalized = normalizeText(response);
|
|
1456
|
+
if (!normalized)
|
|
1457
|
+
return false;
|
|
1458
|
+
return attribution.injectedKeys.some((entityKey) => {
|
|
1459
|
+
const key = entityKey.split('/').slice(2).join('/');
|
|
1460
|
+
return (/\b(next step|current step|blocker|blockers|risk|risks|status|progress|file|files|changed|handoff|resume|recovery)\b/.test(normalized)
|
|
1461
|
+
&& /\b(next_step|current_step|open_risks|status|checkpoint_summary|recent_file_changes|recent_actions|implementation_status|blockers?)\b/i.test(key));
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1464
|
+
scorePendingMemoryAttributions(response) {
|
|
1465
|
+
if (this.pendingMemoryAttributions.length === 0) {
|
|
1466
|
+
return [];
|
|
1467
|
+
}
|
|
1468
|
+
const scoredAt = new Date().toISOString();
|
|
1469
|
+
const scored = this.pendingMemoryAttributions.map((entry) => {
|
|
1470
|
+
const evidenceKinds = [...entry.evidenceKinds];
|
|
1471
|
+
const rediscoveredManually = evidenceKinds.includes('rediscovery');
|
|
1472
|
+
if (!rediscoveredManually && this.responseMentionsInjectedMemory(response, entry) && !evidenceKinds.includes('response_reference')) {
|
|
1473
|
+
evidenceKinds.push('response_reference');
|
|
1474
|
+
}
|
|
1475
|
+
if (!rediscoveredManually && this.responseShowsRecoveryValue(response, entry) && !evidenceKinds.includes('response_recovery')) {
|
|
1476
|
+
evidenceKinds.push('response_recovery');
|
|
1477
|
+
}
|
|
1478
|
+
const used = evidenceKinds.includes('write')
|
|
1479
|
+
|| evidenceKinds.includes('checkpoint')
|
|
1480
|
+
|| evidenceKinds.includes('response_reference')
|
|
1481
|
+
|| evidenceKinds.includes('response_recovery');
|
|
1482
|
+
const helpful = evidenceKinds.includes('checkpoint')
|
|
1483
|
+
|| evidenceKinds.includes('write')
|
|
1484
|
+
|| evidenceKinds.includes('response_recovery');
|
|
1485
|
+
const reason = helpful
|
|
1486
|
+
? 'response_or_action_confirmed_memory_helpfulness'
|
|
1487
|
+
: used
|
|
1488
|
+
? 'response_referenced_injected_memory'
|
|
1489
|
+
: 'memory_was_only_surfaced';
|
|
1490
|
+
const scoredEntry = {
|
|
1491
|
+
...entry,
|
|
1492
|
+
used,
|
|
1493
|
+
helpful,
|
|
1494
|
+
status: 'scored',
|
|
1495
|
+
scoredAt,
|
|
1496
|
+
reason,
|
|
1497
|
+
evidenceKinds,
|
|
1498
|
+
};
|
|
1499
|
+
(0, staffEventRegistry_1.getStaffEventEmitter)().emit({
|
|
1500
|
+
staffComponent: 'Attendant',
|
|
1501
|
+
actionType: 'memory_injection_scored',
|
|
1502
|
+
agentId: this.agentId,
|
|
1503
|
+
source: this.eventSource,
|
|
1504
|
+
reason,
|
|
1505
|
+
level: 'audit',
|
|
1506
|
+
metadata: this.buildEventMetadata({
|
|
1507
|
+
injectionId: scoredEntry.injectionId,
|
|
1508
|
+
surfaced: true,
|
|
1509
|
+
used,
|
|
1510
|
+
helpful,
|
|
1511
|
+
phase: scoredEntry.phase,
|
|
1512
|
+
injectedKeys: scoredEntry.injectedKeys,
|
|
1513
|
+
injectedEntryIds: scoredEntry.injectedEntryIds,
|
|
1514
|
+
evidenceKinds,
|
|
1515
|
+
scoredAt,
|
|
1516
|
+
}),
|
|
1517
|
+
});
|
|
1518
|
+
return scoredEntry;
|
|
1519
|
+
});
|
|
1520
|
+
if (scored.some((entry) => entry.used)) {
|
|
1521
|
+
this.consecutiveUnusedMemoryInjections = 0;
|
|
1522
|
+
}
|
|
1523
|
+
else if (scored.some((entry) => entry.surfaced)) {
|
|
1524
|
+
this.consecutiveUnusedMemoryInjections += 1;
|
|
1525
|
+
}
|
|
1526
|
+
this.pendingMemoryAttributions = [];
|
|
1527
|
+
this.updateBriefPendingMemoryAttributions();
|
|
1528
|
+
return scored;
|
|
1529
|
+
}
|
|
1530
|
+
async noteDiscoveryOccurred() {
|
|
1531
|
+
if (this.pendingMemoryAttributions.length === 0) {
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
this.recordMemoryEvidence('rediscovery');
|
|
1535
|
+
await this.persistState();
|
|
1536
|
+
}
|
|
1235
1537
|
async loadSessionLedgerSignals(taskType) {
|
|
1236
1538
|
try {
|
|
1237
1539
|
const source = this.eventSource === 'internal' ? undefined : this.eventSource;
|
|
@@ -1260,6 +1562,33 @@ class AttendantInstance {
|
|
|
1260
1562
|
return { learnings: [], profile: null };
|
|
1261
1563
|
}
|
|
1262
1564
|
}
|
|
1565
|
+
async loadProjectPolicies() {
|
|
1566
|
+
const configured = (0, autoRemember_1.getProjectMemoryEntity)();
|
|
1567
|
+
if (!configured)
|
|
1568
|
+
return [];
|
|
1569
|
+
const parsed = (0, entity_resolution_1.parseEntityString)(configured);
|
|
1570
|
+
const entries = await (0, queries_1.findEntriesByEntity)(parsed.entityType, parsed.entityId);
|
|
1571
|
+
const policies = entries
|
|
1572
|
+
.filter((entry) => isProjectPolicyEntry({
|
|
1573
|
+
key: entry.key,
|
|
1574
|
+
properties: entry.properties ?? null,
|
|
1575
|
+
}))
|
|
1576
|
+
.map((entry) => {
|
|
1577
|
+
const rules = normalizeProjectPolicyRuleLines(entry.valueRaw, entry.valueSummary);
|
|
1578
|
+
if (rules.length === 0)
|
|
1579
|
+
return null;
|
|
1580
|
+
return {
|
|
1581
|
+
entityKey: `${entry.entityType}/${entry.entityId}/${entry.key}`,
|
|
1582
|
+
summary: rules.join(' '),
|
|
1583
|
+
key: entry.key,
|
|
1584
|
+
source: entry.source,
|
|
1585
|
+
lastUpdated: entry.updatedAt.toISOString(),
|
|
1586
|
+
rules,
|
|
1587
|
+
};
|
|
1588
|
+
})
|
|
1589
|
+
.filter((entry) => Boolean(entry));
|
|
1590
|
+
return policies;
|
|
1591
|
+
}
|
|
1263
1592
|
// ── Handshake ────────────────────────────────────────────────────────────
|
|
1264
1593
|
async handshake(context) {
|
|
1265
1594
|
const t0 = (0, metrics_1.timeStart)();
|
|
@@ -1271,13 +1600,15 @@ class AttendantInstance {
|
|
|
1271
1600
|
// Infer task type
|
|
1272
1601
|
const inferredTaskType = await this.inferTask(context);
|
|
1273
1602
|
// Load knowledge — agent entries + related entities
|
|
1274
|
-
const [workingMemory, ledgerSignals] = await Promise.all([
|
|
1603
|
+
const [workingMemory, ledgerSignals, projectPolicies] = await Promise.all([
|
|
1275
1604
|
this.buildWorkingMemory(inferredTaskType),
|
|
1276
1605
|
this.loadSessionLedgerSignals(inferredTaskType),
|
|
1606
|
+
this.loadProjectPolicies(),
|
|
1277
1607
|
]);
|
|
1278
1608
|
const sessionLedgerLearnings = ledgerSignals.learnings;
|
|
1279
1609
|
this.advisoryLearningProfile = ledgerSignals.profile;
|
|
1280
|
-
const
|
|
1610
|
+
const workingMemoryWithPolicies = mergeWorkingMemoryWithProjectPolicies(workingMemory, projectPolicies);
|
|
1611
|
+
const workingMemoryWithLedger = mergeWorkingMemoryWithLedger(workingMemoryWithPolicies, sessionLedgerLearnings);
|
|
1281
1612
|
const recoveryResult = persisted?.sessionCheckpoint
|
|
1282
1613
|
? this.buildRecovery(context, persisted.sessionCheckpoint)
|
|
1283
1614
|
: { interrupted: false, recovery: null };
|
|
@@ -1294,9 +1625,10 @@ class AttendantInstance {
|
|
|
1294
1625
|
}
|
|
1295
1626
|
this.brief = {
|
|
1296
1627
|
agentId: this.agentId,
|
|
1297
|
-
operatingRules: applyAdvisoryOperatingRules(operatingRules, this.advisoryLearningProfile),
|
|
1628
|
+
operatingRules: applyAdvisoryOperatingRules(applyProjectPolicyOperatingRules(operatingRules, projectPolicies), this.advisoryLearningProfile),
|
|
1298
1629
|
inferredTaskType,
|
|
1299
1630
|
workingMemory: workingMemoryWithLedger,
|
|
1631
|
+
projectPolicies,
|
|
1300
1632
|
sessionStarted: persisted?.sessionStarted ?? this.sessionStarted,
|
|
1301
1633
|
briefGeneratedAt: new Date().toISOString(),
|
|
1302
1634
|
contextCallCount: this.contextCallCount,
|
|
@@ -1323,6 +1655,7 @@ class AttendantInstance {
|
|
|
1323
1655
|
metadata: this.buildEventMetadata({
|
|
1324
1656
|
briefSize: this.brief?.workingMemory.length ?? 0,
|
|
1325
1657
|
ledgerLearningCount: sessionLedgerLearnings.length,
|
|
1658
|
+
projectPolicyCount: projectPolicies.length,
|
|
1326
1659
|
advisoryScopes: this.advisoryLearningProfile?.scopesUsed ?? [],
|
|
1327
1660
|
taskSummary: context.task.slice(0, 120),
|
|
1328
1661
|
}),
|
|
@@ -1340,7 +1673,10 @@ class AttendantInstance {
|
|
|
1340
1673
|
return result;
|
|
1341
1674
|
}
|
|
1342
1675
|
const newTaskType = await this.inferTask(context);
|
|
1343
|
-
const ledgerSignals = await
|
|
1676
|
+
const [ledgerSignals, projectPolicies] = await Promise.all([
|
|
1677
|
+
this.loadSessionLedgerSignals(newTaskType),
|
|
1678
|
+
this.loadProjectPolicies(),
|
|
1679
|
+
]);
|
|
1344
1680
|
this.advisoryLearningProfile = ledgerSignals.profile;
|
|
1345
1681
|
// Task hasn't shifted — update timestamp only
|
|
1346
1682
|
if (newTaskType.toLowerCase() === this.brief.inferredTaskType.toLowerCase()) {
|
|
@@ -1354,8 +1690,9 @@ class AttendantInstance {
|
|
|
1354
1690
|
}
|
|
1355
1691
|
this.brief = {
|
|
1356
1692
|
...this.brief,
|
|
1357
|
-
operatingRules: applyAdvisoryOperatingRules(this.
|
|
1358
|
-
workingMemory: mergeWorkingMemoryWithLedger(this.brief.workingMemory, ledgerSignals.learnings),
|
|
1693
|
+
operatingRules: applyAdvisoryOperatingRules(applyProjectPolicyOperatingRules(await this.loadOperatingRules(), projectPolicies), this.advisoryLearningProfile),
|
|
1694
|
+
workingMemory: mergeWorkingMemoryWithLedger(mergeWorkingMemoryWithProjectPolicies(this.brief.workingMemory, projectPolicies), ledgerSignals.learnings),
|
|
1695
|
+
projectPolicies,
|
|
1359
1696
|
briefGeneratedAt: new Date().toISOString(),
|
|
1360
1697
|
contextCallCount: this.contextCallCount,
|
|
1361
1698
|
sessionLedgerLearnings: ledgerSignals.learnings,
|
|
@@ -1375,6 +1712,7 @@ class AttendantInstance {
|
|
|
1375
1712
|
metadata: this.buildEventMetadata({
|
|
1376
1713
|
briefSize: this.brief?.workingMemory.length ?? 0,
|
|
1377
1714
|
contextCallCount: this.contextCallCount,
|
|
1715
|
+
projectPolicyCount: projectPolicies.length,
|
|
1378
1716
|
advisoryScopes: this.advisoryLearningProfile?.scopesUsed ?? [],
|
|
1379
1717
|
}),
|
|
1380
1718
|
});
|
|
@@ -1385,9 +1723,10 @@ class AttendantInstance {
|
|
|
1385
1723
|
const workingMemory = await this.buildWorkingMemory(newTaskType);
|
|
1386
1724
|
this.brief = {
|
|
1387
1725
|
...this.brief,
|
|
1388
|
-
operatingRules: applyAdvisoryOperatingRules(this.
|
|
1726
|
+
operatingRules: applyAdvisoryOperatingRules(applyProjectPolicyOperatingRules(await this.loadOperatingRules(), projectPolicies), this.advisoryLearningProfile),
|
|
1389
1727
|
inferredTaskType: newTaskType,
|
|
1390
|
-
workingMemory: mergeWorkingMemoryWithLedger(workingMemory, ledgerSignals.learnings),
|
|
1728
|
+
workingMemory: mergeWorkingMemoryWithLedger(mergeWorkingMemoryWithProjectPolicies(workingMemory, projectPolicies), ledgerSignals.learnings),
|
|
1729
|
+
projectPolicies,
|
|
1391
1730
|
briefGeneratedAt: new Date().toISOString(),
|
|
1392
1731
|
contextCallCount: this.contextCallCount,
|
|
1393
1732
|
sessionLedgerLearnings: ledgerSignals.learnings,
|
|
@@ -1407,6 +1746,7 @@ class AttendantInstance {
|
|
|
1407
1746
|
metadata: this.buildEventMetadata({
|
|
1408
1747
|
briefSize: this.brief?.workingMemory.length ?? 0,
|
|
1409
1748
|
contextCallCount: this.contextCallCount,
|
|
1749
|
+
projectPolicyCount: projectPolicies.length,
|
|
1410
1750
|
advisoryScopes: this.advisoryLearningProfile?.scopesUsed ?? [],
|
|
1411
1751
|
}),
|
|
1412
1752
|
});
|
|
@@ -1434,8 +1774,10 @@ class AttendantInstance {
|
|
|
1434
1774
|
const operatingRules = rulesResult.found && rulesResult.entry
|
|
1435
1775
|
? formatOperatingRulesText(rulesResult.entry.valueRaw, rulesResult.entry.valueSummary)
|
|
1436
1776
|
: formatOperatingRulesText(null, 'Attendant operating rules:');
|
|
1777
|
+
const projectPolicies = this.brief?.projectPolicies ?? await this.loadProjectPolicies();
|
|
1437
1778
|
if (this.brief) {
|
|
1438
|
-
this.brief.operatingRules = operatingRules;
|
|
1779
|
+
this.brief.operatingRules = applyAdvisoryOperatingRules(applyProjectPolicyOperatingRules(operatingRules, projectPolicies), this.advisoryLearningProfile);
|
|
1780
|
+
this.brief.projectPolicies = projectPolicies;
|
|
1439
1781
|
this.brief.contextCallCount = 0;
|
|
1440
1782
|
}
|
|
1441
1783
|
this.contextCallCount = 0;
|
|
@@ -1472,16 +1814,20 @@ class AttendantInstance {
|
|
|
1472
1814
|
buildComplianceState(lastUpdated) {
|
|
1473
1815
|
return buildSessionComplianceState({
|
|
1474
1816
|
attendsWithoutPersist: this.attendsWithoutPersist,
|
|
1817
|
+
turnsWithoutWrite: this.turnsWithoutWrite,
|
|
1475
1818
|
consecutivePreResponseWithoutPost: this.consecutivePreResponseWithoutPost,
|
|
1819
|
+
consecutiveUnusedMemoryInjections: this.consecutiveUnusedMemoryInjections,
|
|
1476
1820
|
lastAttendPhase: this.lastAttendPhase,
|
|
1477
1821
|
lastUpdated: lastUpdated ?? this.complianceUpdatedAt,
|
|
1478
1822
|
});
|
|
1479
1823
|
}
|
|
1480
1824
|
async notifyWriteOccurred() {
|
|
1481
1825
|
this.attendsWithoutPersist = 0;
|
|
1826
|
+
this.turnsWithoutWrite = 0;
|
|
1482
1827
|
this.lastAttendPhase = undefined;
|
|
1483
1828
|
this.consecutivePreResponseWithoutPost = 0;
|
|
1484
1829
|
this.complianceUpdatedAt = new Date().toISOString();
|
|
1830
|
+
this.recordMemoryEvidence('write');
|
|
1485
1831
|
if (!this.brief) {
|
|
1486
1832
|
return;
|
|
1487
1833
|
}
|
|
@@ -1494,9 +1840,11 @@ class AttendantInstance {
|
|
|
1494
1840
|
}
|
|
1495
1841
|
async checkpoint(input) {
|
|
1496
1842
|
this.attendsWithoutPersist = 0;
|
|
1843
|
+
this.turnsWithoutWrite = 0;
|
|
1497
1844
|
this.lastAttendPhase = undefined;
|
|
1498
1845
|
this.consecutivePreResponseWithoutPost = 0;
|
|
1499
1846
|
this.complianceUpdatedAt = new Date().toISOString();
|
|
1847
|
+
this.recordMemoryEvidence('checkpoint');
|
|
1500
1848
|
this.setLedgerContext(input.ledgerContext);
|
|
1501
1849
|
const now = new Date().toISOString();
|
|
1502
1850
|
if (!this.brief) {
|
|
@@ -1730,9 +2078,12 @@ class AttendantInstance {
|
|
|
1730
2078
|
this.attendsWithoutPersist++;
|
|
1731
2079
|
const phase = input.phase;
|
|
1732
2080
|
let complianceWarning;
|
|
2081
|
+
const ignoredMemoryWarning = 'COMPLIANCE: injected memory was surfaced but not used. On the next turn, either answer from injected facts directly or persist why the injected memory was insufficient before rediscovering the same state manually.';
|
|
1733
2082
|
if (phase === 'post-response') {
|
|
1734
|
-
// Correct post-response call — reset counters
|
|
2083
|
+
// Correct post-response call — reset attend counters but NOT turnsWithoutWrite
|
|
2084
|
+
// turnsWithoutWrite only resets on actual writes/checkpoints
|
|
1735
2085
|
this.attendsWithoutPersist = 0;
|
|
2086
|
+
this.turnsWithoutWrite++;
|
|
1736
2087
|
this.lastAttendPhase = 'post-response';
|
|
1737
2088
|
this.consecutivePreResponseWithoutPost = 0;
|
|
1738
2089
|
}
|
|
@@ -1753,7 +2104,10 @@ class AttendantInstance {
|
|
|
1753
2104
|
}
|
|
1754
2105
|
}
|
|
1755
2106
|
this.complianceUpdatedAt = new Date().toISOString();
|
|
1756
|
-
|
|
2107
|
+
let compliance = this.buildComplianceState(this.complianceUpdatedAt);
|
|
2108
|
+
if (!complianceWarning && compliance.issues.some((issue) => issue.code === 'ignored_injected_memory')) {
|
|
2109
|
+
complianceWarning = ignoredMemoryWarning;
|
|
2110
|
+
}
|
|
1757
2111
|
if (!this.brief) {
|
|
1758
2112
|
const bootstrapTask = buildAttendBootstrapTask(latestMessage, currentContext);
|
|
1759
2113
|
const bootstrapBrief = await this.handshake({
|
|
@@ -1788,6 +2142,52 @@ class AttendantInstance {
|
|
|
1788
2142
|
}),
|
|
1789
2143
|
});
|
|
1790
2144
|
}
|
|
2145
|
+
if (phase === 'post-response') {
|
|
2146
|
+
const memoryAttributions = this.scorePendingMemoryAttributions(latestMessage || currentContext);
|
|
2147
|
+
compliance = this.buildComplianceState(this.complianceUpdatedAt);
|
|
2148
|
+
if (memoryAttributions.some((entry) => !entry.used)) {
|
|
2149
|
+
complianceWarning = ignoredMemoryWarning;
|
|
2150
|
+
}
|
|
2151
|
+
if (this.brief) {
|
|
2152
|
+
this.brief = {
|
|
2153
|
+
...this.brief,
|
|
2154
|
+
compliance,
|
|
2155
|
+
briefGeneratedAt: this.complianceUpdatedAt,
|
|
2156
|
+
};
|
|
2157
|
+
await this.persistState();
|
|
2158
|
+
}
|
|
2159
|
+
(0, metrics_1.timeEnd)('attendant.attend_ms', t0);
|
|
2160
|
+
return {
|
|
2161
|
+
shouldInject: false,
|
|
2162
|
+
reason: 'memory_not_needed',
|
|
2163
|
+
decision: {
|
|
2164
|
+
needed: false,
|
|
2165
|
+
confidence: 1,
|
|
2166
|
+
method: 'heuristic',
|
|
2167
|
+
explanation: 'post_response_closeout',
|
|
2168
|
+
},
|
|
2169
|
+
bootstrap,
|
|
2170
|
+
complianceWarning,
|
|
2171
|
+
compliance,
|
|
2172
|
+
memoryAttributions,
|
|
2173
|
+
usageGuidance: buildUsageGuidance('attend', this.turnsWithoutWrite),
|
|
2174
|
+
facts: [],
|
|
2175
|
+
entitiesDetected: [],
|
|
2176
|
+
alreadyPresent: 0,
|
|
2177
|
+
totalFound: 0,
|
|
2178
|
+
entitiesResolved: [],
|
|
2179
|
+
debug: {
|
|
2180
|
+
skipped: 'empty_context',
|
|
2181
|
+
contextLength: currentContext.length,
|
|
2182
|
+
detectionWindowChars: Math.min(currentContext.length, ENTITY_DETECTION_WINDOW_CHARS),
|
|
2183
|
+
detectedCandidates: 0,
|
|
2184
|
+
keptCandidates: 0,
|
|
2185
|
+
hintsProvided: effectiveEntityHints.length,
|
|
2186
|
+
hintsResolved: 0,
|
|
2187
|
+
dropped: [{ name: latestMessage || '(none)', reason: 'post_response_closeout' }],
|
|
2188
|
+
},
|
|
2189
|
+
};
|
|
2190
|
+
}
|
|
1791
2191
|
let decision = await this.decideMemoryNeed({
|
|
1792
2192
|
currentContext,
|
|
1793
2193
|
latestMessage,
|
|
@@ -1847,7 +2247,8 @@ class AttendantInstance {
|
|
|
1847
2247
|
bootstrap,
|
|
1848
2248
|
complianceWarning,
|
|
1849
2249
|
compliance,
|
|
1850
|
-
|
|
2250
|
+
memoryAttributions: [],
|
|
2251
|
+
usageGuidance: buildUsageGuidance('attend', this.turnsWithoutWrite),
|
|
1851
2252
|
facts: [],
|
|
1852
2253
|
entitiesDetected: [],
|
|
1853
2254
|
alreadyPresent: 0,
|
|
@@ -1870,11 +2271,11 @@ class AttendantInstance {
|
|
|
1870
2271
|
currentContext: observationContext,
|
|
1871
2272
|
maxFacts: input.maxFacts,
|
|
1872
2273
|
entityHints: observeEntityHints,
|
|
1873
|
-
priorityKeys: Array.from(new Set([
|
|
2274
|
+
priorityKeys: expandContinuityPriorityKeys(Array.from(new Set([
|
|
1874
2275
|
...(mandatoryRecall.key ? [mandatoryRecall.key] : []),
|
|
1875
2276
|
...(this.advisoryLearningProfile?.priorityKeys ?? []),
|
|
1876
2277
|
...freshState.priorityKeys,
|
|
1877
|
-
])),
|
|
2278
|
+
]))),
|
|
1878
2279
|
skipContextFilter: forceInject,
|
|
1879
2280
|
ledgerContext: input.ledgerContext,
|
|
1880
2281
|
});
|
|
@@ -1891,10 +2292,11 @@ class AttendantInstance {
|
|
|
1891
2292
|
const remainder = slashIdx2 === -1 ? '' : fact.entityKey.slice(slashIdx2);
|
|
1892
2293
|
return { ...fact, entityKey: `${canonicalPersonalType}/${canonicalPersonalId}${remainder}` };
|
|
1893
2294
|
});
|
|
2295
|
+
const structuredFacts = (0, hostMemoryFormatting_1.assignStructuredFactIds)(remappedFacts);
|
|
1894
2296
|
watchedEntitiesChanged = this.updateWatchedEntities(observed.entitiesResolved?.map((entry) => entry.canonicalEntity) ?? []) || watchedEntitiesChanged;
|
|
1895
2297
|
this.markSharedStateObserved(observeEntityHints.length > 0 ? observeEntityHints : freshState.entities);
|
|
1896
2298
|
let reason = 'memory_needed_injected';
|
|
1897
|
-
const shouldInject =
|
|
2299
|
+
const shouldInject = structuredFacts.length > 0;
|
|
1898
2300
|
let searchSuggestion;
|
|
1899
2301
|
if (!shouldInject) {
|
|
1900
2302
|
const allAlreadyInContext = observed.totalFound > 0 && observed.alreadyPresent >= observed.totalFound;
|
|
@@ -1916,17 +2318,29 @@ class AttendantInstance {
|
|
|
1916
2318
|
else if (forceInject) {
|
|
1917
2319
|
reason = 'forced';
|
|
1918
2320
|
}
|
|
2321
|
+
const memoryAttributions = shouldInject
|
|
2322
|
+
? [
|
|
2323
|
+
this.addPendingMemoryAttribution({
|
|
2324
|
+
phase: phase === 'mid-turn' ? 'mid-turn' : 'pre-response',
|
|
2325
|
+
injectedKeys: structuredFacts.map((fact) => fact.entityKey),
|
|
2326
|
+
injectedEntryIds: structuredFacts
|
|
2327
|
+
.map((fact) => fact.knowledgeEntryId)
|
|
2328
|
+
.filter((value) => typeof value === 'number'),
|
|
2329
|
+
}),
|
|
2330
|
+
]
|
|
2331
|
+
: [];
|
|
1919
2332
|
const attendResult = {
|
|
1920
2333
|
...observed,
|
|
1921
|
-
facts:
|
|
1922
|
-
shouldInject,
|
|
2334
|
+
facts: structuredFacts,
|
|
2335
|
+
shouldInject: structuredFacts.length > 0,
|
|
1923
2336
|
reason,
|
|
1924
2337
|
decision,
|
|
1925
2338
|
bootstrap,
|
|
1926
2339
|
searchSuggestion,
|
|
1927
2340
|
complianceWarning,
|
|
1928
2341
|
compliance,
|
|
1929
|
-
|
|
2342
|
+
memoryAttributions,
|
|
2343
|
+
usageGuidance: buildUsageGuidance('attend', this.turnsWithoutWrite),
|
|
1930
2344
|
};
|
|
1931
2345
|
if (input.suppressEvents !== true) {
|
|
1932
2346
|
(0, staffEventRegistry_1.getStaffEventEmitter)().emit({
|
|
@@ -1940,6 +2354,7 @@ class AttendantInstance {
|
|
|
1940
2354
|
contextCallCount: this.contextCallCount,
|
|
1941
2355
|
shouldInject,
|
|
1942
2356
|
attendReason: reason,
|
|
2357
|
+
injectionId: memoryAttributions[0]?.injectionId ?? null,
|
|
1943
2358
|
advisoryDecisionMethod: decision.method,
|
|
1944
2359
|
advisoryScopes: this.advisoryLearningProfile?.scopesUsed ?? [],
|
|
1945
2360
|
freshStateEntities: freshState.entities,
|
|
@@ -1956,7 +2371,11 @@ class AttendantInstance {
|
|
|
1956
2371
|
metadata: this.buildEventMetadata({
|
|
1957
2372
|
shouldInject,
|
|
1958
2373
|
factCount: observed.facts.length,
|
|
2374
|
+
injectionId: memoryAttributions[0]?.injectionId ?? null,
|
|
1959
2375
|
injectedKeys: observed.facts.map((fact) => fact.entityKey),
|
|
2376
|
+
injectedEntryIds: observed.facts
|
|
2377
|
+
.map((fact) => fact.knowledgeEntryId)
|
|
2378
|
+
.filter((value) => typeof value === 'number'),
|
|
1960
2379
|
entitiesResolved: observed.entitiesResolved?.map((entry) => entry.canonicalEntity) ?? [],
|
|
1961
2380
|
alreadyPresent: observed.alreadyPresent,
|
|
1962
2381
|
totalFound: observed.totalFound,
|
|
@@ -1973,7 +2392,7 @@ class AttendantInstance {
|
|
|
1973
2392
|
briefGeneratedAt: this.complianceUpdatedAt,
|
|
1974
2393
|
};
|
|
1975
2394
|
}
|
|
1976
|
-
if (watchedEntitiesChanged || this.brief?.compliance !== compliance) {
|
|
2395
|
+
if (watchedEntitiesChanged || this.brief?.compliance !== compliance || memoryAttributions.length > 0) {
|
|
1977
2396
|
await this.persistState();
|
|
1978
2397
|
}
|
|
1979
2398
|
(0, metrics_1.timeEnd)('attendant.attend_ms', t0);
|
|
@@ -2253,7 +2672,7 @@ ${detectionWindow}`,
|
|
|
2253
2672
|
const allEntries = await (0, queries_1.findEntriesByEntity)(resolvedInfo.entityType, resolvedInfo.entityId);
|
|
2254
2673
|
// Priority keys first
|
|
2255
2674
|
const policyPriorityKeys = policy.observeKeyPriority?.[resolvedInfo.entityType] ?? [];
|
|
2256
|
-
const priorityKeys = new Set([...policyPriorityKeys, ...requestedPriorityKeys]);
|
|
2675
|
+
const priorityKeys = new Set(expandContinuityPriorityKeys([...policyPriorityKeys, ...requestedPriorityKeys]));
|
|
2257
2676
|
const priorityEntries = allEntries.filter((e) => priorityKeys.has(e.key));
|
|
2258
2677
|
const remainingEntries = allEntries
|
|
2259
2678
|
.filter((e) => !priorityKeys.has(e.key))
|
|
@@ -2264,13 +2683,20 @@ ${detectionWindow}`,
|
|
|
2264
2683
|
|| a.key.localeCompare(b.key));
|
|
2265
2684
|
});
|
|
2266
2685
|
const selectedEntries = [...priorityEntries, ...remainingEntries].slice(0, maxKeysPerEntity);
|
|
2686
|
+
const freshestEntry = allEntries
|
|
2687
|
+
.slice()
|
|
2688
|
+
.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime() || b.confidence - a.confidence)[0];
|
|
2689
|
+
if (freshestEntry && !selectedEntries.some((entry) => entry.id === freshestEntry.id)) {
|
|
2690
|
+
selectedEntries[selectedEntries.length - 1] = freshestEntry;
|
|
2691
|
+
}
|
|
2267
2692
|
for (const entry of selectedEntries) {
|
|
2268
2693
|
allFacts.push({
|
|
2269
|
-
entityKey: `${resolvedInfo.entityType}/${resolvedInfo.entityId}/${entry.key}`,
|
|
2694
|
+
entityKey: `${resolvedInfo.entityType}/${resolvedInfo.entityId}/${normalizeContinuityKey(entry.key)}`,
|
|
2270
2695
|
summary: entry.valueSummary,
|
|
2271
2696
|
value: entry.valueRaw,
|
|
2272
2697
|
confidence: entry.confidence,
|
|
2273
2698
|
source: entry.source,
|
|
2699
|
+
lastUpdated: entry.updatedAt.toISOString(),
|
|
2274
2700
|
entryId: entry.id,
|
|
2275
2701
|
});
|
|
2276
2702
|
}
|
|
@@ -2312,13 +2738,15 @@ ${detectionWindow}`,
|
|
|
2312
2738
|
});
|
|
2313
2739
|
(0, metrics_1.timeEnd)('attendant.observe_ms', t0);
|
|
2314
2740
|
return {
|
|
2315
|
-
facts: topFacts.map(({ entityKey, summary, value, confidence, source }) => ({
|
|
2741
|
+
facts: (0, hostMemoryFormatting_1.assignStructuredFactIds)(topFacts.map(({ entityKey, summary, value, confidence, source, lastUpdated, entryId }) => ({
|
|
2742
|
+
knowledgeEntryId: entryId,
|
|
2316
2743
|
entityKey,
|
|
2317
2744
|
summary,
|
|
2318
2745
|
value,
|
|
2319
2746
|
confidence,
|
|
2320
2747
|
source,
|
|
2321
|
-
|
|
2748
|
+
lastUpdated,
|
|
2749
|
+
}))),
|
|
2322
2750
|
entitiesDetected: Array.from(entitiesDetected),
|
|
2323
2751
|
alreadyPresent,
|
|
2324
2752
|
totalFound: allFacts.length,
|
|
@@ -2832,6 +3260,7 @@ If nothing is relevant, return: none`,
|
|
|
2832
3260
|
this.brief = {
|
|
2833
3261
|
...this.brief,
|
|
2834
3262
|
compliance: this.buildComplianceState(),
|
|
3263
|
+
pendingMemoryAttributions: this.pendingMemoryAttributions.map((entry) => ({ ...entry })),
|
|
2835
3264
|
};
|
|
2836
3265
|
await (0, client_1.getDb)().knowledgeEntry.upsert({
|
|
2837
3266
|
where: {
|
|
@@ -2870,9 +3299,14 @@ If nothing is relevant, return: none`,
|
|
|
2870
3299
|
this.advisoryLearningProfile = null;
|
|
2871
3300
|
this.sharedStateObservedAt = state.briefGeneratedAt;
|
|
2872
3301
|
this.attendsWithoutPersist = state.compliance?.counters.attendsWithoutPersist ?? 0;
|
|
3302
|
+
this.turnsWithoutWrite = state.compliance?.counters.turnsWithoutWrite ?? 0;
|
|
2873
3303
|
this.consecutivePreResponseWithoutPost = state.compliance?.counters.consecutivePreResponseWithoutPost ?? 0;
|
|
3304
|
+
this.consecutiveUnusedMemoryInjections = state.compliance?.counters.consecutiveUnusedMemoryInjections ?? 0;
|
|
2874
3305
|
this.lastAttendPhase = state.compliance?.counters.lastAttendPhase ?? undefined;
|
|
2875
3306
|
this.complianceUpdatedAt = state.compliance?.lastUpdated ?? state.briefGeneratedAt;
|
|
3307
|
+
this.pendingMemoryAttributions = Array.isArray(state.pendingMemoryAttributions)
|
|
3308
|
+
? state.pendingMemoryAttributions.map((entry) => ({ ...entry }))
|
|
3309
|
+
: [];
|
|
2876
3310
|
this.brief = state;
|
|
2877
3311
|
return state;
|
|
2878
3312
|
}
|