@kindgi/agents 0.0.0-bootstrap.0 → 0.1.1

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 (240) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +131 -2
  3. package/dist/agent-turn-flow.d.ts +57 -0
  4. package/dist/agent-turn-flow.d.ts.map +1 -0
  5. package/dist/agent-turn-flow.js +172 -0
  6. package/dist/agent-turn-flow.js.map +1 -0
  7. package/dist/conversation-binding.d.ts +111 -0
  8. package/dist/conversation-binding.d.ts.map +1 -0
  9. package/dist/conversation-binding.js +4 -0
  10. package/dist/conversation-binding.js.map +1 -0
  11. package/dist/define.d.ts +180 -0
  12. package/dist/define.d.ts.map +1 -0
  13. package/dist/define.js +361 -0
  14. package/dist/define.js.map +1 -0
  15. package/dist/errors.d.ts +62 -0
  16. package/dist/errors.d.ts.map +1 -0
  17. package/dist/errors.js +4 -0
  18. package/dist/errors.js.map +1 -0
  19. package/dist/guardrails-gate.d.ts +169 -0
  20. package/dist/guardrails-gate.d.ts.map +1 -0
  21. package/dist/guardrails-gate.js +202 -0
  22. package/dist/guardrails-gate.js.map +1 -0
  23. package/dist/handlers/budget-check.d.ts +22 -0
  24. package/dist/handlers/budget-check.d.ts.map +1 -0
  25. package/dist/handlers/budget-check.js +109 -0
  26. package/dist/handlers/budget-check.js.map +1 -0
  27. package/dist/handlers/build-initial-messages.d.ts +17 -0
  28. package/dist/handlers/build-initial-messages.d.ts.map +1 -0
  29. package/dist/handlers/build-initial-messages.js +86 -0
  30. package/dist/handlers/build-initial-messages.js.map +1 -0
  31. package/dist/handlers/compose-result.d.ts +10 -0
  32. package/dist/handlers/compose-result.d.ts.map +1 -0
  33. package/dist/handlers/compose-result.js +79 -0
  34. package/dist/handlers/compose-result.js.map +1 -0
  35. package/dist/handlers/constants.d.ts +8 -0
  36. package/dist/handlers/constants.d.ts.map +1 -0
  37. package/dist/handlers/constants.js +10 -0
  38. package/dist/handlers/constants.js.map +1 -0
  39. package/dist/handlers/context.d.ts +210 -0
  40. package/dist/handlers/context.d.ts.map +1 -0
  41. package/dist/handlers/context.js +4 -0
  42. package/dist/handlers/context.js.map +1 -0
  43. package/dist/handlers/dispatch-tools.d.ts +15 -0
  44. package/dist/handlers/dispatch-tools.d.ts.map +1 -0
  45. package/dist/handlers/dispatch-tools.js +511 -0
  46. package/dist/handlers/dispatch-tools.js.map +1 -0
  47. package/dist/handlers/errors.d.ts +133 -0
  48. package/dist/handlers/errors.d.ts.map +1 -0
  49. package/dist/handlers/errors.js +134 -0
  50. package/dist/handlers/errors.js.map +1 -0
  51. package/dist/handlers/evaluate-guardrails.d.ts +14 -0
  52. package/dist/handlers/evaluate-guardrails.d.ts.map +1 -0
  53. package/dist/handlers/evaluate-guardrails.js +128 -0
  54. package/dist/handlers/evaluate-guardrails.js.map +1 -0
  55. package/dist/handlers/final-iteration.d.ts +7 -0
  56. package/dist/handlers/final-iteration.d.ts.map +1 -0
  57. package/dist/handlers/final-iteration.js +26 -0
  58. package/dist/handlers/final-iteration.js.map +1 -0
  59. package/dist/handlers/index.d.ts +5 -0
  60. package/dist/handlers/index.d.ts.map +1 -0
  61. package/dist/handlers/index.js +41 -0
  62. package/dist/handlers/index.js.map +1 -0
  63. package/dist/handlers/model-call.d.ts +13 -0
  64. package/dist/handlers/model-call.d.ts.map +1 -0
  65. package/dist/handlers/model-call.js +136 -0
  66. package/dist/handlers/model-call.js.map +1 -0
  67. package/dist/handlers/persist-final-message.d.ts +14 -0
  68. package/dist/handlers/persist-final-message.d.ts.map +1 -0
  69. package/dist/handlers/persist-final-message.js +54 -0
  70. package/dist/handlers/persist-final-message.js.map +1 -0
  71. package/dist/handlers/persist-provenance.d.ts +12 -0
  72. package/dist/handlers/persist-provenance.d.ts.map +1 -0
  73. package/dist/handlers/persist-provenance.js +34 -0
  74. package/dist/handlers/persist-provenance.js.map +1 -0
  75. package/dist/handlers/persist-user-message.d.ts +15 -0
  76. package/dist/handlers/persist-user-message.d.ts.map +1 -0
  77. package/dist/handlers/persist-user-message.js +59 -0
  78. package/dist/handlers/persist-user-message.js.map +1 -0
  79. package/dist/handlers/public-types.d.ts +201 -0
  80. package/dist/handlers/public-types.d.ts.map +1 -0
  81. package/dist/handlers/public-types.js +4 -0
  82. package/dist/handlers/public-types.js.map +1 -0
  83. package/dist/handlers/rehydrate.d.ts +8 -0
  84. package/dist/handlers/rehydrate.d.ts.map +1 -0
  85. package/dist/handlers/rehydrate.js +94 -0
  86. package/dist/handlers/rehydrate.js.map +1 -0
  87. package/dist/handlers/render-prompt.d.ts +12 -0
  88. package/dist/handlers/render-prompt.d.ts.map +1 -0
  89. package/dist/handlers/render-prompt.js +41 -0
  90. package/dist/handlers/render-prompt.js.map +1 -0
  91. package/dist/handlers/resolve-tools.d.ts +10 -0
  92. package/dist/handlers/resolve-tools.d.ts.map +1 -0
  93. package/dist/handlers/resolve-tools.js +55 -0
  94. package/dist/handlers/resolve-tools.js.map +1 -0
  95. package/dist/handlers/result-shape.d.ts +94 -0
  96. package/dist/handlers/result-shape.d.ts.map +1 -0
  97. package/dist/handlers/result-shape.js +19 -0
  98. package/dist/handlers/result-shape.js.map +1 -0
  99. package/dist/handlers/run-retrievals.d.ts +10 -0
  100. package/dist/handlers/run-retrievals.d.ts.map +1 -0
  101. package/dist/handlers/run-retrievals.js +64 -0
  102. package/dist/handlers/run-retrievals.js.map +1 -0
  103. package/dist/handlers/run-snapshot.d.ts +4 -0
  104. package/dist/handlers/run-snapshot.d.ts.map +1 -0
  105. package/dist/handlers/run-snapshot.js +26 -0
  106. package/dist/handlers/run-snapshot.js.map +1 -0
  107. package/dist/handlers/setup.d.ts +25 -0
  108. package/dist/handlers/setup.d.ts.map +1 -0
  109. package/dist/handlers/setup.js +231 -0
  110. package/dist/handlers/setup.js.map +1 -0
  111. package/dist/handlers/structured-output.d.ts +38 -0
  112. package/dist/handlers/structured-output.d.ts.map +1 -0
  113. package/dist/handlers/structured-output.js +89 -0
  114. package/dist/handlers/structured-output.js.map +1 -0
  115. package/dist/handlers/tool-errors.d.ts +56 -0
  116. package/dist/handlers/tool-errors.d.ts.map +1 -0
  117. package/dist/handlers/tool-errors.js +73 -0
  118. package/dist/handlers/tool-errors.js.map +1 -0
  119. package/dist/handlers/tool-hitl.d.ts +45 -0
  120. package/dist/handlers/tool-hitl.d.ts.map +1 -0
  121. package/dist/handlers/tool-hitl.js +81 -0
  122. package/dist/handlers/tool-hitl.js.map +1 -0
  123. package/dist/handlers/turn-environment.d.ts +26 -0
  124. package/dist/handlers/turn-environment.d.ts.map +1 -0
  125. package/dist/handlers/turn-environment.js +154 -0
  126. package/dist/handlers/turn-environment.js.map +1 -0
  127. package/dist/hitl-policy.d.ts +45 -0
  128. package/dist/hitl-policy.d.ts.map +1 -0
  129. package/dist/hitl-policy.js +74 -0
  130. package/dist/hitl-policy.js.map +1 -0
  131. package/dist/index.d.ts +31 -0
  132. package/dist/index.d.ts.map +1 -0
  133. package/dist/index.js +19 -0
  134. package/dist/index.js.map +1 -0
  135. package/dist/invoke.d.ts +36 -0
  136. package/dist/invoke.d.ts.map +1 -0
  137. package/dist/invoke.js +228 -0
  138. package/dist/invoke.js.map +1 -0
  139. package/dist/migrations-dir.d.ts +11 -0
  140. package/dist/migrations-dir.d.ts.map +1 -0
  141. package/dist/migrations-dir.js +14 -0
  142. package/dist/migrations-dir.js.map +1 -0
  143. package/dist/project-run-result.d.ts +23 -0
  144. package/dist/project-run-result.d.ts.map +1 -0
  145. package/dist/project-run-result.js +116 -0
  146. package/dist/project-run-result.js.map +1 -0
  147. package/dist/prompt.d.ts +83 -0
  148. package/dist/prompt.d.ts.map +1 -0
  149. package/dist/prompt.js +119 -0
  150. package/dist/prompt.js.map +1 -0
  151. package/dist/provenance-emit.d.ts +44 -0
  152. package/dist/provenance-emit.d.ts.map +1 -0
  153. package/dist/provenance-emit.js +51 -0
  154. package/dist/provenance-emit.js.map +1 -0
  155. package/dist/registry.d.ts +38 -0
  156. package/dist/registry.d.ts.map +1 -0
  157. package/dist/registry.js +125 -0
  158. package/dist/registry.js.map +1 -0
  159. package/dist/retrieval.d.ts +47 -0
  160. package/dist/retrieval.d.ts.map +1 -0
  161. package/dist/retrieval.js +155 -0
  162. package/dist/retrieval.js.map +1 -0
  163. package/dist/run-snapshot-binding.d.ts +77 -0
  164. package/dist/run-snapshot-binding.d.ts.map +1 -0
  165. package/dist/run-snapshot-binding.js +4 -0
  166. package/dist/run-snapshot-binding.js.map +1 -0
  167. package/dist/schema.d.ts +497 -0
  168. package/dist/schema.d.ts.map +1 -0
  169. package/dist/schema.js +133 -0
  170. package/dist/schema.js.map +1 -0
  171. package/dist/streaming.d.ts +118 -0
  172. package/dist/streaming.d.ts.map +1 -0
  173. package/dist/streaming.js +17 -0
  174. package/dist/streaming.js.map +1 -0
  175. package/dist/tenant-policy.d.ts +16 -0
  176. package/dist/tenant-policy.d.ts.map +1 -0
  177. package/dist/tenant-policy.js +77 -0
  178. package/dist/tenant-policy.js.map +1 -0
  179. package/dist/types.d.ts +435 -0
  180. package/dist/types.d.ts.map +1 -0
  181. package/dist/types.js +4 -0
  182. package/dist/types.js.map +1 -0
  183. package/dist/versioning.d.ts +29 -0
  184. package/dist/versioning.d.ts.map +1 -0
  185. package/dist/versioning.js +58 -0
  186. package/dist/versioning.js.map +1 -0
  187. package/migrations/0000_sparkling_talkback.sql +18 -0
  188. package/migrations/0001_tired_warhawk.sql +16 -0
  189. package/migrations/0002_violet_ezekiel.sql +2 -0
  190. package/migrations/meta/0000_snapshot.json +172 -0
  191. package/migrations/meta/0001_snapshot.json +275 -0
  192. package/migrations/meta/0002_snapshot.json +287 -0
  193. package/migrations/meta/_journal.json +27 -0
  194. package/package.json +76 -4
  195. package/src/agent-turn-flow.ts +183 -0
  196. package/src/conversation-binding.ts +147 -0
  197. package/src/define.ts +572 -0
  198. package/src/errors.ts +80 -0
  199. package/src/guardrails-gate.ts +342 -0
  200. package/src/handlers/budget-check.ts +143 -0
  201. package/src/handlers/build-initial-messages.ts +103 -0
  202. package/src/handlers/compose-result.ts +90 -0
  203. package/src/handlers/constants.ts +10 -0
  204. package/src/handlers/context.ts +226 -0
  205. package/src/handlers/dispatch-tools.ts +633 -0
  206. package/src/handlers/errors.ts +282 -0
  207. package/src/handlers/evaluate-guardrails.ts +153 -0
  208. package/src/handlers/final-iteration.ts +30 -0
  209. package/src/handlers/index.ts +63 -0
  210. package/src/handlers/model-call.ts +151 -0
  211. package/src/handlers/persist-final-message.ts +67 -0
  212. package/src/handlers/persist-provenance.ts +39 -0
  213. package/src/handlers/persist-user-message.ts +70 -0
  214. package/src/handlers/public-types.ts +209 -0
  215. package/src/handlers/rehydrate.ts +161 -0
  216. package/src/handlers/render-prompt.ts +46 -0
  217. package/src/handlers/resolve-tools.ts +68 -0
  218. package/src/handlers/result-shape.ts +113 -0
  219. package/src/handlers/run-retrievals.ts +77 -0
  220. package/src/handlers/run-snapshot.ts +44 -0
  221. package/src/handlers/setup.ts +269 -0
  222. package/src/handlers/structured-output.ts +117 -0
  223. package/src/handlers/tool-errors.ts +122 -0
  224. package/src/handlers/tool-hitl.ts +126 -0
  225. package/src/handlers/turn-environment.ts +191 -0
  226. package/src/hitl-policy.ts +128 -0
  227. package/src/index.ts +154 -0
  228. package/src/invoke.ts +299 -0
  229. package/src/migrations-dir.ts +17 -0
  230. package/src/project-run-result.ts +131 -0
  231. package/src/prompt.ts +185 -0
  232. package/src/provenance-emit.ts +100 -0
  233. package/src/registry.ts +164 -0
  234. package/src/retrieval.ts +219 -0
  235. package/src/run-snapshot-binding.ts +87 -0
  236. package/src/schema.ts +154 -0
  237. package/src/streaming.ts +153 -0
  238. package/src/tenant-policy.ts +78 -0
  239. package/src/types.ts +453 -0
  240. package/src/versioning.ts +77 -0
