@elevasis/sdk 1.40.0 → 1.41.0

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/dist/cli.cjs CHANGED
@@ -45834,7 +45834,7 @@ function wrapAction(commandName, fn) {
45834
45834
  // package.json
45835
45835
  var package_default = {
45836
45836
  name: "@elevasis/sdk",
45837
- version: "1.40.0",
45837
+ version: "1.41.0",
45838
45838
  description: "SDK for building Elevasis organization resources",
45839
45839
  type: "module",
45840
45840
  bin: {
package/dist/index.d.ts CHANGED
@@ -1062,6 +1062,20 @@ interface LLMGenerateResponse<T = unknown> {
1062
1062
  totalTokens: number;
1063
1063
  };
1064
1064
  cost?: number;
1065
+ /**
1066
+ * Why this call went out WITHOUT `strict`, on an adapter that tried to send it with one.
1067
+ *
1068
+ * Present only on a refusal, so absence means either "strict was in effect" or "this adapter
1069
+ * does not attempt strict at all" — the two are distinguished by which adapter answered, not by
1070
+ * this field. `toStrictSchema` already computes these reasons and, until now, nothing consumed
1071
+ * them at the call site: an unstrict call was indistinguishable from a strict one anywhere
1072
+ * outside a dev-only flow log. That invisibility is what let every tenant run unstrict against a
1073
+ * strict-capable API for as long as it took someone to recognise a pre-strict error signature.
1074
+ *
1075
+ * Internal-only, like `usage` — `UniversalLLMAdapter` lifts it onto the `ai_calls` row and
1076
+ * strips it before the response reaches callers.
1077
+ */
1078
+ strictRefusalReasons?: string[];
1065
1079
  }
1066
1080
  /**
1067
1081
  * LLM Adapter interface
@@ -1093,6 +1107,14 @@ interface LLMAdapter {
1093
1107
  * Memory types mirror action types for clarity and filtering
1094
1108
  */
1095
1109
  type MemoryEntryType = 'context' | 'input' | 'reasoning' | 'tool-result' | 'delegation-result' | 'error';
1110
+ /**
1111
+ * Who authored an entry's content.
1112
+ *
1113
+ * This is what lets the assembled prompt tell framework-authored text apart from text that
1114
+ * originated outside the trust boundary. `'framework'` content is ours; the other three are not
1115
+ * and are rendered inside the JSON data envelope (see `MemoryManager.toContextParts`).
1116
+ */
1117
+ type MemoryEntrySource = 'framework' | 'user' | 'tool' | 'model';
1096
1118
  /**
1097
1119
  * Memory entry - represents a single entry in agent memory
1098
1120
  * Stored in agent memory, translated by adapters to vendor-specific formats
@@ -1103,6 +1125,16 @@ interface MemoryEntry {
1103
1125
  timestamp: number;
1104
1126
  turnNumber: number | null;
1105
1127
  iterationNumber: number | null;
1128
+ /**
1129
+ * Provenance. **Optional on purpose** — `undefined` means unknown, which is what every
1130
+ * pre-existing snapshot and every not-yet-redeployed tenant bundle produces. Read sites MUST
1131
+ * test `== null`, never `=== undefined`: the `inTurnScope` predicate in `manager.ts` is the
1132
+ * cautionary precedent, where a `=== undefined` check silently dropped every `null`-stamped
1133
+ * entry. `isMemoryEntry` is deliberately NOT tightened to require this field; doing so would
1134
+ * make every stored snapshot fail validation, and `restoreSessionMemory` fails open by
1135
+ * starting the agent with empty memory rather than throwing.
1136
+ */
1137
+ source?: MemoryEntrySource;
1106
1138
  }
1107
1139
  /**
1108
1140
  * Agent memory - Self-orchestrated memory with session + working storage
@@ -1130,8 +1162,17 @@ interface MemoryStatus {
1130
1162
  sessionMemoryKeys: number;
1131
1163
  sessionMemoryLimit: number;
1132
1164
  currentKeys: string[];
1165
+ sessionMemoryTokens: number;
1166
+ sessionMemoryTokenLimit: number;
1167
+ /**
1168
+ * History tokens as a percentage of `historyBudget` — history ALONE, not history plus session
1169
+ * memory. It previously reported the combined total under this name, so session memory growth
1170
+ * read as history pressure and triggered history compaction that could not relieve it.
1171
+ */
1133
1172
  historyPercent: number;
1134
1173
  historyTokens: number;
1174
+ historyBudget: number;
1175
+ totalTokens: number;
1135
1176
  tokenBudget: number;
1136
1177
  }
