melchizedek-agents 1.7.0 → 1.10.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/CHANGELOG.md +161 -1
- package/DOCUMENTATION.md +77 -10
- package/README.md +2 -0
- package/config/agents/syndicate.schema.json +69 -0
- package/config/agents/syndicateSchema.yaml +17 -0
- package/db/hardening.sql +3 -1
- package/db/migrations/0016_span_tree.sql +52 -0
- package/db/migrations/0017_cache_pricing.sql +146 -0
- package/db/migrations/0018_queue_view.sql +82 -0
- package/db/telemetry.sql +36 -8
- package/dist/lib/a2a/app.d.ts +26 -4
- package/dist/lib/a2a/app.js +41 -6
- package/dist/lib/a2a/executor.d.ts +28 -1
- package/dist/lib/a2a/executor.js +65 -2
- package/dist/lib/a2a/feedback.d.ts +121 -0
- package/dist/lib/a2a/feedback.js +219 -0
- package/dist/lib/a2a/policy.d.ts +55 -5
- package/dist/lib/a2a/policy.js +56 -12
- package/dist/lib/compile.d.ts +2 -0
- package/dist/lib/compile.js +2 -0
- package/dist/lib/compileNative.js +2 -0
- package/dist/lib/doctor.d.ts +10 -0
- package/dist/lib/doctor.js +33 -4
- package/dist/lib/index.d.ts +6 -2
- package/dist/lib/index.js +3 -0
- package/dist/lib/loadSyndicate.d.ts +14 -0
- package/dist/lib/loadSyndicate.js +22 -5
- package/dist/lib/memory/providers.js +7 -3
- package/dist/lib/observability/lineage.d.ts +19 -0
- package/dist/lib/observability/lineage.js +31 -0
- package/dist/lib/observability/metadata.d.ts +69 -0
- package/dist/lib/observability/metadata.js +153 -0
- package/dist/lib/observability/otlpFilter.js +1 -0
- package/dist/lib/observability/supabaseSpanExporter.d.ts +31 -2
- package/dist/lib/observability/supabaseSpanExporter.js +45 -6
- package/dist/lib/observability/tracer.d.ts +17 -1
- package/dist/lib/observability/tracer.js +61 -3
- package/dist/lib/project.d.ts +116 -0
- package/dist/lib/project.js +274 -0
- package/dist/lib/runtime/native/compaction.js +7 -3
- package/dist/lib/runtime/native/request.d.ts +2 -0
- package/dist/lib/runtime/native/step.js +1 -1
- package/dist/lib/runtime/native/telemetry.d.ts +10 -3
- package/dist/lib/runtime/native/telemetry.js +49 -4
- package/dist/lib/runtime/syndicateTurn.js +5 -1
- package/dist/lib/storage/postgres/index.d.ts +10 -0
- package/dist/lib/storage/postgres/index.js +6 -1
- package/dist/lib/syndicateSchema.d.ts +5 -0
- package/dist/lib/syndicateSchema.js +13 -0
- package/dist/lib/tools/taskTools.d.ts +6 -0
- package/dist/lib/tools/taskTools.js +24 -5
- package/dist/lib/workflow/toolNode.d.ts +1 -0
- package/dist/lib/workflow/toolNode.js +1 -1
- package/dist/scripts/a2a_server.js +3 -3
- package/dist/scripts/doctor.js +2 -1
- package/dist/scripts/mcp_server.js +2 -1
- package/package.json +6 -4
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/project.ts — the project manifest, `melchizedek.json` (ADR 0138).
|
|
3
|
+
*
|
|
4
|
+
* A project is a folder, usually a git repository, with a `melchizedek.json`
|
|
5
|
+
* at its root. The manifest records what the engine cannot infer from the
|
|
6
|
+
* files: which folder holds the syndicates, which engine version the project
|
|
7
|
+
* is written for, and how each file ships. It holds names, never values: an
|
|
8
|
+
* `env` entry is a variable name, and no field may carry a secret.
|
|
9
|
+
*
|
|
10
|
+
* `findProject()` walks up from a directory to the nearest manifest and stops
|
|
11
|
+
* at the repository root (the first directory holding `.git`) or the home
|
|
12
|
+
* directory, so a manifest outside the repository never steers it.
|
|
13
|
+
* `resolveAgentsDir()` is the one agents-directory chain every loader and bin
|
|
14
|
+
* uses: explicit option → MELCHIZEDEK_AGENTS_DIR → the manifest's `agents` →
|
|
15
|
+
* <cwd>/config/agents. A deployed server sets the variable, so the manifest
|
|
16
|
+
* only changes what a bare command run inside a project finds.
|
|
17
|
+
*
|
|
18
|
+
* Imports only node built-ins, zod and lib/paths.ts.
|
|
19
|
+
*/
|
|
20
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
21
|
+
import { homedir } from 'node:os';
|
|
22
|
+
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
23
|
+
import { z } from 'zod';
|
|
24
|
+
import { enginePackageRoot } from './paths.js';
|
|
25
|
+
export const PROJECT_MANIFEST = 'melchizedek.json';
|
|
26
|
+
export const MANIFEST_VERSION = 1;
|
|
27
|
+
/** A path relative to the project root: no absolute paths, no `..` segments. */
|
|
28
|
+
const relPath = z
|
|
29
|
+
.string()
|
|
30
|
+
.min(1)
|
|
31
|
+
.refine((p) => !/^([a-zA-Z]:)?[\\/]/.test(p) && !p.split(/[\\/]/).includes('..'), 'must be relative to the project root and stay inside it');
|
|
32
|
+
const envName = z.string().regex(/^[A-Z][A-Z0-9_]{0,63}$/, 'must be an environment variable name');
|
|
33
|
+
const syndicateRecord = z
|
|
34
|
+
.object({
|
|
35
|
+
deliverable: z.enum(['service', 'library', 'mcp']).optional(),
|
|
36
|
+
publish: z.object({ kind: z.literal('registry'), id: z.string().min(1) }).passthrough().optional(),
|
|
37
|
+
})
|
|
38
|
+
.passthrough();
|
|
39
|
+
export const projectManifestSchema = z
|
|
40
|
+
.object({
|
|
41
|
+
manifest: z.number().int().min(1),
|
|
42
|
+
engine: z.string().regex(/^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/, 'must be an exact version, e.g. 1.10.0'),
|
|
43
|
+
agents: relPath,
|
|
44
|
+
ignore: z.array(z.string().min(1)).optional(),
|
|
45
|
+
tests: relPath.optional(),
|
|
46
|
+
env: z.array(envName).optional(),
|
|
47
|
+
syndicates: z.record(z.string(), syndicateRecord).optional(),
|
|
48
|
+
registry: z
|
|
49
|
+
.object({ kind: z.literal('supabase'), url_env: envName.optional(), restart: z.string().optional() })
|
|
50
|
+
.passthrough()
|
|
51
|
+
.optional(),
|
|
52
|
+
production: z
|
|
53
|
+
.object({ ledger: z.object({ kind: z.literal('postgres'), url_env: envName }).passthrough().optional() })
|
|
54
|
+
.passthrough()
|
|
55
|
+
.optional(),
|
|
56
|
+
tools: z
|
|
57
|
+
.object({ remote: z.record(z.string(), z.object({ mcp_env: envName }).passthrough()).optional() })
|
|
58
|
+
.passthrough()
|
|
59
|
+
.optional(),
|
|
60
|
+
})
|
|
61
|
+
// Unknown keys are kept: a newer writer's fields survive an older reader.
|
|
62
|
+
.passthrough();
|
|
63
|
+
/** Thrown for a manifest that does not parse or validate; names the file and fields, never a value. */
|
|
64
|
+
export class ProjectManifestError extends Error {
|
|
65
|
+
file;
|
|
66
|
+
issues;
|
|
67
|
+
constructor(file, issues) {
|
|
68
|
+
super(`${file}: ${issues.join('; ')}`);
|
|
69
|
+
this.file = file;
|
|
70
|
+
this.issues = issues;
|
|
71
|
+
this.name = 'ProjectManifestError';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The nearest folder at or above `from` holding a manifest, without crossing
|
|
76
|
+
* the repository root or the home directory. Undefined when there is none.
|
|
77
|
+
*/
|
|
78
|
+
export function findProjectRoot(from = process.cwd()) {
|
|
79
|
+
const home = resolve(homedir());
|
|
80
|
+
let dir = resolve(from);
|
|
81
|
+
for (;;) {
|
|
82
|
+
if (existsSync(join(dir, PROJECT_MANIFEST)))
|
|
83
|
+
return dir;
|
|
84
|
+
if (existsSync(join(dir, '.git')) || dir === home)
|
|
85
|
+
return undefined;
|
|
86
|
+
const parent = dirname(dir);
|
|
87
|
+
if (parent === dir)
|
|
88
|
+
return undefined;
|
|
89
|
+
dir = parent;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** Parse and validate a manifest's text. Throws `ProjectManifestError`. */
|
|
93
|
+
export function parseProjectManifest(text, file = PROJECT_MANIFEST) {
|
|
94
|
+
let raw;
|
|
95
|
+
try {
|
|
96
|
+
raw = JSON.parse(text);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
throw new ProjectManifestError(file, ['not valid JSON']);
|
|
100
|
+
}
|
|
101
|
+
const parsed = projectManifestSchema.safeParse(raw);
|
|
102
|
+
if (!parsed.success) {
|
|
103
|
+
throw new ProjectManifestError(file, parsed.error.issues.map((i) => `${i.path.join('.') || '(root)'}: ${i.message}`));
|
|
104
|
+
}
|
|
105
|
+
if (parsed.data.manifest > MANIFEST_VERSION) {
|
|
106
|
+
throw new ProjectManifestError(file, [`manifest: version ${parsed.data.manifest} is newer than this engine reads (${MANIFEST_VERSION}); upgrade melchizedek-agents`]);
|
|
107
|
+
}
|
|
108
|
+
return parsed.data;
|
|
109
|
+
}
|
|
110
|
+
/** A path under the root, or an error when it would leave it. */
|
|
111
|
+
function under(root, rel, field, file) {
|
|
112
|
+
const abs = resolve(root, rel);
|
|
113
|
+
if (abs !== root && !abs.startsWith(root + sep))
|
|
114
|
+
throw new ProjectManifestError(file, [`${field}: resolves outside the project root`]);
|
|
115
|
+
return abs;
|
|
116
|
+
}
|
|
117
|
+
/** Load the project rooted at `root` (the folder holding the manifest). Throws `ProjectManifestError`. */
|
|
118
|
+
export function loadProject(root) {
|
|
119
|
+
const r = resolve(root);
|
|
120
|
+
const file = join(r, PROJECT_MANIFEST);
|
|
121
|
+
const manifest = parseProjectManifest(readFileSync(file, 'utf-8'), file);
|
|
122
|
+
return {
|
|
123
|
+
root: r,
|
|
124
|
+
file,
|
|
125
|
+
manifest,
|
|
126
|
+
agentsDir: under(r, manifest.agents, 'agents', file),
|
|
127
|
+
testsDir: under(r, manifest.tests ?? join('tests', 'agents'), 'tests', file),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/** The project at or above `from`, or undefined when there is none. Throws on a broken manifest. */
|
|
131
|
+
export function findProject(from = process.cwd()) {
|
|
132
|
+
const root = findProjectRoot(from);
|
|
133
|
+
return root ? loadProject(root) : undefined;
|
|
134
|
+
}
|
|
135
|
+
// The agents-directory chain runs on every syndicate read (an A2A route
|
|
136
|
+
// reads per request), so the manifest lookup is cached per working
|
|
137
|
+
// directory. A broken manifest is cached as "none" and reported by
|
|
138
|
+
// `melchizedek-doctor`, never by a request path.
|
|
139
|
+
const agentsDirCache = new Map();
|
|
140
|
+
/** Forget cached manifest lookups (tests, and an embedder that edits the manifest in-process). */
|
|
141
|
+
export function clearProjectCache() {
|
|
142
|
+
agentsDirCache.clear();
|
|
143
|
+
}
|
|
144
|
+
function manifestAgentsDir(cwd) {
|
|
145
|
+
if (!agentsDirCache.has(cwd)) {
|
|
146
|
+
let dir = null;
|
|
147
|
+
try {
|
|
148
|
+
dir = findProject(cwd)?.agentsDir ?? null;
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
dir = null;
|
|
152
|
+
}
|
|
153
|
+
agentsDirCache.set(cwd, dir);
|
|
154
|
+
}
|
|
155
|
+
return agentsDirCache.get(cwd) ?? undefined;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The agents directory: `explicit` → MELCHIZEDEK_AGENTS_DIR → the nearest
|
|
159
|
+
* manifest's `agents` → <cwd>/config/agents. Always absolute.
|
|
160
|
+
*/
|
|
161
|
+
export function resolveAgentsDir(explicit, env = process.env) {
|
|
162
|
+
const fromEnv = env.MELCHIZEDEK_AGENTS_DIR?.trim();
|
|
163
|
+
const cwd = process.cwd();
|
|
164
|
+
return resolve(explicit ?? (fromEnv || undefined) ?? manifestAgentsDir(cwd) ?? join(cwd, 'config', 'agents'));
|
|
165
|
+
}
|
|
166
|
+
/** Glob to RegExp for `*` (one segment), `**` (any depth) and `?`, on `/`-separated paths. */
|
|
167
|
+
function globToRegExp(glob) {
|
|
168
|
+
let re = '';
|
|
169
|
+
for (let i = 0; i < glob.length; i++) {
|
|
170
|
+
const c = glob[i];
|
|
171
|
+
if (c === '*') {
|
|
172
|
+
if (glob[i + 1] === '*') {
|
|
173
|
+
i++;
|
|
174
|
+
if (glob[i + 1] === '/') {
|
|
175
|
+
i++;
|
|
176
|
+
re += '(?:.*/)?';
|
|
177
|
+
}
|
|
178
|
+
else
|
|
179
|
+
re += '.*';
|
|
180
|
+
}
|
|
181
|
+
else
|
|
182
|
+
re += '[^/]*';
|
|
183
|
+
}
|
|
184
|
+
else if (c === '?')
|
|
185
|
+
re += '[^/]';
|
|
186
|
+
else
|
|
187
|
+
re += c.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
188
|
+
}
|
|
189
|
+
return new RegExp(`^${re}$`);
|
|
190
|
+
}
|
|
191
|
+
/** True when `relFromRoot` (a `/`-separated path relative to the project root) matches an `ignore` pattern. */
|
|
192
|
+
export function isIgnored(project, relFromRoot) {
|
|
193
|
+
const p = relFromRoot.split(sep).join('/');
|
|
194
|
+
return (project.manifest.ignore ?? []).some((g) => globToRegExp(g).test(p));
|
|
195
|
+
}
|
|
196
|
+
/** The syndicate files directly in the agents folder, as names relative to it, minus `ignore`. */
|
|
197
|
+
export function listProjectSyndicates(project) {
|
|
198
|
+
if (!existsSync(project.agentsDir))
|
|
199
|
+
return [];
|
|
200
|
+
const rel = relative(project.root, project.agentsDir);
|
|
201
|
+
return readdirSync(project.agentsDir)
|
|
202
|
+
.filter((f) => (f.endsWith('.yaml') || f.endsWith('.yml')) && f !== 'syndicateSchema.yaml')
|
|
203
|
+
.filter((f) => statSync(join(project.agentsDir, f)).isFile())
|
|
204
|
+
.filter((f) => !isIgnored(project, rel ? `${rel}/${f}` : f))
|
|
205
|
+
.sort();
|
|
206
|
+
}
|
|
207
|
+
/** The installed engine's package version (no commit suffix). */
|
|
208
|
+
export function enginePackageVersion() {
|
|
209
|
+
try {
|
|
210
|
+
const root = enginePackageRoot();
|
|
211
|
+
if (root)
|
|
212
|
+
return String(JSON.parse(readFileSync(join(root, 'package.json'), 'utf-8')).version);
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
/* fall through */
|
|
216
|
+
}
|
|
217
|
+
return '0.0.0';
|
|
218
|
+
}
|
|
219
|
+
/** What is wrong with a project: engine pin, missing folders, stale ignores, unset env names. Never reads a value. */
|
|
220
|
+
export function checkProject(project, running = enginePackageVersion(), env = process.env) {
|
|
221
|
+
const problems = [];
|
|
222
|
+
const warnings = [];
|
|
223
|
+
const pinned = project.manifest.engine;
|
|
224
|
+
const matches = pinned === running;
|
|
225
|
+
if (!matches)
|
|
226
|
+
problems.push(`engine: the project pins melchizedek-agents ${pinned}; ${running} is running`);
|
|
227
|
+
if (!existsSync(project.agentsDir))
|
|
228
|
+
problems.push(`agents: ${project.manifest.agents} does not exist`);
|
|
229
|
+
if (project.manifest.tests && !existsSync(project.testsDir))
|
|
230
|
+
problems.push(`tests: ${project.manifest.tests} does not exist`);
|
|
231
|
+
for (const g of project.manifest.ignore ?? []) {
|
|
232
|
+
if (!anyPathMatches(project.root, globToRegExp(g)))
|
|
233
|
+
warnings.push(`ignore: "${g}" matches nothing`);
|
|
234
|
+
}
|
|
235
|
+
for (const name of project.manifest.env ?? [])
|
|
236
|
+
if (!env[name]?.trim())
|
|
237
|
+
warnings.push(`env: ${name} is not set`);
|
|
238
|
+
const syndicates = listProjectSyndicates(project);
|
|
239
|
+
for (const f of Object.keys(project.manifest.syndicates ?? {})) {
|
|
240
|
+
if (!syndicates.includes(f))
|
|
241
|
+
warnings.push(`syndicates: ${f} is not in ${project.manifest.agents}`);
|
|
242
|
+
}
|
|
243
|
+
return { root: project.root, agentsDir: project.agentsDir, engine: { pinned, running, matches }, syndicates, problems, warnings };
|
|
244
|
+
}
|
|
245
|
+
const SKIP_DIRS = new Set(['.git', 'node_modules', 'venv', '.venv', '__pycache__', 'dist']);
|
|
246
|
+
/** Whether any path under root (depth-limited, skipping vendor folders) matches. */
|
|
247
|
+
function anyPathMatches(root, re, rel = '', depth = 0) {
|
|
248
|
+
if (depth > 6)
|
|
249
|
+
return false;
|
|
250
|
+
let entries;
|
|
251
|
+
try {
|
|
252
|
+
entries = readdirSync(join(root, rel));
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
for (const e of entries) {
|
|
258
|
+
if (SKIP_DIRS.has(e))
|
|
259
|
+
continue;
|
|
260
|
+
const p = rel ? `${rel}/${e}` : e;
|
|
261
|
+
if (re.test(p))
|
|
262
|
+
return true;
|
|
263
|
+
let dir = false;
|
|
264
|
+
try {
|
|
265
|
+
dir = statSync(join(root, p)).isDirectory();
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
/* unreadable entry */
|
|
269
|
+
}
|
|
270
|
+
if (dir && anyPathMatches(root, re, p, depth + 1))
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
@@ -144,11 +144,15 @@ export async function summarize(events, adapter, model, options = {}) {
|
|
|
144
144
|
const startTime = events[0].timestamp;
|
|
145
145
|
const endTime = events[events.length - 1].timestamp;
|
|
146
146
|
const request = summaryRequest(events, model, options.signal);
|
|
147
|
+
const sources = new WeakMap();
|
|
147
148
|
async function* inner() {
|
|
148
|
-
for await (const response of adapter.generate(request))
|
|
149
|
-
|
|
149
|
+
for await (const response of adapter.generate(request)) {
|
|
150
|
+
const mapped = modelResponseToLlmResponse(response);
|
|
151
|
+
sources.set(mapped, response);
|
|
152
|
+
yield mapped;
|
|
153
|
+
}
|
|
150
154
|
}
|
|
151
|
-
const responses = traceLlmGeneration({ provider: adapter.provider, model, request }, inner());
|
|
155
|
+
const responses = traceLlmGeneration({ provider: adapter.provider, model, request, source: (r) => sources.get(r) }, inner());
|
|
152
156
|
// As ADK: the first response must carry text; nothing after a failure is read.
|
|
153
157
|
const first = await responses.next();
|
|
154
158
|
const head = first.done ? undefined : first.value.content?.parts?.[0]?.text;
|
|
@@ -112,6 +112,8 @@ export interface NativeAgent {
|
|
|
112
112
|
mode?: 'task';
|
|
113
113
|
/** The session-state key the agent's final answer is saved under (the loop writes it, lib/runtime/native/agentLoop.ts). */
|
|
114
114
|
outputKey?: string;
|
|
115
|
+
/** The YAML's `metadata:` (ADR 0141): `meta.agent.<key>` attributes on the agent's `agent.invoke` span. */
|
|
116
|
+
metadata?: Record<string, string | number | boolean>;
|
|
115
117
|
/** `fallback_model:`: answers a provider-side failure of the agent's model (ADR 0044). The loop calls it as its own leaf adapter. */
|
|
116
118
|
fallbackModel?: string;
|
|
117
119
|
/** `context:` (ADR 0033): compact the history into a summary past a token threshold. The loop runs it before each step (compaction.ts). */
|
|
@@ -218,7 +218,7 @@ export async function runModelStep(options) {
|
|
|
218
218
|
return { ...result, event: stored, error: { code, message, retryable: false } };
|
|
219
219
|
}
|
|
220
220
|
async function readResponses() {
|
|
221
|
-
for await (const traced of traceLlmGeneration({ provider: adapter.provider, model: request.model, request }, inner())) {
|
|
221
|
+
for await (const traced of traceLlmGeneration({ provider: adapter.provider, model: request.model, request, source: (r) => sources.get(r) }, inner())) {
|
|
222
222
|
if (signal?.aborted)
|
|
223
223
|
return { ...result, stopped: stopOf() };
|
|
224
224
|
const source = sources.get(traced);
|
|
@@ -30,8 +30,12 @@
|
|
|
30
30
|
* adapter's final response). A failed step carries none: its llm.request
|
|
31
31
|
* span carries the failed call's payload, as under ADK, where a failed
|
|
32
32
|
* call_llm never ended. With TELEMETRY_PAYLOADS=off no payload is
|
|
33
|
-
* recorded at all.
|
|
34
|
-
*
|
|
33
|
+
* recorded at all. A tool span carries its call's arguments (`tool.args`)
|
|
34
|
+
* and the start of its result (`tool.result`), each as JSON, redacted by
|
|
35
|
+
* the telemetry redactor and cut at TOOL_ATTRIBUTE_MAX_CHARS
|
|
36
|
+
* (`tool.args_truncated` / `tool.result_truncated` when cut), so the
|
|
37
|
+
* ledger's span tree shows a call without the payload tier (ADR 0139). The
|
|
38
|
+
* root span's ToolCall/ToolResponse events still hold the whole of both.
|
|
35
39
|
*
|
|
36
40
|
* The loop calls three hooks (agentLoop.ts): traceAgentInvocation around the
|
|
37
41
|
* whole run, traceModelCall around each step, traceToolCall around each call.
|
|
@@ -41,8 +45,10 @@ import type { AgentLoopContext, AgentLoopEnd } from './agentLoop.ts';
|
|
|
41
45
|
import type { NativeAgent } from './request.ts';
|
|
42
46
|
import type { ModelStepResult } from './step.ts';
|
|
43
47
|
import type { NodeResult, TracedNode } from '../../workflow/scheduler.ts';
|
|
48
|
+
/** The most a tool span's `tool.args` or `tool.result` holds: the first 8 KB of its redacted JSON (ADR 0139). */
|
|
49
|
+
export declare const TOOL_ATTRIBUTE_MAX_CHARS = 8192;
|
|
44
50
|
/** The agent's run as an `agent.invoke <name>` span, opened when the run starts. */
|
|
45
|
-
export declare function traceAgentInvocation(agent: Pick<NativeAgent, 'name' | 'description'>, ctx: Pick<AgentLoopContext, 'session' | 'invocationId'>, run: () => AsyncGenerator<TurnEvent, AgentLoopEnd>): AsyncGenerator<TurnEvent, AgentLoopEnd>;
|
|
51
|
+
export declare function traceAgentInvocation(agent: Pick<NativeAgent, 'name' | 'description' | 'metadata'>, ctx: Pick<AgentLoopContext, 'session' | 'invocationId'>, run: () => AsyncGenerator<TurnEvent, AgentLoopEnd>): AsyncGenerator<TurnEvent, AgentLoopEnd>;
|
|
46
52
|
/**
|
|
47
53
|
* One model step as a `model.call` span. `step` gets the loop's context with
|
|
48
54
|
* an `adapterFor` that notes the adapter it hands out, so the span names the
|
|
@@ -72,4 +78,5 @@ export declare function traceNodeExecution(node: TracedNode, run: () => Promise<
|
|
|
72
78
|
export declare function traceToolNodeCall<T>(call: {
|
|
73
79
|
id: string;
|
|
74
80
|
name: string;
|
|
81
|
+
args?: unknown;
|
|
75
82
|
}, tool: unknown, run: () => Promise<T>): Promise<T>;
|
|
@@ -30,8 +30,12 @@
|
|
|
30
30
|
* adapter's final response). A failed step carries none: its llm.request
|
|
31
31
|
* span carries the failed call's payload, as under ADK, where a failed
|
|
32
32
|
* call_llm never ended. With TELEMETRY_PAYLOADS=off no payload is
|
|
33
|
-
* recorded at all.
|
|
34
|
-
*
|
|
33
|
+
* recorded at all. A tool span carries its call's arguments (`tool.args`)
|
|
34
|
+
* and the start of its result (`tool.result`), each as JSON, redacted by
|
|
35
|
+
* the telemetry redactor and cut at TOOL_ATTRIBUTE_MAX_CHARS
|
|
36
|
+
* (`tool.args_truncated` / `tool.result_truncated` when cut), so the
|
|
37
|
+
* ledger's span tree shows a call without the payload tier (ADR 0139). The
|
|
38
|
+
* root span's ToolCall/ToolResponse events still hold the whole of both.
|
|
35
39
|
*
|
|
36
40
|
* The loop calls three hooks (agentLoop.ts): traceAgentInvocation around the
|
|
37
41
|
* whole run, traceModelCall around each step, traceToolCall around each call.
|
|
@@ -40,9 +44,39 @@ import { SpanStatusCode, context, trace } from '@opentelemetry/api';
|
|
|
40
44
|
import { resolveAdapter } from '../../models/registry.js';
|
|
41
45
|
import { RUNTIME_SPAN_SCOPE } from '../../observability/lineage.js';
|
|
42
46
|
import { payloadPolicyFromEnv } from '../../observability/supabaseSpanExporter.js';
|
|
47
|
+
import { telemetryRedactor } from '../../observability/redact.js';
|
|
48
|
+
import { META_AGENT_PREFIX, metaAttributes } from '../../observability/metadata.js';
|
|
43
49
|
import { startEngineSpan } from '../../observability/tracer.js';
|
|
44
50
|
/** The most a payload attribute holds, as for a failed call's (lib/observability/tracer.ts). */
|
|
45
51
|
const PAYLOAD_MAX_CHARS = 200_000;
|
|
52
|
+
/** The most a tool span's `tool.args` or `tool.result` holds: the first 8 KB of its redacted JSON (ADR 0139). */
|
|
53
|
+
export const TOOL_ATTRIBUTE_MAX_CHARS = 8_192;
|
|
54
|
+
/**
|
|
55
|
+
* Sets `key` to `value` as JSON, redacted first (so a cut never splits a
|
|
56
|
+
* secret the redactor would have caught) and then cut to the cap, with
|
|
57
|
+
* `<key>_truncated` when it was cut. Nothing on a span that records nothing.
|
|
58
|
+
*/
|
|
59
|
+
function setToolContent(span, key, value) {
|
|
60
|
+
if (!span.isRecording() || value === undefined)
|
|
61
|
+
return;
|
|
62
|
+
let json;
|
|
63
|
+
try {
|
|
64
|
+
json = typeof value === 'string' ? value : (JSON.stringify(value) ?? String(value));
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
json = String(value);
|
|
68
|
+
}
|
|
69
|
+
const redactor = telemetryRedactor();
|
|
70
|
+
if (redactor)
|
|
71
|
+
json = redactor(json);
|
|
72
|
+
if (json.length > TOOL_ATTRIBUTE_MAX_CHARS) {
|
|
73
|
+
span.setAttribute(key, json.slice(0, TOOL_ATTRIBUTE_MAX_CHARS));
|
|
74
|
+
span.setAttribute(`${key}_truncated`, true);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
span.setAttribute(key, json);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
46
80
|
/** A loop span in scope melchizedek.runtime; a non-recording one in a turn that opted out of tracing. */
|
|
47
81
|
const startRuntimeSpan = (name, options) => startEngineSpan(RUNTIME_SPAN_SCOPE, name, options);
|
|
48
82
|
/** JSON that never throws and never exceeds the cap. */
|
|
@@ -93,6 +127,8 @@ export async function* traceAgentInvocation(agent, ctx, run) {
|
|
|
93
127
|
'gen_ai.agent.description': agent.description ?? '',
|
|
94
128
|
'gen_ai.conversation.id': ctx.session.id,
|
|
95
129
|
'adk.invocation_id': ctx.invocationId,
|
|
130
|
+
// The YAML's agent metadata (ADR 0141), redacted as every ledger value is.
|
|
131
|
+
...metaAttributes(agent.metadata, META_AGENT_PREFIX),
|
|
96
132
|
},
|
|
97
133
|
});
|
|
98
134
|
try {
|
|
@@ -158,8 +194,10 @@ function recordModelCall(span, result, provider) {
|
|
|
158
194
|
span.setAttribute('gen_ai.usage.input_tokens', response.usage.inputTokens);
|
|
159
195
|
span.setAttribute('gen_ai.usage.output_tokens', response.usage.outputTokens);
|
|
160
196
|
}
|
|
161
|
-
if (response?.finishReason)
|
|
197
|
+
if (response?.finishReason) {
|
|
162
198
|
span.setAttribute('gen_ai.response.finish_reasons', [response.finishReason]);
|
|
199
|
+
span.setAttribute('llm.finish_reason', response.finishReason);
|
|
200
|
+
}
|
|
163
201
|
if (result.stopped) {
|
|
164
202
|
span.setAttribute('model.call.stop_code', result.stopped.code);
|
|
165
203
|
return;
|
|
@@ -190,9 +228,12 @@ export async function traceToolCall(call, tool, run) {
|
|
|
190
228
|
...(call.id ? { 'gen_ai.tool.call.id': call.id } : {}),
|
|
191
229
|
},
|
|
192
230
|
});
|
|
231
|
+
setToolContent(span, 'tool.args', call.args ?? {});
|
|
193
232
|
try {
|
|
194
233
|
const outcome = await context.with(trace.setSpan(context.active(), span), run);
|
|
195
234
|
const response = outcome?.part?.functionResponse?.response;
|
|
235
|
+
if (response !== undefined)
|
|
236
|
+
setToolContent(span, 'tool.result', response);
|
|
196
237
|
if (!outcome?.part)
|
|
197
238
|
span.setAttribute('tool.pending', true);
|
|
198
239
|
else if (response && Object.hasOwn(response, 'error')) {
|
|
@@ -279,8 +320,12 @@ export async function traceToolNodeCall(call, tool, run) {
|
|
|
279
320
|
'gen_ai.tool.call.id': call.id,
|
|
280
321
|
},
|
|
281
322
|
});
|
|
323
|
+
if (call.args !== undefined)
|
|
324
|
+
setToolContent(span, 'tool.args', call.args);
|
|
282
325
|
try {
|
|
283
|
-
|
|
326
|
+
const result = await context.with(trace.setSpan(context.active(), span), run);
|
|
327
|
+
setToolContent(span, 'tool.result', result ?? null);
|
|
328
|
+
return result;
|
|
284
329
|
}
|
|
285
330
|
catch (error) {
|
|
286
331
|
// The tool node turns the throw into its { error } response; the span notes the failure, not the message.
|
|
@@ -85,6 +85,7 @@ import { journalsEveryCall, openRun } from '../script/journal.js';
|
|
|
85
85
|
import { toEngineAgent } from '../agentDialect.js';
|
|
86
86
|
import { ScriptFatal, runScript } from '../script/run.js';
|
|
87
87
|
import { createTurnControl, runWithTurnControl, stopCode, stopMessage } from './turnControl.js';
|
|
88
|
+
import { metaAttributes } from '../observability/metadata.js';
|
|
88
89
|
const THOUGHTS_PREVIEW_CHARS = 600;
|
|
89
90
|
const CLASSIFIER_PREAMBLE = `RECENT CONVERSATION, oldest first — context for your classification only. `
|
|
90
91
|
+ `Never answer it, and never route on it alone; it is here so you can tell a `
|
|
@@ -397,7 +398,10 @@ async function runTurnInner(opts, control, _runtime, nestedTurn) {
|
|
|
397
398
|
// (a registry version, ADR 0018 item 6), else from files.
|
|
398
399
|
const compileOpts = { ...opts.compile, loadNested: opts.compile?.loadNested ?? nestedLoader(config) };
|
|
399
400
|
const subagentNames = new Set((config.subagents ?? []).map((s) => s.name));
|
|
400
|
-
const
|
|
401
|
+
const traced = opts.trace === false ? undefined : opts.trace ?? {};
|
|
402
|
+
// The YAML's metadata (ADR 0141) as `meta.<key>` on every root span; the caller's own attributes win a clash.
|
|
403
|
+
const yamlMeta = traced && config.metadata ? metaAttributes(config.metadata) : undefined;
|
|
404
|
+
const trace = traced && yamlMeta && Object.keys(yamlMeta).length ? { ...traced, attributes: { ...yamlMeta, ...traced.attributes } } : traced;
|
|
401
405
|
let parts = opts.parts.map((p) => (typeof p === 'string' ? { text: p } : p));
|
|
402
406
|
const messageText = parts.map((p) => (typeof p.text === 'string' ? p.text : '')).join('\n');
|
|
403
407
|
// What the runtime does not run fails here, before the session is touched.
|
|
@@ -19,6 +19,7 @@ import { PostgresSessionService } from './sessionService.ts';
|
|
|
19
19
|
import { PostgresTaskStore } from './taskStore.ts';
|
|
20
20
|
import type { CredentialStoreOptions } from '../../tools/credentialStore.ts';
|
|
21
21
|
import type { CredentialStore } from '../../tools/auth.ts';
|
|
22
|
+
import type { FeedbackStore } from '../../a2a/feedback.ts';
|
|
22
23
|
import type { PayloadPolicy } from '../../observability/supabaseSpanExporter.ts';
|
|
23
24
|
import type { AuditSink } from '../../observability/audit.ts';
|
|
24
25
|
import type { RlsHardeningStatus } from '../rlsStatus.ts';
|
|
@@ -119,6 +120,8 @@ export interface PostgresStorage {
|
|
|
119
120
|
rlsHardening: () => Promise<RlsHardeningStatus>;
|
|
120
121
|
/** Appends to melchizedek_audit (migration 0012, ADR 0042). */
|
|
121
122
|
audit: AuditSink;
|
|
123
|
+
/** End-user feedback into the ledger's adk_labels (POST /feedback, ADR 0140); present when `ledger` is on. */
|
|
124
|
+
feedback?: FeedbackStore;
|
|
122
125
|
/** Tool credentials, sealed (migration 0013, ADR 0072); present when `credentials` was given. */
|
|
123
126
|
credentials?: CredentialStore;
|
|
124
127
|
/**
|
|
@@ -129,6 +132,13 @@ export interface PostgresStorage {
|
|
|
129
132
|
turnLock: TurnLock;
|
|
130
133
|
/** The task tools' lists and job queue (lib/tools/taskTools.ts setTaskBackend). */
|
|
131
134
|
taskQueue: TaskBackend;
|
|
135
|
+
/**
|
|
136
|
+
* Puts a failed or cancelled background job back on the queue, attempts
|
|
137
|
+
* reset (the Retry of an operator's queue view, melchizedek_task_queue,
|
|
138
|
+
* migration 0018). Ids are per owner, so the owner names the list.
|
|
139
|
+
* 'not-found' and 'not-retryable' leave the queue as it was.
|
|
140
|
+
*/
|
|
141
|
+
retryTask: (owner: string, id: string) => Promise<'queued' | 'not-found' | 'not-retryable'>;
|
|
132
142
|
/**
|
|
133
143
|
* Task leases: renew this instance's, fail other instances' expired ones,
|
|
134
144
|
* and list this instance's running tasks a caller asked another instance
|
|
@@ -24,6 +24,8 @@ import { POSTGRES_RLS_QUERY, evaluatePostgresRls } from '../rlsStatus.js';
|
|
|
24
24
|
import { postgresAuditSink } from '../../observability/audit.js';
|
|
25
25
|
import { attachLedger } from '../../observability/tracer.js';
|
|
26
26
|
import { postgresLedgerClient } from '../../observability/supabaseSpanExporter.js';
|
|
27
|
+
import { postgresFeedbackStore } from '../../a2a/feedback.js';
|
|
28
|
+
import { applyRetry } from '../../tools/taskTools.js';
|
|
27
29
|
import { createHash, randomUUID } from 'node:crypto';
|
|
28
30
|
import { hostname } from 'node:os';
|
|
29
31
|
export { PostgresSessionService } from './sessionService.js';
|
|
@@ -114,6 +116,7 @@ export function postgresStorage(options) {
|
|
|
114
116
|
? attachLedger(postgresLedgerClient(pool), options.ledger === true ? {} : options.ledger)
|
|
115
117
|
: undefined;
|
|
116
118
|
const audit = postgresAuditSink(pool);
|
|
119
|
+
const taskQueue = postgresTaskBackend(pool, options.taskQueue);
|
|
117
120
|
const credentials = options.credentials ? postgresCredentialStore(pool, { ...options.credentials, audit }) : undefined;
|
|
118
121
|
return {
|
|
119
122
|
pool,
|
|
@@ -121,6 +124,7 @@ export function postgresStorage(options) {
|
|
|
121
124
|
turnLock,
|
|
122
125
|
...(credentials ? { credentials } : {}),
|
|
123
126
|
...(memoryService ? { memoryService } : {}),
|
|
127
|
+
...(options.ledger ? { feedback: postgresFeedbackStore(pool) } : {}),
|
|
124
128
|
taskStore: (agentId) => new PostgresTaskStore(pool, agentId, { ttlDays: options.ttlDays, ownerResolver: options.taskOwner, lease }),
|
|
125
129
|
leases: {
|
|
126
130
|
...lease,
|
|
@@ -128,7 +132,8 @@ export function postgresStorage(options) {
|
|
|
128
132
|
reap: () => reapExpiredTasks(pool),
|
|
129
133
|
cancelRequested: () => cancelRequestedTasks(pool, lease),
|
|
130
134
|
},
|
|
131
|
-
taskQueue
|
|
135
|
+
taskQueue,
|
|
136
|
+
retryTask: (owner, id) => taskQueue.mutate(owner, (store) => applyRetry(store, id)),
|
|
132
137
|
async schemaVersion() {
|
|
133
138
|
try {
|
|
134
139
|
const r = await pool.query('SELECT max(version) AS v FROM melchizedek_schema_version');
|
|
@@ -245,6 +245,7 @@ export declare const agentSchema: z.ZodObject<{
|
|
|
245
245
|
}>>;
|
|
246
246
|
delegates: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
247
247
|
}, z.core.$strict>>;
|
|
248
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
248
249
|
}, z.core.$strict>;
|
|
249
250
|
/**
|
|
250
251
|
* Model and instruction are optional at the type level because a nested
|
|
@@ -462,6 +463,7 @@ export declare const subagentSchema: z.ZodObject<{
|
|
|
462
463
|
}>>;
|
|
463
464
|
delegates: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
464
465
|
}, z.core.$strict>>;
|
|
466
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
465
467
|
description: z.ZodString;
|
|
466
468
|
model: z.ZodOptional<z.ZodString>;
|
|
467
469
|
instruction: z.ZodOptional<z.ZodString>;
|
|
@@ -685,6 +687,7 @@ export declare const syndicateSchema: z.ZodObject<{
|
|
|
685
687
|
}>>;
|
|
686
688
|
delegates: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
687
689
|
}, z.core.$strict>>;
|
|
690
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
688
691
|
}, z.core.$strict>;
|
|
689
692
|
subagents: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
690
693
|
name: z.ZodString;
|
|
@@ -896,6 +899,7 @@ export declare const syndicateSchema: z.ZodObject<{
|
|
|
896
899
|
}>>;
|
|
897
900
|
delegates: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
898
901
|
}, z.core.$strict>>;
|
|
902
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
899
903
|
description: z.ZodString;
|
|
900
904
|
model: z.ZodOptional<z.ZodString>;
|
|
901
905
|
instruction: z.ZodOptional<z.ZodString>;
|
|
@@ -945,6 +949,7 @@ export declare const syndicateSchema: z.ZodObject<{
|
|
|
945
949
|
model_errors: z.ZodOptional<z.ZodNumber>;
|
|
946
950
|
tool_errors: z.ZodOptional<z.ZodNumber>;
|
|
947
951
|
}, z.core.$strict>>;
|
|
952
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
948
953
|
variables: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
949
954
|
memory_system: z.ZodOptional<z.ZodEnum<{
|
|
950
955
|
"internal-only": "internal-only";
|
|
@@ -27,6 +27,7 @@ import { isSecretShapedEnvName } from './tools/skills/env.js';
|
|
|
27
27
|
import { OAUTH_SCOPE, PROVIDER_NAME } from './tools/auth.js';
|
|
28
28
|
import { V2_AGENT_KEYS, conflictMessage, conflictsOf, toEngineAgent } from './agentDialect.js';
|
|
29
29
|
import { MCP_TRANSPORTS } from './tools/mcpTransports.js';
|
|
30
|
+
import { MAX_META_KEYS, MAX_META_VALUE_CHARS, META_KEY } from './observability/metadata.js';
|
|
30
31
|
// ── Leaf rules ───────────────────────────────────────────────────────────────
|
|
31
32
|
/**
|
|
32
33
|
* ADK's agent-name rule (validateAgentName in ADK's base_agent), kept: checked here
|
|
@@ -337,6 +338,12 @@ const openapiEntry = z
|
|
|
337
338
|
.describe('Credentials, always from the environment, never written in YAML.'),
|
|
338
339
|
})
|
|
339
340
|
.describe('An HTTP API as tools, from its OpenAPI spec (lib/tools/openapiTools.ts).');
|
|
341
|
+
// ── Metadata (ADR 0141) ──────────────────────────────────────────────────────
|
|
342
|
+
/** Static labels copied onto the turn's spans as `meta.*` attributes (lib/observability/metadata.ts). */
|
|
343
|
+
const metadataSchema = (where) => z
|
|
344
|
+
.record(z.string().regex(META_KEY, 'a metadata key is letters, digits and _ . : - (at most 64, not starting with . : -)'), z.union([z.string().max(MAX_META_VALUE_CHARS), z.number(), z.boolean()]))
|
|
345
|
+
.refine((m) => Object.keys(m).length <= MAX_META_KEYS, { message: `at most ${MAX_META_KEYS} metadata keys` })
|
|
346
|
+
.describe(`Labels for the ledger: strings (at most ${MAX_META_VALUE_CHARS} characters), numbers or booleans, at most ${MAX_META_KEYS} keys. ${where} A value shaped like a key or token is dropped at load with a warning; string values pass through the telemetry redactor (ADR 0141).`);
|
|
340
347
|
// ── Agents ───────────────────────────────────────────────────────────────────
|
|
341
348
|
const agentFields = {
|
|
342
349
|
name: agentName,
|
|
@@ -440,6 +447,7 @@ const agentFields = {
|
|
|
440
447
|
delegates: z.array(z.string()).optional(),
|
|
441
448
|
})
|
|
442
449
|
.optional(),
|
|
450
|
+
metadata: metadataSchema('Each becomes `meta.agent.<key>` on this agent\'s agent.invoke span.').optional(),
|
|
443
451
|
};
|
|
444
452
|
export const agentSchema = z
|
|
445
453
|
.strictObject(agentFields)
|
|
@@ -583,6 +591,7 @@ export const syndicateSchema = z
|
|
|
583
591
|
})
|
|
584
592
|
.optional()
|
|
585
593
|
.describe('Self-correction on model and tool errors: the engine\'s reflect-and-retry. On by default.'),
|
|
594
|
+
metadata: metadataSchema('Each becomes `meta.<key>` on every turn\'s root span, so on the ledger row (adk_turns.attributes); a key the YAML names cannot be set by an A2A caller.').optional(),
|
|
586
595
|
variables: z
|
|
587
596
|
.record(z.string(), z.union([z.string(), z.number(), z.boolean()]))
|
|
588
597
|
.optional()
|
|
@@ -1391,6 +1400,10 @@ export function syndicateJsonSchema() {
|
|
|
1391
1400
|
if (ctx.zodSchema === samplingSchema || ctx.zodSchema === outputSchemaV2) {
|
|
1392
1401
|
ctx.jsonSchema.minProperties = 1;
|
|
1393
1402
|
}
|
|
1403
|
+
// metadata: (ADR 0141): the key-count refinement, which JSON Schema says as maxProperties.
|
|
1404
|
+
if (ctx.jsonSchema.propertyNames?.pattern === META_KEY.source) {
|
|
1405
|
+
ctx.jsonSchema.maxProperties = MAX_META_KEYS;
|
|
1406
|
+
}
|
|
1394
1407
|
if (ctx.zodSchema === modelOverrideEntry) {
|
|
1395
1408
|
ctx.jsonSchema.oneOf = [{ required: ['instruction'] }, { required: ['instruction_append'] }];
|
|
1396
1409
|
}
|
|
@@ -180,6 +180,12 @@ export declare function claimNextJob(): TaskRecord | null;
|
|
|
180
180
|
export declare function finishJob(id: string, outcome: JobOutcome): void;
|
|
181
181
|
/** The outcome rule, on a snapshot (every backend). */
|
|
182
182
|
export declare function applyFinish(store: TaskStore, id: string, outcome: JobOutcome): void;
|
|
183
|
+
/**
|
|
184
|
+
* The retry rule (every backend; an operator's Retry and task_update's
|
|
185
|
+
* `queued` alike): a failed or cancelled background job goes back to the
|
|
186
|
+
* queue with its attempts reset. Anything else is left alone.
|
|
187
|
+
*/
|
|
188
|
+
export declare function applyRetry(store: TaskStore, id: string): 'queued' | 'not-found' | 'not-retryable';
|
|
183
189
|
/** The interrupted-job rule for one record: back to the queue, or failed. */
|
|
184
190
|
export declare function applyInterrupted(t: TaskRecord): 'requeued' | 'failed';
|
|
185
191
|
/** The single-user JSON file, its checkpoints capped by `options` (the default backend is fileTaskBackend). */
|