@kindgi/agents 0.0.0-bootstrap.0 → 0.1.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/LICENSE +201 -0
- package/README.md +131 -2
- package/dist/agent-turn-flow.d.ts +57 -0
- package/dist/agent-turn-flow.d.ts.map +1 -0
- package/dist/agent-turn-flow.js +172 -0
- package/dist/agent-turn-flow.js.map +1 -0
- package/dist/conversation-binding.d.ts +111 -0
- package/dist/conversation-binding.d.ts.map +1 -0
- package/dist/conversation-binding.js +4 -0
- package/dist/conversation-binding.js.map +1 -0
- package/dist/define.d.ts +180 -0
- package/dist/define.d.ts.map +1 -0
- package/dist/define.js +361 -0
- package/dist/define.js.map +1 -0
- package/dist/errors.d.ts +62 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +4 -0
- package/dist/errors.js.map +1 -0
- package/dist/guardrails-gate.d.ts +169 -0
- package/dist/guardrails-gate.d.ts.map +1 -0
- package/dist/guardrails-gate.js +202 -0
- package/dist/guardrails-gate.js.map +1 -0
- package/dist/handlers/budget-check.d.ts +22 -0
- package/dist/handlers/budget-check.d.ts.map +1 -0
- package/dist/handlers/budget-check.js +109 -0
- package/dist/handlers/budget-check.js.map +1 -0
- package/dist/handlers/build-initial-messages.d.ts +17 -0
- package/dist/handlers/build-initial-messages.d.ts.map +1 -0
- package/dist/handlers/build-initial-messages.js +86 -0
- package/dist/handlers/build-initial-messages.js.map +1 -0
- package/dist/handlers/compose-result.d.ts +10 -0
- package/dist/handlers/compose-result.d.ts.map +1 -0
- package/dist/handlers/compose-result.js +79 -0
- package/dist/handlers/compose-result.js.map +1 -0
- package/dist/handlers/constants.d.ts +8 -0
- package/dist/handlers/constants.d.ts.map +1 -0
- package/dist/handlers/constants.js +10 -0
- package/dist/handlers/constants.js.map +1 -0
- package/dist/handlers/context.d.ts +210 -0
- package/dist/handlers/context.d.ts.map +1 -0
- package/dist/handlers/context.js +4 -0
- package/dist/handlers/context.js.map +1 -0
- package/dist/handlers/dispatch-tools.d.ts +15 -0
- package/dist/handlers/dispatch-tools.d.ts.map +1 -0
- package/dist/handlers/dispatch-tools.js +511 -0
- package/dist/handlers/dispatch-tools.js.map +1 -0
- package/dist/handlers/errors.d.ts +133 -0
- package/dist/handlers/errors.d.ts.map +1 -0
- package/dist/handlers/errors.js +134 -0
- package/dist/handlers/errors.js.map +1 -0
- package/dist/handlers/evaluate-guardrails.d.ts +14 -0
- package/dist/handlers/evaluate-guardrails.d.ts.map +1 -0
- package/dist/handlers/evaluate-guardrails.js +128 -0
- package/dist/handlers/evaluate-guardrails.js.map +1 -0
- package/dist/handlers/final-iteration.d.ts +7 -0
- package/dist/handlers/final-iteration.d.ts.map +1 -0
- package/dist/handlers/final-iteration.js +26 -0
- package/dist/handlers/final-iteration.js.map +1 -0
- package/dist/handlers/index.d.ts +5 -0
- package/dist/handlers/index.d.ts.map +1 -0
- package/dist/handlers/index.js +41 -0
- package/dist/handlers/index.js.map +1 -0
- package/dist/handlers/model-call.d.ts +13 -0
- package/dist/handlers/model-call.d.ts.map +1 -0
- package/dist/handlers/model-call.js +136 -0
- package/dist/handlers/model-call.js.map +1 -0
- package/dist/handlers/persist-final-message.d.ts +14 -0
- package/dist/handlers/persist-final-message.d.ts.map +1 -0
- package/dist/handlers/persist-final-message.js +54 -0
- package/dist/handlers/persist-final-message.js.map +1 -0
- package/dist/handlers/persist-provenance.d.ts +12 -0
- package/dist/handlers/persist-provenance.d.ts.map +1 -0
- package/dist/handlers/persist-provenance.js +34 -0
- package/dist/handlers/persist-provenance.js.map +1 -0
- package/dist/handlers/persist-user-message.d.ts +15 -0
- package/dist/handlers/persist-user-message.d.ts.map +1 -0
- package/dist/handlers/persist-user-message.js +59 -0
- package/dist/handlers/persist-user-message.js.map +1 -0
- package/dist/handlers/public-types.d.ts +201 -0
- package/dist/handlers/public-types.d.ts.map +1 -0
- package/dist/handlers/public-types.js +4 -0
- package/dist/handlers/public-types.js.map +1 -0
- package/dist/handlers/rehydrate.d.ts +8 -0
- package/dist/handlers/rehydrate.d.ts.map +1 -0
- package/dist/handlers/rehydrate.js +94 -0
- package/dist/handlers/rehydrate.js.map +1 -0
- package/dist/handlers/render-prompt.d.ts +12 -0
- package/dist/handlers/render-prompt.d.ts.map +1 -0
- package/dist/handlers/render-prompt.js +41 -0
- package/dist/handlers/render-prompt.js.map +1 -0
- package/dist/handlers/resolve-tools.d.ts +10 -0
- package/dist/handlers/resolve-tools.d.ts.map +1 -0
- package/dist/handlers/resolve-tools.js +55 -0
- package/dist/handlers/resolve-tools.js.map +1 -0
- package/dist/handlers/result-shape.d.ts +94 -0
- package/dist/handlers/result-shape.d.ts.map +1 -0
- package/dist/handlers/result-shape.js +19 -0
- package/dist/handlers/result-shape.js.map +1 -0
- package/dist/handlers/run-retrievals.d.ts +10 -0
- package/dist/handlers/run-retrievals.d.ts.map +1 -0
- package/dist/handlers/run-retrievals.js +64 -0
- package/dist/handlers/run-retrievals.js.map +1 -0
- package/dist/handlers/run-snapshot.d.ts +4 -0
- package/dist/handlers/run-snapshot.d.ts.map +1 -0
- package/dist/handlers/run-snapshot.js +26 -0
- package/dist/handlers/run-snapshot.js.map +1 -0
- package/dist/handlers/setup.d.ts +25 -0
- package/dist/handlers/setup.d.ts.map +1 -0
- package/dist/handlers/setup.js +231 -0
- package/dist/handlers/setup.js.map +1 -0
- package/dist/handlers/structured-output.d.ts +38 -0
- package/dist/handlers/structured-output.d.ts.map +1 -0
- package/dist/handlers/structured-output.js +89 -0
- package/dist/handlers/structured-output.js.map +1 -0
- package/dist/handlers/tool-errors.d.ts +56 -0
- package/dist/handlers/tool-errors.d.ts.map +1 -0
- package/dist/handlers/tool-errors.js +73 -0
- package/dist/handlers/tool-errors.js.map +1 -0
- package/dist/handlers/tool-hitl.d.ts +45 -0
- package/dist/handlers/tool-hitl.d.ts.map +1 -0
- package/dist/handlers/tool-hitl.js +81 -0
- package/dist/handlers/tool-hitl.js.map +1 -0
- package/dist/handlers/turn-environment.d.ts +26 -0
- package/dist/handlers/turn-environment.d.ts.map +1 -0
- package/dist/handlers/turn-environment.js +154 -0
- package/dist/handlers/turn-environment.js.map +1 -0
- package/dist/hitl-policy.d.ts +45 -0
- package/dist/hitl-policy.d.ts.map +1 -0
- package/dist/hitl-policy.js +74 -0
- package/dist/hitl-policy.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/invoke.d.ts +36 -0
- package/dist/invoke.d.ts.map +1 -0
- package/dist/invoke.js +228 -0
- package/dist/invoke.js.map +1 -0
- package/dist/migrations-dir.d.ts +11 -0
- package/dist/migrations-dir.d.ts.map +1 -0
- package/dist/migrations-dir.js +14 -0
- package/dist/migrations-dir.js.map +1 -0
- package/dist/project-run-result.d.ts +23 -0
- package/dist/project-run-result.d.ts.map +1 -0
- package/dist/project-run-result.js +116 -0
- package/dist/project-run-result.js.map +1 -0
- package/dist/prompt.d.ts +83 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +119 -0
- package/dist/prompt.js.map +1 -0
- package/dist/provenance-emit.d.ts +44 -0
- package/dist/provenance-emit.d.ts.map +1 -0
- package/dist/provenance-emit.js +51 -0
- package/dist/provenance-emit.js.map +1 -0
- package/dist/registry.d.ts +38 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +125 -0
- package/dist/registry.js.map +1 -0
- package/dist/retrieval.d.ts +47 -0
- package/dist/retrieval.d.ts.map +1 -0
- package/dist/retrieval.js +155 -0
- package/dist/retrieval.js.map +1 -0
- package/dist/run-snapshot-binding.d.ts +77 -0
- package/dist/run-snapshot-binding.d.ts.map +1 -0
- package/dist/run-snapshot-binding.js +4 -0
- package/dist/run-snapshot-binding.js.map +1 -0
- package/dist/schema.d.ts +497 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +133 -0
- package/dist/schema.js.map +1 -0
- package/dist/streaming.d.ts +118 -0
- package/dist/streaming.d.ts.map +1 -0
- package/dist/streaming.js +17 -0
- package/dist/streaming.js.map +1 -0
- package/dist/tenant-policy.d.ts +16 -0
- package/dist/tenant-policy.d.ts.map +1 -0
- package/dist/tenant-policy.js +77 -0
- package/dist/tenant-policy.js.map +1 -0
- package/dist/types.d.ts +435 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/dist/versioning.d.ts +29 -0
- package/dist/versioning.d.ts.map +1 -0
- package/dist/versioning.js +58 -0
- package/dist/versioning.js.map +1 -0
- package/migrations/0000_sparkling_talkback.sql +18 -0
- package/migrations/0001_tired_warhawk.sql +16 -0
- package/migrations/0002_violet_ezekiel.sql +2 -0
- package/migrations/meta/0000_snapshot.json +172 -0
- package/migrations/meta/0001_snapshot.json +275 -0
- package/migrations/meta/0002_snapshot.json +287 -0
- package/migrations/meta/_journal.json +27 -0
- package/package.json +76 -4
- package/src/agent-turn-flow.ts +183 -0
- package/src/conversation-binding.ts +147 -0
- package/src/define.ts +572 -0
- package/src/errors.ts +80 -0
- package/src/guardrails-gate.ts +342 -0
- package/src/handlers/budget-check.ts +143 -0
- package/src/handlers/build-initial-messages.ts +103 -0
- package/src/handlers/compose-result.ts +90 -0
- package/src/handlers/constants.ts +10 -0
- package/src/handlers/context.ts +226 -0
- package/src/handlers/dispatch-tools.ts +633 -0
- package/src/handlers/errors.ts +282 -0
- package/src/handlers/evaluate-guardrails.ts +153 -0
- package/src/handlers/final-iteration.ts +30 -0
- package/src/handlers/index.ts +63 -0
- package/src/handlers/model-call.ts +151 -0
- package/src/handlers/persist-final-message.ts +67 -0
- package/src/handlers/persist-provenance.ts +39 -0
- package/src/handlers/persist-user-message.ts +70 -0
- package/src/handlers/public-types.ts +209 -0
- package/src/handlers/rehydrate.ts +161 -0
- package/src/handlers/render-prompt.ts +46 -0
- package/src/handlers/resolve-tools.ts +68 -0
- package/src/handlers/result-shape.ts +113 -0
- package/src/handlers/run-retrievals.ts +77 -0
- package/src/handlers/run-snapshot.ts +44 -0
- package/src/handlers/setup.ts +269 -0
- package/src/handlers/structured-output.ts +117 -0
- package/src/handlers/tool-errors.ts +122 -0
- package/src/handlers/tool-hitl.ts +126 -0
- package/src/handlers/turn-environment.ts +191 -0
- package/src/hitl-policy.ts +128 -0
- package/src/index.ts +154 -0
- package/src/invoke.ts +299 -0
- package/src/migrations-dir.ts +17 -0
- package/src/project-run-result.ts +131 -0
- package/src/prompt.ts +185 -0
- package/src/provenance-emit.ts +100 -0
- package/src/registry.ts +164 -0
- package/src/retrieval.ts +219 -0
- package/src/run-snapshot-binding.ts +87 -0
- package/src/schema.ts +154 -0
- package/src/streaming.ts +153 -0
- package/src/tenant-policy.ts +78 -0
- package/src/types.ts +453 -0
- package/src/versioning.ts +77 -0
package/src/prompt.ts
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
|
|
4
|
+
import { Liquid } from 'liquidjs';
|
|
5
|
+
|
|
6
|
+
import type { Agent, PromptParameter } from './types.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Reserved variable namespaces the runtime injects automatically.
|
|
10
|
+
* Callers do not (and cannot) declare these as `PromptParameter`s —
|
|
11
|
+
* they're populated by `renderInstructions` at invoke time.
|
|
12
|
+
*
|
|
13
|
+
* - `today` — ISO date string (yyyy-mm-dd) at UTC.
|
|
14
|
+
* - `now` — ISO datetime string at UTC.
|
|
15
|
+
* - `agent.id` — the invoked agent's id.
|
|
16
|
+
* - `agent.name` — display name.
|
|
17
|
+
* - `agent.version` — semver of the invoked definition.
|
|
18
|
+
* - `conversation.id` — current conversation id (undefined for one-shot).
|
|
19
|
+
* - `conversation.turn` — 1-indexed turn number within this conversation.
|
|
20
|
+
* - `input` — the turn's structured input (`InvokeAgentInput.input`),
|
|
21
|
+
* e.g. `{{ input.grievance.summary }}`; unset when
|
|
22
|
+
* the turn has none.
|
|
23
|
+
*/
|
|
24
|
+
export const AUTO_INJECTED_VARS = ['today', 'now', 'agent', 'conversation', 'input'] as const;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Shape passed to `renderInstructions`. Framework auto-vars are
|
|
28
|
+
* computed inside; the caller supplies parameter values only.
|
|
29
|
+
*/
|
|
30
|
+
export interface RenderContext {
|
|
31
|
+
/** Caller-supplied parameter values. Keyed by `PromptParameter.name`. */
|
|
32
|
+
readonly parameters: Readonly<Record<string, string | number | boolean>>;
|
|
33
|
+
/** Turn context — used to fill in `conversation.*` and stamp `now`. */
|
|
34
|
+
readonly conversation?: {
|
|
35
|
+
readonly id: string;
|
|
36
|
+
readonly turn: number;
|
|
37
|
+
};
|
|
38
|
+
/** The turn's structured input, rendered as `{{ input.* }}`. */
|
|
39
|
+
readonly input?: unknown;
|
|
40
|
+
/**
|
|
41
|
+
* Optional clock override — tests inject a fixed date. Defaults to
|
|
42
|
+
* `new Date()`.
|
|
43
|
+
*/
|
|
44
|
+
readonly clock?: () => Date;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface RenderResult {
|
|
48
|
+
readonly rendered: string;
|
|
49
|
+
/**
|
|
50
|
+
* The full context the template was rendered against (parameters +
|
|
51
|
+
* auto-vars). Captured for provenance — the auditor sees exactly
|
|
52
|
+
* what values filled the placeholders.
|
|
53
|
+
*/
|
|
54
|
+
readonly context: Readonly<Record<string, unknown>>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type PromptRenderError = MissingParameterError | RenderFailureError;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* A required parameter has no value at invoke time.
|
|
61
|
+
* `parameterName` is undefined if LiquidJS caught a variable that isn't
|
|
62
|
+
* even declared (typo, or template references a name we didn't validate
|
|
63
|
+
* at define time).
|
|
64
|
+
*/
|
|
65
|
+
export interface MissingParameterError {
|
|
66
|
+
readonly code: 'missing-parameter';
|
|
67
|
+
readonly message: string;
|
|
68
|
+
readonly parameterName?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Template failed to render for a reason other than a missing var. */
|
|
72
|
+
export interface RenderFailureError {
|
|
73
|
+
readonly code: 'render-failure';
|
|
74
|
+
readonly message: string;
|
|
75
|
+
readonly cause: unknown;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Render an agent's instructions template with the given context.
|
|
80
|
+
* Populates framework auto-vars, merges caller parameter values, and
|
|
81
|
+
* applies defaults for optional parameters. Missing required parameters
|
|
82
|
+
* are surfaced as `missing-parameter` before Liquid ever runs.
|
|
83
|
+
*
|
|
84
|
+
* Uses `strictVariables: true` so an unresolved `{{ var }}` is an error
|
|
85
|
+
* — never a silent empty string. This is load-bearing for correctness
|
|
86
|
+
* (a system prompt with a missing firm name is worse than a hard fail).
|
|
87
|
+
*/
|
|
88
|
+
export function renderInstructions(
|
|
89
|
+
agent: Agent,
|
|
90
|
+
context: RenderContext,
|
|
91
|
+
):
|
|
92
|
+
| { readonly ok: true; readonly value: RenderResult }
|
|
93
|
+
| { readonly ok: false; readonly error: PromptRenderError } {
|
|
94
|
+
const declared = agent.parameters ?? [];
|
|
95
|
+
const missing = requiredMissing(declared, context.parameters);
|
|
96
|
+
if (missing.length > 0) {
|
|
97
|
+
return {
|
|
98
|
+
ok: false,
|
|
99
|
+
error: {
|
|
100
|
+
code: 'missing-parameter',
|
|
101
|
+
message: `Required parameter${missing.length === 1 ? '' : 's'} missing: ${missing.join(', ')}`,
|
|
102
|
+
...(missing.length === 1 && missing[0] !== undefined && { parameterName: missing[0] }),
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const merged = mergeContext(agent, declared, context);
|
|
108
|
+
const liquid = new Liquid({ strictVariables: true, strictFilters: true });
|
|
109
|
+
try {
|
|
110
|
+
const rendered = liquid.parseAndRenderSync(agent.instructions, merged);
|
|
111
|
+
return { ok: true, value: { rendered, context: merged } };
|
|
112
|
+
} catch (cause) {
|
|
113
|
+
// LiquidJS throws UndefinedVariableError for unresolved refs — surface
|
|
114
|
+
// as missing-parameter so callers get one consistent error code.
|
|
115
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
116
|
+
if (message.includes('undefined variable')) {
|
|
117
|
+
const nameMatch = /undefined variable: ([\w.]+)/.exec(message);
|
|
118
|
+
return {
|
|
119
|
+
ok: false,
|
|
120
|
+
error: {
|
|
121
|
+
code: 'missing-parameter',
|
|
122
|
+
message: `Template references an unresolved variable: ${nameMatch?.[1] ?? '?'}`,
|
|
123
|
+
...(nameMatch?.[1] !== undefined && { parameterName: nameMatch[1] }),
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
ok: false,
|
|
129
|
+
error: {
|
|
130
|
+
code: 'render-failure',
|
|
131
|
+
message: `Template render failed: ${message}`,
|
|
132
|
+
cause,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function requiredMissing(
|
|
139
|
+
declared: readonly PromptParameter[],
|
|
140
|
+
supplied: Readonly<Record<string, unknown>>,
|
|
141
|
+
): string[] {
|
|
142
|
+
const missing: string[] = [];
|
|
143
|
+
for (const p of declared) {
|
|
144
|
+
const isRequired = p.required !== false;
|
|
145
|
+
if (!isRequired) continue;
|
|
146
|
+
if (p.default !== undefined) continue;
|
|
147
|
+
if (!(p.name in supplied) || supplied[p.name] === undefined) missing.push(p.name);
|
|
148
|
+
}
|
|
149
|
+
return missing;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function mergeContext(
|
|
153
|
+
agent: Agent,
|
|
154
|
+
declared: readonly PromptParameter[],
|
|
155
|
+
context: RenderContext,
|
|
156
|
+
): Record<string, unknown> {
|
|
157
|
+
const clock = context.clock ?? (() => new Date());
|
|
158
|
+
const at = clock();
|
|
159
|
+
const today = at.toISOString().slice(0, 10);
|
|
160
|
+
const now = at.toISOString();
|
|
161
|
+
|
|
162
|
+
// Start with declared defaults, layer caller-supplied on top so
|
|
163
|
+
// callers can override defaults explicitly.
|
|
164
|
+
const values: Record<string, unknown> = {};
|
|
165
|
+
for (const p of declared) {
|
|
166
|
+
if (p.default !== undefined) values[p.name] = p.default;
|
|
167
|
+
}
|
|
168
|
+
for (const [k, v] of Object.entries(context.parameters)) values[k] = v;
|
|
169
|
+
|
|
170
|
+
values.today = today;
|
|
171
|
+
values.now = now;
|
|
172
|
+
values.agent = {
|
|
173
|
+
id: agent.id,
|
|
174
|
+
name: agent.name,
|
|
175
|
+
version: agent.version,
|
|
176
|
+
};
|
|
177
|
+
if (context.conversation !== undefined) {
|
|
178
|
+
values.conversation = {
|
|
179
|
+
id: context.conversation.id,
|
|
180
|
+
turn: context.conversation.turn,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
if (context.input !== undefined) values.input = context.input;
|
|
184
|
+
return values;
|
|
185
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
KeyProvider,
|
|
6
|
+
Provenance,
|
|
7
|
+
ProvenanceBuilder,
|
|
8
|
+
ProvenanceEmitBinding,
|
|
9
|
+
} from '@kindgi/provenance';
|
|
10
|
+
import { signProvenance } from '@kindgi/provenance';
|
|
11
|
+
import type { Result } from '@kindgi/types';
|
|
12
|
+
|
|
13
|
+
import type { PersistenceError } from './errors.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Bindings the provenance emission flow consumes. All optional — if
|
|
17
|
+
* `newBuilder` isn't set, provenance is skipped entirely.
|
|
18
|
+
*/
|
|
19
|
+
export interface ProvenanceBindings {
|
|
20
|
+
/**
|
|
21
|
+
* Factory the runtime calls at turn start to construct a fresh
|
|
22
|
+
* builder. Callers supply this so they control id + flowRef +
|
|
23
|
+
* schema version.
|
|
24
|
+
*/
|
|
25
|
+
readonly newBuilder?: (opts: {
|
|
26
|
+
readonly runId: string;
|
|
27
|
+
readonly tenantId: string;
|
|
28
|
+
}) => ProvenanceBuilder;
|
|
29
|
+
/**
|
|
30
|
+
* When true, the DAG persists via `emitBinding.emit(...)` after a
|
|
31
|
+
* successful turn. Failed turns still build the DAG but don't emit —
|
|
32
|
+
* the caller sees them via the streaming event stream.
|
|
33
|
+
*/
|
|
34
|
+
readonly emit?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Caller-plugged emit surface. Required whenever `emit` is true.
|
|
37
|
+
* The Kindgi runtime supplies a Postgres-backed implementation;
|
|
38
|
+
* any conforming object works.
|
|
39
|
+
*/
|
|
40
|
+
readonly emitBinding?: ProvenanceEmitBinding;
|
|
41
|
+
/**
|
|
42
|
+
* When set, `signProvenance` is called with this key provider before
|
|
43
|
+
* emit. Unsigned DAGs still emit but forensic verification can't
|
|
44
|
+
* chain back to a keyId.
|
|
45
|
+
*/
|
|
46
|
+
readonly keyProvider?: KeyProvider;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Persist a completed provenance DAG. Signs first if a key provider is
|
|
51
|
+
* bound. Failures surface as `persistence-error`; upstream turn logic
|
|
52
|
+
* decides how much to bail out — usually the turn already succeeded so
|
|
53
|
+
* this is best-effort.
|
|
54
|
+
*/
|
|
55
|
+
export async function persistProvenance(
|
|
56
|
+
builder: ProvenanceBuilder,
|
|
57
|
+
bindings: ProvenanceBindings,
|
|
58
|
+
): Promise<Result<Provenance, PersistenceError>> {
|
|
59
|
+
let provenance: Provenance = builder.snapshot();
|
|
60
|
+
if (bindings.keyProvider !== undefined) {
|
|
61
|
+
const signed = signProvenance(provenance, bindings.keyProvider);
|
|
62
|
+
if (signed.kind === 'err') {
|
|
63
|
+
return {
|
|
64
|
+
kind: 'err',
|
|
65
|
+
error: {
|
|
66
|
+
code: 'persistence-error',
|
|
67
|
+
message: `Failed to sign provenance: ${signed.error.message}`,
|
|
68
|
+
cause: signed.error,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
provenance = signed.value;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (bindings.emit === true) {
|
|
76
|
+
if (bindings.emitBinding === undefined) {
|
|
77
|
+
return {
|
|
78
|
+
kind: 'err',
|
|
79
|
+
error: {
|
|
80
|
+
code: 'persistence-error',
|
|
81
|
+
message:
|
|
82
|
+
'Provenance emit=true but no emitBinding wired — pass a ProvenanceEmitBinding as bindings.provenance.emitBinding.',
|
|
83
|
+
cause: null,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const emitted = await bindings.emitBinding.emit(provenance);
|
|
88
|
+
if (emitted.kind === 'err') {
|
|
89
|
+
return {
|
|
90
|
+
kind: 'err',
|
|
91
|
+
error: {
|
|
92
|
+
code: 'persistence-error',
|
|
93
|
+
message: `Failed to emit provenance: ${emitted.error.message}`,
|
|
94
|
+
cause: emitted.error,
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return { kind: 'ok', value: provenance };
|
|
100
|
+
}
|
package/src/registry.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
|
|
4
|
+
import type { Result } from '@kindgi/types';
|
|
5
|
+
|
|
6
|
+
import type { AgentAlreadyRegisteredError, AgentNotFoundError } from './errors.js';
|
|
7
|
+
import type { Agent, AgentId } from './types.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* In-memory catalog of agent definitions. Keyed by `(id, version)`,
|
|
11
|
+
* because the same agent id at different versions is a different
|
|
12
|
+
* agent for provenance / audit purposes. `getLatest(id)` returns the
|
|
13
|
+
* highest semver registered under that id.
|
|
14
|
+
*
|
|
15
|
+
* The registry is per-tenant at the application layer — callers
|
|
16
|
+
* typically instantiate one registry per tenant boot. The registry
|
|
17
|
+
* itself does not enforce tenancy: it's a substrate primitive, and
|
|
18
|
+
* multi-tenant deployments layer RLS + tenant-scoped registries on
|
|
19
|
+
* top.
|
|
20
|
+
*/
|
|
21
|
+
export interface AgentRegistry {
|
|
22
|
+
register(agent: Agent): Result<void, AgentAlreadyRegisteredError>;
|
|
23
|
+
/**
|
|
24
|
+
* Look up an agent by id + version. If `version` is omitted, returns
|
|
25
|
+
* the latest semver registered under `id`.
|
|
26
|
+
*/
|
|
27
|
+
get(id: AgentId, version?: string): Result<Agent, AgentNotFoundError>;
|
|
28
|
+
/** Enumerate every version of every registered agent. */
|
|
29
|
+
list(): readonly Agent[];
|
|
30
|
+
/** Enumerate all versions registered under a given id, semver-sorted asc. */
|
|
31
|
+
listVersions(id: AgentId): readonly Agent[];
|
|
32
|
+
/**
|
|
33
|
+
* Latest semver for id, or `agent-not-found`.
|
|
34
|
+
*/
|
|
35
|
+
getLatest(id: AgentId): Result<Agent, AgentNotFoundError>;
|
|
36
|
+
/**
|
|
37
|
+
* Deregister a specific version. Rare — typically used for
|
|
38
|
+
* pack-uninstall flows. Returns `agent-not-found` on miss.
|
|
39
|
+
*/
|
|
40
|
+
unregister(id: AgentId, version: string): Result<void, AgentNotFoundError>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function createAgentRegistry(seed: readonly Agent[] = []): AgentRegistry {
|
|
44
|
+
const byIdVersion = new Map<string, Agent>();
|
|
45
|
+
|
|
46
|
+
function key(id: string, version: string): string {
|
|
47
|
+
return `${id}@${version}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function versionsForId(id: AgentId): Agent[] {
|
|
51
|
+
return [...byIdVersion.values()]
|
|
52
|
+
.filter((a) => a.id === id)
|
|
53
|
+
.sort((a, b) => compareSemver(a.version, b.version));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for (const agent of seed) byIdVersion.set(key(agent.id, agent.version), agent);
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
register(agent): Result<void, AgentAlreadyRegisteredError> {
|
|
60
|
+
const k = key(agent.id, agent.version);
|
|
61
|
+
if (byIdVersion.has(k)) {
|
|
62
|
+
return {
|
|
63
|
+
kind: 'err',
|
|
64
|
+
error: {
|
|
65
|
+
code: 'agent-already-registered',
|
|
66
|
+
message: `Agent "${agent.id}" version "${agent.version}" is already registered`,
|
|
67
|
+
agentId: agent.id,
|
|
68
|
+
version: agent.version,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
byIdVersion.set(k, agent);
|
|
73
|
+
return { kind: 'ok', value: undefined };
|
|
74
|
+
},
|
|
75
|
+
get(id, version): Result<Agent, AgentNotFoundError> {
|
|
76
|
+
if (version !== undefined) {
|
|
77
|
+
const hit = byIdVersion.get(key(id, version));
|
|
78
|
+
if (hit === undefined) {
|
|
79
|
+
return {
|
|
80
|
+
kind: 'err',
|
|
81
|
+
error: {
|
|
82
|
+
code: 'agent-not-found',
|
|
83
|
+
message: `No agent "${id}" at version "${version}"`,
|
|
84
|
+
agentId: id,
|
|
85
|
+
version,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return { kind: 'ok', value: hit };
|
|
90
|
+
}
|
|
91
|
+
const versions = versionsForId(id);
|
|
92
|
+
const latest = versions[versions.length - 1];
|
|
93
|
+
if (latest === undefined) {
|
|
94
|
+
return {
|
|
95
|
+
kind: 'err',
|
|
96
|
+
error: {
|
|
97
|
+
code: 'agent-not-found',
|
|
98
|
+
message: `No agent registered with id "${id}"`,
|
|
99
|
+
agentId: id,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return { kind: 'ok', value: latest };
|
|
104
|
+
},
|
|
105
|
+
list(): readonly Agent[] {
|
|
106
|
+
return [...byIdVersion.values()];
|
|
107
|
+
},
|
|
108
|
+
listVersions(id): readonly Agent[] {
|
|
109
|
+
return versionsForId(id);
|
|
110
|
+
},
|
|
111
|
+
getLatest(id): Result<Agent, AgentNotFoundError> {
|
|
112
|
+
const versions = versionsForId(id);
|
|
113
|
+
const latest = versions[versions.length - 1];
|
|
114
|
+
if (latest === undefined) {
|
|
115
|
+
return {
|
|
116
|
+
kind: 'err',
|
|
117
|
+
error: {
|
|
118
|
+
code: 'agent-not-found',
|
|
119
|
+
message: `No agent registered with id "${id}"`,
|
|
120
|
+
agentId: id,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return { kind: 'ok', value: latest };
|
|
125
|
+
},
|
|
126
|
+
unregister(id, version): Result<void, AgentNotFoundError> {
|
|
127
|
+
const k = key(id, version);
|
|
128
|
+
if (!byIdVersion.has(k)) {
|
|
129
|
+
return {
|
|
130
|
+
kind: 'err',
|
|
131
|
+
error: {
|
|
132
|
+
code: 'agent-not-found',
|
|
133
|
+
message: `No agent "${id}" at version "${version}" to unregister`,
|
|
134
|
+
agentId: id,
|
|
135
|
+
version,
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
byIdVersion.delete(k);
|
|
140
|
+
return { kind: 'ok', value: undefined };
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Compare two semver strings. Returns negative if a < b, positive if
|
|
147
|
+
* a > b, 0 if equal. Handles pre-release tags by treating any tagged
|
|
148
|
+
* version as lower than the equivalent untagged version — matches
|
|
149
|
+
* semver.org §11.
|
|
150
|
+
*/
|
|
151
|
+
function compareSemver(a: string, b: string): number {
|
|
152
|
+
const [aBase, aPre] = a.split('-', 2);
|
|
153
|
+
const [bBase, bPre] = b.split('-', 2);
|
|
154
|
+
const aParts = (aBase ?? '0.0.0').split('.').map(Number);
|
|
155
|
+
const bParts = (bBase ?? '0.0.0').split('.').map(Number);
|
|
156
|
+
for (let i = 0; i < 3; i++) {
|
|
157
|
+
const diff = (aParts[i] ?? 0) - (bParts[i] ?? 0);
|
|
158
|
+
if (diff !== 0) return diff;
|
|
159
|
+
}
|
|
160
|
+
if (aPre === undefined && bPre === undefined) return 0;
|
|
161
|
+
if (aPre === undefined) return 1;
|
|
162
|
+
if (bPre === undefined) return -1;
|
|
163
|
+
return aPre.localeCompare(bPre);
|
|
164
|
+
}
|
package/src/retrieval.ts
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
|
|
4
|
+
import type { EmbeddingProviderRegistry } from '@kindgi/embedding';
|
|
5
|
+
import type { Fact, MemoryQueryBinding, RetrievalHit } from '@kindgi/memory';
|
|
6
|
+
import type { ProjectId, Result, TenantId, ThreadId } from '@kindgi/types';
|
|
7
|
+
|
|
8
|
+
import type { AgentError, PersistenceError } from './errors.js';
|
|
9
|
+
import type {
|
|
10
|
+
Agent,
|
|
11
|
+
Conversation,
|
|
12
|
+
ConversationId,
|
|
13
|
+
RetrievalIntent,
|
|
14
|
+
RetrievedFact,
|
|
15
|
+
} from './types.js';
|
|
16
|
+
|
|
17
|
+
/** Bindings the retrieval flow consumes. `memory` is required; the
|
|
18
|
+
* rest are optional — without an embedding registry, the semantic
|
|
19
|
+
* part of an intent is silently skipped. `embeddingModel` picks a
|
|
20
|
+
* specific registered provider; omit to fall back to the registry's
|
|
21
|
+
* sole provider. */
|
|
22
|
+
export interface RetrievalBindings {
|
|
23
|
+
/**
|
|
24
|
+
* Caller-plugged data-access surface for memory reads. Every listFacts /
|
|
25
|
+
* searchByKeyword / searchBySemantic call inside `runRetrievals` routes
|
|
26
|
+
* through this binding — the runtime never touches a database client directly.
|
|
27
|
+
*/
|
|
28
|
+
readonly memory: MemoryQueryBinding;
|
|
29
|
+
readonly embeddingRegistry?: EmbeddingProviderRegistry;
|
|
30
|
+
readonly embeddingModel?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Execute every retrieval intent declared on the agent against the
|
|
35
|
+
* current conversation's scope. Returns retrieved facts paired with
|
|
36
|
+
* the intent that pulled them, so downstream provenance can attribute
|
|
37
|
+
* each fact to its retrieval declaration.
|
|
38
|
+
*
|
|
39
|
+
* Retrieval modes:
|
|
40
|
+
* - `mode: 'keyword'` — `searchByKeyword` with the user's message
|
|
41
|
+
* as the query (full-text search in the memory implementation).
|
|
42
|
+
* - `mode: 'semantic'` — `searchBySemantic` with the user's message
|
|
43
|
+
* embedded. Requires `bindings.embeddingRegistry`; without one,
|
|
44
|
+
* the semantic search is silently skipped.
|
|
45
|
+
* - `mode: 'both'` — run both, merge results, dedup by fact id
|
|
46
|
+
* with keyword score preserved.
|
|
47
|
+
* - `mode` omitted — `listFacts` scoped + typed, latest-first.
|
|
48
|
+
* No query needed; useful for "always pull the current
|
|
49
|
+
* working-memory snapshot."
|
|
50
|
+
*/
|
|
51
|
+
export async function runRetrievals(
|
|
52
|
+
agent: Agent,
|
|
53
|
+
conversation: Conversation,
|
|
54
|
+
conversationId: ConversationId,
|
|
55
|
+
userMessage: string,
|
|
56
|
+
bindings: RetrievalBindings,
|
|
57
|
+
): Promise<Result<readonly RetrievedFact[], AgentError>> {
|
|
58
|
+
const out: RetrievedFact[] = [];
|
|
59
|
+
for (const intent of agent.retrieval) {
|
|
60
|
+
for (const type of intent.types) {
|
|
61
|
+
const one = await runOneIntent(
|
|
62
|
+
conversation,
|
|
63
|
+
conversationId,
|
|
64
|
+
userMessage,
|
|
65
|
+
intent,
|
|
66
|
+
type,
|
|
67
|
+
bindings,
|
|
68
|
+
);
|
|
69
|
+
if (one.kind === 'err') return one;
|
|
70
|
+
out.push(...one.value);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { kind: 'ok', value: out };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Format retrieved facts as a system-role message the model can read
|
|
78
|
+
* as background context. Kept structural (fact headers + JSON content)
|
|
79
|
+
* rather than freeform prose — models handle discriminable fact
|
|
80
|
+
* boundaries better than blended narrative.
|
|
81
|
+
*/
|
|
82
|
+
export function formatRetrievedForPrompt(facts: readonly RetrievedFact[]): string {
|
|
83
|
+
if (facts.length === 0) return '';
|
|
84
|
+
const parts: string[] = ['[Retrieved context — background facts for this turn]', ''];
|
|
85
|
+
for (const { fact, intent, score } of facts) {
|
|
86
|
+
const header = `## Fact type=${fact.type} id=${fact.id} version=${fact.version}${score !== undefined ? ` score=${score.toFixed(3)}` : ''} scope=${intent.scope}`;
|
|
87
|
+
parts.push(header);
|
|
88
|
+
parts.push(stringifyContent(fact.content));
|
|
89
|
+
parts.push('');
|
|
90
|
+
}
|
|
91
|
+
return parts.join('\n').trim();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ============ internals ============
|
|
95
|
+
|
|
96
|
+
async function runOneIntent(
|
|
97
|
+
conversation: Conversation,
|
|
98
|
+
conversationId: ConversationId,
|
|
99
|
+
userMessage: string,
|
|
100
|
+
intent: RetrievalIntent,
|
|
101
|
+
type: string,
|
|
102
|
+
bindings: RetrievalBindings,
|
|
103
|
+
): Promise<Result<readonly RetrievedFact[], PersistenceError>> {
|
|
104
|
+
const limit = intent.limit ?? 10;
|
|
105
|
+
const tenantId = conversation.tenantId;
|
|
106
|
+
const scope = scopeFilter(intent, conversation, conversationId);
|
|
107
|
+
|
|
108
|
+
if (intent.mode === undefined) {
|
|
109
|
+
return await listMode(bindings.memory, tenantId, type, scope, limit, intent);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const doKeyword = intent.mode === 'keyword' || intent.mode === 'both';
|
|
113
|
+
const doSemantic =
|
|
114
|
+
(intent.mode === 'semantic' || intent.mode === 'both') &&
|
|
115
|
+
bindings.embeddingRegistry !== undefined;
|
|
116
|
+
|
|
117
|
+
const results: RetrievedFact[] = [];
|
|
118
|
+
if (doKeyword) {
|
|
119
|
+
const kw = await bindings.memory.searchByKeyword({
|
|
120
|
+
tenantId,
|
|
121
|
+
query: userMessage,
|
|
122
|
+
type,
|
|
123
|
+
...(scope !== undefined && { scope }),
|
|
124
|
+
topK: limit,
|
|
125
|
+
});
|
|
126
|
+
if (kw.kind === 'err') return persistErr('runOneIntent.keyword', kw.error);
|
|
127
|
+
results.push(...kw.value.map((h) => hitToRetrieved(h, intent)));
|
|
128
|
+
}
|
|
129
|
+
if (doSemantic && bindings.embeddingRegistry !== undefined) {
|
|
130
|
+
const sem = await bindings.memory.searchBySemantic({
|
|
131
|
+
tenantId,
|
|
132
|
+
query: userMessage,
|
|
133
|
+
embeddingRegistry: bindings.embeddingRegistry,
|
|
134
|
+
...(bindings.embeddingModel !== undefined && { embeddingModel: bindings.embeddingModel }),
|
|
135
|
+
type,
|
|
136
|
+
...(scope !== undefined && { scope }),
|
|
137
|
+
topK: limit,
|
|
138
|
+
});
|
|
139
|
+
if (sem.kind === 'err') return persistErr('runOneIntent.semantic', sem.error);
|
|
140
|
+
results.push(...sem.value.map((h) => hitToRetrieved(h, intent)));
|
|
141
|
+
}
|
|
142
|
+
return { kind: 'ok', value: dedupById(results).slice(0, limit) };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function listMode(
|
|
146
|
+
memory: MemoryQueryBinding,
|
|
147
|
+
tenantId: TenantId,
|
|
148
|
+
type: string,
|
|
149
|
+
scope: Partial<import('@kindgi/memory').MemoryScope> | undefined,
|
|
150
|
+
limit: number,
|
|
151
|
+
intent: RetrievalIntent,
|
|
152
|
+
): Promise<Result<readonly RetrievedFact[], PersistenceError>> {
|
|
153
|
+
const listed = await memory.listFacts({
|
|
154
|
+
tenantId,
|
|
155
|
+
type,
|
|
156
|
+
...(scope !== undefined && { scope }),
|
|
157
|
+
limit,
|
|
158
|
+
latestOnly: true,
|
|
159
|
+
});
|
|
160
|
+
if (listed.kind === 'err') return persistErr('runOneIntent.list', listed.error);
|
|
161
|
+
return {
|
|
162
|
+
kind: 'ok',
|
|
163
|
+
value: listed.value.map((f) => ({ fact: f as Fact<unknown>, intent })),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function scopeFilter(
|
|
168
|
+
intent: RetrievalIntent,
|
|
169
|
+
conversation: Conversation,
|
|
170
|
+
conversationId: ConversationId,
|
|
171
|
+
): Partial<import('@kindgi/memory').MemoryScope> | undefined {
|
|
172
|
+
if (intent.scope === 'same-conversation') {
|
|
173
|
+
return { threadId: conversationId as unknown as ThreadId };
|
|
174
|
+
}
|
|
175
|
+
if (intent.scope === 'same-project') {
|
|
176
|
+
const projectId = conversation.scope.projectId;
|
|
177
|
+
// If the conversation has no project scope, treat as tenant-wide
|
|
178
|
+
// rather than empty-result — the intent still applies conceptually.
|
|
179
|
+
if (projectId === undefined) return undefined;
|
|
180
|
+
return { projectId: projectId as ProjectId };
|
|
181
|
+
}
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function hitToRetrieved(hit: RetrievalHit<unknown>, intent: RetrievalIntent): RetrievedFact {
|
|
186
|
+
return {
|
|
187
|
+
fact: hit.fact,
|
|
188
|
+
intent,
|
|
189
|
+
score: hit.score,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function dedupById(facts: readonly RetrievedFact[]): readonly RetrievedFact[] {
|
|
194
|
+
const seen = new Set<string>();
|
|
195
|
+
const out: RetrievedFact[] = [];
|
|
196
|
+
for (const f of facts) {
|
|
197
|
+
if (seen.has(f.fact.id)) continue;
|
|
198
|
+
seen.add(f.fact.id);
|
|
199
|
+
out.push(f);
|
|
200
|
+
}
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function stringifyContent(content: unknown): string {
|
|
205
|
+
if (content === undefined) return '(no content)';
|
|
206
|
+
if (typeof content === 'string') return content;
|
|
207
|
+
return JSON.stringify(content, null, 2);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function persistErr(step: string, cause: unknown): Result<never, PersistenceError> {
|
|
211
|
+
return {
|
|
212
|
+
kind: 'err',
|
|
213
|
+
error: {
|
|
214
|
+
code: 'persistence-error',
|
|
215
|
+
message: `Agents operation "${step}" failed`,
|
|
216
|
+
cause,
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|