iranti 0.3.2 → 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.
@@ -72,7 +72,7 @@ const ATTEND_EXPECTED_CALL_SEQUENCE = [
72
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.",
73
73
  'Call iranti_attend again when the new knowledge should change what is loaded next.',
74
74
  ];
75
- const ATTEND_USAGE_REMINDER = 'Iranti is a hive mind. iranti_attend is mandatory before each reply and around knowledge discovery, and knowledge-changing actions must leave breadcrumbs through iranti_write and/or iranti_checkpoint so later sessions do not have to rediscover context.';
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.';
76
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
77
  function normalizeContinuityKey(key) {
78
78
  return LEGACY_CONTINUITY_KEY_MAP[key] ?? key;
@@ -352,10 +352,20 @@ function advisoryTaskTokens(taskType) {
352
352
  .map((token) => token.trim())
353
353
  .filter((token) => token.length >= 4)));
354
354
  }
355
- 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
+ }
356
366
  return {
357
367
  tool,
358
- reminder: ATTEND_USAGE_REMINDER,
368
+ reminder,
359
369
  expectedCallSequence: ATTEND_EXPECTED_CALL_SEQUENCE,
360
370
  note: tool === 'observe'
361
371
  ? OBSERVE_USAGE_NOTE
@@ -587,6 +597,16 @@ function buildSessionComplianceState(input) {
587
597
  requiredAction: 'Persist durable findings with iranti_write or iranti_checkpoint before the next turn if new knowledge, validation, or file changes occurred.',
588
598
  });