1137
1178
  /**
@@ -1148,6 +1189,20 @@ interface MemoryConstraints {
1148
1189
  * Agent provides strings, framework handles wrapping and auto-compaction
1149
1190
  */
1150
1191
 
1192
+ /**
1193
+ * The framework's own framing message and the untrusted data envelope, as separate strings.
1194
+ *
1195
+ * They are separate because the model must be able to tell them apart, and so must the input
1196
+ * sanitizer: the framing is framework-authored and trusted, the envelope is not. Concatenating
1197
+ * them — which is what this replaced — made that distinction undecidable at the adapter and left
1198
+ * the framework's own section headers inside the region scanned for delimiter injection.
1199
+ */
1200
+ interface MemoryContextParts {
1201
+ /** Framework-authored. Memory status and a description of the envelope. Carries NO stored content. */
1202
+ framing: string;
1203
+ /** Every stored fragment, JSON-encoded and source-tagged. Untrusted. */
1204
+ dataEnvelope: string;
1205
+ }
1151
1206
  /**
1152
1207
  * Memory Manager - Agent memory orchestration
1153
1208
  * Provides ultra-simple API for agents (strings only)
@@ -1164,7 +1219,7 @@ declare class MemoryManager {
1164
1219
  * @param key - Session memory key
1165
1220
  * @param content - String content from agent
1166
1221
  */
1167
- set(key: string, content: string): void;
1222
+ set(key: string, content: string, source?: MemoryEntrySource): void;
1168
1223
  /**
1169
1224
  * Get session memory entry content
1170
1225
  * @param key - Session memory key
@@ -1193,6 +1248,15 @@ declare class MemoryManager {
1193
1248
  * Emergency fallback if agent exceeds limits
1194
1249
  */
1195
1250
  enforceHardLimits(): void;
1251
+ /**
1252
+ * Evict oldest session memory entries until the pool fits its token limit.
1253
+ *
1254
+ * Key count and token count are different constraints: 25 short keys are fine, 25 large ones
1255
+ * are not. Eviction is oldest-first by timestamp, matching the key-count path, and always
1256
+ * leaves at least one entry so a single oversized key degrades to "one key" rather than to
1257
+ * "memory silently emptied".
1258
+ */
1259
+ private enforceSessionMemoryTokenLimit;
1196
1260
  /**
1197
1261
  * Get history length (for logging and introspection)
1198
1262
  * @returns Number of entries in history
@@ -1216,14 +1280,29 @@ declare class MemoryManager {
1216
1280
  */
1217
1281
  getSnapshot(): AgentMemory | undefined;
1218
1282
  /**
1219
- * Build context string for LLM
1220
- * Serializes sessionmemory + history memory with clear sections
1221
- * Shows current iteration entries FIRST (reverse chronological) for LLM attention
1283
+ * Build the framework framing and the untrusted data envelope for an LLM call.
1284
+ *
1285
+ * These are two separate strings because they are two different trust levels, and they used to
1286
+ * be one. Concatenated, the framework's own `=== ... ===` section headers sat in the same string
1287
+ * as stored tool output and user text, so the input sanitizer matched its own scaffolding on
1288
+ * every call and nothing downstream could tell which half a match came from. Splitting them
1289
+ * makes that distinction structural: the framing is ours, the envelope is not.
1290
+ *
1291
+ * The envelope is JSON, which additionally neutralizes the anchored-delimiter attack class —
1292
+ * `JSON.stringify` escapes newlines, so a stored fragment cannot produce a line that starts
1293
+ * with `===` no matter what it contains.
1294
+ *
1295
+ * The current turn's own input is deliberately NOT in either string. It travels as its own
1296
+ * `role:'user'` message (see `buildAgentMessages`), which is the whole point: a model asked to
1297
+ * treat "everything in this block" as data was also being handed the live question inside that
1298
+ * block.
1299
+ *
1300
+ * Shows current iteration entries FIRST (reverse chronological) for LLM attention.
1301
+ *
1222
1302
  * @param currentIteration - Current iteration number (0 = pre-iteration)
1223
1303
  * @param currentTurn - Current turn number (optional, for session context filtering)
1224
- * @returns Formatted memory context for LLM prompt
1225
1304
  */
