melchizedek-agents 1.14.0 → 1.15.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/.env.example +9 -1
- package/CHANGELOG.md +74 -0
- package/DOCUMENTATION.md +22 -0
- package/config/agents/syndicate.schema.json +66 -44
- package/config/agents/syndicateSchema.yaml +11 -6
- package/dist/lib/a2a/app.d.ts +8 -0
- package/dist/lib/a2a/app.js +2 -2
- package/dist/lib/compile.d.ts +4 -4
- package/dist/lib/compileNative.js +4 -1
- package/dist/lib/envSyndicate.d.ts +35 -0
- package/dist/lib/envSyndicate.js +70 -0
- package/dist/lib/loadSyndicate.d.ts +19 -7
- package/dist/lib/loadSyndicate.js +14 -5
- package/dist/lib/models/capabilities.d.ts +2 -0
- package/dist/lib/models/capabilities.js +2 -0
- package/dist/lib/models/chatCompletionsAdapter.js +13 -0
- package/dist/lib/models/claudeAdapter.js +15 -0
- package/dist/lib/models/contextWindow.d.ts +32 -0
- package/dist/lib/models/contextWindow.js +73 -0
- package/dist/lib/models/contract.d.ts +15 -1
- package/dist/lib/models/errorResponse.d.ts +4 -0
- package/dist/lib/models/errorResponse.js +25 -0
- package/dist/lib/models/geminiAdapter.js +9 -0
- package/dist/lib/models/genaiMapping.js +10 -1
- package/dist/lib/models/gptAdapter.d.ts +1 -1
- package/dist/lib/models/gptAdapter.js +3 -0
- package/dist/lib/models/jevAdapter.js +2 -0
- package/dist/lib/registry.d.ts +23 -9
- package/dist/lib/registry.js +57 -3
- package/dist/lib/runtime/native/agentLoop.js +25 -3
- package/dist/lib/runtime/native/compaction.d.ts +91 -42
- package/dist/lib/runtime/native/compaction.js +188 -86
- package/dist/lib/runtime/native/history.d.ts +52 -1
- package/dist/lib/runtime/native/history.js +80 -6
- package/dist/lib/runtime/native/recall.d.ts +9 -0
- package/dist/lib/runtime/native/recall.js +42 -0
- package/dist/lib/runtime/native/request.d.ts +8 -2
- package/dist/lib/runtime/native/request.js +30 -9
- package/dist/lib/storage/postgres/index.d.ts +3 -0
- package/dist/lib/storage/postgres/index.js +2 -0
- package/dist/lib/syndicateSchema.d.ts +16 -12
- package/dist/lib/syndicateSchema.js +10 -12
- package/dist/lib/tools/memoryTools.js +3 -0
- package/dist/lib/tools/tool.d.ts +9 -2
- package/dist/lib/tools/toolContract.d.ts +1 -1
- package/dist/lib/tools/toolContract.js +4 -2
- package/dist/lib/tools/webExtractTool.js +4 -0
- package/dist/scripts/a2a_server.js +28 -4
- package/dist/scripts/registry.d.ts +7 -4
- package/dist/scripts/registry.js +17 -5
- package/package.json +2 -1
package/.env.example
CHANGED
|
@@ -251,9 +251,17 @@ PUBLIC_URL=
|
|
|
251
251
|
# byok: each caller's X-API-Key funds its X-Provider. With A2A_AUTH=secret,
|
|
252
252
|
# byok also scopes data by a hash of that key (the pre-0.16 behaviour).
|
|
253
253
|
# A2A_KEY_MODE=server
|
|
254
|
-
# Bare agent ids that load from the adk_agent_registry table (comma list)
|
|
254
|
+
# Bare agent ids that load from the adk_agent_registry table (comma list),
|
|
255
|
+
# over DATABASE_URL when it is set, else over the Supabase API.
|
|
255
256
|
# Every other bare id is a file; registry:<id> always means the registry.
|
|
256
257
|
# A2A_REGISTRY_AGENTS=
|
|
258
|
+
# The whole syndicate file in one variable, base64 (base64 < agent.yaml), for a
|
|
259
|
+
# host that only takes variables (the published image without a database). It
|
|
260
|
+
# is served as MELCHIZEDEK_AGENT_ID (default agent) and as the default, from a
|
|
261
|
+
# private directory that becomes the agents directory, so MELCHIZEDEK_AGENTS_DIR
|
|
262
|
+
# must be unset. Nested yaml_references travel in its bundled_references.
|
|
263
|
+
# MELCHIZEDEK_AGENT_YAML=
|
|
264
|
+
# MELCHIZEDEK_AGENT_ID=agent
|
|
257
265
|
|
|
258
266
|
# ── A2A server: binding, limits, lifecycle (all optional) ─────────────────────
|
|
259
267
|
# Without A2A_SERVER_SECRET the server binds 127.0.0.1 only. To bind another
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,80 @@ Consumers of the package read this file; it records changes to the
|
|
|
4
4
|
**published API surface** (the exports map in `package.json`, the bins,
|
|
5
5
|
the starter pack and the templates), not the repo's full history.
|
|
6
6
|
|
|
7
|
+
## 1.15.0 — 2026-11-02
|
|
8
|
+
|
|
9
|
+
**Breaking — read before upgrading**
|
|
10
|
+
|
|
11
|
+
- **Compaction and masking are on for every agent** (ADR 0152, 0154). A
|
|
12
|
+
`context:` block used to be allowed on delegates only and nothing
|
|
13
|
+
compacted unless it set `compact_after_tokens`. Now every agent compacts
|
|
14
|
+
when a request passes 60% of its model's context window
|
|
15
|
+
(`lib/models/contextWindow.ts` holds the table; an unknown model counts
|
|
16
|
+
as 100k; an Ollama model reads its own context length, default 4,096),
|
|
17
|
+
and a tool result older than the last 6 events and longer than 1,000
|
|
18
|
+
characters goes to the model as a stub it can fetch back with
|
|
19
|
+
`recall_tool_result`. `context: off` on an agent restores the old
|
|
20
|
+
behaviour; `compact_after_tokens: 0` and `mask_over_chars: 0` switch
|
|
21
|
+
the two off separately; `keep_recent_events` and `summary_model` are
|
|
22
|
+
the other keys. A request the provider refuses as too long is retried
|
|
23
|
+
once after a forced compaction.
|
|
24
|
+
- **A `defineTool` result is capped at 20,000 characters** unless the spec
|
|
25
|
+
sets `maxResultChars` (0 lifts the cap). A tool that returned more now
|
|
26
|
+
returns the head and a note saying so.
|
|
27
|
+
|
|
28
|
+
**The prompt prefix is stable** (ADR 0151). Text that changes between an
|
|
29
|
+
agent's requests (`preload_memory`'s recall) leaves the system prompt for
|
|
30
|
+
`ModelRequest.turnNotes`, which every adapter places after the history, so
|
|
31
|
+
a provider's prefix cache covers the tools, the system prompt and the
|
|
32
|
+
history. An `InstructionTool` marked `volatile: true` writes there. The
|
|
33
|
+
Claude adapter sends `cache_control: { type: 'ephemeral' }` on Anthropic's
|
|
34
|
+
own API.
|
|
35
|
+
|
|
36
|
+
**The summary is sectioned and anchored** (ADR 0153): Intent, Stated facts,
|
|
37
|
+
Decisions, Artifacts, Open, Last state, written from the previous summary
|
|
38
|
+
and the new span only, tool calls and results included, within 1,200 words.
|
|
39
|
+
A summary the model fails to write is skipped (the turn goes on with the
|
|
40
|
+
full history, and the span records `compaction.skipped`) instead of
|
|
41
|
+
replacing the history with nothing. A compacted event carries
|
|
42
|
+
`summaryFormat: 'sections/1'`; the summary's own model call carries
|
|
43
|
+
`llm.purpose: compaction` and `llm.compaction.events`.
|
|
44
|
+
|
|
45
|
+
**`CapabilityReport.contextWindow`** says how many tokens a model's context
|
|
46
|
+
holds, when the table knows the model.
|
|
47
|
+
|
|
48
|
+
**The server image is published with every release** (ADR 0149).
|
|
49
|
+
`ghcr.io/jhwadman/melchizedek-serve:<version>` (and `:latest`) for
|
|
50
|
+
linux/amd64 and linux/arm64, with a build-provenance attestation; the
|
|
51
|
+
GitHub Release carries its digest as `melchizedek-serve-v<version>.digest`.
|
|
52
|
+
Pull by that digest.
|
|
53
|
+
|
|
54
|
+
**A syndicate in one environment variable.** `melchizedek-serve` (and
|
|
55
|
+
`startServer` from `melchizedek-agents/server`) reads
|
|
56
|
+
`MELCHIZEDEK_AGENT_YAML`, the whole syndicate file base64-encoded, and
|
|
57
|
+
serves it as `MELCHIZEDEK_AGENT_ID` (default `agent`) and as the default
|
|
58
|
+
syndicate, from a private directory that becomes the agents directory. A
|
|
59
|
+
definition that does not load stops the boot. It refuses
|
|
60
|
+
`MELCHIZEDEK_AGENTS_DIR` beside it, a syndicate argument other than its
|
|
61
|
+
own, and a nested `yaml_reference` its `bundled_references` does not carry.
|
|
62
|
+
Unset, nothing changes.
|
|
63
|
+
|
|
64
|
+
**The `Dockerfile` builds again.** `npm ci` ran the package's own
|
|
65
|
+
`prepare` (the build) before the sources were copied, so `docker build`
|
|
66
|
+
failed; both stages now install with `--ignore-scripts`.
|
|
67
|
+
|
|
68
|
+
**The registry works on any Postgres** (ADR 0150). With `DATABASE_URL`, the
|
|
69
|
+
server loads `registry:<id>` and `A2A_REGISTRY_AGENTS` rows from that
|
|
70
|
+
database (in `MELCHIZEDEK_DB_SCHEMA` when it names one) instead of over the
|
|
71
|
+
Supabase API, and `melchizedek-registry` publishes, rolls back and retires
|
|
72
|
+
there too. Without `DATABASE_URL`, nothing changes. In the library:
|
|
73
|
+
`melchizedek-agents/registry` exports `postgresRegistryReader(pool)` and its
|
|
74
|
+
functions take a supabase-js client or anything with pg's `query` (a
|
|
75
|
+
`pg.Pool`, PGlite); `loadSyndicateFromRegistry(id, { registry })` reads
|
|
76
|
+
through a `RegistryReader`; `postgresStorage` returns `registry`, and
|
|
77
|
+
`createA2AApp` reads `storage.registry` when it is given. A deployment that
|
|
78
|
+
sets both `DATABASE_URL` and Supabase credentials, for different databases,
|
|
79
|
+
now reads the registry from `DATABASE_URL`'s.
|
|
80
|
+
|
|
7
81
|
## 1.14.0 — 2026-11-01
|
|
8
82
|
|
|
9
83
|
**Files in a message.** The A2A server takes inline file parts (`raw`, or 0.3's
|
package/DOCUMENTATION.md
CHANGED
|
@@ -1342,6 +1342,19 @@ local conversation keeps talking to the same remote conversation.
|
|
|
1342
1342
|
|
|
1343
1343
|
`Dockerfile` builds the compiled server and runs it as a non-root user with
|
|
1344
1344
|
a health check; `compose.yaml` adds optional Ollama and Phoenix (traces).
|
|
1345
|
+
Every release also publishes it as `ghcr.io/jhwadman/melchizedek-serve:<version>`
|
|
1346
|
+
for linux/amd64 and linux/arm64, with a build-provenance attestation; the
|
|
1347
|
+
release carries the digest (`melchizedek-serve-v<version>.digest`), and a
|
|
1348
|
+
deployment should pull by that digest. The image needs no agents directory:
|
|
1349
|
+
`MELCHIZEDEK_AGENT_YAML` carries the syndicate file base64-encoded and serves
|
|
1350
|
+
it as `MELCHIZEDEK_AGENT_ID` (`agent`), or `A2A_REGISTRY_AGENTS` loads it from
|
|
1351
|
+
the registry.
|
|
1352
|
+
|
|
1353
|
+
```bash
|
|
1354
|
+
docker run -p 4000:4000 -e A2A_SERVER_SECRET=… -e GOOGLE_GENAI_API_KEY=… \
|
|
1355
|
+
-e MELCHIZEDEK_AGENT_YAML="$(base64 < agent.yaml)" ghcr.io/jhwadman/melchizedek-serve@sha256:…
|
|
1356
|
+
```
|
|
1357
|
+
|
|
1345
1358
|
`npx melchizedek-db print|apply|status` installs and checks the database.
|
|
1346
1359
|
Set `PUBLIC_URL`, `A2A_SERVER_SECRET`, `A2A_AUTH`, `A2A_SERVED_AGENTS`,
|
|
1347
1360
|
`A2A_TRUST_PROXY` and the provider keys from your secret manager; give the orchestrator's stop timeout at least
|
|
@@ -1421,6 +1434,15 @@ npx melchizedek-registry rollback desk 3 --note "v4 misroutes refunds"
|
|
|
1421
1434
|
npx melchizedek-registry retire desk --yes # stop serving; history stays
|
|
1422
1435
|
```
|
|
1423
1436
|
|
|
1437
|
+
The server reads the registry from the database its storage is on: over
|
|
1438
|
+
`DATABASE_URL` when it is set (any Postgres with the migrations, in
|
|
1439
|
+
`MELCHIZEDEK_DB_SCHEMA` when that names a private schema), else over the
|
|
1440
|
+
Supabase API with `SUPABASE_URL` and `SUPABASE_SERVICE_ROLE_KEY`.
|
|
1441
|
+
`melchizedek-registry` reaches it the same way, `DATABASE_URL` first, and
|
|
1442
|
+
the library's functions take either a supabase-js client or a `pg.Pool`
|
|
1443
|
+
(`postgresRegistryReader(pool)` is the read side, which `postgresStorage`
|
|
1444
|
+
supplies as `registry`).
|
|
1445
|
+
|
|
1424
1446
|
`publish` and `rollback` validate against the same schema the loader uses
|
|
1425
1447
|
before anything is written. The author recorded is
|
|
1426
1448
|
`MELCHIZEDEK_REGISTRY_AUTHOR`, else the local user name; a direct write
|
|
@@ -1013,31 +1013,42 @@
|
|
|
1013
1013
|
"additionalProperties": false
|
|
1014
1014
|
},
|
|
1015
1015
|
"context": {
|
|
1016
|
-
"description": "
|
|
1017
|
-
"
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
"
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1016
|
+
"description": "What the model is sent of a long conversation: older tool results as recallable stubs, and a summary of earlier turns past a share of the window; both on by default. \"off\" sends everything as it is.",
|
|
1017
|
+
"anyOf": [
|
|
1018
|
+
{
|
|
1019
|
+
"type": "object",
|
|
1020
|
+
"properties": {
|
|
1021
|
+
"compact_after_tokens": {
|
|
1022
|
+
"description": "Compact when the last request's prompt passed this many tokens. Default: 60% of the model's context window; 0 never compacts.",
|
|
1023
|
+
"type": "integer",
|
|
1024
|
+
"minimum": 0,
|
|
1025
|
+
"maximum": 9007199254740991
|
|
1026
|
+
},
|
|
1027
|
+
"keep_recent_events": {
|
|
1028
|
+
"description": "Events kept verbatim after the summary, and before masking. Default 6.",
|
|
1029
|
+
"type": "integer",
|
|
1030
|
+
"minimum": 1,
|
|
1031
|
+
"maximum": 100
|
|
1032
|
+
},
|
|
1033
|
+
"summary_model": {
|
|
1034
|
+
"description": "The model that writes the summary. Default: the agent's own.",
|
|
1035
|
+
"type": "string",
|
|
1036
|
+
"minLength": 1
|
|
1037
|
+
},
|
|
1038
|
+
"mask_over_chars": {
|
|
1039
|
+
"description": "An older tool result longer than this many characters is sent as a stub the model can recall. Default 1000; 0 masks none.",
|
|
1040
|
+
"type": "integer",
|
|
1041
|
+
"minimum": 0,
|
|
1042
|
+
"maximum": 9007199254740991
|
|
1043
|
+
}
|
|
1044
|
+
},
|
|
1045
|
+
"additionalProperties": false
|
|
1030
1046
|
},
|
|
1031
|
-
|
|
1032
|
-
"description": "The model that writes the summary. Default: the agent's own.",
|
|
1047
|
+
{
|
|
1033
1048
|
"type": "string",
|
|
1034
|
-
"
|
|
1049
|
+
"const": "off"
|
|
1035
1050
|
}
|
|
1036
|
-
|
|
1037
|
-
"required": [
|
|
1038
|
-
"compact_after_tokens"
|
|
1039
|
-
],
|
|
1040
|
-
"additionalProperties": false
|
|
1051
|
+
]
|
|
1041
1052
|
},
|
|
1042
1053
|
"examples": {
|
|
1043
1054
|
"description": "Few-shot exchanges (input, output) added to every request's instruction.",
|
|
@@ -2129,31 +2140,42 @@
|
|
|
2129
2140
|
"additionalProperties": false
|
|
2130
2141
|
},
|
|
2131
2142
|
"context": {
|
|
2132
|
-
"description": "
|
|
2133
|
-
"
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
"
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2143
|
+
"description": "What the model is sent of a long conversation: older tool results as recallable stubs, and a summary of earlier turns past a share of the window; both on by default. \"off\" sends everything as it is.",
|
|
2144
|
+
"anyOf": [
|
|
2145
|
+
{
|
|
2146
|
+
"type": "object",
|
|
2147
|
+
"properties": {
|
|
2148
|
+
"compact_after_tokens": {
|
|
2149
|
+
"description": "Compact when the last request's prompt passed this many tokens. Default: 60% of the model's context window; 0 never compacts.",
|
|
2150
|
+
"type": "integer",
|
|
2151
|
+
"minimum": 0,
|
|
2152
|
+
"maximum": 9007199254740991
|
|
2153
|
+
},
|
|
2154
|
+
"keep_recent_events": {
|
|
2155
|
+
"description": "Events kept verbatim after the summary, and before masking. Default 6.",
|
|
2156
|
+
"type": "integer",
|
|
2157
|
+
"minimum": 1,
|
|
2158
|
+
"maximum": 100
|
|
2159
|
+
},
|
|
2160
|
+
"summary_model": {
|
|
2161
|
+
"description": "The model that writes the summary. Default: the agent's own.",
|
|
2162
|
+
"type": "string",
|
|
2163
|
+
"minLength": 1
|
|
2164
|
+
},
|
|
2165
|
+
"mask_over_chars": {
|
|
2166
|
+
"description": "An older tool result longer than this many characters is sent as a stub the model can recall. Default 1000; 0 masks none.",
|
|
2167
|
+
"type": "integer",
|
|
2168
|
+
"minimum": 0,
|
|
2169
|
+
"maximum": 9007199254740991
|
|
2170
|
+
}
|
|
2171
|
+
},
|
|
2172
|
+
"additionalProperties": false
|
|
2146
2173
|
},
|
|
2147
|
-
|
|
2148
|
-
"description": "The model that writes the summary. Default: the agent's own.",
|
|
2174
|
+
{
|
|
2149
2175
|
"type": "string",
|
|
2150
|
-
"
|
|
2176
|
+
"const": "off"
|
|
2151
2177
|
}
|
|
2152
|
-
|
|
2153
|
-
"required": [
|
|
2154
|
-
"compact_after_tokens"
|
|
2155
|
-
],
|
|
2156
|
-
"additionalProperties": false
|
|
2178
|
+
]
|
|
2157
2179
|
},
|
|
2158
2180
|
"examples": {
|
|
2159
2181
|
"description": "Few-shot exchanges (input, output) added to every request's instruction.",
|
|
@@ -368,14 +368,19 @@ orchestrator:
|
|
|
368
368
|
# run_code:
|
|
369
369
|
# attach: true
|
|
370
370
|
|
|
371
|
-
# [optional]
|
|
372
|
-
#
|
|
373
|
-
#
|
|
374
|
-
#
|
|
375
|
-
#
|
|
371
|
+
# [optional] What the model is sent of a long conversation; both parts are
|
|
372
|
+
# on by default, and `context: off` turns both off. A tool result older
|
|
373
|
+
# than the last `keep_recent_events` events and longer than
|
|
374
|
+
# `mask_over_chars` goes as a stub the model can read back with
|
|
375
|
+
# recall_tool_result (0 masks none). Past `compact_after_tokens` (default
|
|
376
|
+
# 60% of the model's context window; 0 never compacts) the earlier turns
|
|
377
|
+
# become one sectioned summary, and a prompt that overflows the window
|
|
378
|
+
# anyway is compacted and retried once. The full history stays stored
|
|
379
|
+
# either way (ADR 0152, ADR 0153, ADR 0154).
|
|
376
380
|
# context:
|
|
377
|
-
#
|
|
381
|
+
# mask_over_chars: 1000
|
|
378
382
|
# keep_recent_events: 6
|
|
383
|
+
# compact_after_tokens: 60000
|
|
379
384
|
# summary_model: "gemini-3.5-flash-lite" # default: this agent's model
|
|
380
385
|
|
|
381
386
|
# [optional] "task" on a WORKFLOW NODE: the agent works with its tools
|
package/dist/lib/a2a/app.d.ts
CHANGED
|
@@ -46,6 +46,7 @@ import type { TaskStore } from '@a2a-js/sdk/server';
|
|
|
46
46
|
import type { CompileOptions } from '../compile.ts';
|
|
47
47
|
import type { SessionService } from '../runtime/sessions.ts';
|
|
48
48
|
import type { MemoryService } from '../runtime/memoryService.ts';
|
|
49
|
+
import type { RegistryReader } from '../loadSyndicate.ts';
|
|
49
50
|
import type { SyndicateYamlConfig } from '../loadSyndicate.ts';
|
|
50
51
|
import type { ProviderEndpoint, ProviderId } from '../models/registry.ts';
|
|
51
52
|
import type { TurnLock } from './turnLock.ts';
|
|
@@ -128,6 +129,13 @@ export interface A2AAppOptions {
|
|
|
128
129
|
* route answers 501.
|
|
129
130
|
*/
|
|
130
131
|
feedback?: FeedbackStore;
|
|
132
|
+
/**
|
|
133
|
+
* Where registry rows load from (`registry:<id>`, `registryAgents`): a
|
|
134
|
+
* direct connection to the database holding adk_agent_registry.
|
|
135
|
+
* postgresStorage supplies one on its pool. Without it they load over
|
|
136
|
+
* the Supabase API (SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY).
|
|
137
|
+
*/
|
|
138
|
+
registry?: RegistryReader;
|
|
131
139
|
/**
|
|
132
140
|
* One turn at a time per conversation across instances (postgresStorage
|
|
133
141
|
* supplies an advisory lock). Default: a lock in this process.
|
package/dist/lib/a2a/app.js
CHANGED
|
@@ -320,7 +320,7 @@ export async function createA2AApp(options) {
|
|
|
320
320
|
};
|
|
321
321
|
// ── The default syndicate ──────────────────────────────────────────────────
|
|
322
322
|
const config = checkOAuthHosts(options.defaultSyndicate.startsWith('registry:')
|
|
323
|
-
? await loadSyndicateFromRegistry(options.defaultSyndicate.slice('registry:'.length), { bindings: bindings() })
|
|
323
|
+
? await loadSyndicateFromRegistry(options.defaultSyndicate.slice('registry:'.length), { bindings: bindings(), ...(options.storage?.registry ? { registry: options.storage.registry } : {}) })
|
|
324
324
|
: loadSyndicate(options.defaultSyndicate, { bindings: bindings() }), options.defaultSyndicate);
|
|
325
325
|
// ── Persistence ────────────────────────────────────────────────────────────
|
|
326
326
|
let durableSessions;
|
|
@@ -976,7 +976,7 @@ export async function createA2AApp(options) {
|
|
|
976
976
|
const registryId = agentId.startsWith('registry:') ? agentId.slice('registry:'.length) : registryIds.has(agentId) ? agentId : undefined;
|
|
977
977
|
if (registryId) {
|
|
978
978
|
try {
|
|
979
|
-
const loaded = await loadSyndicateFromRegistry(registryId, { bindings: bindings() });
|
|
979
|
+
const loaded = await loadSyndicateFromRegistry(registryId, { bindings: bindings(), ...(options.storage?.registry ? { registry: options.storage.registry } : {}) });
|
|
980
980
|
return { config: checkOAuthHosts(loaded, `registry:${registryId}`), source: `registry:${registryId}` };
|
|
981
981
|
}
|
|
982
982
|
catch (err) {
|
package/dist/lib/compile.d.ts
CHANGED
|
@@ -70,8 +70,8 @@ export declare function withReasoning(agent: {
|
|
|
70
70
|
generateContentConfig?: object;
|
|
71
71
|
}, model: string | undefined): Record<string, unknown> | undefined;
|
|
72
72
|
/** An agent's `context:` block and its default, owned by the compactor (ADR 0033). */
|
|
73
|
-
export type { ContextConfig } from './runtime/native/compaction.ts';
|
|
74
|
-
import type {
|
|
73
|
+
export type { ContextConfig, ContextSetting } from './runtime/native/compaction.ts';
|
|
74
|
+
import type { ContextSetting } from './runtime/native/compaction.ts';
|
|
75
75
|
export { DEFAULT_KEEP_RECENT_EVENTS } from './runtime/native/compaction.ts';
|
|
76
76
|
/** True when any agent of a syndicate may pause for approval: a gated tool, or skill scripts. */
|
|
77
77
|
export declare function declaresApprovals(config: SyndicateYamlConfig): boolean;
|
|
@@ -202,8 +202,8 @@ export interface AgentSpec {
|
|
|
202
202
|
disallowTransferToPeers?: boolean;
|
|
203
203
|
/** `code_execution: gemini`. */
|
|
204
204
|
codeExecution?: 'gemini';
|
|
205
|
-
/** `context:`: compaction (ADR 0033)
|
|
206
|
-
context?:
|
|
205
|
+
/** `context:`: masking and compaction (ADR 0033, ADR 0154), or `off`. */
|
|
206
|
+
context?: ContextSetting;
|
|
207
207
|
/** `mode: task`. */
|
|
208
208
|
mode?: 'task';
|
|
209
209
|
/** The syndicate's `max_concurrency`, on its orchestrator (ADR 0116): delegated calls one step runs at once. */
|
|
@@ -147,6 +147,9 @@ export function compileNative(spec) {
|
|
|
147
147
|
agent.fallbackModel = spec.fallbackModel;
|
|
148
148
|
if (spec.context)
|
|
149
149
|
agent.context = spec.context;
|
|
150
|
+
// The YAML's id when the adapter's differs: the context-window table reads it (ADR 0154).
|
|
151
|
+
if (spec.modelId && spec.modelId !== model)
|
|
152
|
+
agent.modelId = spec.modelId;
|
|
150
153
|
if (spec.maxConcurrency !== undefined)
|
|
151
154
|
agent.maxConcurrency = spec.maxConcurrency;
|
|
152
155
|
if (spec.metadata)
|
|
@@ -271,7 +274,7 @@ export function nativeAdapterFor(opts = {}, spec) {
|
|
|
271
274
|
if (id && !known.has(id))
|
|
272
275
|
known.set(id, s.resolvedModel);
|
|
273
276
|
}
|
|
274
|
-
for (const id of [s.fallbackModel, s.context
|
|
277
|
+
for (const id of [s.fallbackModel, typeof s.context === 'object' ? s.context.summary_model : undefined]) {
|
|
275
278
|
if (!id || known.has(id) || !opts.resolveModel)
|
|
276
279
|
continue;
|
|
277
280
|
const resolved = opts.resolveModel(id);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/envSyndicate.ts — a syndicate handed to the server in the environment.
|
|
3
|
+
*
|
|
4
|
+
* A container started from the published server image has no agents
|
|
5
|
+
* directory of its own and, without a database, no registry to load from.
|
|
6
|
+
* `MELCHIZEDEK_AGENT_YAML` carries the whole syndicate file, base64-encoded
|
|
7
|
+
* (`base64 < agent.yaml`), so a host that only takes environment variables
|
|
8
|
+
* can still run one. `MELCHIZEDEK_AGENT_ID` names it (default `agent`): the
|
|
9
|
+
* id callers address at `/<id>/` and list in `A2A_SERVED_AGENTS`.
|
|
10
|
+
*
|
|
11
|
+
* The file is written into a fresh private directory (0700, the file 0600)
|
|
12
|
+
* that becomes the agents directory, then loaded once, so a bad definition
|
|
13
|
+
* stops the boot instead of failing the first request. Nested
|
|
14
|
+
* `yaml_reference`s have no files to load from here: each must be carried in
|
|
15
|
+
* the definition's own `bundled_references`, as a registry
|
|
16
|
+
* version carries them, or the boot names the missing ones.
|
|
17
|
+
*
|
|
18
|
+
* Imports only node built-ins and lib/loadSyndicate.ts.
|
|
19
|
+
*/
|
|
20
|
+
/** The id a syndicate from the environment is served under unless MELCHIZEDEK_AGENT_ID says otherwise. */
|
|
21
|
+
export declare const ENV_AGENT_ID = "agent";
|
|
22
|
+
export interface EnvSyndicate {
|
|
23
|
+
/** The private directory the file was written to; the server's agents directory. */
|
|
24
|
+
agentsDir: string;
|
|
25
|
+
/** The served id. */
|
|
26
|
+
id: string;
|
|
27
|
+
/** `<id>.yaml`, the default syndicate's file name. */
|
|
28
|
+
file: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The syndicate in `MELCHIZEDEK_AGENT_YAML`, written to disk and checked, or
|
|
32
|
+
* undefined when the variable is unset. Throws naming the variable and, for
|
|
33
|
+
* a definition that does not load, the loader's own reason.
|
|
34
|
+
*/
|
|
35
|
+
export declare function envSyndicate(env?: NodeJS.ProcessEnv, parent?: string): EnvSyndicate | undefined;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/envSyndicate.ts — a syndicate handed to the server in the environment.
|
|
3
|
+
*
|
|
4
|
+
* A container started from the published server image has no agents
|
|
5
|
+
* directory of its own and, without a database, no registry to load from.
|
|
6
|
+
* `MELCHIZEDEK_AGENT_YAML` carries the whole syndicate file, base64-encoded
|
|
7
|
+
* (`base64 < agent.yaml`), so a host that only takes environment variables
|
|
8
|
+
* can still run one. `MELCHIZEDEK_AGENT_ID` names it (default `agent`): the
|
|
9
|
+
* id callers address at `/<id>/` and list in `A2A_SERVED_AGENTS`.
|
|
10
|
+
*
|
|
11
|
+
* The file is written into a fresh private directory (0700, the file 0600)
|
|
12
|
+
* that becomes the agents directory, then loaded once, so a bad definition
|
|
13
|
+
* stops the boot instead of failing the first request. Nested
|
|
14
|
+
* `yaml_reference`s have no files to load from here: each must be carried in
|
|
15
|
+
* the definition's own `bundled_references`, as a registry
|
|
16
|
+
* version carries them, or the boot names the missing ones.
|
|
17
|
+
*
|
|
18
|
+
* Imports only node built-ins and lib/loadSyndicate.ts.
|
|
19
|
+
*/
|
|
20
|
+
import { mkdtempSync, writeFileSync } from 'node:fs';
|
|
21
|
+
import { tmpdir } from 'node:os';
|
|
22
|
+
import { join } from 'node:path';
|
|
23
|
+
import { loadSyndicate } from './loadSyndicate.js';
|
|
24
|
+
/** The id a syndicate from the environment is served under unless MELCHIZEDEK_AGENT_ID says otherwise. */
|
|
25
|
+
export const ENV_AGENT_ID = 'agent';
|
|
26
|
+
/** Same charset as an A2A agent id (lib/a2a/app.ts isValidAgentId), without `:` and `.`, which name registry rows and files. */
|
|
27
|
+
const AGENT_ID = /^[A-Za-z0-9_-]{1,120}$/;
|
|
28
|
+
const BASE64 = /^[A-Za-z0-9+/_-]+={0,2}$/;
|
|
29
|
+
/**
|
|
30
|
+
* The syndicate in `MELCHIZEDEK_AGENT_YAML`, written to disk and checked, or
|
|
31
|
+
* undefined when the variable is unset. Throws naming the variable and, for
|
|
32
|
+
* a definition that does not load, the loader's own reason.
|
|
33
|
+
*/
|
|
34
|
+
export function envSyndicate(env = process.env, parent = tmpdir()) {
|
|
35
|
+
const encoded = env.MELCHIZEDEK_AGENT_YAML?.replace(/\s+/g, '');
|
|
36
|
+
if (!encoded)
|
|
37
|
+
return undefined;
|
|
38
|
+
const id = env.MELCHIZEDEK_AGENT_ID?.trim() || ENV_AGENT_ID;
|
|
39
|
+
if (!AGENT_ID.test(id)) {
|
|
40
|
+
throw new Error('MELCHIZEDEK_AGENT_ID must be 1-120 letters, digits, _ or -.');
|
|
41
|
+
}
|
|
42
|
+
if (env.MELCHIZEDEK_AGENTS_DIR?.trim()) {
|
|
43
|
+
throw new Error('MELCHIZEDEK_AGENT_YAML and MELCHIZEDEK_AGENTS_DIR are both set: the syndicate comes from one place. Unset one of them.');
|
|
44
|
+
}
|
|
45
|
+
// Node's base64 decoder takes the URL-safe alphabet too.
|
|
46
|
+
const bytes = BASE64.test(encoded) ? Buffer.from(encoded, 'base64') : undefined;
|
|
47
|
+
if (!bytes?.length) {
|
|
48
|
+
throw new Error('MELCHIZEDEK_AGENT_YAML must be the syndicate file base64-encoded (base64 < agent.yaml), not the YAML itself.');
|
|
49
|
+
}
|
|
50
|
+
const agentsDir = mkdtempSync(join(parent, 'melchizedek-agent-'));
|
|
51
|
+
const file = `${id}.yaml`;
|
|
52
|
+
writeFileSync(join(agentsDir, file), bytes, { mode: 0o600 });
|
|
53
|
+
let config;
|
|
54
|
+
try {
|
|
55
|
+
config = loadSyndicate(file, { agentsDir, shippedFallback: false });
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
59
|
+
throw new Error(`MELCHIZEDEK_AGENT_YAML does not load: ${message}`);
|
|
60
|
+
}
|
|
61
|
+
const bundled = config.bundled_references ?? {};
|
|
62
|
+
const missing = (config.subagents ?? [])
|
|
63
|
+
.map((s) => s.yaml_reference)
|
|
64
|
+
.filter((ref) => typeof ref === 'string' && !Object.hasOwn(bundled, ref));
|
|
65
|
+
if (missing.length) {
|
|
66
|
+
throw new Error(`MELCHIZEDEK_AGENT_YAML references nested syndicates it does not carry: ${missing.join(', ')}. `
|
|
67
|
+
+ 'Put each in the definition\'s bundled_references, or serve from the registry (A2A_REGISTRY_AGENTS).');
|
|
68
|
+
}
|
|
69
|
+
return { agentsDir, id, file };
|
|
70
|
+
}
|
|
@@ -5,7 +5,7 @@ import type { OpenApiConfig } from './tools/openapiTools.ts';
|
|
|
5
5
|
import type { McpAuthConfig } from './tools/oauthTools.ts';
|
|
6
6
|
import type { McpTransportKind } from './tools/mcpTransports.ts';
|
|
7
7
|
import type { McpServerConfig } from './tools/mcpToolFactory.ts';
|
|
8
|
-
import type {
|
|
8
|
+
import type { ContextSetting, ExampleConfig } from './compile.ts';
|
|
9
9
|
export type { OpenApiConfig } from './tools/openapiTools.ts';
|
|
10
10
|
export type { SkillsConfig } from './tools/skillToolset.ts';
|
|
11
11
|
import type { WorkflowConfig } from './workflowConfig.ts';
|
|
@@ -182,8 +182,8 @@ export interface AgentYamlConfig {
|
|
|
182
182
|
run_code?: {
|
|
183
183
|
attach?: boolean;
|
|
184
184
|
};
|
|
185
|
-
/**
|
|
186
|
-
context?:
|
|
185
|
+
/** What the model is sent of a long conversation: masking and compaction, both on by default (ADR 0033, ADR 0152, ADR 0154), or `off`. */
|
|
186
|
+
context?: ContextSetting;
|
|
187
187
|
/** `task`: the agent works until it calls finish_task; its arguments are the node's output. Workflow nodes only. */
|
|
188
188
|
mode?: 'task';
|
|
189
189
|
/** Few-shot exchanges added to every request's instruction (ADK's ExampleTool). */
|
|
@@ -415,8 +415,20 @@ export declare function nestedLoader(config: SyndicateYamlConfig, fromFiles?: (r
|
|
|
415
415
|
*/
|
|
416
416
|
export declare function validateRegistryConfig(config: Record<string, any>): void;
|
|
417
417
|
/**
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
418
|
+
* Where registry rows are read from over a direct database connection
|
|
419
|
+
* (DATABASE_URL): `postgresRegistryReader(pool)` in lib/registry.ts, which
|
|
420
|
+
* postgresStorage supplies as `registry`.
|
|
421
421
|
*/
|
|
422
|
-
export
|
|
422
|
+
export interface RegistryReader {
|
|
423
|
+
/** The active definition of `id`, or with `version` that stored version; undefined when there is none. */
|
|
424
|
+
definition(id: string, version?: number): Promise<unknown>;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Loads a syndicate configuration from the registry: through `registry`
|
|
428
|
+
* (a direct Postgres connection) when given, else over the Supabase API.
|
|
429
|
+
* @param registryId The document ID in the adk_agent_registry table; `<id>@<version>` loads one stored version
|
|
430
|
+
* @param options Load options (bindings and overrides), and the reader
|
|
431
|
+
*/
|
|
432
|
+
export declare function loadSyndicateFromRegistry(registryId: string, options?: LoadSyndicateOptions & {
|
|
433
|
+
registry?: RegistryReader;
|
|
434
|
+
}): Promise<SyndicateYamlConfig>;
|
|
@@ -400,13 +400,22 @@ export function validateRegistryConfig(config) {
|
|
|
400
400
|
}
|
|
401
401
|
}
|
|
402
402
|
/**
|
|
403
|
-
* Loads a syndicate configuration from the
|
|
404
|
-
*
|
|
405
|
-
* @param
|
|
403
|
+
* Loads a syndicate configuration from the registry: through `registry`
|
|
404
|
+
* (a direct Postgres connection) when given, else over the Supabase API.
|
|
405
|
+
* @param registryId The document ID in the adk_agent_registry table; `<id>@<version>` loads one stored version
|
|
406
|
+
* @param options Load options (bindings and overrides), and the reader
|
|
406
407
|
*/
|
|
407
408
|
export async function loadSyndicateFromRegistry(registryId, options = {}) {
|
|
409
|
+
const { registry, ...loadOptions } = options;
|
|
410
|
+
if (registry) {
|
|
411
|
+
const pinnedRef = /^(.+)@(\d+)$/.exec(registryId);
|
|
412
|
+
const definition = pinnedRef ? await registry.definition(pinnedRef[1], Number(pinnedRef[2])) : await registry.definition(registryId);
|
|
413
|
+
if (definition === undefined || definition === null)
|
|
414
|
+
throw new Error(`Registry document ${registryId} not found`);
|
|
415
|
+
return resolveAndValidate(definition, `registry:${registryId}`, loadOptions);
|
|
416
|
+
}
|
|
408
417
|
if (!hasSupabaseCredentials()) {
|
|
409
|
-
throw new Error('
|
|
418
|
+
throw new Error('No registry to load from: set DATABASE_URL (a direct Postgres connection), or SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY.');
|
|
410
419
|
}
|
|
411
420
|
const supabaseUrl = process.env.SUPABASE_URL;
|
|
412
421
|
const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
|
@@ -427,5 +436,5 @@ export async function loadSyndicateFromRegistry(registryId, options = {}) {
|
|
|
427
436
|
}
|
|
428
437
|
// The registry row is the live copy of a file (scripts/deploy_agent.ts), so
|
|
429
438
|
// it answers to the same schema; the label says where the bad copy lives.
|
|
430
|
-
return resolveAndValidate(data.yaml_content, `registry:${registryId}`,
|
|
439
|
+
return resolveAndValidate(data.yaml_content, `registry:${registryId}`, loadOptions);
|
|
431
440
|
}
|
|
@@ -48,6 +48,8 @@ export interface CapabilityReport {
|
|
|
48
48
|
dropped: string[];
|
|
49
49
|
/** Declared client-side tools; portable across every path. */
|
|
50
50
|
portable: string[];
|
|
51
|
+
/** The tokens the model's context holds (lib/models/contextWindow.ts); absent for a provider the table does not know. */
|
|
52
|
+
contextWindow?: number;
|
|
51
53
|
}
|
|
52
54
|
export declare function describeCapabilities(model: string, tools?: readonly string[], opts?: {
|
|
53
55
|
callerKey?: boolean;
|
|
@@ -21,6 +21,7 @@ import { PROVIDERS } from './providerMap.js';
|
|
|
21
21
|
import { gatewaySearch, planTransport } from './gateway.js';
|
|
22
22
|
import { nativeSearchOn, PLATFORMS_FOR, platformFromEnv } from './endpoints.js';
|
|
23
23
|
import { claudeGeneration, claudeUrlImagesOn } from './claudeModels.js';
|
|
24
|
+
import { contextWindowOf } from './contextWindow.js';
|
|
24
25
|
/** The platform a direct path uses (ADR 0023); a misconfigured one reads as direct here, the doctor reports it. */
|
|
25
26
|
function directPlatform(provider) {
|
|
26
27
|
try {
|
|
@@ -79,6 +80,7 @@ export function describeCapabilities(model, tools = [], opts = {}) {
|
|
|
79
80
|
native,
|
|
80
81
|
dropped,
|
|
81
82
|
portable,
|
|
83
|
+
...(contextWindowOf(model) !== undefined ? { contextWindow: contextWindowOf(model) } : {}),
|
|
82
84
|
};
|
|
83
85
|
}
|
|
84
86
|
/**
|
|
@@ -896,6 +896,19 @@ export class ChatCompletionsAdapter {
|
|
|
896
896
|
return;
|
|
897
897
|
}
|
|
898
898
|
});
|
|
899
|
+
// This step's notes, after the history: joined to a last user message's
|
|
900
|
+
// text, or a user message of their own after tool results, so the cached
|
|
901
|
+
// prefix before them holds. Never a trailing system message: some
|
|
902
|
+
// OpenAI-compatible servers take system first only.
|
|
903
|
+
if (request.turnNotes) {
|
|
904
|
+
const last = messages[messages.length - 1];
|
|
905
|
+
if (last?.role === 'user' && typeof last.content === 'string')
|
|
906
|
+
last.content = `${last.content}\n\n${request.turnNotes}`;
|
|
907
|
+
else if (last?.role === 'user' && Array.isArray(last.content))
|
|
908
|
+
last.content.push({ type: 'text', text: request.turnNotes });
|
|
909
|
+
else
|
|
910
|
+
messages.push({ role: 'user', content: request.turnNotes });
|
|
911
|
+
}
|
|
899
912
|
if (system.length > 0)
|
|
900
913
|
messages.unshift({ role: 'system', content: system.join('\n\n') });
|
|
901
914
|
return messages;
|