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,146 @@
|
|
|
1
|
+
-- ============================================================================
|
|
2
|
+
-- 0017_cache_pricing — prompt-cache reads and writes at their own rates
|
|
3
|
+
-- (ADR 0139, extends ADR 0136)
|
|
4
|
+
-- ============================================================================
|
|
5
|
+
-- model_pricing gains two rates, USD per million tokens:
|
|
6
|
+
-- cache_read_per_mtok a token read from the provider's prompt cache
|
|
7
|
+
-- cache_write_per_mtok a token written to it (Anthropic's 5-minute cache,
|
|
8
|
+
-- the TTL the engine's requests get; OpenAI's 30-minute
|
|
9
|
+
-- write where its price list names one)
|
|
10
|
+
-- NULL prices that token as input, which is what every row meant before:
|
|
11
|
+
-- a cache token is an input token (lib/models/contract.ts Usage counts both
|
|
12
|
+
-- inside inputTokens), so a row without cache rates prices exactly as 0015.
|
|
13
|
+
--
|
|
14
|
+
-- The cost functions take the two counts:
|
|
15
|
+
-- melchizedek_cost_usd(model, input, output, thinking, cache_read, cache_write)
|
|
16
|
+
-- input less both cache counts at the input rate, each cache count at
|
|
17
|
+
-- its rate, output, thinking at the thinking rate (else output's)
|
|
18
|
+
-- melchizedek_cost_usd(model, input, output, thinking) the same with no
|
|
19
|
+
-- cache tokens, kept for a caller written against 0015
|
|
20
|
+
-- melchizedek_usage_cost(usage) reads each model's cache_read and
|
|
21
|
+
-- cache_write beside input, output and thinking in syndicate.usage;
|
|
22
|
+
-- melchizedek_usage_text_cost (db/telemetry.sql) calls it, so the
|
|
23
|
+
-- ledger trigger prices a turn's cache tokens too.
|
|
24
|
+
--
|
|
25
|
+
-- The seed: rates checked against the vendors' own price lists on
|
|
26
|
+
-- 2026-10-20 (platform.claude.com/docs/en/about-claude/pricing and
|
|
27
|
+
-- developers.openai.com/api/docs/pricing, standard tier), with verified_at
|
|
28
|
+
-- set to that day. An Anthropic row 0015 seeded gains its cache rates only
|
|
29
|
+
-- while it still holds 0015's input and output rates and no cache rate: an
|
|
30
|
+
-- operator's own rates are never overwritten. New rows never overwrite a
|
|
31
|
+
-- row that exists. Models the engine names but the lists price by prompt
|
|
32
|
+
-- length beyond a flat row (claude-haiku-5-5, gpt-5.5 and gpt-5.4 over
|
|
33
|
+
-- 272K) are left as noted below. Gemini, Grok and Kimi rows get no cache
|
|
34
|
+
-- rates here (none was checked): their cache tokens price as input.
|
|
35
|
+
--
|
|
36
|
+
-- Nothing secret, nothing personal. Idempotent; safe to re-run.
|
|
37
|
+
-- ============================================================================
|
|
38
|
+
|
|
39
|
+
ALTER TABLE model_pricing ADD COLUMN IF NOT EXISTS cache_read_per_mtok NUMERIC CHECK (cache_read_per_mtok >= 0);
|
|
40
|
+
ALTER TABLE model_pricing ADD COLUMN IF NOT EXISTS cache_write_per_mtok NUMERIC CHECK (cache_write_per_mtok >= 0);
|
|
41
|
+
|
|
42
|
+
-- Anthropic: the rows 0015 seeded, at the rates it seeded them.
|
|
43
|
+
UPDATE model_pricing p
|
|
44
|
+
SET cache_read_per_mtok = s.cache_read,
|
|
45
|
+
cache_write_per_mtok = s.cache_write,
|
|
46
|
+
verified_at = '2026-10-20',
|
|
47
|
+
source = 'platform.claude.com/docs/en/about-claude/pricing',
|
|
48
|
+
note = 'Anthropic first-party, checked 2026-10-20; cache write is the 5-minute rate',
|
|
49
|
+
updated_at = now()
|
|
50
|
+
FROM (VALUES
|
|
51
|
+
('claude-fable-5-1', 10.00, 50.00, 0.25, 12.50),
|
|
52
|
+
('claude-fable-5', 10.00, 50.00, 1.00, 12.50),
|
|
53
|
+
('claude-opus-5', 5.00, 25.00, 0.50, 6.25),
|
|
54
|
+
('claude-opus-4-8', 5.00, 25.00, 0.50, 6.25),
|
|
55
|
+
('claude-opus-4-7', 5.00, 25.00, 0.50, 6.25),
|
|
56
|
+
('claude-opus-4-6', 5.00, 25.00, 0.50, 6.25),
|
|
57
|
+
('claude-sonnet-5', 2.00, 10.00, 0.20, 2.50),
|
|
58
|
+
('claude-sonnet-4-6', 3.00, 15.00, 0.30, 3.75),
|
|
59
|
+
('claude-haiku-4-5', 1.00, 5.00, 0.10, 1.25)
|
|
60
|
+
) AS s(model, input, output, cache_read, cache_write)
|
|
61
|
+
WHERE p.model = s.model
|
|
62
|
+
AND p.input_per_mtok = s.input
|
|
63
|
+
AND p.output_per_mtok = s.output
|
|
64
|
+
AND p.cache_read_per_mtok IS NULL
|
|
65
|
+
AND p.cache_write_per_mtok IS NULL;
|
|
66
|
+
|
|
67
|
+
INSERT INTO model_pricing (model, input_per_mtok, output_per_mtok, cache_read_per_mtok, cache_write_per_mtok, verified_at, source, note) VALUES
|
|
68
|
+
-- Anthropic models 0015 did not seed. claude-haiku-5-5 is left out: its
|
|
69
|
+
-- price doubles past a 100K-token prompt, which a flat row cannot say.
|
|
70
|
+
('claude-opus-5-5', 4.00, 20.00, 0.20, 5.00, '2026-10-20', 'platform.claude.com/docs/en/about-claude/pricing', 'Anthropic first-party, checked 2026-10-20; cache write is the 5-minute rate'),
|
|
71
|
+
('claude-sonnet-5-5', 2.00, 10.00, 0.10, 2.50, '2026-10-20', 'platform.claude.com/docs/en/about-claude/pricing', 'Anthropic first-party, checked 2026-10-20; cache write is the 5-minute rate'),
|
|
72
|
+
-- OpenAI, standard tier. Reasoning tokens bill as output (thinking NULL).
|
|
73
|
+
-- A NULL cache write: the list names no write rate, so a write prices as input.
|
|
74
|
+
('gpt-6-astra', 10.00, 50.00, 1.00, 12.50, '2026-10-20', 'developers.openai.com/api/docs/pricing', 'OpenAI standard tier, checked 2026-10-20; cache write is the 30-minute rate'),
|
|
75
|
+
('gpt-6.1-sol', 2.00, 10.00, 0.10, 2.50, '2026-10-20', 'developers.openai.com/api/docs/pricing', 'OpenAI standard tier, checked 2026-10-20; cache write is the 30-minute rate'),
|
|
76
|
+
('gpt-6-sol', 2.00, 10.00, 0.20, 2.50, '2026-10-20', 'developers.openai.com/api/docs/pricing', 'OpenAI standard tier, checked 2026-10-20; cache write is the 30-minute rate'),
|
|
77
|
+
('gpt-6-luna', 0.10, 0.50, 0.01, 0.125, '2026-10-20', 'developers.openai.com/api/docs/pricing', 'OpenAI standard tier, checked 2026-10-20; cache write is the 30-minute rate'),
|
|
78
|
+
('gpt-5.6-sol', 4.00, 20.00, 0.40, 5.00, '2026-10-20', 'developers.openai.com/api/docs/pricing', 'OpenAI standard tier, checked 2026-10-20; cache write is the 30-minute rate'),
|
|
79
|
+
('gpt-5.6-terra', 2.00, 12.00, 0.20, 2.50, '2026-10-20', 'developers.openai.com/api/docs/pricing', 'OpenAI standard tier, checked 2026-10-20; cache write is the 30-minute rate'),
|
|
80
|
+
('gpt-5.6-luna', 0.20, 1.20, 0.02, 0.25, '2026-10-20', 'developers.openai.com/api/docs/pricing', 'OpenAI standard tier, checked 2026-10-20; cache write is the 30-minute rate'),
|
|
81
|
+
('gpt-5.5', 5.00, 30.00, 0.50, NULL, '2026-10-20', 'developers.openai.com/api/docs/pricing', 'OpenAI standard tier, checked 2026-10-20; the rate for prompts under 272K tokens'),
|
|
82
|
+
('gpt-5.4', 2.50, 15.00, 0.25, NULL, '2026-10-20', 'developers.openai.com/api/docs/pricing', 'OpenAI standard tier, checked 2026-10-20; the rate for prompts under 272K tokens'),
|
|
83
|
+
('gpt-5.2', 1.75, 14.00, 0.175, NULL, '2026-10-20', 'developers.openai.com/api/docs/pricing', 'OpenAI standard tier, checked 2026-10-20'),
|
|
84
|
+
('gpt-5.1', 1.25, 10.00, 0.125, NULL, '2026-10-20', 'developers.openai.com/api/docs/pricing', 'OpenAI standard tier, checked 2026-10-20'),
|
|
85
|
+
('gpt-5', 1.25, 10.00, 0.125, NULL, '2026-10-20', 'developers.openai.com/api/docs/pricing', 'OpenAI standard tier, checked 2026-10-20'),
|
|
86
|
+
('gpt-5-mini', 0.25, 2.00, 0.025, NULL, '2026-10-20', 'developers.openai.com/api/docs/pricing', 'OpenAI standard tier, checked 2026-10-20'),
|
|
87
|
+
('gpt-5-nano', 0.05, 0.40, 0.005, NULL, '2026-10-20', 'developers.openai.com/api/docs/pricing', 'OpenAI standard tier, checked 2026-10-20')
|
|
88
|
+
ON CONFLICT (model) DO NOTHING;
|
|
89
|
+
|
|
90
|
+
-- The row that prices `model`: its own, else its prefix's `<prefix>/*`.
|
|
91
|
+
CREATE OR REPLACE FUNCTION melchizedek_cost_usd(model TEXT, input BIGINT, output BIGINT, thinking BIGINT, cache_read BIGINT, cache_write BIGINT)
|
|
92
|
+
RETURNS NUMERIC
|
|
93
|
+
LANGUAGE sql
|
|
94
|
+
STABLE
|
|
95
|
+
SET search_path = public
|
|
96
|
+
AS $$
|
|
97
|
+
SELECT (greatest(coalesce(input, 0) - coalesce(cache_read, 0) - coalesce(cache_write, 0), 0) * p.input_per_mtok
|
|
98
|
+
+ coalesce(cache_read, 0) * coalesce(p.cache_read_per_mtok, p.input_per_mtok)
|
|
99
|
+
+ coalesce(cache_write, 0) * coalesce(p.cache_write_per_mtok, p.input_per_mtok)
|
|
100
|
+
+ coalesce(output, 0) * p.output_per_mtok
|
|
101
|
+
+ coalesce(thinking, 0) * coalesce(p.thinking_per_mtok, p.output_per_mtok)) / 1000000
|
|
102
|
+
FROM model_pricing p
|
|
103
|
+
WHERE p.model = melchizedek_cost_usd.model
|
|
104
|
+
OR (position('/' IN melchizedek_cost_usd.model) > 0
|
|
105
|
+
AND p.model = split_part(melchizedek_cost_usd.model, '/', 1) || '/*')
|
|
106
|
+
ORDER BY (p.model = melchizedek_cost_usd.model) DESC
|
|
107
|
+
LIMIT 1
|
|
108
|
+
$$;
|
|
109
|
+
|
|
110
|
+
CREATE OR REPLACE FUNCTION melchizedek_cost_usd(model TEXT, input BIGINT, output BIGINT, thinking BIGINT)
|
|
111
|
+
RETURNS NUMERIC
|
|
112
|
+
LANGUAGE sql
|
|
113
|
+
STABLE
|
|
114
|
+
SET search_path = public
|
|
115
|
+
AS $$
|
|
116
|
+
SELECT melchizedek_cost_usd(model, input, output, thinking, 0::bigint, 0::bigint)
|
|
117
|
+
$$;
|
|
118
|
+
|
|
119
|
+
CREATE OR REPLACE FUNCTION melchizedek_usage_cost(usage JSONB)
|
|
120
|
+
RETURNS NUMERIC
|
|
121
|
+
LANGUAGE sql
|
|
122
|
+
STABLE
|
|
123
|
+
SET search_path = public
|
|
124
|
+
AS $$
|
|
125
|
+
SELECT CASE
|
|
126
|
+
WHEN usage IS NULL OR jsonb_typeof(usage) <> 'object' THEN NULL
|
|
127
|
+
-- bool_and over no rows is NULL: a turn that made no model call costs nothing.
|
|
128
|
+
WHEN coalesce(bool_and(c.cost IS NOT NULL), true) THEN coalesce(sum(c.cost), 0)
|
|
129
|
+
ELSE NULL
|
|
130
|
+
END
|
|
131
|
+
FROM (
|
|
132
|
+
SELECT melchizedek_cost_usd(
|
|
133
|
+
e.key,
|
|
134
|
+
CASE WHEN jsonb_typeof(e.value->'input') = 'number' THEN (e.value->>'input')::numeric::bigint END,
|
|
135
|
+
CASE WHEN jsonb_typeof(e.value->'output') = 'number' THEN (e.value->>'output')::numeric::bigint END,
|
|
136
|
+
CASE WHEN jsonb_typeof(e.value->'thinking') = 'number' THEN (e.value->>'thinking')::numeric::bigint END,
|
|
137
|
+
CASE WHEN jsonb_typeof(e.value->'cache_read') = 'number' THEN (e.value->>'cache_read')::numeric::bigint END,
|
|
138
|
+
CASE WHEN jsonb_typeof(e.value->'cache_write') = 'number' THEN (e.value->>'cache_write')::numeric::bigint END
|
|
139
|
+
) AS cost
|
|
140
|
+
FROM jsonb_each(CASE WHEN jsonb_typeof(usage) = 'object' THEN usage ELSE '{}'::jsonb END) e
|
|
141
|
+
) c
|
|
142
|
+
$$;
|
|
143
|
+
|
|
144
|
+
INSERT INTO melchizedek_schema_version (version, name)
|
|
145
|
+
VALUES (17, '0017_cache_pricing')
|
|
146
|
+
ON CONFLICT (version) DO NOTHING;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
-- ============================================================================
|
|
2
|
+
-- 0018_queue_view — the job queue as an operator reads it (ADR 0021, 0113)
|
|
3
|
+
-- ============================================================================
|
|
4
|
+
-- A read model over melchizedek_tasks (migration 0009, checkpoint columns
|
|
5
|
+
-- from 0014): one row per background job, with what an operator asks of a
|
|
6
|
+
-- queue — is it stuck, how often was it tried, why did it fail. Todo items
|
|
7
|
+
-- are not jobs and are left out. Nothing is stored; the view reads the
|
|
8
|
+
-- table as it stands.
|
|
9
|
+
--
|
|
10
|
+
-- owner the list the job belongs to (the caller's scope key;
|
|
11
|
+
-- '' for a shared list)
|
|
12
|
+
-- id the job's id within that list (t1, t2, …)
|
|
13
|
+
-- title the job's title as queued (user text)
|
|
14
|
+
-- status queued | running | done | failed | cancelled
|
|
15
|
+
-- attempts claims so far (a claim counts one, a retry resets it)
|
|
16
|
+
-- retries attempts beyond the first: GREATEST(attempts - 1, 0)
|
|
17
|
+
-- max_attempts the claims a job gets before an interruption fails it
|
|
18
|
+
-- (MAX_ATTEMPTS in lib/tools/taskTools.ts)
|
|
19
|
+
-- dead_lettered true when the job failed and will not run again unless
|
|
20
|
+
-- someone retries it (status 'failed')
|
|
21
|
+
-- last_error the error the job failed with, or why it was not retried
|
|
22
|
+
-- (cut at 1,000 characters when written)
|
|
23
|
+
-- lease_owner the worker holding a running job, and lease_until when
|
|
24
|
+
-- its claim runs out: a running job whose lease is past is
|
|
25
|
+
-- stuck until the next recover()
|
|
26
|
+
-- has_checkpoint whether a resumable checkpoint is kept (running or
|
|
27
|
+
-- queued only), and checkpoint_at when it was saved
|
|
28
|
+
-- created_at, started_at, finished_at, updated_at
|
|
29
|
+
--
|
|
30
|
+
-- Retry a dead-lettered or cancelled job with postgresStorage().retryTask
|
|
31
|
+
-- (owner, id), the same rule task_update's `queued` applies. The view has
|
|
32
|
+
-- the table's owner's privileges where security_invoker is not available
|
|
33
|
+
-- (Postgres < 15), so it is revoked from the API roles here and in
|
|
34
|
+
-- db/hardening.sql, like the table. Idempotent; safe to re-run.
|
|
35
|
+
-- ============================================================================
|
|
36
|
+
|
|
37
|
+
CREATE OR REPLACE VIEW melchizedek_task_queue AS
|
|
38
|
+
SELECT
|
|
39
|
+
t.owner,
|
|
40
|
+
t.id,
|
|
41
|
+
t.record->>'title' AS title,
|
|
42
|
+
t.status,
|
|
43
|
+
COALESCE((t.record->>'attempts')::int, 0) AS attempts,
|
|
44
|
+
GREATEST(COALESCE((t.record->>'attempts')::int, 0) - 1, 0) AS retries,
|
|
45
|
+
2 AS max_attempts,
|
|
46
|
+
(t.status = 'failed') AS dead_lettered,
|
|
47
|
+
t.record->>'error' AS last_error,
|
|
48
|
+
t.lease_owner,
|
|
49
|
+
t.lease_until,
|
|
50
|
+
(t.checkpoint IS NOT NULL) AS has_checkpoint,
|
|
51
|
+
t.checkpoint_at,
|
|
52
|
+
(t.record->>'created_at')::timestamptz AS created_at,
|
|
53
|
+
(t.record->>'started_at')::timestamptz AS started_at,
|
|
54
|
+
(t.record->>'finished_at')::timestamptz AS finished_at,
|
|
55
|
+
t.updated_at
|
|
56
|
+
FROM melchizedek_tasks t
|
|
57
|
+
WHERE t.kind = 'background';
|
|
58
|
+
|
|
59
|
+
COMMENT ON VIEW melchizedek_task_queue IS
|
|
60
|
+
'Background jobs as an operator reads them: status, attempts, retries, dead_lettered, last_error, lease and checkpoint. Retry with postgresStorage().retryTask(owner, id). Migration 0018.';
|
|
61
|
+
|
|
62
|
+
-- Postgres 15+: the view reads with the querying role's rights.
|
|
63
|
+
DO $$
|
|
64
|
+
BEGIN
|
|
65
|
+
IF current_setting('server_version_num')::int >= 150000 THEN
|
|
66
|
+
EXECUTE 'ALTER VIEW melchizedek_task_queue SET (security_invoker = true)';
|
|
67
|
+
END IF;
|
|
68
|
+
END $$;
|
|
69
|
+
|
|
70
|
+
DO $$
|
|
71
|
+
BEGIN
|
|
72
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'anon') THEN
|
|
73
|
+
EXECUTE 'REVOKE ALL ON melchizedek_task_queue FROM anon';
|
|
74
|
+
END IF;
|
|
75
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'authenticated') THEN
|
|
76
|
+
EXECUTE 'REVOKE ALL ON melchizedek_task_queue FROM authenticated';
|
|
77
|
+
END IF;
|
|
78
|
+
END $$;
|
|
79
|
+
|
|
80
|
+
INSERT INTO melchizedek_schema_version (version, name)
|
|
81
|
+
VALUES (18, '0018_queue_view')
|
|
82
|
+
ON CONFLICT (version) DO NOTHING;
|
package/db/telemetry.sql
CHANGED
|
@@ -22,10 +22,14 @@
|
|
|
22
22
|
-- resolved syndicate that ran) and engine_version. Kept
|
|
23
23
|
-- indefinitely; full-text searchable. Eval runs land here
|
|
24
24
|
-- too, tagged eval_* so production views exclude them.
|
|
25
|
-
-- adk_telemetry ONE ROW PER SPAN — the
|
|
26
|
-
--
|
|
27
|
-
--
|
|
28
|
-
--
|
|
25
|
+
-- adk_telemetry ONE ROW PER SPAN the ledger keeps — the turn's root, each
|
|
26
|
+
-- agent run, model step (model.call) and provider call
|
|
27
|
+
-- (llm.request), tool call, script run and call, workflow
|
|
28
|
+
-- walk and node run — with its parent among them and its
|
|
29
|
+
-- kind, so the turn's span tree is stored (ADR 0139). Per-call
|
|
30
|
+
-- tokens, latency and the agent that made the call; a tool
|
|
31
|
+
-- call's arguments and the start of its result. Small; kept
|
|
32
|
+
-- indefinitely.
|
|
29
33
|
-- adk_payloads FULL REQUEST/RESPONSE PER MODEL CALL — the assembled
|
|
30
34
|
-- prompt (system instruction, history, tool schemas) and the
|
|
31
35
|
-- raw response. 10-100x the size of a turn row and repeats
|
|
@@ -47,7 +51,7 @@ CREATE TABLE IF NOT EXISTS adk_telemetry (
|
|
|
47
51
|
ts TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
48
52
|
trace_id TEXT NOT NULL,
|
|
49
53
|
span_id TEXT NOT NULL,
|
|
50
|
-
span_name TEXT NOT NULL, -- 'llm.request' | 'Syndicate Execution: <name>'
|
|
54
|
+
span_name TEXT NOT NULL, -- 'llm.request' | 'Syndicate Execution: <name>' | 'agent.invoke <name>' | ...
|
|
51
55
|
syndicate TEXT, -- syndicate name, when known
|
|
52
56
|
agent TEXT, -- the agent that made the call / answered the turn
|
|
53
57
|
provider TEXT, -- 'gemini' | 'anthropic' | 'openai' | 'xai' | 'ollama'
|
|
@@ -68,6 +72,23 @@ CREATE INDEX IF NOT EXISTS idx_adk_telemetry_ts ON adk_telemetry (ts DESC);
|
|
|
68
72
|
CREATE INDEX IF NOT EXISTS idx_adk_telemetry_trace ON adk_telemetry (trace_id);
|
|
69
73
|
CREATE INDEX IF NOT EXISTS idx_adk_telemetry_session ON adk_telemetry (session_id);
|
|
70
74
|
|
|
75
|
+
-- The span tree (migration 0016_span_tree, ADR 0139), for a fresh install;
|
|
76
|
+
-- 0016 adds the same columns to a ledger that already exists:
|
|
77
|
+
-- parent_span_id the nearest ancestor span the ledger keeps; NULL for a root
|
|
78
|
+
-- kind turn | agent | model | tool | script | node
|
|
79
|
+
-- error the span failed (status, error code, a tool's error response)
|
|
80
|
+
-- cache_read_tokens parts of input_tokens read from / written to the
|
|
81
|
+
-- cache_write_tokens provider's prompt cache: one call's, or a root's sum
|
|
82
|
+
-- All NULL on rows written before 0016. Tokens and cost sit on llm.request
|
|
83
|
+
-- rows (and a root's on the root): a model.call row carries neither, so a
|
|
84
|
+
-- trace's rows sum without counting a call twice.
|
|
85
|
+
ALTER TABLE adk_telemetry ADD COLUMN IF NOT EXISTS parent_span_id TEXT;
|
|
86
|
+
ALTER TABLE adk_telemetry ADD COLUMN IF NOT EXISTS kind TEXT;
|
|
87
|
+
ALTER TABLE adk_telemetry ADD COLUMN IF NOT EXISTS error BOOLEAN;
|
|
88
|
+
ALTER TABLE adk_telemetry ADD COLUMN IF NOT EXISTS cache_read_tokens INTEGER;
|
|
89
|
+
ALTER TABLE adk_telemetry ADD COLUMN IF NOT EXISTS cache_write_tokens INTEGER;
|
|
90
|
+
CREATE INDEX IF NOT EXISTS idx_adk_telemetry_parent ON adk_telemetry (trace_id, parent_span_id);
|
|
91
|
+
|
|
71
92
|
-- ── adk_turns: one row per turn — the system of record ───────────────────
|
|
72
93
|
CREATE TABLE IF NOT EXISTS adk_turns (
|
|
73
94
|
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
@@ -123,6 +144,10 @@ CREATE TABLE IF NOT EXISTS adk_turns (
|
|
|
123
144
|
UNIQUE (trace_id, span_id)
|
|
124
145
|
);
|
|
125
146
|
|
|
147
|
+
-- Cache tokens summed over the turn's calls (migration 0016, ADR 0139).
|
|
148
|
+
ALTER TABLE adk_turns ADD COLUMN IF NOT EXISTS cache_read_tokens INTEGER;
|
|
149
|
+
ALTER TABLE adk_turns ADD COLUMN IF NOT EXISTS cache_write_tokens INTEGER;
|
|
150
|
+
|
|
126
151
|
CREATE INDEX IF NOT EXISTS idx_adk_turns_ts ON adk_turns (ts DESC);
|
|
127
152
|
CREATE INDEX IF NOT EXISTS idx_adk_turns_trace ON adk_turns (trace_id);
|
|
128
153
|
CREATE INDEX IF NOT EXISTS idx_adk_turns_session ON adk_turns (session_id, ts);
|
|
@@ -167,8 +192,10 @@ CREATE INDEX IF NOT EXISTS idx_adk_turns_surface_user
|
|
|
167
192
|
-- cost_usd is filled at write time by the trigger below from model_pricing
|
|
168
193
|
-- (db/migrations/0015_model_pricing.sql), so both sinks and a replayed
|
|
169
194
|
-- dead-letter spool get it, and the price table is the one source:
|
|
170
|
-
-- adk_telemetry an llm.request row: its model and tokens
|
|
171
|
-
--
|
|
195
|
+
-- adk_telemetry an llm.request row: its model and tokens, cache reads and
|
|
196
|
+
-- writes at their own rates (migration 0017). A root row: the
|
|
197
|
+
-- turn's per-model usage on the span (syndicate.usage). Any
|
|
198
|
+
-- other row (an agent run, a model step, a tool call): NULL.
|
|
172
199
|
-- adk_turns the turn's per-model usage (attributes->'syndicate.usage',
|
|
173
200
|
-- the tracer sums each model's calls), so a turn that used
|
|
174
201
|
-- two models is priced per model, input summed per call.
|
|
@@ -225,7 +252,8 @@ BEGIN
|
|
|
225
252
|
RETURN NEW;
|
|
226
253
|
END IF;
|
|
227
254
|
IF NEW.model IS NOT NULL THEN
|
|
228
|
-
NEW.cost_usd := melchizedek_cost_usd(NEW.model, NEW.input_tokens, NEW.output_tokens, NEW.thinking_tokens
|
|
255
|
+
NEW.cost_usd := melchizedek_cost_usd(NEW.model, NEW.input_tokens, NEW.output_tokens, NEW.thinking_tokens,
|
|
256
|
+
NEW.cache_read_tokens::bigint, NEW.cache_write_tokens::bigint);
|
|
229
257
|
ELSE
|
|
230
258
|
NEW.cost_usd := melchizedek_usage_text_cost(NEW.span->'attributes'->>'syndicate.usage');
|
|
231
259
|
END IF;
|
package/dist/lib/a2a/app.d.ts
CHANGED
|
@@ -19,8 +19,10 @@
|
|
|
19
19
|
* 5. BYOK headers → per-request AsyncLocalStorage context. Agent-card GETs
|
|
20
20
|
* are exempt from X-API-Key: discovery must not require a model key.
|
|
21
21
|
* 6. Task rate limiter (POSTs only; polling GETs are exempt).
|
|
22
|
-
* 7. Routes: the adopter's own (`routes`), DELETE /memory,
|
|
23
|
-
*
|
|
22
|
+
* 7. Routes: the adopter's own (`routes`), DELETE /memory, POST /feedback
|
|
23
|
+
* and /:agentId/feedback (ADR 0140; their own 32 KB body limit), the
|
|
24
|
+
* default syndicate at /a2a/*, and every other syndicate at
|
|
25
|
+
* /:agentId/a2a/*.
|
|
24
26
|
*
|
|
25
27
|
* State that is per-process (documented, not hidden): the A2A task store,
|
|
26
28
|
* the handler cache, the rate-limiter counters. Sessions and memory are
|
|
@@ -53,9 +55,10 @@ import type { AuditSink } from '../observability/audit.ts';
|
|
|
53
55
|
export declare const DEFAULT_MAX_CONCURRENT_PER_SCOPE = 4;
|
|
54
56
|
import type { EraseCounts } from '../memory/erase.ts';
|
|
55
57
|
import type { Embedder, MemoryExtractor } from '../memory/providers.ts';
|
|
56
|
-
import type { A2AContext } from './executor.ts';
|
|
58
|
+
import type { A2AContext, TaskStateRecord } from './executor.ts';
|
|
57
59
|
import type { IdentityScheme } from './identity.ts';
|
|
58
60
|
import type { Policy } from './policy.ts';
|
|
61
|
+
import type { FeedbackStore } from './feedback.ts';
|
|
59
62
|
import type { TaskRecord } from '../observability/metrics.ts';
|
|
60
63
|
import type { OAuthConsent, ToolCredentials } from '../tools/oauthConsent.ts';
|
|
61
64
|
/** Agent ids address a config (file or "registry:<id>"): a safe charset only. */
|
|
@@ -117,6 +120,14 @@ export interface A2AAppOptions {
|
|
|
117
120
|
rlsHardening?: () => Promise<RlsHardeningStatus>;
|
|
118
121
|
/** Appends audit events (postgresStorage supplies one; ADR 0042). */
|
|
119
122
|
audit?: AuditSink;
|
|
123
|
+
/**
|
|
124
|
+
* Where `POST /feedback` reads turns and writes end-user labels: the
|
|
125
|
+
* telemetry ledger (ADR 0140). postgresStorage supplies one when its
|
|
126
|
+
* `ledger` is on; `postgresFeedbackStore(pool)` (lib/a2a/feedback.ts)
|
|
127
|
+
* builds one on any pool holding db/telemetry.sql. Without it the
|
|
128
|
+
* route answers 501.
|
|
129
|
+
*/
|
|
130
|
+
feedback?: FeedbackStore;
|
|
120
131
|
/**
|
|
121
132
|
* One turn at a time per conversation across instances (postgresStorage
|
|
122
133
|
* supplies an advisory lock). Default: a lock in this process.
|
|
@@ -174,6 +185,15 @@ export interface A2AAppOptions {
|
|
|
174
185
|
metricsToken?: string;
|
|
175
186
|
/** One record per task, however it ended (structured logs, your own metrics). */
|
|
176
187
|
onTaskEnd?: (record: TaskRecord) => void;
|
|
188
|
+
/**
|
|
189
|
+
* Every state a task enters, as the executor publishes it (ADR 0141):
|
|
190
|
+
* `submitted` first, `working` with its first progress line, then the
|
|
191
|
+
* state it ends in (`completed`, `failed`, `canceled`, `rejected`,
|
|
192
|
+
* `input-required`). A repeat of the same state is not reported again. For
|
|
193
|
+
* a live view of in-flight work; `tasks/get` answers the same state to a
|
|
194
|
+
* polling caller. A throwing hook is ignored.
|
|
195
|
+
*/
|
|
196
|
+
onTaskState?: (record: TaskStateRecord) => void;
|
|
177
197
|
/**
|
|
178
198
|
* Credentials plug point (ADR 0017/0023): how a provider is reached for
|
|
179
199
|
* this request — from a secret manager, per tenant, anywhere. Return an
|
|
@@ -194,7 +214,9 @@ export interface A2AAppOptions {
|
|
|
194
214
|
extractor?: MemoryExtractor;
|
|
195
215
|
embedder?: Embedder;
|
|
196
216
|
};
|
|
197
|
-
/** Adopter routes, mounted after authentication and before the A2A routes.
|
|
217
|
+
/** Adopter routes, mounted after authentication and before the A2A routes.
|
|
218
|
+
* A POST to a path ending in /feedback (one segment deep) parses at the
|
|
219
|
+
* feedback routes' 32 KB limit, not `bodyLimit`. */
|
|
198
220
|
routes?: (app: Express) => void;
|
|
199
221
|
/**
|
|
200
222
|
* Tool credentials for every turn (ADR 0072): the sealed per-user store,
|
package/dist/lib/a2a/app.js
CHANGED
|
@@ -19,8 +19,10 @@
|
|
|
19
19
|
* 5. BYOK headers → per-request AsyncLocalStorage context. Agent-card GETs
|
|
20
20
|
* are exempt from X-API-Key: discovery must not require a model key.
|
|
21
21
|
* 6. Task rate limiter (POSTs only; polling GETs are exempt).
|
|
22
|
-
* 7. Routes: the adopter's own (`routes`), DELETE /memory,
|
|
23
|
-
*
|
|
22
|
+
* 7. Routes: the adopter's own (`routes`), DELETE /memory, POST /feedback
|
|
23
|
+
* and /:agentId/feedback (ADR 0140; their own 32 KB body limit), the
|
|
24
|
+
* default syndicate at /a2a/*, and every other syndicate at
|
|
25
|
+
* /:agentId/a2a/*.
|
|
24
26
|
*
|
|
25
27
|
* State that is per-process (documented, not hidden): the A2A task store,
|
|
26
28
|
* the handler cache, the rate-limiter counters. Sessions and memory are
|
|
@@ -61,7 +63,9 @@ import { eraseScope } from '../memory/erase.js';
|
|
|
61
63
|
import { namespacedMemoryService } from '../memory/namespace.js';
|
|
62
64
|
import { memoryCrossesProviders, memoryDestinations } from '../memory/providers.js';
|
|
63
65
|
import { A2A_APP_NAME, SyndicateExecutor, TaskLimiter, deriveUserId, requestContextStorage, } from './executor.js';
|
|
66
|
+
import { droppedHeaderWarning, headerMetadata } from '../observability/metadata.js';
|
|
64
67
|
import { HEADER_VALUE_PATTERN, SCOPE_KEY_PATTERN } from './identity.js';
|
|
68
|
+
import { feedbackBodyParser, feedbackHandler, feedbackLimiter, isFeedbackPath } from './feedback.js';
|
|
65
69
|
import { createMetrics } from '../observability/metrics.js';
|
|
66
70
|
import { ConsentError } from '../tools/oauthConsent.js';
|
|
67
71
|
import { setOAuthHosts } from '../tools/oauthHosts.js';
|
|
@@ -545,6 +549,7 @@ export async function createA2AApp(options) {
|
|
|
545
549
|
agentId,
|
|
546
550
|
policy: options.policy,
|
|
547
551
|
onTaskEnd,
|
|
552
|
+
...(options.onTaskState ? { onTaskState: options.onTaskState } : {}),
|
|
548
553
|
onAudit: audit,
|
|
549
554
|
log,
|
|
550
555
|
warn,
|
|
@@ -716,7 +721,10 @@ export async function createA2AApp(options) {
|
|
|
716
721
|
});
|
|
717
722
|
}
|
|
718
723
|
// ── 4. Body ────────────────────────────────────────────────────────────────
|
|
719
|
-
|
|
724
|
+
// A POST to /feedback or /<id>/feedback parses at 32 KB, its errors as JSON (ADR 0140).
|
|
725
|
+
const jsonBody = express.json({ limit: options.bodyLimit ?? '1mb' });
|
|
726
|
+
const feedbackBody = feedbackBodyParser();
|
|
727
|
+
app.use((req, res, next) => (req.method === 'POST' && isFeedbackPath(req.path) ? feedbackBody(req, res, next) : jsonBody(req, res, next)));
|
|
720
728
|
// ── 5. Caller context: who is calling, and whose data this is ──────────
|
|
721
729
|
// Agent-card GETs need no caller context: discovery must not require a
|
|
722
730
|
// model key or an identity.
|
|
@@ -764,6 +772,18 @@ export async function createA2AApp(options) {
|
|
|
764
772
|
}
|
|
765
773
|
surface[field] = raw;
|
|
766
774
|
}
|
|
775
|
+
// X-Melch-Meta-*: per-request metadata, telemetry only (ADR 0141). Refused when malformed, as X-Surface is.
|
|
776
|
+
const metaHeaders = headerMetadata(req.headers);
|
|
777
|
+
if (metaHeaders.error) {
|
|
778
|
+
res.status(400).json({ error: metaHeaders.error });
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
const meta = metaHeaders.meta;
|
|
782
|
+
// Warned about only once the caller is known: an anonymous request writes no log line.
|
|
783
|
+
const warnDroppedMeta = () => {
|
|
784
|
+
for (const key of metaHeaders.dropped ?? [])
|
|
785
|
+
warn(droppedHeaderWarning(key));
|
|
786
|
+
};
|
|
767
787
|
// The adopter's identity system decides, when one is plugged in.
|
|
768
788
|
if (options.resolveRequest) {
|
|
769
789
|
let identity;
|
|
@@ -796,6 +816,7 @@ export async function createA2AApp(options) {
|
|
|
796
816
|
}
|
|
797
817
|
if (identity.apiKey && !identity.provider)
|
|
798
818
|
provider = 'google';
|
|
819
|
+
warnDroppedMeta();
|
|
799
820
|
requestContextStorage.run({
|
|
800
821
|
apiKey,
|
|
801
822
|
provider,
|
|
@@ -804,7 +825,7 @@ export async function createA2AApp(options) {
|
|
|
804
825
|
caller: identity.caller,
|
|
805
826
|
ownsNested: identity.ownsNested ?? false,
|
|
806
827
|
operator: identity.operator ?? false,
|
|
807
|
-
sourceIp: req.ip, traceparent: validTraceparent(req.headers.traceparent),
|
|
828
|
+
sourceIp: req.ip, traceparent: validTraceparent(req.headers.traceparent), ...(meta ? { meta } : {}),
|
|
808
829
|
}, () => next());
|
|
809
830
|
return;
|
|
810
831
|
}
|
|
@@ -829,7 +850,8 @@ export async function createA2AApp(options) {
|
|
|
829
850
|
// The caller's key funds its provider, and its hash prefixes the scope
|
|
830
851
|
// so no key holder can reach another's data.
|
|
831
852
|
const scopeKey = deriveUserId({ apiKey, siteUserId });
|
|
832
|
-
|
|
853
|
+
warnDroppedMeta();
|
|
854
|
+
requestContextStorage.run({ apiKey, provider, siteUserId, scopeKey, surface, caller: 'shared-secret', ownsNested: !siteUserId, operator: !!options.serverSecret, sourceIp: req.ip, traceparent: validTraceparent(req.headers.traceparent), ...(meta ? { meta } : {}) }, () => next());
|
|
833
855
|
return;
|
|
834
856
|
}
|
|
835
857
|
// Server mode: the server's credentials pay; X-API-Key is not used.
|
|
@@ -837,6 +859,7 @@ export async function createA2AApp(options) {
|
|
|
837
859
|
warnedIgnoredKey = true;
|
|
838
860
|
warn('A caller sent X-API-Key, which server key mode ignores. Set A2A_KEY_MODE=byok (keyMode: \'byok\') if callers fund their own inference — and to keep reaching sessions and memory stored under key-hash silos.');
|
|
839
861
|
}
|
|
862
|
+
warnDroppedMeta();
|
|
840
863
|
requestContextStorage.run({
|
|
841
864
|
apiKey: '',
|
|
842
865
|
provider,
|
|
@@ -846,7 +869,7 @@ export async function createA2AApp(options) {
|
|
|
846
869
|
// Everyone holding the secret is one caller; without a secret (loopback) the caller is local.
|
|
847
870
|
caller: options.serverSecret ? 'shared-secret' : 'local',
|
|
848
871
|
operator: !!options.serverSecret,
|
|
849
|
-
sourceIp: req.ip, traceparent: validTraceparent(req.headers.traceparent),
|
|
872
|
+
sourceIp: req.ip, traceparent: validTraceparent(req.headers.traceparent), ...(meta ? { meta } : {}),
|
|
850
873
|
}, () => next());
|
|
851
874
|
});
|
|
852
875
|
// ── 6. Task rate limit ────────────────────────────────────────────────────
|
|
@@ -933,6 +956,18 @@ export async function createA2AApp(options) {
|
|
|
933
956
|
const served = options.servedAgents ? new Set(options.servedAgents) : undefined;
|
|
934
957
|
class AgentNotFound extends Error {
|
|
935
958
|
}
|
|
959
|
+
// End-user feedback (ADR 0140): behind the same authentication and task
|
|
960
|
+
// rate limit as a task, with its own body limit and per-turn limit.
|
|
961
|
+
const onFeedback = feedbackHandler({
|
|
962
|
+
store: options.storage?.feedback,
|
|
963
|
+
context: () => requestContextStorage.getStore(),
|
|
964
|
+
agentServed: (agentId) => isValidAgentId(agentId) && (!served || served.has(agentId)),
|
|
965
|
+
limiter: feedbackLimiter(),
|
|
966
|
+
log,
|
|
967
|
+
warn,
|
|
968
|
+
});
|
|
969
|
+
app.post('/feedback', onFeedback);
|
|
970
|
+
app.post('/:agentId/feedback', onFeedback);
|
|
936
971
|
const loadAgentConfig = async (agentId) => {
|
|
937
972
|
const registryId = agentId.startsWith('registry:') ? agentId.slice('registry:'.length) : registryIds.has(agentId) ? agentId : undefined;
|
|
938
973
|
if (registryId) {
|
|
@@ -25,6 +25,7 @@ import type { ToolCredentials } from '../tools/oauthConsent.ts';
|
|
|
25
25
|
import type { TaskRecord } from '../observability/metrics.ts';
|
|
26
26
|
import type { AuditSink } from '../observability/audit.ts';
|
|
27
27
|
import type { Policy } from './policy.ts';
|
|
28
|
+
import type { Metadata } from '../observability/metadata.ts';
|
|
28
29
|
/** Per-request caller context, set by the server's identity middleware. */
|
|
29
30
|
export interface A2AContext {
|
|
30
31
|
/** The caller's provider key in BYOK mode (or from resolveRequest); '' otherwise. */
|
|
@@ -54,6 +55,8 @@ export interface A2AContext {
|
|
|
54
55
|
sourceIp?: string;
|
|
55
56
|
/** The caller's W3C traceparent, when well formed: the turn's root span links to it. */
|
|
56
57
|
traceparent?: string;
|
|
58
|
+
/** The request's `X-Melch-Meta-*` headers, checked (ADR 0141): `meta.<key>` on the turn's root span. Telemetry only. */
|
|
59
|
+
meta?: Metadata;
|
|
57
60
|
}
|
|
58
61
|
export interface SurfaceContext {
|
|
59
62
|
name: string;
|
|
@@ -73,6 +76,28 @@ export declare const A2A_APP_NAME = "melchizedek-a2a";
|
|
|
73
76
|
export declare function deriveUserId(ctx: Pick<A2AContext, 'apiKey' | 'siteUserId'>): string;
|
|
74
77
|
/** Root-span attributes for the caller's surface (absent headers add none). */
|
|
75
78
|
export declare function surfaceAttributes(ctx: A2AContext | undefined): Record<string, string>;
|
|
79
|
+
/**
|
|
80
|
+
* A request's metadata as root-span attributes (ADR 0141): the
|
|
81
|
+
* `X-Melch-Meta-*` headers, then the A2A message's `metadata` object (a
|
|
82
|
+
* header wins a key both name), never a key the YAML's own `metadata:`
|
|
83
|
+
* declares, and at most as many keys as the YAML leaves of the 32.
|
|
84
|
+
*/
|
|
85
|
+
export declare function requestMetaAttributes(ctx: Pick<A2AContext, 'meta'> | undefined, messageMetadata: unknown, config: Pick<SyndicateYamlConfig, 'metadata'>, warn?: (message: string) => void): Record<string, string | number | boolean>;
|
|
86
|
+
/** The A2A task states, as the protocol spells them. */
|
|
87
|
+
export type TaskStateName = 'submitted' | 'working' | 'input-required' | 'completed' | 'failed' | 'canceled' | 'rejected' | 'auth-required' | 'unknown';
|
|
88
|
+
/** One state a task entered, as the executor published it (A2AAppOptions.onTaskState). */
|
|
89
|
+
export interface TaskStateRecord {
|
|
90
|
+
taskId: string;
|
|
91
|
+
contextId: string;
|
|
92
|
+
state: TaskStateName;
|
|
93
|
+
/** The syndicate's name, and the agent id the task was sent to (`default` for the one served at /a2a/*). */
|
|
94
|
+
syndicate: string;
|
|
95
|
+
agentId: string;
|
|
96
|
+
/** The turn's trace id, once its root span started (absent on `submitted`, and on a task refused before it ran). */
|
|
97
|
+
traceId?: string;
|
|
98
|
+
/** When the state was published, ms since the epoch. */
|
|
99
|
+
at: number;
|
|
100
|
+
}
|
|
76
101
|
/** One human-readable line naming WHY a turn failed, for the failed task's
|
|
77
102
|
* status message — the text a client renders verbatim. Provider errors
|
|
78
103
|
* sometimes arrive as a raw JSON blob (Gemini ApiError), so the inner
|
|
@@ -115,6 +140,8 @@ export interface ExecutorOptions {
|
|
|
115
140
|
policy?: Policy;
|
|
116
141
|
/** One record per task, however it ended: the task log and metrics. */
|
|
117
142
|
onTaskEnd?: (record: TaskRecord) => void;
|
|
143
|
+
/** Every state the task enters, as it is published (ADR 0141). */
|
|
144
|
+
onTaskState?: (record: TaskStateRecord) => void;
|
|
118
145
|
/** Receives one `task.end` audit event per task (ADR 0042). */
|
|
119
146
|
onAudit?: AuditSink;
|
|
120
147
|
/**
|
|
@@ -204,7 +231,7 @@ export declare class SyndicateExecutor implements AgentExecutor {
|
|
|
204
231
|
constructor(opts: ExecutorOptions);
|
|
205
232
|
/** Provenance stamp for every turn this executor serves (lineage.ts). */
|
|
206
233
|
configHashFor(): string;
|
|
207
|
-
execute(requestContext: RequestContext,
|
|
234
|
+
execute(requestContext: RequestContext, published: ExecutionEventBus): Promise<void>;
|
|
208
235
|
/**
|
|
209
236
|
* Aborts the task's run; `execute` then publishes the `canceled` status.
|
|
210
237
|
* The SDK calls this only on the instance whose event bus carries the
|