@@ -0,0 +1,67 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+
4
+ import type { NodeHandler } from '@kindgi/handler';
5
+ import type { NodeId, Timestamp } from '@kindgi/types';
6
+
7
+ import { AGENT_LOOP_NODE } from '../agent-turn-flow.js';
8
+ import { emitTurnEvent } from '../streaming.js';
9
+ import type { ConversationMessage } from '../types.js';
10
+
11
+ import type { TurnContext } from './context.js';
12
+ import { throwAgentTurnFailure } from './errors.js';
13
+ import { finalIteration } from './final-iteration.js';
14
+
15
+ /**
16
+ * Persist the turn's terminal assistant message (non-intermediate —
17
+ * increments `turnCount`). Runs after `evaluate-guardrails`, so only a
18
+ * response that no `halt` guardrail rejected is stored. Reads the
19
+ * response from the agent loop's output.
20
+ *
21
+ * Dry-run branch: skips the DB write; builds a synthetic
22
+ * `ConversationMessage` with `sequence: -1`. `turnCount` is NOT
23
+ * incremented (no persistence happened).
24
+ */
25
+ export function buildPersistFinalMessageHandler(ctx: TurnContext): NodeHandler {
26
+ return async (_input: unknown, kctx) => {
27
+ const final = finalIteration(
28
+ kctx.nodeOutputs.get(AGENT_LOOP_NODE as NodeId),
29
+ 'persist-final-message',
30
+ );
31
+
32
+ const assistantMsg = final.message;
33
+
34
+ let finalMessage: ConversationMessage;
35
+ if (kctx.dryRun) {
36
+ finalMessage = {
37
+ sequence: -1,
38
+ role: 'agent',
39
+ content: assistantMsg.content ?? '',
40
+ createdAt: new Date().toISOString() as Timestamp,
41
+ actor: ctx.input.agent.id,
42
+ };
43
+ } else {
44
+ const persist = await ctx.bindings.conversationBinding.appendMessage({
45
+ tenantId: ctx.input.tenantId,
46
+ conversationId: ctx.input.conversationId,
47
+ role: 'agent',
48
+ content: assistantMsg.content ?? '',
49
+ actor: ctx.input.agent.id,
50
+ });
51
+ if (persist.kind === 'err') throwAgentTurnFailure(persist.error);
52
+ finalMessage = persist.value;
53
+ }
54
+
55
+ ctx.appended.push(finalMessage);
56
+ ctx.finalMessage = finalMessage;
57
+
58
+ await emitTurnEvent(ctx.bindings.onEvent, {
59
+ kind: 'agent.message',
60
+ step: ctx.usage.steps,
61
+ isFinal: true,
62
+ message: finalMessage,
63
+ });
64
+
65
+ return { sequence: finalMessage.sequence };
66
+ };
67
+ }
@@ -0,0 +1,39 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+
4
+ import type { NodeHandler } from '@kindgi/handler';
5
+
6
+ import { persistProvenance } from '../provenance-emit.js';
7
+
8
+ import type { TurnContext } from './context.js';
9
+
10
+ /**
11
+ * Persist the completed provenance DAG. Best-effort: a persistence
12
+ * failure here does NOT fail the run (the turn already succeeded and
13
+ * the DAG was built in memory); the result then carries no
14
+ * `provenance`.
15
+ *
16
+ * Skipped entirely when no provenance builder was wired.
17
+ */
18
+ export function buildPersistProvenanceHandler(ctx: TurnContext): NodeHandler {
19
+ return async (_input, kctx) => {
20
+ if (ctx.provenance === undefined || ctx.provenanceBindings === undefined) {
21
+ return { persisted: false };
22
+ }
23
+ if (kctx.dryRun) {
24
+ // Dry-run: DAG was built in-memory; skip the write. The
25
+ // returned `AgentTurnResult.provenance` is left undefined
26
+ // (unpersisted provenance is not surfaced on the result —
27
+ // callers can inspect the in-memory builder via bindings if they
28
+ // want).
29
+ return { persisted: false };
30
+ }
31
+ const persisted = await persistProvenance(ctx.provenance, ctx.provenanceBindings);
32
+ if (persisted.kind === 'ok') {
33
+ ctx.persistedProvenance = persisted.value;
34
+ return { persisted: true };
35
+ }
36
+ // Silently swallow — persistence is best-effort (see above).
37
+ return { persisted: false };
38
+ };
39
+ }
@@ -0,0 +1,70 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+
4
+ import type { NodeHandler } from '@kindgi/handler';
5
+ import type { Timestamp } from '@kindgi/types';
6
+
7
+ import type { ConversationMessage } from '../types.js';
8
+
9
+ import type { TurnContext } from './context.js';
10
+ import { throwAgentTurnFailure } from './errors.js';
11
+
12
+ /**
13
+ * Persist the caller's user message BEFORE the model is called. A
14
+ * crash mid-turn leaves the question intact for a later replay. Also
15
+ * seeds the provenance DAG with the `input:<sequence>` node — every
16
+ * subsequent model/tool node ties back through that root.
17
+ *
18
+ * Dry-run branch: skips the DB write; builds a synthetic
19
+ * `ConversationMessage` with `sequence: -1` (sentinel — real sequences
20
+ * are always ≥ 0). Provenance node is still seeded so downstream
21
+ * dry-run handlers can build a plausible plan DAG.
22
+ */
23
+ export function buildPersistUserMessageHandler(ctx: TurnContext): NodeHandler {
24
+ return async (_input, kctx) => {
25
+ if (kctx.dryRun) {
26
+ const synthetic: ConversationMessage = {
27
+ sequence: -1,
28
+ role: 'user',
29
+ content: ctx.input.userMessage,
30
+ createdAt: new Date().toISOString() as Timestamp,
31
+ ...(ctx.input.participantId !== undefined && { actor: ctx.input.participantId }),
32
+ };
33
+ ctx.userMessage = synthetic;
34
+ ctx.appended.push(synthetic);
35
+
36
+ if (ctx.provenance !== undefined) {
37
+ ctx.provenance.addNode({
38
+ id: `input:${synthetic.sequence}`,
39
+ kind: 'input',
40
+ timestamp: synthetic.createdAt,
41
+ ...(synthetic.actor !== undefined && { actor: synthetic.actor }),
42
+ });
43
+ }
44
+ return { sequence: synthetic.sequence };
45
+ }
46
+
47
+ const persisted = await ctx.bindings.conversationBinding.appendMessage({
48
+ tenantId: ctx.input.tenantId,
49
+ conversationId: ctx.input.conversationId,
50
+ role: 'user',
51
+ content: ctx.input.userMessage,
52
+ ...(ctx.input.participantId !== undefined && { actor: ctx.input.participantId }),
53
+ });
54
+ if (persisted.kind === 'err') throwAgentTurnFailure(persisted.error);
55
+
56
+ ctx.userMessage = persisted.value;
57
+ ctx.appended.push(persisted.value);
58
+
59
+ if (ctx.provenance !== undefined) {
60
+ ctx.provenance.addNode({
61
+ id: `input:${persisted.value.sequence}`,
62
+ kind: 'input',
63
+ timestamp: persisted.value.createdAt,
64
+ ...(persisted.value.actor !== undefined && { actor: persisted.value.actor }),
65
+ });
66
+ }
67
+
68
+ return { sequence: persisted.value.sequence };
69
+ };
70
+ }
@@ -0,0 +1,209 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+
4
+ import type { Principal } from '@kindgi/authz';
5
+ import type { ProviderRegistry, TenantPolicy } from '@kindgi/capabilities';
6
+ import type { EmbeddingProviderRegistry } from '@kindgi/embedding';
7
+ import type { MemoryQueryBinding } from '@kindgi/memory';
8
+ import type { PolicyRegistry } from '@kindgi/policy-contract';
9
+ import type { ParentRunRef, RunBinding } from '@kindgi/runtime';
10
+ import type { ToolRegistry, ToolSecretRef } from '@kindgi/tools';
11
+ import type { ProjectId, ProvenanceId, RunId, TenantId, Timestamp } from '@kindgi/types';
12
+
13
+ import type { ConversationBinding } from '../conversation-binding.js';
14
+ import type { GuardrailsBindings } from '../guardrails-gate.js';
15
+ import type { ProvenanceBindings } from '../provenance-emit.js';
16
+ import type { RunSnapshotBinding } from '../run-snapshot-binding.js';
17
+ import type { OnTurnEvent } from '../streaming.js';
18
+ import type { Agent, ConversationId } from '../types.js';
19
+
20
+ /**
21
+ * The user-facing input to `invokeAgent`. Split out from `invoke.ts` so
22
+ * the handler modules can depend on the shape without pulling in the
23
+ * orchestration entrypoint (would cause a circular import).
24
+ */
25
+ export interface InvokeAgentInput {
26
+ readonly tenantId: TenantId;
27
+ /**
28
+ * Content-scope anchor. REQUIRED — every
29
+ * agent turn is a run bound to exactly one project inside the
30
+ * tenant. Callers without a natural project id resolve to the tenant's
31
+ * Default via `projectBinding.getDefault(tenantId)` (`@kindgi/api`)
32
+ * at the caller layer.
33
+ */
34
+ readonly projectId: ProjectId;
35
+ readonly agent: Agent;
36
+ readonly conversationId: ConversationId;
37
+ readonly userMessage: string;
38
+ readonly parameters?: Readonly<Record<string, string | number | boolean>>;
39
+ /**
40
+ * Structured input for this turn — what a flow step hands its agent.
41
+ * Available to the instructions as `{{ input.* }}`, kept in the run
42
+ * snapshot for resume, and passed to guardrails as
43
+ * `trace.attributes.stepInput`.
44
+ */
45
+ readonly input?: unknown;
46
+ /**
47
+ * The flow run and node that started this turn, when it is a flow
48
+ * step. Recorded on the turn's run, so the flow can find its child.
49
+ */
50
+ readonly parent?: ParentRunRef;
51
+ readonly participantId?: string;
52
+ readonly abortSignal?: AbortSignal;
53
+ /**
54
+ * When `true`, executes the turn as a plan preview: no side effects
55
+ * to the conversation, memory, or provenance. Retrievals + guardrails
56
+ * still run for real (pure reads); the model call is skipped and
57
+ * returns a fixed mock response with no tool calls, so no tools run;
58
+ * every `persist-*` node skips its write. The returned
59
+ * `AgentTurnResult` carries `dryRun: true`, and the underlying run is
60
+ * journaled as a dry run.
61
+ */
62
+ readonly dryRun?: boolean;
63
+ /**
64
+ * Authorization — the Principal on whose authority this agent turn
65
+ * runs. When set together with `authz`, the underlying run checks
66
+ * every tool invocation (and subgraph dispatch, if any) against this
67
+ * principal at its enforcement point.
68
+ *
69
+ * Typical caller construction:
70
+ * principal = delegate(
71
+ * { kind: 'agent', id: input.agent.id, tenantId },
72
+ * userPrincipalFromSession
73
+ * )
74
+ * — establishes the confused-deputy-safe intersection semantic.
75
+ *
76
+ * Absent = tool invocations are not authorization-checked.
77
+ */
78
+ readonly principal?: Principal;
79
+ readonly authz?: {
80
+ readonly fgaApiUrl: string;
81
+ };
82
+ }
83
+
84
+ export interface InvokeAgentBindings extends GuardrailsBindings {
85
+ /**
86
+ * Model providers. Setup hydrates the tenant's providers (when the
87
+ * registry supports it) and routes the agent's first capability over
88
+ * `list(tenantId)` under the merged tenant policy; llm-judge
89
+ * guardrails route through it too.
90
+ */
91
+ readonly providerRegistry: ProviderRegistry;
92
+ /**
93
+ * Tools. Setup calls `forTenant(tenantId)` and resolves the agent's
94
+ * tool refs on the returned tenant-bound registry, so a turn never
95
+ * sees another tenant's tools.
96
+ */
97
+ readonly toolRegistry: ToolRegistry;
98
+ /**
99
+ * Caller-plugged data-access surface for memory reads.
100
+ * The Kindgi runtime supplies a Postgres-backed implementation;
101
+ * any object satisfying the interface works (tests, custom stores).
102
+ * Retrieval (listFacts / searchByKeyword / searchBySemantic) inside
103
+ * the agent runtime routes through this binding — the runtime never
104
+ * touches a database client directly for memory operations.
105
+ */
106
+ readonly memoryBinding: MemoryQueryBinding;
107
+ /**
108
+ * Caller-plugged conversation store. Every open / get / list /
109
+ * close / delete / appendMessage / readMessages inside the agent
110
+ * runtime routes through this binding — the runtime never touches
111
+ * a database client directly for conversation ops. The Kindgi runtime
112
+ * supplies a Postgres-backed implementation; any conforming object
113
+ * works. Required.
114
+ */
115
+ readonly conversationBinding: ConversationBinding;
116
+ /**
117
+ * Caller-plugged run-snapshot store. Writes the reconstruction
118
+ * envelope on run-start (idempotent under replay); reads it
119
+ * on `resumeAgentTurn` to rebuild the suspended TurnContext.
120
+ * The Kindgi runtime supplies a
121
+ * Postgres-backed implementation; any conforming object works. Required.
122
+ */
123
+ readonly runSnapshotBinding: RunSnapshotBinding;
124
+ /**
125
+ * Run-lifecycle binding (`RunBinding` from `@kindgi/runtime`). Every
126
+ * agent turn spawns a `runGraph` (or `resumeRun`) through it. The
127
+ * Kindgi runtime supplies an implementation; a bespoke one can be
128
+ * plugged in.
129
+ *
130
+ * Optional in the type, but `invokeAgent` and `resumeAgentTurn`
131
+ * throw when it is missing — every caller MUST supply it.
132
+ */
133
+ readonly runBinding?: RunBinding;
134
+ /**
135
+ * Statically-injected tenant policy for model routing. When a
136
+ * `policyRegistry` also yields a policy, the two are MERGED so the
137
+ * result is at least as strict as each: allow lists intersect, deny
138
+ * lists union, cost/token caps take the smaller value.
139
+ *
140
+ * Absent-and-registry-absent = router runs without any tenant policy.
141
+ */
142
+ readonly tenantPolicy?: TenantPolicy;
143
+ /**
144
+ * Optional policy registry. When present, the agent setup step calls
145
+ * `policyRegistry.evaluate('model-routing', {tenantId})` and merges
146
+ * the result with `tenantPolicy` before routing. Without a registry
147
+ * (or without an executor for that kind), `tenantPolicy` applies
148
+ * alone.
149
+ */
150
+ readonly policyRegistry?: PolicyRegistry;
151
+ readonly embeddingRegistry?: EmbeddingProviderRegistry;
152
+ readonly embeddingModel?: string;
153
+ readonly onEvent?: OnTurnEvent;
154
+ readonly provenance?: ProvenanceBindings;
155
+ readonly hitl?: HitlBindings;
156
+ /**
157
+ * Declarative HTTP tools: optional secret resolver populated
158
+ * from the deployment's tenant-scoped `SecretBinding`. When present,
159
+ * `dispatch-tools` threads it into every `ToolContext` so
160
+ * declarative HTTP tools (spec kind 'http') can resolve declared `secret_ref`s at
161
+ * invoke time. Absent → HTTP tools that declare `authorization`
162
+ * throw a clear "resolveSecret is not wired" error; native tools
163
+ * unaffected.
164
+ */
165
+ readonly resolveSecret?: (ref: ToolSecretRef) => Promise<string>;
166
+ }
167
+
168
+ export interface HitlBindings {
169
+ readonly enqueue: (input: HitlEnqueueInput) => Promise<HitlEnqueueResult>;
170
+ }
171
+
172
+ export interface HitlEnqueueInput {
173
+ readonly tenantId: TenantId;
174
+ readonly subjectKind: string;
175
+ readonly subjectRef: Readonly<Record<string, unknown>>;
176
+ readonly requiredRole?: 'standard' | 'senior' | 'admin';
177
+ readonly title?: string;
178
+ readonly description?: string;
179
+ readonly context?: Readonly<Record<string, unknown>>;
180
+ /**
181
+ * Waitpoint token this approval resolves on approve/reject
182
+ * (park-and-resume). The session gate (setup handler) and the tool
183
+ * gate (dispatch-tools handler) compute it as a deterministic hash —
184
+ * of (runId, gate scope, turnCount) and of (runId, callId, argsHash)
185
+ * respectively. Enqueue is idempotent on this — handler replay is
186
+ * safe.
187
+ */
188
+ readonly waitTokenId?: string;
189
+ /**
190
+ * Provenance reference for the parent run. Required for the
191
+ * approvals-complete route to invoke `completeToken(runId, ...)` +
192
+ * `resumeRun(runId)` on decision. Absent = the approval decides in
193
+ * place, no waitpoint resolution.
194
+ */
195
+ readonly provenanceRef?: {
196
+ readonly runId: RunId;
197
+ readonly provenanceId?: ProvenanceId;
198
+ };
199
+ /**
200
+ * Absolute deadline surfaced to reviewers; an approval still pending
201
+ * after it can be expired. Materialized at enqueue time from the
202
+ * effective HITL policy's `timeoutMs`.
203
+ */
204
+ readonly expiresAt?: Timestamp;
205
+ }
206
+
207
+ export interface HitlEnqueueResult {
208
+ readonly approvalId: string;
209
+ }
@@ -0,0 +1,161 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+
4
+ /**
5
+ * Rebuild a resumed turn's in-memory context. The turn's handlers keep
6
+ * what they resolve and accumulate on the `TurnContext`, and the kernel
7
+ * doesn't re-run a step that completed before the park — so a turn
8
+ * parked after `setup` (on a tool-call approval) would resume with none
9
+ * of it. From the durable record:
10
+ *
11
+ * - the environment — conversation, approval rules, guardrails, tools,
12
+ * policies — is resolved again, routed to the provider and model
13
+ * `setup` journaled;
14
+ * - the messages the turn stored come back from the conversation, from
15
+ * the turn's user message (`persist-user-message` journals its
16
+ * sequence);
17
+ * - the retrieved facts come from `run-retrievals`' journaled output;
18
+ * - usage is summed from the journaled model calls, so the step and
19
+ * cost budgets count the whole turn.
20
+ *
21
+ * The loop's steps that completed aren't run again either: the runtime
22
+ * hands each its journaled result (`DerivedRunState.completedBodySteps`
23
+ * in `@kindgi/runtime`). The step the turn parked in
24
+ * (`dispatch-tools`, on the approval) runs again, and reuses the messages
25
+ * it already stored (`TurnContext.storedBeforePark`).
26
+ *
27
+ * A turn parked inside `setup` (the session gate) re-runs `setup`, and
28
+ * nothing here applies.
29
+ *
30
+ * Not restored: provenance nodes recorded before the park. The resumed
31
+ * turn's provenance covers what happens from the resume on.
32
+ */
33
+
34
+ import type { LoopContext } from '@kindgi/handler';
35
+ import { type JournalEntry, bodyStepKey } from '@kindgi/runtime';
36
+
37
+ import type { ConversationMessage, RetrievedFact } from '../types.js';
38
+ import type { TurnContext } from './context.js';
39
+ import { throwAgentTurnFailure } from './errors.js';
40
+ import {
41
+ loadTurnConversation,
42
+ resolveTurnEnvironment,
43
+ resolveTurnHitlPolicy,
44
+ } from './turn-environment.js';
45
+
46
+ interface StepRecord {
47
+ readonly nodeId: string;
48
+ readonly output: unknown;
49
+ readonly inLoop: boolean;
50
+ }
51
+
52
+ /** Each step's last completion — a step at one loop iteration counts once. */
53
+ function completedSteps(journal: readonly JournalEntry[]): readonly StepRecord[] {
54
+ const byStep = new Map<string, StepRecord>();
55
+ for (const e of journal) {
56
+ if (e.kind !== 'step.completed' || e.nodeId === undefined) continue;
57
+ const payload = (e.payload ?? {}) as {
58
+ readonly output?: unknown;
59
+ readonly loopContext?: LoopContext;
60
+ };
61
+ const key =
62
+ payload.loopContext === undefined
63
+ ? (e.nodeId as unknown as string)
64
+ : bodyStepKey(e.nodeId, payload.loopContext);
65
+ byStep.delete(key);
66
+ byStep.set(key, {
67
+ nodeId: e.nodeId as unknown as string,
68
+ output: payload.output,
69
+ inLoop: payload.loopContext !== undefined,
70
+ });
71
+ }
72
+ return [...byStep.values()];
73
+ }
74
+
75
+ /** The output of an outer step that completed, if it did. */
76
+ function outputOf<T>(steps: readonly StepRecord[], nodeId: string): T | undefined {
77
+ return steps.find((s) => s.nodeId === nodeId && !s.inLoop)?.output as T | undefined;
78
+ }
79
+
80
+ /**
81
+ * Restore `ctx` for a turn being resumed. Returns `false` when `setup`
82
+ * hasn't completed (it will run, and resolve everything itself).
83
+ */
84
+ export async function rehydrateTurnContext(
85
+ ctx: TurnContext,
86
+ runId: string,
87
+ journal: readonly JournalEntry[],
88
+ ): Promise<boolean> {
89
+ const steps = completedSteps(journal);
90
+ const setup = outputOf<{ readonly providerId: string; readonly providerModel: string }>(
91
+ steps,
92
+ 'setup',
93
+ );
94
+ if (setup === undefined) return false;
95
+
96
+ if (ctx.bindings.provenance?.newBuilder !== undefined) {
97
+ ctx.provenance = ctx.bindings.provenance.newBuilder({ runId, tenantId: ctx.input.tenantId });
98
+ ctx.provenanceBindings = ctx.bindings.provenance;
99
+ }
100
+ await loadTurnConversation(ctx);
101
+ ctx.hitlPolicy = await resolveTurnHitlPolicy(ctx);
102
+ await resolveTurnEnvironment(ctx, { providerId: setup.providerId, model: setup.providerModel });
103
+
104
+ const userMessage = outputOf<{ readonly sequence: number }>(steps, 'persist-user-message');
105
+ if (userMessage !== undefined) {
106
+ await restoreAppended(ctx, userMessage.sequence);
107
+ ctx.storedBeforePark = storedBeforePark(ctx, steps);
108
+ }
109
+
110
+ const retrievals = outputOf<{ readonly retrieved?: readonly RetrievedFact[] }>(
111
+ steps,
112
+ 'run-retrievals',
113
+ );
114
+ if (retrievals?.retrieved !== undefined) ctx.retrieved = retrievals.retrieved;
115
+
116
+ for (const s of steps.filter((s) => s.nodeId === 'model-call' && s.inLoop)) {
117
+ const out = s.output as {
118
+ readonly step?: number;
119
+ readonly iterationUsage?: {
120
+ readonly promptTokens?: number;
121
+ readonly completionTokens?: number;
122
+ readonly costUsd?: number;
123
+ };
124
+ readonly provider?: { readonly id: string; readonly model: string };
125
+ };
126
+ ctx.usage.steps = Math.max(ctx.usage.steps, out.step ?? 0);
127
+ ctx.usage.promptTokens += out.iterationUsage?.promptTokens ?? 0;
128
+ ctx.usage.completionTokens += out.iterationUsage?.completionTokens ?? 0;
129
+ ctx.usage.totalCostUsd += out.iterationUsage?.costUsd ?? 0;
130
+ if (out.provider !== undefined) ctx.lastProvider = out.provider;
131
+ }
132
+ return true;
133
+ }
134
+
135
+ /**
136
+ * The messages no completed step accounts for: those the step the turn
137
+ * parked in stored before it parked.
138
+ */
139
+ function storedBeforePark(ctx: TurnContext, steps: readonly StepRecord[]): ConversationMessage[] {
140
+ const accounted = new Set<number>();
141
+ if (ctx.userMessage !== undefined) accounted.add(ctx.userMessage.sequence);
142
+ for (const s of steps.filter((s) => s.nodeId === 'dispatch-tools' && s.inLoop)) {
143
+ const out = s.output as { readonly iterationAppended?: readonly ConversationMessage[] };
144
+ for (const m of out.iterationAppended ?? []) accounted.add(m.sequence);
145
+ }
146
+ return ctx.appended.filter((m) => !accounted.has(m.sequence));
147
+ }
148
+
149
+ /** The messages the turn stored before the park, from its user message on. */
150
+ async function restoreAppended(ctx: TurnContext, fromSequence: number): Promise<void> {
151
+ const read = await ctx.bindings.conversationBinding.readMessages({
152
+ tenantId: ctx.input.tenantId,
153
+ conversationId: ctx.input.conversationId,
154
+ sinceSequence: fromSequence - 1,
155
+ });
156
+ if (read.kind === 'err') throwAgentTurnFailure(read.error);
157
+ const turnMessages: ConversationMessage[] = read.value.filter((m) => m.sequence >= fromSequence);
158
+ ctx.appended.push(...turnMessages);
159
+ const user = turnMessages.find((m) => m.sequence === fromSequence && m.role === 'user');
160
+ if (user !== undefined) ctx.userMessage = user;
161
+ }
@@ -0,0 +1,46 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+
4
+ import type { NodeHandler } from '@kindgi/handler';
5
+
6
+ import { renderInstructions } from '../prompt.js';
7
+
8
+ import type { TurnContext } from './context.js';
9
+ import { throwAgentTurnFailure } from './errors.js';
10
+
11
+ /**
12
+ * Render the agent's instructions template with caller-supplied
13
+ * parameters + framework auto-vars. Fails the run with a
14
+ * `model-invocation-failed` (surfaced through `AgentTurnFailure`) if
15
+ * required parameters are missing or the template throws.
16
+ *
17
+ * Setup must have run first — `ctx.conversation` is required.
18
+ */
19
+ export function buildRenderPromptHandler(ctx: TurnContext): NodeHandler {
20
+ return async () => {
21
+ if (ctx.conversation === undefined) {
22
+ throwAgentTurnFailure({
23
+ code: 'model-invocation-failed',
24
+ message: 'render-prompt invoked before setup completed',
25
+ cause: null,
26
+ });
27
+ }
28
+ const rendered = renderInstructions(ctx.input.agent, {
29
+ parameters: ctx.input.parameters ?? {},
30
+ ...(ctx.input.input !== undefined && { input: ctx.input.input }),
31
+ conversation: {
32
+ id: ctx.input.conversationId,
33
+ turn: ctx.conversation.turnCount + 1,
34
+ },
35
+ });
36
+ if (!rendered.ok) {
37
+ throwAgentTurnFailure({
38
+ code: 'model-invocation-failed',
39
+ message: `Prompt render failed: ${rendered.error.message}`,
40
+ cause: rendered.error,
41
+ });
42
+ }
43
+ ctx.renderedPrompt = rendered.value.rendered;
44
+ return { rendered: rendered.value.rendered };
45
+ };
46
+ }
@@ -0,0 +1,68 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+
4
+ import type { ModelToolDefinition } from '@kindgi/capabilities';
5
+ import type { Tool, ToolRegistry } from '@kindgi/tools';
6
+
7
+ import type { Agent } from '../types.js';
8
+
9
+ import type { TurnContext } from './context.js';
10
+ import { throwAgentTurnFailure } from './errors.js';
11
+
12
+ /**
13
+ * Resolve the agent's tool references against one tenant's registry
14
+ * (`ToolRegistry.forTenant`). Throws the turn failure for an unknown
15
+ * tool or an unsatisfiable version range.
16
+ */
17
+ export function resolveTurnTools(
18
+ registry: ToolRegistry,
19
+ agent: Agent,
20
+ ): NonNullable<TurnContext['tools']> {
21
+ const definitions: ModelToolDefinition[] = [];
22
+ const byName = new Map<
23
+ string,
24
+ { readonly tool: Tool; readonly resolvedVersion: string; readonly requestedRange: string }
25
+ >();
26
+ for (const ref of agent.tools) {
27
+ // Agent references tools by (id, range).
28
+ // Resolve at run start via `resolve` on the tenant-bound registry
29
+ // (npm-compatible semver, backed by `maxSatisfying`); capture `resolvedVersion` in
30
+ // the byName map so `dispatch-tools` can emit it in provenance +
31
+ // telemetry — a replay can then pin against the same version.
32
+ const resolved = registry.resolve(ref.id as never, ref.version);
33
+ if (resolved.kind === 'err') {
34
+ const err = resolved.error;
35
+ if (err.code === 'tool-not-found') {
36
+ throwAgentTurnFailure({
37
+ code: 'unresolved-tool',
38
+ message: `Tool "${ref.id}" declared by agent "${agent.id}" is not registered`,
39
+ toolId: ref.id,
40
+ });
41
+ }
42
+ if (err.code === 'invalid-version-range' || err.code === 'tool-version-unresolvable') {
43
+ throwAgentTurnFailure({
44
+ code: 'tool-version-unresolvable',
45
+ message: err.message,
46
+ toolId: ref.id,
47
+ requestedRange: ref.version,
48
+ ...(err.code === 'tool-version-unresolvable' && {
49
+ availableVersions: err.availableVersions,
50
+ }),
51
+ });
52
+ }
53
+ throwAgentTurnFailure({
54
+ code: 'unresolved-tool',
55
+ message: err.message,
56
+ toolId: ref.id,
57
+ });
58
+ }
59
+ const { tool, resolvedVersion } = resolved.value;
60
+ definitions.push({
61
+ name: tool.id,
62
+ description: tool.description,
63
+ inputSchema: tool.input as Readonly<Record<string, unknown>>,
64
+ });
65
+ byName.set(tool.id, { tool, resolvedVersion, requestedRange: ref.version });
66
+ }
67
+ return { definitions, byName };
68
+ }