@kindgi/agents 0.1.4-rc.5 → 0.1.5-rc.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.
Files changed (137) hide show
  1. package/README.md +1 -1
  2. package/dist/blocks.d.ts +19 -0
  3. package/dist/blocks.d.ts.map +1 -1
  4. package/dist/blocks.js +59 -1
  5. package/dist/blocks.js.map +1 -1
  6. package/dist/conversation-binding.d.ts +49 -3
  7. package/dist/conversation-binding.d.ts.map +1 -1
  8. package/dist/define.d.ts +10 -2
  9. package/dist/define.d.ts.map +1 -1
  10. package/dist/define.js +132 -7
  11. package/dist/define.js.map +1 -1
  12. package/dist/drafted-template.d.ts +34 -0
  13. package/dist/drafted-template.d.ts.map +1 -0
  14. package/dist/drafted-template.js +95 -0
  15. package/dist/drafted-template.js.map +1 -0
  16. package/dist/guardrails-gate.d.ts +28 -13
  17. package/dist/guardrails-gate.d.ts.map +1 -1
  18. package/dist/guardrails-gate.js +59 -21
  19. package/dist/guardrails-gate.js.map +1 -1
  20. package/dist/handlers/build-initial-messages.d.ts +8 -2
  21. package/dist/handlers/build-initial-messages.d.ts.map +1 -1
  22. package/dist/handlers/build-initial-messages.js +23 -21
  23. package/dist/handlers/build-initial-messages.js.map +1 -1
  24. package/dist/handlers/compose-result.d.ts.map +1 -1
  25. package/dist/handlers/compose-result.js +2 -0
  26. package/dist/handlers/compose-result.js.map +1 -1
  27. package/dist/handlers/context.d.ts +6 -1
  28. package/dist/handlers/context.d.ts.map +1 -1
  29. package/dist/handlers/dispatch-tools.d.ts.map +1 -1
  30. package/dist/handlers/dispatch-tools.js +17 -5
  31. package/dist/handlers/dispatch-tools.js.map +1 -1
  32. package/dist/handlers/errors.d.ts +14 -1
  33. package/dist/handlers/errors.d.ts.map +1 -1
  34. package/dist/handlers/errors.js.map +1 -1
  35. package/dist/handlers/evaluate-guardrails.d.ts +6 -1
  36. package/dist/handlers/evaluate-guardrails.d.ts.map +1 -1
  37. package/dist/handlers/evaluate-guardrails.js +46 -4
  38. package/dist/handlers/evaluate-guardrails.js.map +1 -1
  39. package/dist/handlers/history.d.ts +24 -0
  40. package/dist/handlers/history.d.ts.map +1 -0
  41. package/dist/handlers/history.js +52 -0
  42. package/dist/handlers/history.js.map +1 -0
  43. package/dist/handlers/persist-final-message.d.ts.map +1 -1
  44. package/dist/handlers/persist-final-message.js +2 -0
  45. package/dist/handlers/persist-final-message.js.map +1 -1
  46. package/dist/handlers/persist-user-message.d.ts.map +1 -1
  47. package/dist/handlers/persist-user-message.js +2 -0
  48. package/dist/handlers/persist-user-message.js.map +1 -1
  49. package/dist/handlers/public-types.d.ts +12 -2
  50. package/dist/handlers/public-types.d.ts.map +1 -1
  51. package/dist/handlers/rehydrate.d.ts.map +1 -1
  52. package/dist/handlers/rehydrate.js +7 -4
  53. package/dist/handlers/rehydrate.js.map +1 -1
  54. package/dist/handlers/remember-tool.d.ts +20 -0
  55. package/dist/handlers/remember-tool.d.ts.map +1 -0
  56. package/dist/handlers/remember-tool.js +149 -0
  57. package/dist/handlers/remember-tool.js.map +1 -0
  58. package/dist/handlers/replay.d.ts +55 -1
  59. package/dist/handlers/replay.d.ts.map +1 -1
  60. package/dist/handlers/replay.js +23 -6
  61. package/dist/handlers/replay.js.map +1 -1
  62. package/dist/handlers/resolve-blocks.d.ts.map +1 -1
  63. package/dist/handlers/resolve-blocks.js +19 -8
  64. package/dist/handlers/resolve-blocks.js.map +1 -1
  65. package/dist/handlers/result-shape.d.ts +3 -1
  66. package/dist/handlers/result-shape.d.ts.map +1 -1
  67. package/dist/handlers/result-shape.js.map +1 -1
  68. package/dist/handlers/run-retrievals.d.ts +2 -1
  69. package/dist/handlers/run-retrievals.d.ts.map +1 -1
  70. package/dist/handlers/run-retrievals.js +42 -16
  71. package/dist/handlers/run-retrievals.js.map +1 -1
  72. package/dist/handlers/turn-environment.d.ts.map +1 -1
  73. package/dist/handlers/turn-environment.js +2 -1
  74. package/dist/handlers/turn-environment.js.map +1 -1
  75. package/dist/handlers/turn-provenance.d.ts +19 -3
  76. package/dist/handlers/turn-provenance.d.ts.map +1 -1
  77. package/dist/handlers/turn-provenance.js +116 -2
  78. package/dist/handlers/turn-provenance.js.map +1 -1
  79. package/dist/index.d.ts +13 -8
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +5 -3
  82. package/dist/index.js.map +1 -1
  83. package/dist/invoke.d.ts +1 -1
  84. package/dist/invoke.d.ts.map +1 -1
  85. package/dist/invoke.js +2 -0
  86. package/dist/invoke.js.map +1 -1
  87. package/dist/remember.d.ts +49 -0
  88. package/dist/remember.d.ts.map +1 -0
  89. package/dist/remember.js +89 -0
  90. package/dist/remember.js.map +1 -0
  91. package/dist/retrieval.d.ts +111 -28
  92. package/dist/retrieval.d.ts.map +1 -1
  93. package/dist/retrieval.js +432 -104
  94. package/dist/retrieval.js.map +1 -1
  95. package/dist/schema.d.ts +17 -0
  96. package/dist/schema.d.ts.map +1 -1
  97. package/dist/schema.js +6 -0
  98. package/dist/schema.js.map +1 -1
  99. package/dist/streaming.d.ts +16 -1
  100. package/dist/streaming.d.ts.map +1 -1
  101. package/dist/streaming.js.map +1 -1
  102. package/dist/types.d.ts +131 -10
  103. package/dist/types.d.ts.map +1 -1
  104. package/migrations/0005_condemned_hellcat.sql +1 -0
  105. package/migrations/meta/0005_snapshot.json +333 -0
  106. package/migrations/meta/_journal.json +7 -0
  107. package/package.json +15 -15
  108. package/src/blocks.ts +75 -1
  109. package/src/conversation-binding.ts +53 -3
  110. package/src/define.ts +147 -10
  111. package/src/drafted-template.ts +118 -0
  112. package/src/guardrails-gate.ts +90 -26
  113. package/src/handlers/build-initial-messages.ts +29 -22
  114. package/src/handlers/compose-result.ts +2 -0
  115. package/src/handlers/context.ts +12 -1
  116. package/src/handlers/dispatch-tools.ts +19 -5
  117. package/src/handlers/errors.ts +16 -1
  118. package/src/handlers/evaluate-guardrails.ts +47 -4
  119. package/src/handlers/history.ts +57 -0
  120. package/src/handlers/persist-final-message.ts +2 -0
  121. package/src/handlers/persist-user-message.ts +2 -0
  122. package/src/handlers/public-types.ts +18 -2
  123. package/src/handlers/rehydrate.ts +12 -8
  124. package/src/handlers/remember-tool.ts +200 -0
  125. package/src/handlers/replay.ts +80 -8
  126. package/src/handlers/resolve-blocks.ts +21 -7
  127. package/src/handlers/result-shape.ts +8 -1
  128. package/src/handlers/run-retrievals.ts +52 -19
  129. package/src/handlers/turn-environment.ts +2 -1
  130. package/src/handlers/turn-provenance.ts +133 -2
  131. package/src/index.ts +33 -2
  132. package/src/invoke.ts +3 -0
  133. package/src/remember.ts +135 -0
  134. package/src/retrieval.ts +587 -125
  135. package/src/schema.ts +6 -0
  136. package/src/streaming.ts +17 -0
  137. package/src/types.ts +129 -10