1226
- toContext(currentIteration: number, currentTurn?: number): string;
1305
+ toContextParts(currentIteration: number, currentTurn?: number): MemoryContextParts;
1227
1306
  }
1228
1307
 
1229
1308
  /**
@@ -1422,6 +1501,12 @@ interface IterationContext {
1422
1501
  modelConfig: ModelConfig;
1423
1502
  adapterFactory: LLMAdapterFactory;
1424
1503
  knowledgeMap?: KnowledgeMap;
1504
+ /**
1505
+ * The validated input for this execution, serialized. It travels here because the model gets
1506
+ * it as its own `role:'user'` message; nothing else in this context carried it, so the input
1507
+ * had to be read back out of memory history and shipped inside the memory block.
1508
+ */
1509
+ currentInput: string;
1425
1510
  }
1426
1511
 
1427
1512
  type Json = string | number | boolean | null | {
@@ -4168,6 +4253,7 @@ type Database = {
4168
4253
  deleted_at: string | null;
4169
4254
  ended_at: string | null;
4170
4255
  memory_snapshot: Json;
4256
+ memory_version: number;
4171
4257
  metadata: Json | null;
4172
4258
  organization_id: string;
4173
4259
  resource_id: string;
@@ -4184,6 +4270,7 @@ type Database = {
4184
4270
  deleted_at?: string | null;
4185
4271
  ended_at?: string | null;
4186
4272
  memory_snapshot: Json;
4273
+ memory_version?: number;
4187
4274
  metadata?: Json | null;
4188
4275
  organization_id: string;
4189
4276
  resource_id: string;
@@ -4200,6 +4287,7 @@ type Database = {
4200
4287
  deleted_at?: string | null;
4201
4288
  ended_at?: string | null;
4202
4289
  memory_snapshot?: Json;
4290
+ memory_version?: number;
4203
4291
  metadata?: Json | null;
4204
4292
  organization_id?: string;
4205
4293
  resource_id?: string;
@@ -4537,9 +4625,14 @@ type Database = {
4537
4625
  p_session_id: string;
4538
4626
  };
4539
4627
  Returns: {
4628
+ context_window_size: number;
4540
4629
  created_at: string;
4630
+ cumulative_input_tokens: number;
4631
+ cumulative_output_tokens: number;
4632
+ deleted_at: string;
4541
4633
  ended_at: string;
4542
4634
  memory_snapshot: Json;
4635
+ memory_version: number;
4543
4636
  metadata: Json;
4544
4637
  organization_id: string;
4545
4638
  resource_id: string;
@@ -4581,6 +4674,18 @@ type Database = {
4581
4674
  };
4582
4675
  Returns: boolean;
4583
4676
  };
4677
+ increment_session_tokens: {
4678
+ Args: {
4679
+ p_input_tokens: number;
4680
+ p_output_tokens: number;
4681
+ p_session_id: string;
4682
+ };
4683
+ Returns: {
4684
+ context_window_size: number;
4685
+ cumulative_input_tokens: number;
4686
+ cumulative_output_tokens: number;
4687
+ }[];
4688
+ };
4584
4689
  is_org_member: {
4585
4690
  Args: {
4586
4691
  org_id: string;
@@ -11072,6 +11177,25 @@ declare class MetricsCollector {
11072
11177
  buildExecutionMetrics(metricsConfig?: ResourceMetricsConfig): ExecutionMetricsSummary;
11073
11178
  }
11074
11179
 
11180
+ /**
11181
+ * Which `role:'user'` message slot a sanitizer warning came from, derived from the request's
11182
+ * message array (no new plumbing from callers):
11183
+ * - `'memory-context'` — the framework's framing message, identified by the `=== MEMORY STATUS ===`
11184
+ * banner. Framework-authored and trusted; it is no longer scanned at all, so this source should
11185
+ * not appear for agent calls. Retained because stale tenant bundles still emit the old combined
11186
+ * block, and their rows must stay readable.
11187
+ * - `'data-envelope'` — the JSON envelope carrying every stored fragment. Untrusted, and the slot
11188
+ * where a match is genuine signal.
11189
+ * - `'input'` — the turn's own input, on its own message. Also untrusted.
11190
+ * - `'history'` — replayed prior turns. Untrusted, but already screened at their own front door,
11191
+ * so warnings here are recorded and never block (see `screenInput`).
11192
+ */
11193
+ type InputWarningSource = 'memory-context' | 'data-envelope' | 'history' | 'input';
11194
+ /** Per-source breakdown of sanitizer warnings, so a memory-context echo is distinguishable from a genuine hit. */
11195
+ interface SourcedInputWarnings {
11196
+ source: InputWarningSource;
11197
+ warnings: string[];
11198
+ }
11075
11199
  interface BaseAICall {
11076
11200
  callSequence: number;
11077
11201
  callType: 'agent-reasoning' | 'agent-completion' | 'workflow-step' | 'tool' | 'other';
@@ -11081,6 +11205,62 @@ interface BaseAICall {
11081
11205
  costUsd: number;
11082
11206
  latencyMs: number;
11083
11207
  context?: AICallContext;
11208
+ /**
11209
+ * Distinct prompt-injection pattern types detected in the request's user-role messages.
11210
+ * Present only when the input sanitizer matched something. Non-blocking matches ride along on
11211
+ * the successful call's row; a blocked call records a row of its own (see `inputBlocked`).
11212
+ *
11213
+ * Flat union across all `role:'user'` messages — unchanged shape, kept for existing readers.
11214
+ * See `inputWarningsBySource` for the per-slot breakdown.
11215
+ */
11216
+ inputWarnings?: string[];
11217
+ /**
11218
+ * Additive breakdown of `inputWarnings` by message slot (see `InputWarningSource`). Present only
11219
+ * when at least one source produced a warning. Existing readers that only look at the flat
11220
+ * `inputWarnings` array are unaffected.
11221
+ */
11222
+ inputWarningsBySource?: SourcedInputWarnings[];
11223
+ /**
11224
+ * True when the sanitizer blocked the request and no provider call was made.
11225
+ * Such a row carries zero tokens, zero cost, and zero latency — it exists so a hard,
11226
+ * user-visible failure is observable at all. Before this, a blocked call produced no row.
11227
+ */
11228
+ inputBlocked?: boolean;
11229
+ /**
11230
+ * The validator's message when the provider responded but its output failed `responseSchema`
11231
+ * validation (e.g. `missing required field 'nextActions'`). Present only on such a row.
11232
+ *
11233
+ * This is NOT the blocked-input case: the provider DID respond and tokens WERE spent, so the row
11234
+ * carries real `inputTokens`, `outputTokens`, cost and latency. It is a paid call that produced
11235
+ * nothing usable, and before this it produced no row at all.
11236
+ *
11237
+ * One row per failed attempt — the adapter retries a validation failure up to `LLM_MAX_ATTEMPTS`,
11238
+ * so a turn that exhausts its retries records three. Reading `inputTokens` across these rows is
11239
+ * what measures malformed-output rate against context size rather than inferring it.
11240
+ *
11241
+ * Existing readers that only look at the fields above are unaffected.
11242
+ */
11243
+ outputValidationError?: string;
11244
+ /**
11245
+ * The raw model output that failed validation, JSON-stringified and truncated to a bounded
11246
+ * length. Truncation is visible in the value itself (a trailing `…[truncated: N chars total]`),
11247
+ * never silent. Present only alongside `outputValidationError`.
11248
+ */
11249
+ unvalidatedOutput?: string;
11250
+ /**
11251
+ * Why this call went out without `strict` structured output, on an adapter that tried to send it
11252
+ * with one. Present ONLY on a refusal — its absence on a row from a strict-capable adapter means
11253
+ * strict was in effect.
11254
+ *
11255
+ * This is the row that answers "is this agent's output actually being enforced?" without reading
11256
+ * source. Counting rows that carry it, per resource, is the refusal count: a redeployed agent on
11257
+ * a current bundle should read zero. It exists because the previous answer was a dev-only flow
11258
+ * log, which meant production had no answer — and every sync-managed tenant ran unstrict against
11259
+ * a strict-capable API until a 14-turn session surfaced a pre-strict error signature.
11260
+ *
11261
+ * Existing readers that only look at the fields above are unaffected.
11262
+ */
11263
+ strictRefusalReasons?: string[];
11084
11264
  }
11085
11265
  type AICallContext = AgentReasoningContext | AgentCompletionContext | WorkflowStepContext | ToolCallContext | OtherCallContext;
11086
11266
  interface AgentReasoningContext {
@@ -11126,6 +11306,18 @@ interface LLMUsageData {
11126
11306
  latencyMs: number;
11127
11307
  /** Actual cost from provider in USD (when available, e.g., OpenRouter) */
11128
11308
  cost?: number;
11309
+ /** Distinct prompt-injection pattern types detected in the request's user-role messages */
11310
+ inputWarnings?: string[];
11311
+ /** Additive per-source breakdown of `inputWarnings` — see `SourcedInputWarnings` */
11312
+ inputWarningsBySource?: SourcedInputWarnings[];
11313
+ /** True when the sanitizer blocked the request and no provider call was made */
11314
+ inputBlocked?: boolean;
11315
+ /** Validator message when the provider responded but the output failed `responseSchema` validation */
11316
+ outputValidationError?: string;
11317
+ /** Raw model output that failed validation — JSON-stringified, truncated, truncation marked inline */
11318
+ unvalidatedOutput?: string;
11319
+ /** Why the call went out unstrict, when a strict-capable adapter refused the schema */
11320
+ strictRefusalReasons?: string[];
11129
11321
  }
11130
11322
  interface AIUsageSummary {
11131
11323
  model: LLMModel;
@@ -932,6 +932,20 @@ interface LLMGenerateResponse<T = unknown> {
932
932
  totalTokens: number;
933
933
  };
934
934
  cost?: number;
935
+ /**
936
+ * Why this call went out WITHOUT `strict`, on an adapter that tried to send it with one.
937
+ *
938
+ * Present only on a refusal, so absence means either "strict was in effect" or "this adapter
939
+ * does not attempt strict at all" — the two are distinguished by which adapter answered, not by
940
+ * this field. `toStrictSchema` already computes these reasons and, until now, nothing consumed
941
+ * them at the call site: an unstrict call was indistinguishable from a strict one anywhere
942
+ * outside a dev-only flow log. That invisibility is what let every tenant run unstrict against a
943
+ * strict-capable API for as long as it took someone to recognise a pre-strict error signature.
944
+ *
945
+ * Internal-only, like `usage` — `UniversalLLMAdapter` lifts it onto the `ai_calls` row and
946
+ * strips it before the response reaches callers.
947
+ */
948
+ strictRefusalReasons?: string[];
935
949
  }
936
950
  /**
937
951
  * LLM Adapter interface
@@ -963,6 +977,14 @@ interface LLMAdapter {
963
977
  * Memory types mirror action types for clarity and filtering
964
978
  */
965
979
  type MemoryEntryType = 'context' | 'input' | 'reasoning' | 'tool-result' | 'delegation-result' | 'error';
980
+ /**
981
+ * Who authored an entry's content.
982
+ *
983
+ * This is what lets the assembled prompt tell framework-authored text apart from text that
984
+ * originated outside the trust boundary. `'framework'` content is ours; the other three are not
985
+ * and are rendered inside the JSON data envelope (see `MemoryManager.toContextParts`).
986
+ */
987
+ type MemoryEntrySource = 'framework' | 'user' | 'tool' | 'model';
966
988
  /**
967
989
  * Memory entry - represents a single entry in agent memory
968
990
  * Stored in agent memory, translated by adapters to vendor-specific formats
@@ -973,6 +995,16 @@ interface MemoryEntry {
973
995
  timestamp: number;
974
996
  turnNumber: number | null;
975
997
  iterationNumber: number | null;
998
+ /**
999
+ * Provenance. **Optional on purpose** — `undefined` means unknown, which is what every
1000
+ * pre-existing snapshot and every not-yet-redeployed tenant bundle produces. Read sites MUST
1001
+ * test `== null`, never `=== undefined`: the `inTurnScope` predicate in `manager.ts` is the
1002
+ * cautionary precedent, where a `=== undefined` check silently dropped every `null`-stamped
1003
+ * entry. `isMemoryEntry` is deliberately NOT tightened to require this field; doing so would
1004
+ * make every stored snapshot fail validation, and `restoreSessionMemory` fails open by
1005
+ * starting the agent with empty memory rather than throwing.
1006
+ */
1007
+ source?: MemoryEntrySource;
976
1008
  }
977
1009
  /**
978
1010
  * Agent memory - Self-orchestrated memory with session + working storage
@@ -1000,8 +1032,17 @@ interface MemoryStatus {
1000
1032
  sessionMemoryKeys: number;
1001
1033
  sessionMemoryLimit: number;
1002
1034
  currentKeys: string[];
1035
+ sessionMemoryTokens: number;
1036
+ sessionMemoryTokenLimit: number;
1037
+ /**
1038
+ * History tokens as a percentage of `historyBudget` — history ALONE, not history plus session
1039
+ * memory. It previously reported the combined total under this name, so session memory growth
1040
+ * read as history pressure and triggered history compaction that could not relieve it.
1041
+ */
1003
1042
  historyPercent: number;
1004
1043
  historyTokens: number;
1044
+ historyBudget: number;
1045
+ totalTokens: number;
1005
1046
  tokenBudget: number;
1006
1047
  }
1007
1048
  /**
@@ -1018,6 +1059,20 @@ interface MemoryConstraints {
1018
1059
  * Agent provides strings, framework handles wrapping and auto-compaction
1019
1060
  */
1020
1061
 
1062
+ /**
1063
+ * The framework's own framing message and the untrusted data envelope, as separate strings.
1064
+ *
1065
+ * They are separate because the model must be able to tell them apart, and so must the input
1066
+ * sanitizer: the framing is framework-authored and trusted, the envelope is not. Concatenating
1067
+ * them — which is what this replaced — made that distinction undecidable at the adapter and left
1068
+ * the framework's own section headers inside the region scanned for delimiter injection.
1069
+ */
1070
+ interface MemoryContextParts {
1071
+ /** Framework-authored. Memory status and a description of the envelope. Carries NO stored content. */
1072
+ framing: string;
1073
+ /** Every stored fragment, JSON-encoded and source-tagged. Untrusted. */
1074
+ dataEnvelope: string;
1075
+ }
1021
1076
  /**
1022
1077
  * Memory Manager - Agent memory orchestration
1023
1078
  * Provides ultra-simple API for agents (strings only)
@@ -1034,7 +1089,7 @@ declare class MemoryManager {
1034
1089
  * @param key - Session memory key
1035
1090
  * @param content - String content from agent
1036
1091
  */
1037
- set(key: string, content: string): void;
1092
+ set(key: string, content: string, source?: MemoryEntrySource): void;
1038
1093
  /**
1039
1094
  * Get session memory entry content
1040
1095
  * @param key - Session memory key
@@ -1063,6 +1118,15 @@ declare class MemoryManager {
1063
1118
  * Emergency fallback if agent exceeds limits
1064
1119
  */
1065
1120
  enforceHardLimits(): void;
1121
+ /**
1122
+ * Evict oldest session memory entries until the pool fits its token limit.
1123
+ *
1124
+ * Key count and token count are different constraints: 25 short keys are fine, 25 large ones
1125
+ * are not. Eviction is oldest-first by timestamp, matching the key-count path, and always
1126
+ * leaves at least one entry so a single oversized key degrades to "one key" rather than to
1127
+ * "memory silently emptied".
1128
+ */
1129
+ private enforceSessionMemoryTokenLimit;
1066
1130
  /**
1067
1131
  * Get history length (for logging and introspection)
1068
1132
  * @returns Number of entries in history
@@ -1086,14 +1150,29 @@ declare class MemoryManager {
1086
1150
  */
1087
1151
  getSnapshot(): AgentMemory | undefined;
1088
1152
  /**
1089
- * Build context string for LLM
1090
- * Serializes sessionmemory + history memory with clear sections
1091
- * Shows current iteration entries FIRST (reverse chronological) for LLM attention
1153
+ * Build the framework framing and the untrusted data envelope for an LLM call.
1154
+ *
1155
+ * These are two separate strings because they are two different trust levels, and they used to
1156
+ * be one. Concatenated, the framework's own `=== ... ===` section headers sat in the same string
1157
+ * as stored tool output and user text, so the input sanitizer matched its own scaffolding on
1158
+ * every call and nothing downstream could tell which half a match came from. Splitting them
1159
+ * makes that distinction structural: the framing is ours, the envelope is not.
1160
+ *
1161
+ * The envelope is JSON, which additionally neutralizes the anchored-delimiter attack class —
1162
+ * `JSON.stringify` escapes newlines, so a stored fragment cannot produce a line that starts
1163
+ * with `===` no matter what it contains.
1164
+ *
1165
+ * The current turn's own input is deliberately NOT in either string. It travels as its own
1166
+ * `role:'user'` message (see `buildAgentMessages`), which is the whole point: a model asked to
1167
+ * treat "everything in this block" as data was also being handed the live question inside that
1168
+ * block.
1169
+ *
1170
+ * Shows current iteration entries FIRST (reverse chronological) for LLM attention.
1171
+ *
1092
1172
  * @param currentIteration - Current iteration number (0 = pre-iteration)
1093
1173
  * @param currentTurn - Current turn number (optional, for session context filtering)
1094
- * @returns Formatted memory context for LLM prompt
1095
1174
  */
1096
- toContext(currentIteration: number, currentTurn?: number): string;
1175
+ toContextParts(currentIteration: number, currentTurn?: number): MemoryContextParts;
1097
1176
  }
1098
1177
 
1099
1178
  /**
@@ -1292,6 +1371,12 @@ interface IterationContext {
1292
1371
  modelConfig: ModelConfig;
1293
1372
  adapterFactory: LLMAdapterFactory;
1294
1373
  knowledgeMap?: KnowledgeMap;
1374
+ /**
1375
+ * The validated input for this execution, serialized. It travels here because the model gets
1376
+ * it as its own `role:'user'` message; nothing else in this context carried it, so the input
1377
+ * had to be read back out of memory history and shipped inside the memory block.
1378
+ */
1379
+ currentInput: string;
1295
1380
  }
1296
1381
 
1297
1382
  declare const OntologyScopeSchema: z.ZodDefault<z.ZodObject<{
@@ -2593,6 +2678,25 @@ declare class MetricsCollector {
2593
2678
  buildExecutionMetrics(metricsConfig?: ResourceMetricsConfig): ExecutionMetricsSummary;
2594
2679
  }
2595
2680
 
2681
+ /**
2682
+ * Which `role:'user'` message slot a sanitizer warning came from, derived from the request's
2683
+ * message array (no new plumbing from callers):
2684
+ * - `'memory-context'` — the framework's framing message, identified by the `=== MEMORY STATUS ===`
2685
+ * banner. Framework-authored and trusted; it is no longer scanned at all, so this source should
2686
+ * not appear for agent calls. Retained because stale tenant bundles still emit the old combined
2687
+ * block, and their rows must stay readable.
2688
+ * - `'data-envelope'` — the JSON envelope carrying every stored fragment. Untrusted, and the slot
2689
+ * where a match is genuine signal.
2690
+ * - `'input'` — the turn's own input, on its own message. Also untrusted.
2691
+ * - `'history'` — replayed prior turns. Untrusted, but already screened at their own front door,
2692
+ * so warnings here are recorded and never block (see `screenInput`).
2693
+ */
2694
+ type InputWarningSource = 'memory-context' | 'data-envelope' | 'history' | 'input';
2695
+ /** Per-source breakdown of sanitizer warnings, so a memory-context echo is distinguishable from a genuine hit. */
2696
+ interface SourcedInputWarnings {
2697
+ source: InputWarningSource;
2698
+ warnings: string[];
2699
+ }
2596
2700
  interface BaseAICall {
2597
2701
  callSequence: number;
2598
2702
  callType: 'agent-reasoning' | 'agent-completion' | 'workflow-step' | 'tool' | 'other';
@@ -2602,6 +2706,62 @@ interface BaseAICall {
2602
2706
  costUsd: number;
2603
2707
  latencyMs: number;
2604
2708
  context?: AICallContext;
2709
+ /**
2710
+ * Distinct prompt-injection pattern types detected in the request's user-role messages.
2711
+ * Present only when the input sanitizer matched something. Non-blocking matches ride along on
2712
+ * the successful call's row; a blocked call records a row of its own (see `inputBlocked`).
2713
+ *
2714
+ * Flat union across all `role:'user'` messages — unchanged shape, kept for existing readers.
2715
+ * See `inputWarningsBySource` for the per-slot breakdown.
2716
+ */
2717
+ inputWarnings?: string[];
2718
+ /**
2719
+ * Additive breakdown of `inputWarnings` by message slot (see `InputWarningSource`). Present only
2720
+ * when at least one source produced a warning. Existing readers that only look at the flat
2721
+ * `inputWarnings` array are unaffected.
2722
+ */
2723
+ inputWarningsBySource?: SourcedInputWarnings[];
2724
+ /**
2725
+ * True when the sanitizer blocked the request and no provider call was made.
2726
+ * Such a row carries zero tokens, zero cost, and zero latency — it exists so a hard,
2727
+ * user-visible failure is observable at all. Before this, a blocked call produced no row.
2728
+ */
2729
+ inputBlocked?: boolean;
2730
+ /**
2731
+ * The validator's message when the provider responded but its output failed `responseSchema`
2732
+ * validation (e.g. `missing required field 'nextActions'`). Present only on such a row.
2733
+ *
2734
+ * This is NOT the blocked-input case: the provider DID respond and tokens WERE spent, so the row
2735
+ * carries real `inputTokens`, `outputTokens`, cost and latency. It is a paid call that produced
2736
+ * nothing usable, and before this it produced no row at all.
2737
+ *
2738
+ * One row per failed attempt — the adapter retries a validation failure up to `LLM_MAX_ATTEMPTS`,
2739
+ * so a turn that exhausts its retries records three. Reading `inputTokens` across these rows is
2740
+ * what measures malformed-output rate against context size rather than inferring it.
2741
+ *
2742
+ * Existing readers that only look at the fields above are unaffected.
2743
+ */
2744
+ outputValidationError?: string;
2745
+ /**
2746
+ * The raw model output that failed validation, JSON-stringified and truncated to a bounded
2747
+ * length. Truncation is visible in the value itself (a trailing `…[truncated: N chars total]`),
2748
+ * never silent. Present only alongside `outputValidationError`.
2749
+ */
2750
+ unvalidatedOutput?: string;
2751
+ /**
2752
+ * Why this call went out without `strict` structured output, on an adapter that tried to send it
2753
+ * with one. Present ONLY on a refusal — its absence on a row from a strict-capable adapter means
2754
+ * strict was in effect.
2755
+ *
2756
+ * This is the row that answers "is this agent's output actually being enforced?" without reading
2757
+ * source. Counting rows that carry it, per resource, is the refusal count: a redeployed agent on
2758
+ * a current bundle should read zero. It exists because the previous answer was a dev-only flow
2759
+ * log, which meant production had no answer — and every sync-managed tenant ran unstrict against
2760
+ * a strict-capable API until a 14-turn session surfaced a pre-strict error signature.
2761
+ *
2762
+ * Existing readers that only look at the fields above are unaffected.
2763
+ */
2764
+ strictRefusalReasons?: string[];
2605
2765
  }
2606
2766
  type AICallContext = AgentReasoningContext | AgentCompletionContext | WorkflowStepContext | ToolCallContext | OtherCallContext;
2607
2767
  interface AgentReasoningContext {
@@ -2647,6 +2807,18 @@ interface LLMUsageData {
2647
2807
  latencyMs: number;
2648
2808
  /** Actual cost from provider in USD (when available, e.g., OpenRouter) */
2649
2809
  cost?: number;
2810
+ /** Distinct prompt-injection pattern types detected in the request's user-role messages */
2811
+ inputWarnings?: string[];
2812
+ /** Additive per-source breakdown of `inputWarnings` — see `SourcedInputWarnings` */
2813
+ inputWarningsBySource?: SourcedInputWarnings[];
2814
+ /** True when the sanitizer blocked the request and no provider call was made */
2815
+ inputBlocked?: boolean;
2816
+ /** Validator message when the provider responded but the output failed `responseSchema` validation */
2817
+ outputValidationError?: string;
2818
+ /** Raw model output that failed validation — JSON-stringified, truncated, truncation marked inline */
2819
+ unvalidatedOutput?: string;
2820
+ /** Why the call went out unstrict, when a strict-capable adapter refused the schema */
2821
+ strictRefusalReasons?: string[];
2650
2822
  }
2651
2823
  interface AIUsageSummary {
2652
2824
  model: LLMModel;