589
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
+ }
590
610
  if (input.consecutiveUnusedMemoryInjections > 0) {
591
611
  const severity = input.consecutiveUnusedMemoryInjections >= 2 ? 'error' : 'warn';
592
612
  issues.push({
@@ -613,12 +633,12 @@ function buildSessionComplianceState(input) {
613
633
  : status === 'degraded'
614
634
  ? input.consecutiveUnusedMemoryInjections > 0
615
635
  ? 'Lifecycle is degraded: injected memory was surfaced but not used.'
616
- : 'Lifecycle is degraded: persistence breadcrumbs are lagging.'
636
+ : 'Lifecycle is degraded: iranti_write has not been called after recent knowledge-changing actions.'
617
637
  : input.consecutivePreResponseWithoutPost > 0
618
638
  ? 'Lifecycle is non-compliant: the previous turn is still missing a post-response attend.'
619
639
  : input.consecutiveUnusedMemoryInjections > 0
620
640
  ? 'Lifecycle is non-compliant: injected memory is being ignored instead of used or explicitly challenged.'
621
- : 'Lifecycle is non-compliant: accountability breadcrumbs are missing.';
641
+ : 'Lifecycle is non-compliant: iranti_write calls are missing — durable findings are not being persisted.';
622
642
  return {
623
643
  status,
624
644
  summary,
@@ -626,6 +646,7 @@ function buildSessionComplianceState(input) {
626
646
  lastUpdated: input.lastUpdated ?? new Date().toISOString(),
627
647
  counters: {
628
648
  attendsWithoutPersist: input.attendsWithoutPersist,
649
+ turnsWithoutWrite: input.turnsWithoutWrite,
629
650
  consecutivePreResponseWithoutPost: input.consecutivePreResponseWithoutPost,
630
651
  consecutiveUnusedMemoryInjections: input.consecutiveUnusedMemoryInjections,
631
652
  pendingPostResponse,
@@ -1327,6 +1348,7 @@ class AttendantInstance {
1327
1348
  this.advisoryLearningProfile = null;
1328
1349
  this.contextCallCount = 0;
1329
1350
  this.attendsWithoutPersist = 0;
1351
+ this.turnsWithoutWrite = 0;
1330
1352
  this.consecutivePreResponseWithoutPost = 0;
1331
1353
  this.consecutiveUnusedMemoryInjections = 0;
1332
1354
  this.lastAttendPhase = undefined;
@@ -1792,6 +1814,7 @@ class AttendantInstance {
1792
1814
  buildComplianceState(lastUpdated) {
1793
1815
  return buildSessionComplianceState({
1794
1816
  attendsWithoutPersist: this.attendsWithoutPersist,
1817
+ turnsWithoutWrite: this.turnsWithoutWrite,
1795
1818
  consecutivePreResponseWithoutPost: this.consecutivePreResponseWithoutPost,
1796
1819
  consecutiveUnusedMemoryInjections: this.consecutiveUnusedMemoryInjections,
1797
1820
  lastAttendPhase: this.lastAttendPhase,
@@ -1800,6 +1823,7 @@ class AttendantInstance {
1800
1823
  }
1801
1824
  async notifyWriteOccurred() {
1802
1825
  this.attendsWithoutPersist = 0;
1826
+ this.turnsWithoutWrite = 0;
1803
1827
  this.lastAttendPhase = undefined;
1804
1828
  this.consecutivePreResponseWithoutPost = 0;
1805
1829
  this.complianceUpdatedAt = new Date().toISOString();
@@ -1816,6 +1840,7 @@ class AttendantInstance {
1816
1840
  }
1817
1841
  async checkpoint(input) {
1818
1842
  this.attendsWithoutPersist = 0;
1843
+ this.turnsWithoutWrite = 0;
1819
1844
  this.lastAttendPhase = undefined;
1820
1845
  this.consecutivePreResponseWithoutPost = 0;
1821
1846
  this.complianceUpdatedAt = new Date().toISOString();
@@ -2055,8 +2080,10 @@ class AttendantInstance {
2055
2080
  let complianceWarning;
2056
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.';
2057
2082
  if (phase === 'post-response') {
2058
- // 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
2059
2085
  this.attendsWithoutPersist = 0;
2086
+ this.turnsWithoutWrite++;
2060
2087
  this.lastAttendPhase = 'post-response';
2061
2088
  this.consecutivePreResponseWithoutPost = 0;
2062
2089
  }
@@ -2143,7 +2170,7 @@ class AttendantInstance {
2143
2170
  complianceWarning,
2144
2171
  compliance,
2145
2172
  memoryAttributions,
2146
- usageGuidance: buildUsageGuidance('attend'),
2173
+ usageGuidance: buildUsageGuidance('attend', this.turnsWithoutWrite),
2147
2174
  facts: [],
2148
2175
  entitiesDetected: [],
2149
2176
  alreadyPresent: 0,
@@ -2221,7 +2248,7 @@ class AttendantInstance {
2221
2248
  complianceWarning,
2222
2249
  compliance,
2223
2250
  memoryAttributions: [],
2224
- usageGuidance: buildUsageGuidance('attend'),
2251
+ usageGuidance: buildUsageGuidance('attend', this.turnsWithoutWrite),
2225
2252
  facts: [],
2226
2253
  entitiesDetected: [],
2227
2254
  alreadyPresent: 0,
@@ -2313,7 +2340,7 @@ class AttendantInstance {
2313
2340
  complianceWarning,
2314
2341
  compliance,
2315
2342
  memoryAttributions,
2316
- usageGuidance: buildUsageGuidance('attend'),
2343
+ usageGuidance: buildUsageGuidance('attend', this.turnsWithoutWrite),
2317
2344
  };
2318
2345
  if (input.suppressEvents !== true) {
2319
2346
  (0, staffEventRegistry_1.getStaffEventEmitter)().emit({
@@ -2426,29 +2453,29 @@ class AttendantInstance {
2426
2453
  const entityResponse = await (0, router_1.route)('extraction', [
2427
2454
  {
2428
2455
  role: 'user',
2429
- content: `Extract explicitly named entities from the text.
2430
- An entity can be a person, organization, project, technology, or named concept.
2431
-
2432
- Return ONLY valid JSON as an array of objects in this exact shape:
2433
- [
2434
- {
2435
- "type": "project",
2436
- "name": "Project Atlas",
2437
- "id_guess": "project_atlas",
2438
- "confidence": 0.92,
2439
- "evidence": "Project Atlas",
2440
- "start": 123,
2441
- "end": 136
2442
- }
2443
- ]
2444
-
2445
- Rules:
2446
- - Only include entities explicitly named in the provided text.
2447
- - Do not infer or carry over entities not present in the text.
2448
- - If uncertain, omit.
2449
- - If none are present, return [].
2450
-
2451
- Text:
2456
+ content: `Extract explicitly named entities from the text.
2457
+ An entity can be a person, organization, project, technology, or named concept.
2458
+
2459
+ Return ONLY valid JSON as an array of objects in this exact shape:
2460
+ [
2461
+ {
2462
+ "type": "project",
2463
+ "name": "Project Atlas",
2464
+ "id_guess": "project_atlas",
2465
+ "confidence": 0.92,
2466
+ "evidence": "Project Atlas",
2467
+ "start": 123,
2468
+ "end": 136
2469
+ }
2470
+ ]
2471
+
2472
+ Rules:
2473
+ - Only include entities explicitly named in the provided text.
2474
+ - Do not infer or carry over entities not present in the text.
2475
+ - If uncertain, omit.
2476
+ - If none are present, return [].
2477
+
2478
+ Text:
2452
2479
  ${detectionWindow}`,
2453
2480
  },
2454
2481
  ], 512);
@@ -2782,21 +2809,21 @@ ${detectionWindow}`,
2782
2809
  const response = await (0, router_1.route)('classification', [
2783
2810
  {
2784
2811
  role: 'user',
2785
- content: `Decide whether this assistant should fetch persistent memory before replying.
2786
-
2787
- Latest user message:
2788
- ${input.latestMessage || '(none)'}
2789
-
2790
- Recent context excerpt:
2791
- ${contextWindow || '(empty)'}
2792
-
2793
- Return ONLY valid JSON with this exact shape:
2794
- {"needsMemory":true,"confidence":0.81,"reason":"short_reason"}
2795
-
2796
- Rules:
2797
- - needsMemory=true when the message involves project context, technical decisions, code state, prior work, open tasks, bugs, architecture, preferences, or anything session- or project-specific.
2798
- - needsMemory=true when in doubt — false positives are cheap, false negatives lose context.
2799
- - needsMemory=false ONLY for clear one-word acks, simple greetings, or purely generic factual questions with no project relevance.
2812
+ content: `Decide whether this assistant should fetch persistent memory before replying.
2813
+
2814
+ Latest user message:
2815
+ ${input.latestMessage || '(none)'}
2816
+
2817
+ Recent context excerpt:
2818
+ ${contextWindow || '(empty)'}
2819
+
2820
+ Return ONLY valid JSON with this exact shape:
2821
+ {"needsMemory":true,"confidence":0.81,"reason":"short_reason"}
2822
+
2823
+ Rules:
2824
+ - needsMemory=true when the message involves project context, technical decisions, code state, prior work, open tasks, bugs, architecture, preferences, or anything session- or project-specific.
2825
+ - needsMemory=true when in doubt — false positives are cheap, false negatives lose context.
2826
+ - needsMemory=false ONLY for clear one-word acks, simple greetings, or purely generic factual questions with no project relevance.
2800
2827
  - confidence is a float from 0 to 1.`,
2801
2828
  },
2802
2829
  ], 128);
@@ -3159,14 +3186,14 @@ Rules:
3159
3186
  const response = await (0, router_1.route)('task_inference', [
3160
3187
  {
3161
3188
  role: 'user',
3162
- content: `You are analyzing what an AI agent is currently working on.
3163
-
3164
- Agent ID: ${this.agentId}
3165
- Task description: ${context.task}
3166
- Recent messages:
3167
- ${context.recentMessages.map((m, i) => `${i + 1}. ${m}`).join('\n')}
3168
-
3169
- In one short sentence, describe the specific type of task this agent is currently performing.
3189
+ content: `You are analyzing what an AI agent is currently working on.
3190
+
3191
+ Agent ID: ${this.agentId}
3192
+ Task description: ${context.task}
3193
+ Recent messages:
3194
+ ${context.recentMessages.map((m, i) => `${i + 1}. ${m}`).join('\n')}
3195
+
3196
+ In one short sentence, describe the specific type of task this agent is currently performing.
3170
3197
  Be specific and concrete.`,
3171
3198
  },
3172
3199
  ], 256);
@@ -3199,15 +3226,15 @@ Be specific and concrete.`,
3199
3226
  const response = await (0, router_1.route)('relevance_filtering', [
3200
3227
  {
3201
3228
  role: 'user',
3202
- content: `You are deciding what knowledge an AI agent needs for its current task.
3203
-
3204
- Agent task: ${taskType}
3205
-
3206
- Available knowledge entries:
3207
- ${entryInputs.map((e, i) => `${i + 1}. [${e.key}] ${e.valueSummary} (confidence: ${e.confidence})`).join('\n')}
3208
-
3209
- Return only the numbers of entries that are directly relevant to the current task.
3210
- Format: comma-separated numbers only. Example: 1,3,5
3229
+ content: `You are deciding what knowledge an AI agent needs for its current task.
3230
+
3231
+ Agent task: ${taskType}
3232
+
3233
+ Available knowledge entries:
3234
+ ${entryInputs.map((e, i) => `${i + 1}. [${e.key}] ${e.valueSummary} (confidence: ${e.confidence})`).join('\n')}
3235
+
3236
+ Return only the numbers of entries that are directly relevant to the current task.
3237
+ Format: comma-separated numbers only. Example: 1,3,5
3211
3238
  If nothing is relevant, return: none`,
3212
3239
  },
3213
3240
  ], 128);
@@ -3272,6 +3299,7 @@ If nothing is relevant, return: none`,
3272
3299
  this.advisoryLearningProfile = null;
3273
3300
  this.sharedStateObservedAt = state.briefGeneratedAt;
3274
3301
  this.attendsWithoutPersist = state.compliance?.counters.attendsWithoutPersist ?? 0;
3302
+ this.turnsWithoutWrite = state.compliance?.counters.turnsWithoutWrite ?? 0;
3275
3303
  this.consecutivePreResponseWithoutPost = state.compliance?.counters.consecutivePreResponseWithoutPost ?? 0;
3276
3304
  this.consecutiveUnusedMemoryInjections = state.compliance?.counters.consecutiveUnusedMemoryInjections ?? 0;
3277
3305
  this.lastAttendPhase = state.compliance?.counters.lastAttendPhase ?? undefined;