@@ -0,0 +1,333 @@
1
+ {
2
+ "id": "e25b83b3-c87b-4e04-98fe-d8af651080e5",
3
+ "prevId": "cfefefb3-52e4-4b8b-b5ec-18d54c0263a0",
4
+ "version": "7",
5
+ "dialect": "postgresql",
6
+ "tables": {
7
+ "public.agent_conversations": {
8
+ "name": "agent_conversations",
9
+ "schema": "",
10
+ "columns": {
11
+ "id": {
12
+ "name": "id",
13
+ "type": "uuid",
14
+ "primaryKey": true,
15
+ "notNull": true,
16
+ "default": "gen_random_uuid()"
17
+ },
18
+ "tenant_id": {
19
+ "name": "tenant_id",
20
+ "type": "uuid",
21
+ "primaryKey": false,
22
+ "notNull": true
23
+ },
24
+ "agent_id": {
25
+ "name": "agent_id",
26
+ "type": "text",
27
+ "primaryKey": false,
28
+ "notNull": true
29
+ },
30
+ "agent_version": {
31
+ "name": "agent_version",
32
+ "type": "text",
33
+ "primaryKey": false,
34
+ "notNull": true
35
+ },
36
+ "title": {
37
+ "name": "title",
38
+ "type": "text",
39
+ "primaryKey": false,
40
+ "notNull": true
41
+ },
42
+ "participant_id": {
43
+ "name": "participant_id",
44
+ "type": "text",
45
+ "primaryKey": false,
46
+ "notNull": false
47
+ },
48
+ "project_id": {
49
+ "name": "project_id",
50
+ "type": "uuid",
51
+ "primaryKey": false,
52
+ "notNull": false
53
+ },
54
+ "scope": {
55
+ "name": "scope",
56
+ "type": "jsonb",
57
+ "primaryKey": false,
58
+ "notNull": true
59
+ },
60
+ "opened_at": {
61
+ "name": "opened_at",
62
+ "type": "timestamp with time zone",
63
+ "primaryKey": false,
64
+ "notNull": true,
65
+ "default": "now()"
66
+ },
67
+ "closed_at": {
68
+ "name": "closed_at",
69
+ "type": "timestamp with time zone",
70
+ "primaryKey": false,
71
+ "notNull": false
72
+ },
73
+ "unregistered_at": {
74
+ "name": "unregistered_at",
75
+ "type": "timestamp with time zone",
76
+ "primaryKey": false,
77
+ "notNull": false
78
+ },
79
+ "turn_count": {
80
+ "name": "turn_count",
81
+ "type": "integer",
82
+ "primaryKey": false,
83
+ "notNull": true,
84
+ "default": 0
85
+ },
86
+ "last_message_at": {
87
+ "name": "last_message_at",
88
+ "type": "timestamp with time zone",
89
+ "primaryKey": false,
90
+ "notNull": false
91
+ },
92
+ "metadata": {
93
+ "name": "metadata",
94
+ "type": "jsonb",
95
+ "primaryKey": false,
96
+ "notNull": false
97
+ }
98
+ },
99
+ "indexes": {
100
+ "agent_conversations_tenant_agent_idx": {
101
+ "name": "agent_conversations_tenant_agent_idx",
102
+ "columns": [
103
+ {
104
+ "expression": "tenant_id",
105
+ "isExpression": false,
106
+ "asc": true,
107
+ "nulls": "last"
108
+ },
109
+ {
110
+ "expression": "agent_id",
111
+ "isExpression": false,
112
+ "asc": true,
113
+ "nulls": "last"
114
+ }
115
+ ],
116
+ "isUnique": false,
117
+ "concurrently": false,
118
+ "method": "btree",
119
+ "with": {}
120
+ },
121
+ "agent_conversations_tenant_participant_idx": {
122
+ "name": "agent_conversations_tenant_participant_idx",
123
+ "columns": [
124
+ {
125
+ "expression": "tenant_id",
126
+ "isExpression": false,
127
+ "asc": true,
128
+ "nulls": "last"
129
+ },
130
+ {
131
+ "expression": "participant_id",
132
+ "isExpression": false,
133
+ "asc": true,
134
+ "nulls": "last"
135
+ }
136
+ ],
137
+ "isUnique": false,
138
+ "concurrently": false,
139
+ "method": "btree",
140
+ "with": {}
141
+ },
142
+ "agent_conversations_tenant_project_opened_idx": {
143
+ "name": "agent_conversations_tenant_project_opened_idx",
144
+ "columns": [
145
+ {
146
+ "expression": "tenant_id",
147
+ "isExpression": false,
148
+ "asc": true,
149
+ "nulls": "last"
150
+ },
151
+ {
152
+ "expression": "project_id",
153
+ "isExpression": false,
154
+ "asc": true,
155
+ "nulls": "last"
156
+ },
157
+ {
158
+ "expression": "opened_at",
159
+ "isExpression": false,
160
+ "asc": false,
161
+ "nulls": "last"
162
+ }
163
+ ],
164
+ "isUnique": false,
165
+ "where": "\"agent_conversations\".\"project_id\" IS NOT NULL",
166
+ "concurrently": false,
167
+ "method": "btree",
168
+ "with": {}
169
+ },
170
+ "agent_conversations_open_recent_idx": {
171
+ "name": "agent_conversations_open_recent_idx",
172
+ "columns": [
173
+ {
174
+ "expression": "tenant_id",
175
+ "isExpression": false,
176
+ "asc": true,
177
+ "nulls": "last"
178
+ },
179
+ {
180
+ "expression": "last_message_at",
181
+ "isExpression": false,
182
+ "asc": false,
183
+ "nulls": "last"
184
+ }
185
+ ],
186
+ "isUnique": false,
187
+ "where": "\"agent_conversations\".\"closed_at\" IS NULL",
188
+ "concurrently": false,
189
+ "method": "btree",
190
+ "with": {}
191
+ }
192
+ },
193
+ "foreignKeys": {},
194
+ "compositePrimaryKeys": {},
195
+ "uniqueConstraints": {},
196
+ "policies": {},
197
+ "checkConstraints": {},
198
+ "isRLSEnabled": false
199
+ },
200
+ "public.agent_run_snapshots": {
201
+ "name": "agent_run_snapshots",
202
+ "schema": "",
203
+ "columns": {
204
+ "id": {
205
+ "name": "id",
206
+ "type": "uuid",
207
+ "primaryKey": true,
208
+ "notNull": true
209
+ },
210
+ "tenant_id": {
211
+ "name": "tenant_id",
212
+ "type": "uuid",
213
+ "primaryKey": false,
214
+ "notNull": true
215
+ },
216
+ "project_id": {
217
+ "name": "project_id",
218
+ "type": "uuid",
219
+ "primaryKey": false,
220
+ "notNull": true
221
+ },
222
+ "agent_id": {
223
+ "name": "agent_id",
224
+ "type": "text",
225
+ "primaryKey": false,
226
+ "notNull": true
227
+ },
228
+ "agent_version": {
229
+ "name": "agent_version",
230
+ "type": "text",
231
+ "primaryKey": false,
232
+ "notNull": true
233
+ },
234
+ "conversation_id": {
235
+ "name": "conversation_id",
236
+ "type": "uuid",
237
+ "primaryKey": false,
238
+ "notNull": true
239
+ },
240
+ "user_message": {
241
+ "name": "user_message",
242
+ "type": "text",
243
+ "primaryKey": false,
244
+ "notNull": true
245
+ },
246
+ "parameters": {
247
+ "name": "parameters",
248
+ "type": "jsonb",
249
+ "primaryKey": false,
250
+ "notNull": false
251
+ },
252
+ "input": {
253
+ "name": "input",
254
+ "type": "jsonb",
255
+ "primaryKey": false,
256
+ "notNull": false
257
+ },
258
+ "participant_id": {
259
+ "name": "participant_id",
260
+ "type": "text",
261
+ "primaryKey": false,
262
+ "notNull": false
263
+ },
264
+ "dry_run": {
265
+ "name": "dry_run",
266
+ "type": "integer",
267
+ "primaryKey": false,
268
+ "notNull": true,
269
+ "default": 0
270
+ },
271
+ "principal": {
272
+ "name": "principal",
273
+ "type": "jsonb",
274
+ "primaryKey": false,
275
+ "notNull": false
276
+ },
277
+ "authz": {
278
+ "name": "authz",
279
+ "type": "jsonb",
280
+ "primaryKey": false,
281
+ "notNull": false
282
+ },
283
+ "replay": {
284
+ "name": "replay",
285
+ "type": "jsonb",
286
+ "primaryKey": false,
287
+ "notNull": false
288
+ },
289
+ "created_at": {
290
+ "name": "created_at",
291
+ "type": "timestamp with time zone",
292
+ "primaryKey": false,
293
+ "notNull": true,
294
+ "default": "now()"
295
+ }
296
+ },
297
+ "indexes": {
298
+ "agent_run_snapshots_tenant_idx": {
299
+ "name": "agent_run_snapshots_tenant_idx",
300
+ "columns": [
301
+ {
302
+ "expression": "tenant_id",
303
+ "isExpression": false,
304
+ "asc": true,
305
+ "nulls": "last"
306
+ }
307
+ ],
308
+ "isUnique": false,
309
+ "concurrently": false,
310
+ "method": "btree",
311
+ "with": {}
312
+ }
313
+ },
314
+ "foreignKeys": {},
315
+ "compositePrimaryKeys": {},
316
+ "uniqueConstraints": {},
317
+ "policies": {},
318
+ "checkConstraints": {},
319
+ "isRLSEnabled": false
320
+ }
321
+ },
322
+ "enums": {},
323
+ "schemas": {},
324
+ "sequences": {},
325
+ "roles": {},
326
+ "policies": {},
327
+ "views": {},
328
+ "_meta": {
329
+ "columns": {},
330
+ "schemas": {},
331
+ "tables": {}
332
+ }
333
+ }
@@ -36,6 +36,13 @@
36
36
  "when": 1791247643606,
37
37
  "tag": "0004_stormy_moondragon",
38
38
  "breakpoints": true
39
+ },
40
+ {
41
+ "idx": 5,
42
+ "version": "7",
43
+ "when": 1791379726212,
44
+ "tag": "0005_condemned_hellcat",
45
+ "breakpoints": true
39
46
  }
40
47
  ]
41
48
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kindgi/agents",
3
- "version": "0.1.4-rc.5",
3
+ "version": "0.1.5-rc.0",
4
4
  "description": "Agent primitive for Kindgi. Composes capabilities (model routing), tools (execution), memory (facts + retrieval), guardrails (declarative guardrails), and provenance into a single declarative unit. Agents are data-as-code — a defineAgent() call produces a durable definition that runs on the kernel, with first-class multi-turn conversations, streaming output, and a provenance record per turn.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -31,20 +31,20 @@
31
31
  "type-manifest.json"
32
32
  ],
33
33
  "dependencies": {
34
- "@kindgi/authz": "0.1.4-rc.5",
35
- "@kindgi/capabilities": "0.1.4-rc.5",
36
- "@kindgi/compliance": "0.1.4-rc.5",
37
- "@kindgi/embedding": "0.1.4-rc.5",
38
- "@kindgi/flow": "0.1.4-rc.5",
39
- "@kindgi/handler": "0.1.4-rc.5",
40
- "@kindgi/guardrails": "0.1.4-rc.5",
41
- "@kindgi/memory": "0.1.4-rc.5",
42
- "@kindgi/policy-contract": "0.1.4-rc.5",
43
- "@kindgi/provenance": "0.1.4-rc.5",
44
- "@kindgi/runtime": "0.1.4-rc.5",
45
- "@kindgi/schema": "0.1.4-rc.5",
46
- "@kindgi/tools": "0.1.4-rc.5",
47
- "@kindgi/types": "0.1.4-rc.5",
34
+ "@kindgi/authz": "0.1.5-rc.0",
35
+ "@kindgi/capabilities": "0.1.5-rc.0",
36
+ "@kindgi/compliance": "0.1.5-rc.0",
37
+ "@kindgi/embedding": "0.1.5-rc.0",
38
+ "@kindgi/flow": "0.1.5-rc.0",
39
+ "@kindgi/handler": "0.1.5-rc.0",
40
+ "@kindgi/guardrails": "0.1.5-rc.0",
41
+ "@kindgi/memory": "0.1.5-rc.0",
42
+ "@kindgi/policy-contract": "0.1.5-rc.0",
43
+ "@kindgi/provenance": "0.1.5-rc.0",
44
+ "@kindgi/runtime": "0.1.5-rc.0",
45
+ "@kindgi/schema": "0.1.5-rc.0",
46
+ "@kindgi/tools": "0.1.5-rc.0",
47
+ "@kindgi/types": "0.1.5-rc.0",
48
48
  "drizzle-orm": "^0.45.3",
49
49
  "liquidjs": "^10.29.0"
50
50
  },
package/src/blocks.ts CHANGED
@@ -180,7 +180,8 @@ function settingsIssues(content: unknown): BlockIssue[] {
180
180
  if (c.schema === null || typeof c.schema !== 'object' || Array.isArray(c.schema)) {
181
181
  return [{ path: '/content/schema', message: 'schema must be a JSON Schema object' }];
182
182
  }
183
- return settingsSchemaIssues(c.values, c.schema as Readonly<Record<string, unknown>>);
183
+ const schema = c.schema as Readonly<Record<string, unknown>>;
184
+ return [...tunableIssues(schema), ...settingsSchemaIssues(c.values, schema)];
184
185
  }
185
186
 
186
187
  function invalid(issues: readonly BlockIssue[]): Result<never, InvalidBlock> {
@@ -231,3 +232,76 @@ export interface BlockReader {
231
232
  readonly blockId: string;
232
233
  }): Promise<readonly string[]>;
233
234
  }
235
+
236
+ /** The schema marker that lets an improvement pass search a settings key's values. */
237
+ export const TUNABLE_MARKER = 'x-kindgi-tunable';
238
+
239
+ /** A settings key an improvement pass may search: a bounded number, or one of a list. */
240
+ export type TunableKey =
241
+ | {
242
+ readonly key: string;
243
+ readonly kind: 'number' | 'integer';
244
+ readonly minimum: number;
245
+ readonly maximum: number;
246
+ }
247
+ | { readonly key: string; readonly kind: 'enum'; readonly values: readonly unknown[] };
248
+
249
+ function schemaProperties(
250
+ schema: Readonly<Record<string, unknown>>,
251
+ ): readonly [string, Readonly<Record<string, unknown>>][] {
252
+ const props = schema.properties;
253
+ if (props === null || typeof props !== 'object' || Array.isArray(props)) return [];
254
+ return Object.entries(props).flatMap(([key, value]) =>
255
+ value !== null && typeof value === 'object' && !Array.isArray(value)
256
+ ? [[key, value as Readonly<Record<string, unknown>>] as const]
257
+ : [],
258
+ );
259
+ }
260
+
261
+ function tunableOf(key: string, prop: Readonly<Record<string, unknown>>): TunableKey | undefined {
262
+ if (Array.isArray(prop.enum) && prop.enum.length > 1) {
263
+ return { key, kind: 'enum', values: prop.enum };
264
+ }
265
+ const { type, minimum, maximum } = prop;
266
+ if (
267
+ (type === 'number' || type === 'integer') &&
268
+ typeof minimum === 'number' &&
269
+ typeof maximum === 'number' &&
270
+ minimum < maximum
271
+ ) {
272
+ return { key, kind: type, minimum, maximum };
273
+ }
274
+ return undefined;
275
+ }
276
+
277
+ /**
278
+ * The settings keys a schema marks tunable (`"x-kindgi-tunable": true` on
279
+ * a top-level property): what an improvement pass may search, within the
280
+ * bounds the schema gives. Nothing is tunable unless its author says so.
281
+ */
282
+ export function tunableKeys(schema: Readonly<Record<string, unknown>> | undefined): TunableKey[] {
283
+ if (schema === undefined) return [];
284
+ return schemaProperties(schema).flatMap(([key, prop]) => {
285
+ if (prop[TUNABLE_MARKER] !== true) return [];
286
+ const tunable = tunableOf(key, prop);
287
+ return tunable === undefined ? [] : [tunable];
288
+ });
289
+ }
290
+
291
+ /** A tunable mark on a key it can't apply to: a number without both bounds, say. */
292
+ function tunableIssues(schema: Readonly<Record<string, unknown>>): BlockIssue[] {
293
+ return schemaProperties(schema).flatMap(([key, prop]) => {
294
+ const mark = prop[TUNABLE_MARKER];
295
+ if (mark === undefined) return [];
296
+ const path = `/content/schema/properties/${key.replaceAll('~', '~0').replaceAll('/', '~1')}/${TUNABLE_MARKER}`;
297
+ if (mark !== true) return [{ path, message: `${TUNABLE_MARKER} must be true when given` }];
298
+ return tunableOf(key, prop) === undefined
299
+ ? [
300
+ {
301
+ path,
302
+ message: `"${key}" can be tunable only as a number or integer with a minimum below its maximum, or with an enum of two or more values`,
303
+ },
304
+ ]
305
+ : [];
306
+ });
307
+ }
@@ -2,7 +2,7 @@
2
2
  // Copyright (C) 2026 Kindgi Inc.
3
3
 
4
4
  import type { RunBinding } from '@kindgi/runtime';
5
- import type { ListScope, ProjectId, Result, Semver, TenantId } from '@kindgi/types';
5
+ import type { ListScope, ProjectId, Result, ScopeSegment, Semver, TenantId } from '@kindgi/types';
6
6
 
7
7
  import type { AgentError } from './errors.js';
8
8
  import type {
@@ -51,12 +51,31 @@ export interface AppendMessageInput {
51
51
  * assistant response for a turn omits this flag (counts as a turn).
52
52
  */
53
53
  readonly isIntermediate?: boolean;
54
+ /**
55
+ * What the conversation-recall index keeps with the message, from the
56
+ * turn (the conversation row has neither): the user the turn acts for,
57
+ * and the run's segment path. A user's message and an agent's final
58
+ * answer are indexed; tool messages and tool-call turns are not.
59
+ * Absent: indexed without them.
60
+ */
61
+ readonly recall?: {
62
+ readonly userId?: string;
63
+ readonly segments?: readonly ScopeSegment[];
64
+ };
54
65
  }
55
66
 
56
67
  export interface ReadMessagesInput {
57
68
  readonly tenantId: TenantId;
58
69
  readonly conversationId: ConversationId;
59
70
  readonly sinceSequence?: number;
71
+ /** Only messages before this sequence. */
72
+ readonly beforeSequence?: number;
73
+ /**
74
+ * Only the newest `last` messages (after the other bounds), oldest
75
+ * first: a turn's history window. A binding that ignores it returns
76
+ * them all, and the caller keeps the newest.
77
+ */
78
+ readonly last?: number;
60
79
  readonly limit?: number;
61
80
  }
62
81
 
@@ -67,7 +86,14 @@ export interface ReadMessagesInput {
67
86
  * opaque wire cursor.
68
87
  */
69
88
  export interface ConversationPageCursor {
70
- /** ISO 8601 `openedAt` timestamp of the last row from the previous page. */
89
+ /**
90
+ * `openedAt` of the last row from the previous page. As a binding's
91
+ * `ConversationPage.next` gives it, the stored value exactly (Postgres
92
+ * keeps microseconds: `opened_at::text`); a cursor from before carries
93
+ * the row's ISO 8601 `openedAt`. A binding compares it as given, never
94
+ * through a JS `Date`, which keeps milliseconds and would skip the rows
95
+ * opened earlier in the same millisecond.
96
+ */
71
97
  readonly openedAt: string;
72
98
  readonly id: ConversationId;
73
99
  }
@@ -102,6 +128,13 @@ export interface ListConversationsPageInput {
102
128
  export interface ConversationPage {
103
129
  readonly data: readonly Conversation[];
104
130
  readonly hasMore: boolean;
131
+ /**
132
+ * Where the next page starts: the last row's position as stored
133
+ * (`openedAt` to the microsecond, and `id`). Set when `hasMore`. Absent
134
+ * from a binding that doesn't give it: the caller then uses the last
135
+ * row's `openedAt` and `id`.
136
+ */
137
+ readonly next?: ConversationPageCursor;
105
138
  }
106
139
 
107
140
  /**
@@ -152,7 +185,24 @@ export interface ConversationBinding {
152
185
  runBinding?: RunBinding,
153
186
  ): Promise<Result<Conversation, AgentError>>;
154
187
 
155
- deleteConversation(tenantId: TenantId, id: ConversationId): Promise<Result<void, AgentError>>;
188
+ /**
189
+ * Unregister a conversation: a tombstone (`unregisteredAt`). From then
190
+ * on no read, list or recall returns it, and no message can be added;
191
+ * the retention sweep removes it after the tenant's grace. Optional: a
192
+ * binding without it can't unregister (the route answers 501).
193
+ */
194
+ unregisterConversation?(
195
+ tenantId: TenantId,
196
+ id: ConversationId,
197
+ ): Promise<Result<Conversation, AgentError>>;
198
+
199
+ /**
200
+ * @deprecated Removed with the conversation-recall index: deleting a
201
+ * row left its messages behind. Unregister instead
202
+ * (`unregisterConversation`). Optional only so a runtime built before
203
+ * it still compiles; the next release drops it.
204
+ */
205
+ deleteConversation?(tenantId: TenantId, id: ConversationId): Promise<Result<void, AgentError>>;
156
206
 
157
207
  appendMessage(input: AppendMessageInput): Promise<Result<ConversationMessage, AgentError>>;
158
208