melchizedek-agents 0.16.2 → 0.17.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 +17 -0
- package/AGENT_SETUP.md +2 -1
- package/CHANGELOG.md +142 -0
- package/DOCUMENTATION.md +237 -7
- package/QUICKSTART.md +2 -2
- package/README.md +125 -149
- package/config/agents/examples/harness.yaml +135 -0
- package/config/agents/examples/model_zoo.yaml +23 -9
- package/config/agents/examples/pipeline.yaml +120 -0
- package/config/agents/examples/specs/open-meteo-forecast.json +77 -0
- package/config/agents/examples/specs/open-meteo-geocoding.json +53 -0
- package/config/agents/examples/weather.yaml +59 -0
- package/config/agents/syndicateSchema.yaml +112 -1
- package/db/migrations/0011_erase_expired.sql +198 -0
- package/dist/lib/a2a/app.d.ts +7 -0
- package/dist/lib/a2a/app.js +7 -1
- package/dist/lib/a2a/executor.js +41 -0
- package/dist/lib/compile.d.ts +40 -1
- package/dist/lib/compile.js +132 -14
- package/dist/lib/config.d.ts +17 -0
- package/dist/lib/config.js +17 -0
- package/dist/lib/doctor.js +17 -4
- package/dist/lib/index.d.ts +2 -0
- package/dist/lib/index.js +1 -0
- package/dist/lib/loadSyndicate.d.ts +46 -0
- package/dist/lib/loadSyndicate.js +19 -3
- package/dist/lib/memory/erase.d.ts +5 -0
- package/dist/lib/memory/erase.js +5 -0
- package/dist/lib/models/capabilities.js +10 -0
- package/dist/lib/models/endpoints.d.ts +1 -1
- package/dist/lib/models/endpoints.js +4 -1
- package/dist/lib/models/gateway.d.ts +2 -2
- package/dist/lib/models/gateway.js +4 -4
- package/dist/lib/models/kimiLlm.d.ts +123 -0
- package/dist/lib/models/kimiLlm.js +171 -0
- package/dist/lib/models/providerMap.d.ts +2 -2
- package/dist/lib/models/providerMap.js +4 -1
- package/dist/lib/models/registry.d.ts +1 -1
- package/dist/lib/models/registry.js +11 -3
- package/dist/lib/net/redirects.d.ts +14 -0
- package/dist/lib/net/redirects.js +94 -0
- package/dist/lib/observability/tracer.d.ts +1 -1
- package/dist/lib/persistence/supabaseProvider.d.ts +2 -6
- package/dist/lib/persistence/supabaseProvider.js +2 -31
- package/dist/lib/runtime/questions.d.ts +55 -0
- package/dist/lib/runtime/questions.js +107 -0
- package/dist/lib/runtime/syndicateTurn.d.ts +42 -4
- package/dist/lib/runtime/syndicateTurn.js +162 -16
- package/dist/lib/storage/postgres/index.d.ts +6 -0
- package/dist/lib/storage/postgres/index.js +11 -0
- package/dist/lib/storage/rlsStatus.d.ts +45 -0
- package/dist/lib/storage/rlsStatus.js +69 -0
- package/dist/lib/syndicateSchema.d.ts +169 -0
- package/dist/lib/syndicateSchema.js +327 -2
- package/dist/lib/toolRegistry.js +9 -0
- package/dist/lib/tools/openapiTools.d.ts +46 -0
- package/dist/lib/tools/openapiTools.js +195 -0
- package/dist/lib/tools/skillToolset.d.ts +135 -0
- package/dist/lib/tools/skillToolset.js +247 -0
- package/dist/lib/tools/urlContextTool.d.ts +29 -0
- package/dist/lib/tools/urlContextTool.js +39 -0
- package/dist/lib/workflow.d.ts +72 -0
- package/dist/lib/workflow.js +180 -0
- package/dist/lib/workflowConfig.d.ts +95 -0
- package/dist/lib/workflowConfig.js +93 -0
- package/dist/scripts/syndicate_chat.js +39 -5
- package/package.json +4 -1
- package/skills/briefs/_shared.md +1 -1
- package/skills/briefs/melchizedek-author.md +8 -4
- package/skills/briefs/melchizedek-memory.md +1 -1
- package/skills/briefs/melchizedek-models.md +1 -1
- package/skills/briefs/melchizedek-scribe.md +4 -4
- package/skills/briefs/melchizedek-serve.md +1 -1
- package/skills/briefs/melchizedek.md +4 -1
- package/skills/melchizedek/SKILL.md +4 -1
- package/skills/melchizedek-author/SKILL.md +7 -3
- package/skills/melchizedek-memory/SKILL.md +1 -1
- package/skills/melchizedek-models/SKILL.md +1 -1
- package/skills/melchizedek-scribe/SKILL.md +3 -3
- package/skills/melchizedek-serve/SKILL.md +1 -1
- /package/skills/melchizedek-author/{templates → assets}/minimal.yaml +0 -0
- /package/skills/melchizedek-scribe/{templates → assets}/brief.md +0 -0
package/.env.example
CHANGED
|
@@ -25,6 +25,13 @@ GOOGLE_GENAI_API_KEY=
|
|
|
25
25
|
# turn it on for local development — e.g. the librarian demo (npm run mcp:demo).
|
|
26
26
|
ALLOW_PRIVATE_MCP=false
|
|
27
27
|
|
|
28
|
+
# ── Optional: OpenAPI tools (an agent's `openapi:` list) ──────────────────────
|
|
29
|
+
# ALLOW_PRIVATE_OPENAPI: set to "true" to let an OpenAPI tool call a localhost /
|
|
30
|
+
# private / link-local server. Off by default as an SSRF guard. Credentials an
|
|
31
|
+
# `openapi:` entry names (auth.bearer_env, auth.api_key.env) are read from this
|
|
32
|
+
# file or the environment, never written in YAML.
|
|
33
|
+
# ALLOW_PRIVATE_OPENAPI=false
|
|
34
|
+
|
|
28
35
|
# ── Optional: Anthropic / Claude ──────────────────────────────────────────────
|
|
29
36
|
# Required only when using claude-* model identifiers in syndicate YAML configs.
|
|
30
37
|
# Get your key at: https://console.anthropic.com
|
|
@@ -54,6 +61,16 @@ OPENAI_API_KEY=
|
|
|
54
61
|
# Get your key at: https://console.x.ai
|
|
55
62
|
XAI_API_KEY=
|
|
56
63
|
|
|
64
|
+
# ── Optional: Moonshot AI / Kimi ─────────────────────────────────────────────
|
|
65
|
+
# Required only when using kimi-* model identifiers in YAML (kimi-k3 is the
|
|
66
|
+
# flagship; kimi-k2.6 the cheaper tier; kimi-k2.7-code[-highspeed] for code).
|
|
67
|
+
# Get your key at: https://platform.moonshot.ai (the .cn console serves
|
|
68
|
+
# mainland China). Without it, MODEL_GATEWAY serves kimi-* ids as
|
|
69
|
+
# moonshotai/kimi-k3 and friends. web_search is never native on Kimi
|
|
70
|
+
# (Moonshot's built-in search retires 2026-10-20); use web_extract.
|
|
71
|
+
MOONSHOT_API_KEY=
|
|
72
|
+
# MOONSHOT_BASE_URL= # a proxy speaking the same chat-completions dialect
|
|
73
|
+
|
|
57
74
|
# XAI_COLLECTION_IDS: comma-separated xAI Collection ids searched by the
|
|
58
75
|
# `collections_search` tool (grok-* agents only). Create collections and
|
|
59
76
|
# upload documents at console.x.ai. Declared with no ids set, the tool is
|
package/AGENT_SETUP.md
CHANGED
|
@@ -70,7 +70,8 @@ After the agent reports back:
|
|
|
70
70
|
of them — set only what you'll declare in YAML:
|
|
71
71
|
`ANTHROPIC_API_KEY` for `claude-*` (https://console.anthropic.com),
|
|
72
72
|
`OPENAI_API_KEY` for `gpt-*` (https://platform.openai.com/api-keys),
|
|
73
|
-
`XAI_API_KEY` for `grok-*` (https://console.x.ai)
|
|
73
|
+
`XAI_API_KEY` for `grok-*` (https://console.x.ai),
|
|
74
|
+
`MOONSHOT_API_KEY` for `kimi-*` (https://platform.moonshot.ai). Not sure which
|
|
74
75
|
you need? `npm run doctor` reads every syndicate and prints what is
|
|
75
76
|
ready, what is blocked, and which variable unlocks what — read-only,
|
|
76
77
|
no key value shown. Verify the whole multi-provider surface live with
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,148 @@ 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
|
+
## 0.17.0 — 2026-10-06
|
|
8
|
+
|
|
9
|
+
### Read before upgrading
|
|
10
|
+
|
|
11
|
+
- **Run `npx melchizedek-db apply`.** Migration `0011_erase_expired` ships
|
|
12
|
+
with this version, and the server refuses to start against a database
|
|
13
|
+
behind the shipped migrations.
|
|
14
|
+
- **A public Supabase deployment on `DATABASE_URL` must be hardened.** The
|
|
15
|
+
boot-time RLS check now runs on that path; `db/hardening.sql` (which
|
|
16
|
+
`melchizedek-db apply` runs) satisfies it, `ALLOW_UNHARDENED_DB=true`
|
|
17
|
+
opts out.
|
|
18
|
+
- **Self-correction is on by default.** Retries count against `max_steps`
|
|
19
|
+
and can raise spend; `retries: { model_errors: 0, tool_errors: 0 }`
|
|
20
|
+
restores the previous behaviour.
|
|
21
|
+
|
|
22
|
+
### Changes
|
|
23
|
+
|
|
24
|
+
- **Behaviour change: the hardening check runs on the `DATABASE_URL` path.**
|
|
25
|
+
The boot-time RLS check ran only on the deprecated supabase-js path, so a
|
|
26
|
+
deployment that followed the recommended setup was never checked, and
|
|
27
|
+
"fatal on public deployments without it" did not hold. `postgresStorage`
|
|
28
|
+
now supplies `rlsHardening()` and the server applies the same rules
|
|
29
|
+
(`lib/storage/rlsStatus.ts`): with `PUBLIC_URL` set, a Supabase database
|
|
30
|
+
whose `public` tables lack RLS stops the server (set
|
|
31
|
+
`ALLOW_UNHARDENED_DB=true` to accept the risk). A database with no `anon`
|
|
32
|
+
or `authenticated` role, or with the tables in a private schema, passes.
|
|
33
|
+
`createA2AApp`'s `storage` option accepts `rlsHardening` for custom storage.
|
|
34
|
+
- **Fix: OpenAPI tools no longer follow redirects past the SSRF guard.**
|
|
35
|
+
ADK's REST tool calls `fetch`, which follows redirects, and the guard
|
|
36
|
+
checked only the configured server: an allowed API with an open redirect
|
|
37
|
+
could send a call to the cloud metadata service, and an `api_key` header
|
|
38
|
+
went with it. A call now follows redirects one hop at a time
|
|
39
|
+
(`lib/net/redirects.ts`): same-origin hops get the server's own check,
|
|
40
|
+
cross-origin hops the full guard (even with `ALLOW_PRIVATE_OPENAPI`) and
|
|
41
|
+
only content-negotiation headers. A refused hop returns an error to the
|
|
42
|
+
model. Nothing changes for a fetch made outside an OpenAPI tool call.
|
|
43
|
+
- **Fix: `DELETE /memory` erases expired conversations.** A namespace erase
|
|
44
|
+
(the default) found conversations through their live session rows, so once
|
|
45
|
+
a session expired after seven idle days its ledger turns, spans, payloads
|
|
46
|
+
and A2A tasks survived the erase while the response reported success.
|
|
47
|
+
Migration `0011_erase_expired` replaces `melchizedek_erase_scope`: a
|
|
48
|
+
namespace erase now keeps a turn or task only when its conversation is
|
|
49
|
+
still live in another namespace. Run `npx melchizedek-db apply`; the server
|
|
50
|
+
refuses to start against a database behind the shipped migrations.
|
|
51
|
+
- **Moonshot AI (Kimi) is a provider.** `kimi-*` model ids route to a new
|
|
52
|
+
direct adapter (`lib/models/kimiLlm.ts`, chat completions against
|
|
53
|
+
`api.moonshot.ai`), funded by `MOONSHOT_API_KEY` (`MOONSHOT_BASE_URL` for a
|
|
54
|
+
proxy). Tool calling, delegation, strict structured output, streaming,
|
|
55
|
+
images and `reasoning_content` work; native `web_search` is dropped with a
|
|
56
|
+
warning (Moonshot's model-side search retires 2026-10-20), and thinking is
|
|
57
|
+
not carried across a tool loop (K3 re-reasons each step; both stated in
|
|
58
|
+
the capability matrix). `kimi-k3` takes `reasoning_effort` (pinned `high`,
|
|
59
|
+
`DEFAULT_KIMI_REASONING_EFFORT`); K2.x ids take a thinking switch. With no
|
|
60
|
+
direct key the gateway serves the ids as `moonshotai/…`. The doctor,
|
|
61
|
+
`.env.example`, the schema comments and the docs know the provider;
|
|
62
|
+
`model_zoo.yaml` gains a sixth agent, `kimi` on `kimi-k3` at low effort,
|
|
63
|
+
and the Zookeeper names six providers. Cost note: `kimi-k3` is priced
|
|
64
|
+
like Claude Sonnet 4.6 ($3 / $15, thinking billed as output), so
|
|
65
|
+
`kimi-k2.6` is the budget tier and the gateway the cheap route to K3.
|
|
66
|
+
One plain turn verified live on 2026-10-03; tool loops were not.
|
|
67
|
+
- **The doctor's providers line names each provider's own key.** It probed
|
|
68
|
+
every provider past the fourth with a `grok-` id, so Moonshot was reported
|
|
69
|
+
as missing `XAI_API_KEY`.
|
|
70
|
+
- **Behaviour change: self-correction is on by default.** Every turn now runs
|
|
71
|
+
ADK's reflect-and-retry plugins. A model reply ADK marks malformed
|
|
72
|
+
(`MALFORMED_FUNCTION_CALL`) is retried up to twice with guidance instead of
|
|
73
|
+
failing the turn; a tool that throws is answered with structured reflection
|
|
74
|
+
guidance and retried at most three times. Every retry counts against
|
|
75
|
+
`max_steps`. Set `retries: { model_errors: 0, tool_errors: 0 }` at the root
|
|
76
|
+
of a syndicate to restore the previous behaviour. ADR 0034.
|
|
77
|
+
- **`url_context` and `examples:`.** `url_context` lets a Gemini agent read
|
|
78
|
+
the pages at URLs in the conversation, server-side; on other providers it is
|
|
79
|
+
a no-op the doctor reports as dropped. An agent's `examples: [{ input,
|
|
80
|
+
output }]` adds few-shot exchanges to every request (ADK's `ExampleTool`).
|
|
81
|
+
- **Three agent keys from ADK: `code_execution`, `context`, `mode`.**
|
|
82
|
+
`code_execution: gemini` lets a Gemini agent write and run Python in
|
|
83
|
+
Gemini's server-side sandbox (nothing runs on the host). `context:
|
|
84
|
+
{ compact_after_tokens, keep_recent_events?, summary_model? }` on a delegate
|
|
85
|
+
orchestrator summarizes earlier turns once a prompt passes the threshold,
|
|
86
|
+
keeping the recent ones verbatim and the full history stored. `mode: task`
|
|
87
|
+
on a workflow node makes its output the arguments of its `finish_task`
|
|
88
|
+
call. The schema places each where it means something. ADR 0033.
|
|
89
|
+
- **`openapi:`: any HTTP API as an agent's tools, from its spec.** An agent
|
|
90
|
+
lists OpenAPI 3 spec files (relative to the syndicate file); every operation
|
|
91
|
+
becomes a tool named from its `operationId`, its parameters the arguments,
|
|
92
|
+
its summary the description. Read-only by default (GET operations only,
|
|
93
|
+
unless `operations` names others); a named operation can be listed under
|
|
94
|
+
`require_approval`. Credentials come from environment variables
|
|
95
|
+
(`auth.bearer_env`, `auth.api_key`), never YAML, and are never stored in
|
|
96
|
+
session state. Every server passes the SSRF guard (literal rules at
|
|
97
|
+
compile, DNS before each call; `ALLOW_PRIVATE_OPENAPI=true` for local
|
|
98
|
+
development); results are capped at 20,000 characters. On ADK's
|
|
99
|
+
`OpenAPIToolset`. Worked example: `config/agents/examples/weather.yaml`
|
|
100
|
+
(`npm run syndicate:weather`), with two keyless Open-Meteo specs. ADR 0032.
|
|
101
|
+
- **`ask_user`: an agent asks the person and waits.** A registry tool,
|
|
102
|
+
`ask_user(question, options?)`, on the orchestrator or a plan-dispatch
|
|
103
|
+
route. Called, it ends the turn `input-required` with `result.input`
|
|
104
|
+
(`node`, `message`, `payload.options`); the next plain-text message on the
|
|
105
|
+
conversation becomes the call's result and the agent resumes its own tool
|
|
106
|
+
loop (a dispatch turn goes straight back to the route that asked). Over A2A
|
|
107
|
+
the task carries an `input_request` data part — the same one a workflow's
|
|
108
|
+
`ask_user` node publishes. A delegated subagent or a workflow node listing
|
|
109
|
+
it is a load error. ADR 0031.
|
|
110
|
+
- **Workflows: the `workflow:` block, the third orchestration method.** A
|
|
111
|
+
syndicate may be a graph: its agents are the nodes, `edges` says what runs
|
|
112
|
+
after what and on which route (a map after a node routes on the `route_key`
|
|
113
|
+
of its JSON output, else its text, with `default`), and `nodes` declares
|
|
114
|
+
what is not an agent — `join` (fan-in), `map` (one run per list item,
|
|
115
|
+
concurrently), `tool` (a registry tool as a node), `ask_user` (a pause: the
|
|
116
|
+
turn ends `input-required` with the question, the next message answers,
|
|
117
|
+
and the node outputs `{ reply, input }`). Any node may carry `retry` and
|
|
118
|
+
`timeout`. Every node receives the previous node's output as its message
|
|
119
|
+
and sees nothing else of the conversation unless its YAML says so. Runs on
|
|
120
|
+
ADK's `Workflow`; `runSyndicateTurn` gains `status: 'input-required'` with
|
|
121
|
+
`input` (the A2A server publishes an `input_request` data part;
|
|
122
|
+
`melchizedek-chat` prints the question and takes the next line);
|
|
123
|
+
`compileWorkflow` and `isWorkflowSyndicate` are exported. Not yet inside a
|
|
124
|
+
workflow: approval gates, skill scripts, remote subagents. Worked example:
|
|
125
|
+
`config/agents/examples/pipeline.yaml` (`npm run syndicate:pipeline`);
|
|
126
|
+
ADR 0030.
|
|
127
|
+
- **Agent Skills in a syndicate: the `skills:` agent key and the Harness.**
|
|
128
|
+
An agent may declare `skills: { dir, scripts?, tools? }`. Every skill's
|
|
129
|
+
frontmatter (name, description) is appended to the agent's instruction at
|
|
130
|
+
compile time, so no turn is spent discovering skills; `load_skill` reads one
|
|
131
|
+
SKILL.md in full with the names of the files it ships, and
|
|
132
|
+
`load_skill_resource` reads one file (`references/`, `assets/`, `scripts/`,
|
|
133
|
+
the open standard's layout). A skill's `allowed-tools` frontmatter unlocks
|
|
134
|
+
the registry tools the YAML lists under `skills.tools`, once that skill is
|
|
135
|
+
loaded. With `scripts: local`, `run_skill_script` runs a skill's own scripts
|
|
136
|
+
on the host, each run only after a person approves it through the same
|
|
137
|
+
pause as `require_approval` (A2A `input-required`; `melchizedek-chat` now
|
|
138
|
+
asks `[y/N]`); the model's own code never executes. Built on ADK's
|
|
139
|
+
`SkillToolset` (`lib/tools/skillToolset.ts`). `config/agents/examples/harness.yaml`
|
|
140
|
+
is the specimen: a generic agent that works from whatever shelf of skills
|
|
141
|
+
`--bind skills_dir=…` points it at (default: this package's own suite).
|
|
142
|
+
- **The suite's files move to the standard `assets/` directory.**
|
|
143
|
+
`melchizedek-author` ships `assets/minimal.yaml` (was `templates/minimal.yaml`)
|
|
144
|
+
and `melchizedek-scribe` ships `assets/brief.md` (was `templates/brief.md`),
|
|
145
|
+
so a harness, this one or any other, can read them as skill resources. A
|
|
146
|
+
skill installed by an earlier release keeps its old path until it is
|
|
147
|
+
reinstalled with `--force`.
|
|
148
|
+
|
|
7
149
|
## 0.16.2 — 2026-10-02
|
|
8
150
|
|
|
9
151
|
- **`OTEL_EXPORT_CONTENT=off` also drops library content attributes.** In
|
package/DOCUMENTATION.md
CHANGED
|
@@ -98,6 +98,8 @@ Field reference:
|
|
|
98
98
|
| `memory_extraction_model` | root | The model that distils this syndicate's turns into memory records (long-term only); any model id. Default: the deployment's `MEMORY_EXTRACTION_MODEL`. |
|
|
99
99
|
| `memory_retention_days` | root | Days a fact in this syndicate's namespace is kept; the server deletes older facts when it loads the syndicate and daily after. Requires `memory_namespace` (never applied to the shared default namespace). |
|
|
100
100
|
| `dispatch` | root | Switches the syndicate from DELEGATE to PLAN-DISPATCH routing (§6). `default_route` (required) names the fail-static subagent; `route_key` / `reason_key` name the router's JSON properties (defaults `route` / `reason`). Honoured by every surface (the CLI, the server, the worker, evals). |
|
|
101
|
+
| `workflow` | root | Switches the syndicate to a WORKFLOW (§6): a graph whose nodes are its agents plus declared `join`, `map`, `tool` and `ask_user` nodes, and whose `edges` say what runs after what and on which route. The orchestrator is a node like any other; nothing delegates. Cannot be combined with `dispatch`. Contract: `lib/workflow.ts`; worked example: `examples/pipeline.yaml`. |
|
|
102
|
+
| `retries` | root | Self-correction, on by default ([ADR 0034](./wiki/decisions/0034-self-correction.md)): `model_errors` (default 2) retries a model reply ADK marks malformed (`MALFORMED_FUNCTION_CALL`) instead of failing the turn; `tool_errors` (default 3) answers a tool that threw with structured reflection guidance and caps its retries. `0` turns either off. Every retry is a model call under `max_steps`. |
|
|
101
103
|
| `guards` | root | Optional list of post-answer guard NAMES (`lib/guards/index.ts`). Each runs after the answering turn and before the reply publishes, receiving the final text plus every tool-result text of that turn, and rewrites in place rather than re-asking the model; its notes land in the `[STATUS]` stream. Guards declared by a syndicate reached through `yaml_reference:` count too — the server resolves the union via `collectGuards()`. Resolved by name, never by module path, so adding one is a deliberate act in code; **the published registry ships one, `science`** (citation checks for `research.yaml`); `registerGuard()` adds your own from code, and an unregistered name is warned about and skipped. |
|
|
102
104
|
| `name` / `model` / `instruction` | agent | The agent triple. Any Gemini id, `claude-*`, or `ollama/*` for open-weight local models (see §5). |
|
|
103
105
|
| `description` | subagent | **The delegation API.** The orchestrator reads this when deciding to hand off — write it like a function signature ("Use this subagent to…, pass it…"). |
|
|
@@ -109,6 +111,12 @@ Field reference:
|
|
|
109
111
|
| `max_steps` | root | Cap on model calls per turn, counted across every agent the turn reaches (orchestrator, subagents, nested syndicates). Exceeding it fails the turn with `STEP_LIMIT`. |
|
|
110
112
|
| `includeContents` / `outputKey` / `globalInstruction` / `disallowTransferToParent` / `disallowTransferToPeers` | agent | Passed through to ADK's LlmAgent. `includeContents: none` makes an agent see only the current message. |
|
|
111
113
|
| `mcp_server_url` | subagent | Discover this subagent's tools from a remote MCP server at load time (§3). SSRF-guarded; `ALLOW_PRIVATE_MCP=true` permits localhost for development. |
|
|
114
|
+
| `openapi` | any agent | HTTP APIs as tools, each from an OpenAPI 3 spec file (§3, OpenAPI tools): `spec`, and optionally `operations` (default: the GET operations only), `auth` (from environment variables), `base_url`, `prefix`. |
|
|
115
|
+
| `code_execution` | any Gemini agent | `"gemini"`: the model writes Python and Gemini runs it in Google's server-side sandbox, returning the output to the model; nothing runs on this host. For arithmetic, data and checks a model gets wrong in its head. Gemini models only ([ADR 0033](./wiki/decisions/0033-context-task-code.md)). |
|
|
116
|
+
| `context` | orchestrator | Compacts a long conversation: when the last request's prompt passed `compact_after_tokens`, earlier turns become one summary (written by `summary_model`, default the agent's own) and the last `keep_recent_events` stay verbatim. The full history stays stored; only what the model reads shrinks. The orchestrator of a delegate syndicate only: a dispatch route already reads a bounded projection, a workflow node sees only its input. |
|
|
117
|
+
| `mode` | workflow node | `"task"`: the agent works with its tools until it calls `finish_task`, whose arguments (matching its `outputSchema`) become the node's output. Workflow nodes only. |
|
|
118
|
+
| `examples` | any agent | Few-shot exchanges, `[{ input, output }]` (up to 20), added to every request's instruction by ADK's `ExampleTool`; the model never calls it. Keeps worked examples out of the prose of `instruction`. |
|
|
119
|
+
| `skills` | any agent | Agent Skills (a directory of SKILL.md folders) the agent holds the way a coding harness does: every skill's name and description is appended to its instruction at compile time; `load_skill` reads one in full with the names of its files, `load_skill_resource` reads one file. `scripts: local` adds `run_skill_script`, which runs a skill's own scripts on this machine, each after a person approves (the `require_approval` pause); `tools:` names registry tools a skill's `allowed-tools` may unlock once loaded. Worked example: `examples/harness.yaml`; engine: `lib/tools/skillToolset.ts`. |
|
|
112
120
|
|
|
113
121
|
Validation happens at load: missing names, legacy option blocks, and
|
|
114
122
|
malformed agents fail with pointed errors before any model is called.
|
|
@@ -122,16 +130,18 @@ instance:
|
|
|
122
130
|
|
|
123
131
|
| Name | Kind | Does |
|
|
124
132
|
|---|---|---|
|
|
125
|
-
| `web_search` | Provider-agnostic | Live web search via the agent model's NATIVE search: Gemini grounding, Anthropic `web_search` server tool, OpenAI Responses `web_search`, xAI Agent Tools `web_search`. On local `ollama/*` models the tool is omitted with a one-time warning (keyless stays keyless). On grok-* agents, optional server-side domain filters via `XAI_WEB_SEARCH_ALLOWED_DOMAINS` / `_EXCLUDED_DOMAINS` in `.env` (max 5, mutually exclusive; xAI accepts no date bounds — those are `x_search`-only). Prefer this in new YAMLs. |
|
|
133
|
+
| `web_search` | Provider-agnostic | Live web search via the agent model's NATIVE search: Gemini grounding, Anthropic `web_search` server tool, OpenAI Responses `web_search`, xAI Agent Tools `web_search`. On local `ollama/*` models, and on `kimi-*` (Moonshot's model-side search retires 2026-10-20; its successor is a REST API, not a request field), the tool is omitted with a one-time warning (keyless stays keyless). On grok-* agents, optional server-side domain filters via `XAI_WEB_SEARCH_ALLOWED_DOMAINS` / `_EXCLUDED_DOMAINS` in `.env` (max 5, mutually exclusive; xAI accepts no date bounds — those are `x_search`-only). Prefer this in new YAMLs. |
|
|
126
134
|
| `web_extract` | FunctionTool | Deterministic page reading: fetches 1–5 agent-chosen URLs and returns clean page text (no LLM summarization). Keyless — works on every provider including local `ollama/*`. Per-page char budget (default 15k, `WEB_EXTRACT_CHAR_LIMIT`); long pages return a head+tail window with an `offset` continuation call served from a 15-minute cache. SSRF-guarded (http(s) only, private/link-local hosts refused, redirects re-checked). Block pages (bot checks, paywall stubs, JS shells) are detected code-side and returned as labeled `Error:` blocks, never as content. Pair with `web_search`: search to find, extract to read past the headline. |
|
|
127
135
|
| `x_search` | xAI-only | Live search over X (Twitter) posts via xAI Agent Tools. Self-gates to `grok-*` agents; a silent no-op on every other provider, so mixed-provider YAMLs stay safe. Optional server-side constraints in `.env`: `XAI_X_SEARCH_FROM_DATE`/`_TO_DATE` (inclusive `YYYY-MM-DD`) and `_ALLOWED_HANDLES`/`_EXCLUDED_HANDLES` (max 20, mutually exclusive — allowlist wins). |
|
|
128
136
|
| `collections_search` | xAI-only | Semantic search over xAI **Collections** — hosted document stores (PDFs/text/CSVs) uploaded at console.x.ai — server-side RAG with `collections://…` citations. Which collections: `XAI_COLLECTION_IDS` in `.env` (optional `XAI_COLLECTIONS_MAX_RESULTS`). Declared with no ids → omitted with a warning; non-xAI providers → silent no-op. |
|
|
137
|
+
| `url_context` | Gemini built-in | Gemini reads the pages at URLs in the conversation, server-side (Google fetches them, not this host). On any other provider it is a no-op the doctor reports as dropped; use `web_extract` there. |
|
|
129
138
|
| `google_search` | ADK built-in | Live web search — Gemini agents only (legacy alias; use `web_search`). |
|
|
130
139
|
| `preload_memory` | ADK built-in | Silently injects similarity-matched facts into every request (ambient recall). |
|
|
131
140
|
| `load_memory` | ADK built-in | Explicit tool call to search the fact store (deliberate recall). |
|
|
132
141
|
| `generate_image` | FunctionTool | Calls the Gemini image model directly, saves the result under `outputs/`, returns the path. A FunctionTool because binary `inlineData` cannot survive the AgentTool text boundary. |
|
|
133
142
|
| `inspect_image` | FunctionTool | **Blind visual inventory** of a file under `outputs/`: subjects with exact counts, composition, light, palette, medium cues, artifacts — zero quality judgments. Its signature accepts *only* a file path, so an orchestrator cannot leak expectations into the observation (see `image_production.yaml`). |
|
|
134
143
|
| `task_add` / `task_list` / `task_get` / `task_update` | FunctionTool | A to-do list and job queue. Default: a single-user JSON file (`MELCHIZEDEK_TASKS_FILE`, default `outputs/tasks.json`), so every caller of a shared endpoint shares one list. With `DATABASE_URL` (migration 0009) each caller has its own list, scoped by the caller's scope key, and any number of workers take jobs safely. |
|
|
144
|
+
| `ask_user` | LongRunningFunctionTool | Asks the person one question (optionally with `options`) and ends the turn `input-required`; the next message on the conversation is the call's result. Orchestrator or plan-dispatch route only (§6, Questions). |
|
|
135
145
|
| `task_queue` | FunctionTool | Queues a background job (a self-contained instruction). The tool only writes the queue; `npm run assistant:worker` (`melchizedek-worker`) claims each job, runs it through one agent compiled from YAML (default: the Assistant's Worker), and writes the result back for `task_get`. `--once` drains and exits, for cron. |
|
|
136
146
|
|
|
137
147
|
**MCP tools** are the exception to the registry: a subagent with
|
|
@@ -147,12 +157,46 @@ loopback/private hosts unless `ALLOW_PRIVATE_MCP=true` (SSRF guard);
|
|
|
147
157
|
treat any remote MCP server as an untrusted tool vendor whose results
|
|
148
158
|
are data, never instructions.
|
|
149
159
|
|
|
160
|
+
**OpenAPI tools** turn any HTTP API with an OpenAPI 3 spec into an
|
|
161
|
+
agent's tools, with no tool code (`lib/tools/openapiTools.ts`, on ADK's
|
|
162
|
+
`OpenAPIToolset`; [ADR 0032](./wiki/decisions/0032-openapi-tools.md)):
|
|
163
|
+
|
|
164
|
+
```yaml
|
|
165
|
+
orchestrator:
|
|
166
|
+
name: Forecaster
|
|
167
|
+
openapi:
|
|
168
|
+
- spec: "specs/open-meteo-forecast.json" # relative to this YAML file
|
|
169
|
+
operations: [getForecast] # omitted: every GET operation
|
|
170
|
+
auth: { api_key: { env: "WEATHER_KEY", in: "header", name: "X-Api-Key" } }
|
|
171
|
+
# base_url: overrides servers[0].url · prefix: keeps two APIs apart
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
One tool per operation, named from its `operationId` in snake_case
|
|
175
|
+
(`getForecast` → `get_forecast`; no `operationId`: path and method), with
|
|
176
|
+
the parameters and request body as its arguments and the operation's
|
|
177
|
+
`summary` as the description the model reads. Exposure is deliberate:
|
|
178
|
+
without `operations`, only GET operations become tools, so anything that
|
|
179
|
+
writes is exposed only by naming it, and a named operation can be listed
|
|
180
|
+
under `require_approval` (as written under `operations`) so a person
|
|
181
|
+
approves each call. `auth` names an environment variable, never a value
|
|
182
|
+
(`bearer_env`, or `api_key` with `in: header | query` and `name`); an unset
|
|
183
|
+
variable fails the compile, and a static token is applied to the request,
|
|
184
|
+
never stored in session state. Every server must be http(s) and pass the
|
|
185
|
+
SSRF guard: its literal rules when the agent compiles, the full check with
|
|
186
|
+
DNS before each call; `ALLOW_PRIVATE_OPENAPI=true` permits private hosts for
|
|
187
|
+
local development. A response over 20,000 characters is cut and says so; a
|
|
188
|
+
network failure comes back to the model as an error. Specs are files, never
|
|
189
|
+
URLs: save the spec beside the YAML and review it like code (trim it to the
|
|
190
|
+
operations the agent needs, and write each `summary` for the model). Worked
|
|
191
|
+
example: `config/agents/examples/weather.yaml`, two keyless Open-Meteo specs
|
|
192
|
+
in `examples/specs/`.
|
|
193
|
+
|
|
150
194
|
> **Schema dialects, handled for you.** The factory emits Gemini-style
|
|
151
195
|
> UPPERCASE schema types (`'OBJECT'`, `'STRING'`, …) because the ADK is
|
|
152
196
|
> Gemini-native; every non-Gemini adapter normalizes them back to
|
|
153
197
|
> standard lowercase JSON-Schema at request-build time
|
|
154
198
|
> (`lib/models/schemaNormalize.ts`). MCP tools therefore work on any
|
|
155
|
-
> provider's agents — Gemini, Claude, GPT, or
|
|
199
|
+
> provider's agents — Gemini, Claude, GPT, Grok, or Kimi.
|
|
156
200
|
|
|
157
201
|
## 4. Sessions & long-term memory
|
|
158
202
|
|
|
@@ -260,6 +304,7 @@ accordingly — model optionality is a single YAML line per agent:
|
|
|
260
304
|
| `claude-*` | Anthropic | `lib/models/claudeLlm.ts` | `ANTHROPIC_API_KEY` | ✅ server tool |
|
|
261
305
|
| `gpt-*`, o-series | OpenAI | `lib/models/gptLlm.ts` (Responses API) | `OPENAI_API_KEY` | ✅ web_search tool |
|
|
262
306
|
| `grok-*` | xAI | `lib/models/grokLlm.ts` (Responses API) | `XAI_API_KEY` | ✅ Agent Tools search |
|
|
307
|
+
| `kimi-*` | Moonshot AI (Kimi) | `lib/models/kimiLlm.ts` (chat completions) | `MOONSHOT_API_KEY` | ⚠ omitted + warning |
|
|
263
308
|
| `ollama/*` | Local Ollama | `lib/models/ollamaLlm.ts` | none | ⚠ omitted + warning |
|
|
264
309
|
| *any cloud id whose direct key is absent* | the id's own provider, via a gateway | `lib/models/gatewayLlm.ts` (chat completions) | `MODEL_GATEWAY` + `MODEL_GATEWAY_API_KEY` | ⚠ omitted + reported |
|
|
265
310
|
|
|
@@ -271,6 +316,45 @@ structured outputs ride `outputSchema` → `text.format`, and two
|
|
|
271
316
|
xAI-only tools — `x_search` and `collections_search` (§3) — turn on
|
|
272
317
|
live X search and hosted-document RAG. All verified live on grok-4.5.
|
|
273
318
|
|
|
319
|
+
**Moonshot AI (Kimi).** `kimi-*` ids route to `lib/models/kimiLlm.ts`, a
|
|
320
|
+
subclass of the chat-completions base, against `https://api.moonshot.ai/v1`
|
|
321
|
+
(`MOONSHOT_BASE_URL` for a proxy). Get a key at platform.moonshot.ai; the
|
|
322
|
+
`.cn` console serves mainland China. The family (USD per 1M tokens, October
|
|
323
|
+
2026): `kimi-k3`, the flagship — 2.8T-parameter open-weight MoE, 1M context,
|
|
324
|
+
vision, always-on thinking, $3 in / $0.30 cache hit / $15 out; `kimi-k2.6`,
|
|
325
|
+
the cheaper general tier (256K, vision, thinking switchable, $0.95 / $4);
|
|
326
|
+
`kimi-k2.7-code` and `kimi-k2.7-code-highspeed` (256K, coding, thinking
|
|
327
|
+
cannot be turned off; the highspeed variant streams at ~180 tokens/s for
|
|
328
|
+
twice the price). There is no small or "flash" K3. On price, K3 is a mid-tier
|
|
329
|
+
closed model, not a bargain: Claude Sonnet 4.6's rate, and Claude Sonnet 5.5
|
|
330
|
+
($2 / $10) undercuts it per token while leading it on the coding boards;
|
|
331
|
+
thinking is always on and billed as output, which is why the adapter pins
|
|
332
|
+
effort below Moonshot's `max`. Choose K3 for the strongest open weights
|
|
333
|
+
(self-hostable) and the flat-rate 1M context; choose `kimi-k2.6` for cost;
|
|
334
|
+
or serve K3 through `MODEL_GATEWAY` (OpenRouter's endpoints run from about
|
|
335
|
+
$0.88 to $3.45 per 1M input, the cheap ones quantized). What works: tool calling
|
|
336
|
+
and delegation, strict `json_schema` structured output (K3 and K2.7 Code
|
|
337
|
+
document it; K2.6 is unstable on `$ref`/`oneOf`), SSE streaming, images as
|
|
338
|
+
base64 (Moonshot takes no public image URLs), MCP tools (they are function
|
|
339
|
+
tools to the model), `reasoning_content` surfaced as THINKING. Reasoning:
|
|
340
|
+
K3 takes a top-level `reasoning_effort` (`low` | `high` | `max`); the adapter
|
|
341
|
+
pins `DEFAULT_KIMI_REASONING_EFFORT` (`high`, `lib/config.ts`) unless
|
|
342
|
+
`generateContentConfig.reasoningEffort` says otherwise, and maps `none` to
|
|
343
|
+
`low` because K3 cannot stop thinking. K2.x models take a `thinking:
|
|
344
|
+
{ type }` switch instead: `reasoningEffort: "none"` or `thinkingBudget: 0`
|
|
345
|
+
sends `disabled`, and any effort value is dropped from the body. What does
|
|
346
|
+
not: native `web_search` (dropped with a warning; `web_extract` works, and
|
|
347
|
+
Moonshot's standalone `POST /v1/tools/search`, billed per call, could back
|
|
348
|
+
a client-side tool), and thinking carried across a tool loop (Moonshot asks
|
|
349
|
+
for K3's `reasoning_content` back on the assistant message; this adapter
|
|
350
|
+
keeps scratchpads out of history, so K3 re-reasons each step). K3's open
|
|
351
|
+
weights mean the same id is served by OpenRouter, Together, Fireworks and
|
|
352
|
+
Vercel AI Gateway; with no direct key, `MODEL_GATEWAY` maps it to
|
|
353
|
+
`moonshotai/kimi-k3`. The row in the capability matrix is asserted against
|
|
354
|
+
the request body the adapter sends; one plain turn was verified live on
|
|
355
|
+
2026-10-03 (`npm run demo:models`, kimi-k3 at low effort, 17.9 s), tool
|
|
356
|
+
loops and search were not.
|
|
357
|
+
|
|
274
358
|
**Which keys do I need?** `npm run doctor` (the `melchizedek-doctor` bin)
|
|
275
359
|
reads every syndicate YAML, resolves each agent's model under your `.env`,
|
|
276
360
|
and prints one table — agent, model, provider, which declared server-side
|
|
@@ -340,7 +424,7 @@ line each. `config/agents/examples/claude.yaml` is the minimal Claude example;
|
|
|
340
424
|
provider, and `npm run demo:models` proves the whole surface: one
|
|
341
425
|
prompt to every available provider, printing input, thinking (qwen3
|
|
342
426
|
`<think>` blocks, Claude extended thinking, GPT reasoning summaries,
|
|
343
|
-
Grok reasoning), output, and a per-request token/latency trace; add
|
|
427
|
+
Grok reasoning, Kimi reasoning_content), output, and a per-request token/latency trace; add
|
|
344
428
|
`-- --search` to watch four native web searches plus the local
|
|
345
429
|
omission. Providers without keys are skipped, never fatal.
|
|
346
430
|
|
|
@@ -415,7 +499,7 @@ options. `demo/a2a_demo.mjs` is a complete client.
|
|
|
415
499
|
| `POST /a2a/jsonrpc`, `/a2a/rest` | bearer | the default syndicate |
|
|
416
500
|
| `GET /<agentId>/.well-known/agent-card.json` | bearer | another syndicate's card; its URLs point at `/<agentId>/a2a/...` |
|
|
417
501
|
| `POST /<agentId>/a2a/jsonrpc`, `/<agentId>/a2a/rest` | bearer | another syndicate (`A2A_SERVED_AGENTS` restricts which) |
|
|
418
|
-
| `DELETE /memory` | bearer | erase everything stored for the calling scope: facts, sessions (with subagent rows) and
|
|
502
|
+
| `DELETE /memory` | bearer | erase everything stored for the calling scope: facts, sessions (with subagent rows), ledger rows and A2A tasks, including conversations whose sessions have expired, with per-store counts; `?all=1` covers every memory namespace |
|
|
419
503
|
|
|
420
504
|
"bearer" means the credential `A2A_AUTH` asks for (below); with no
|
|
421
505
|
`A2A_SERVER_SECRET` and no authenticator the server binds `127.0.0.1` only.
|
|
@@ -533,6 +617,8 @@ JSON); `file` parts are refused.
|
|
|
533
617
|
|
|
534
618
|
#### Approval gates (`require_approval`)
|
|
535
619
|
|
|
620
|
+
A skill script run (`skills.scripts: local`) pauses the same way, without being listed: every `run_skill_script` call waits for the person's answer.
|
|
621
|
+
|
|
536
622
|
A tool can run only after a person approves the exact call
|
|
537
623
|
([ADR 0028](./wiki/decisions/0028-approval-gates.md)). The agent lists it:
|
|
538
624
|
|
|
@@ -564,6 +650,35 @@ or native-search sentinels. In code, `runSyndicateTurn` returns
|
|
|
564
650
|
`status: 'input-required'` with `approval`, and the next turn's part
|
|
565
651
|
`approvalResponsePart(approval.id, approved)` answers it.
|
|
566
652
|
|
|
653
|
+
#### Questions (`ask_user`)
|
|
654
|
+
|
|
655
|
+
An agent can ask the person something mid-turn and wait for the answer
|
|
656
|
+
(`lib/runtime/questions.ts`, [ADR 0031](./wiki/decisions/0031-ask-user.md)).
|
|
657
|
+
It lists the tool:
|
|
658
|
+
|
|
659
|
+
```yaml
|
|
660
|
+
orchestrator:
|
|
661
|
+
name: Desk
|
|
662
|
+
tools: [ask_user] # ask_user(question, options?)
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
When the model calls `ask_user`, the turn ends `input-required`, final. The
|
|
666
|
+
status message is the question ("Desk asks: Which account? (personal /
|
|
667
|
+
work)") with a data part
|
|
668
|
+
`{ type: 'input_request', interrupt_id, node, message, payload }`, where
|
|
669
|
+
`payload.options` lists the choices when the agent gave some. The caller's
|
|
670
|
+
next message on the same conversation is the answer: its text becomes the
|
|
671
|
+
call's result, and the agent resumes its own tool loop where it asked. A
|
|
672
|
+
workflow's `ask_user` node (§6, Workflows) publishes the same data part and
|
|
673
|
+
is answered the same way, so a client handles both alike.
|
|
674
|
+
|
|
675
|
+
`ask_user` is allowed where an approval gate is: the orchestrator and the
|
|
676
|
+
subagents of a plan-dispatch syndicate (a dispatch turn that answers goes
|
|
677
|
+
straight back to the route that asked, without the classifier). A delegated
|
|
678
|
+
subagent or a workflow node listing it is a load error. In code,
|
|
679
|
+
`runSyndicateTurn` returns `status: 'input-required'` with `input`
|
|
680
|
+
(`node`, `message`, `payload`), and the next message's text answers it.
|
|
681
|
+
|
|
567
682
|
#### Limits
|
|
568
683
|
|
|
569
684
|
| Setting | Default |
|
|
@@ -634,6 +749,49 @@ Set `PUBLIC_URL`, `A2A_SERVER_SECRET` and the provider keys from your
|
|
|
634
749
|
secret manager; give the orchestrator's stop timeout at least
|
|
635
750
|
`A2A_SHUTDOWN_GRACE_MS`.
|
|
636
751
|
|
|
752
|
+
#### Backups
|
|
753
|
+
|
|
754
|
+
Everything durable is in the one Postgres you point the server at:
|
|
755
|
+
conversations (`adk_sessions`, `adk_session_events`), long-term memory
|
|
756
|
+
(`adk_memory_facts`), the telemetry ledger, the agent registry and its
|
|
757
|
+
versions, A2A tasks, the task tools' lists and the budget counters. The
|
|
758
|
+
engine takes no backups of it; back it up as you would any production
|
|
759
|
+
database, with your provider's scheduled backups or point-in-time recovery
|
|
760
|
+
(RDS and Cloud SQL automated backups; on Supabase, a paid plan, since the
|
|
761
|
+
Free plan has none).
|
|
762
|
+
|
|
763
|
+
What a backup protects is the history. The rest can be rebuilt: the schema
|
|
764
|
+
with `npx melchizedek-db apply`, and registry agents by republishing their
|
|
765
|
+
YAML files. Without a backup, lost conversations, memory and ledger rows
|
|
766
|
+
stay lost.
|
|
767
|
+
|
|
768
|
+
A logical backup works on any provider and is worth having beside its
|
|
769
|
+
snapshots. Use a `pg_dump` at least as new as the server, and the
|
|
770
|
+
schema the tables live in (`public`, or `MELCHIZEDEK_DB_SCHEMA`):
|
|
771
|
+
|
|
772
|
+
```bash
|
|
773
|
+
pg_dump --schema=public --no-owner --no-privileges -Fc -f melch.dump "$DATABASE_URL"
|
|
774
|
+
# restore into a new database that has pgvector:
|
|
775
|
+
psql "$TARGET_URL" -c 'CREATE EXTENSION IF NOT EXISTS vector'
|
|
776
|
+
pg_restore --no-owner --no-privileges -d "$TARGET_URL" melch.dump
|
|
777
|
+
npx melchizedek-db status # with DATABASE_URL=$TARGET_URL: schema version and counts
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
Test the restore, not just the dump: restore into a scratch database and
|
|
781
|
+
compare row counts per table with the source. Two things a backup changes:
|
|
782
|
+
|
|
783
|
+
- **Erasure.** `DELETE /memory` removes a scope from the live database, not
|
|
784
|
+
from backups taken before it. Keep backup retention as short as your
|
|
785
|
+
recovery needs allow. After a restore, re-run any erasures made since the
|
|
786
|
+
backup was taken. The server logs each one with its scope (`Erasure for
|
|
787
|
+
scope …`); keep those lines for as long as you keep backups, since a
|
|
788
|
+
platform's log retention is usually shorter. The same holds for the
|
|
789
|
+
telemetry dead-letter spool (`outputs/telemetry-deadletter.ndjson`), which
|
|
790
|
+
holds ledger rows only while the database was unreachable: replay it
|
|
791
|
+
(`npm run telemetry:replay`) before honouring an erasure, then delete it.
|
|
792
|
+
- **Secrets.** A dump holds every conversation in clear text. Encrypt it at
|
|
793
|
+
rest and give it the same access rules as the database.
|
|
794
|
+
|
|
637
795
|
#### The agent registry
|
|
638
796
|
|
|
639
797
|
A file is the default home of a syndicate. The registry is for a definition
|
|
@@ -793,6 +951,74 @@ Implemented in `scripts/a2a_server.ts`; like `yaml_reference`, it is
|
|
|
793
951
|
A2A-only — the CLI runner (`scripts/syndicate_chat.ts`) still compiles
|
|
794
952
|
every syndicate in DELEGATE mode.
|
|
795
953
|
|
|
954
|
+
### Workflows (`workflow:`) — the third orchestration method
|
|
955
|
+
|
|
956
|
+
A syndicate that declares a `workflow:` block is a **graph**. Its agents
|
|
957
|
+
are the nodes; `edges` says what runs after what, and on which route;
|
|
958
|
+
nothing delegates and nothing classifies. It is for the shape the other
|
|
959
|
+
two methods cannot express: run these two at once, join what they
|
|
960
|
+
produce, edit it, ask the person, then publish.
|
|
961
|
+
|
|
962
|
+
```yaml
|
|
963
|
+
workflow:
|
|
964
|
+
edges:
|
|
965
|
+
- [START, Planner, { article: [Writer, Checker], default: Answerer }]
|
|
966
|
+
- [[Writer, Checker], Both, Editor, Confirm, Publisher]
|
|
967
|
+
nodes:
|
|
968
|
+
Planner: { route_key: "kind" } # an agent: modifiers only
|
|
969
|
+
Both: { join: true } # waits for every predecessor
|
|
970
|
+
Editor: { retry: { max_attempts: 2 } }
|
|
971
|
+
Confirm: { ask_user: "Publish this draft? Reply yes, or say what to change." }
|
|
972
|
+
```
|
|
973
|
+
|
|
974
|
+
**Edges.** Each entry is a chain. `START` opens at least one chain. A
|
|
975
|
+
name is an agent or a declared node; a list of names fans out (after one
|
|
976
|
+
node) or fans in (before one node); a map `{ <route>: <node or nodes>,
|
|
977
|
+
default: <node> }` ends a chain and routes on the output of the node
|
|
978
|
+
before it — the `route_key` property of a JSON output (an agent with an
|
|
979
|
+
`outputSchema`), else the trimmed text — with `default` catching what no
|
|
980
|
+
key matched. Every node receives the previous node's output as its
|
|
981
|
+
message; a join hands on `{ <predecessor>: <output>, … }`.
|
|
982
|
+
|
|
983
|
+
**Nodes.** `workflow.nodes` declares the nodes that are not agents, each
|
|
984
|
+
exactly one kind: `join: true`; `map: <Agent>` (runs the agent once per
|
|
985
|
+
item of a list input, concurrently, `max_parallel` at a time, and outputs
|
|
986
|
+
the list of results); `tool: <registry name>` (runs the tool with the
|
|
987
|
+
node input as its arguments); `ask_user: "<question>"` (below). An entry
|
|
988
|
+
named after an agent carries modifiers only: `route_key`, `retry`
|
|
989
|
+
(`max_attempts`, `initial_delay`, `max_delay`, `backoff_factor`, in
|
|
990
|
+
seconds) and `timeout` (seconds), which any node may carry.
|
|
991
|
+
|
|
992
|
+
**The pause.** An `ask_user` node ends the turn `input-required`
|
|
993
|
+
(`result.input`: the node, the question, and what it was asked about).
|
|
994
|
+
The next message on the conversation is the answer: the node's output
|
|
995
|
+
becomes `{ "reply": <the answer>, "input": <what it received> }` and the
|
|
996
|
+
graph resumes where it waited. `melchizedek-chat` prints the question
|
|
997
|
+
and takes the next line; the A2A server ends the task `input-required`
|
|
998
|
+
with a data part `{ type: 'input_request', interrupt_id, node, message,
|
|
999
|
+
payload, schema }`, and the client's next message on the same
|
|
1000
|
+
conversation resumes it. A later message after the graph has finished
|
|
1001
|
+
starts it again from `START`.
|
|
1002
|
+
|
|
1003
|
+
**What a node sees.** Unless its YAML sets `includeContents`, a node
|
|
1004
|
+
agent sees only its input — not the conversation, not the other nodes —
|
|
1005
|
+
which is what makes a graph legible: each agent's input is one value a
|
|
1006
|
+
person can read in the trace. Every node runs in the shared session, so
|
|
1007
|
+
`memory_system: long-term` would ingest node inputs as user turns;
|
|
1008
|
+
`internal-only` is the sensible default for a workflow.
|
|
1009
|
+
|
|
1010
|
+
**Failure.** A node's error is not the turn's: a node with `retry` tries
|
|
1011
|
+
again (the attempt is recorded in `answer.nodeErrors`), and a node that
|
|
1012
|
+
gives up fails the turn with `NODE_FAILED` naming it. `max_steps` and
|
|
1013
|
+
the deadline cap the whole graph as they cap any turn.
|
|
1014
|
+
|
|
1015
|
+
**Not yet.** Approval gates (`require_approval`, `skills.scripts: local`)
|
|
1016
|
+
and remote `a2a_agent_url` subagents are refused inside a workflow by
|
|
1017
|
+
the schema, and a workflow cannot be another syndicate's `yaml_reference`
|
|
1018
|
+
(ADK cannot yet make a `Workflow` a subagent; nested, only its
|
|
1019
|
+
orchestrator runs). The record is [ADR 0030](./wiki/decisions/0030-workflow-graphs.md);
|
|
1020
|
+
the contract is `lib/workflow.ts`, on ADK's `Workflow`.
|
|
1021
|
+
|
|
796
1022
|
## 7. Extending the framework
|
|
797
1023
|
|
|
798
1024
|
**Call ADK directly (no syndicate)**: the YAML layer is a convenience,
|
|
@@ -801,7 +1027,7 @@ the canonical minimal block — `LlmAgent` + `Runner` +
|
|
|
801
1027
|
`InMemorySessionService` straight from `@google/adk`, ~30 lines you can
|
|
802
1028
|
copy into any repo that has `@google/adk` installed. Add
|
|
803
1029
|
`registerAvailableProviders()` from `lib/models/registry.ts` and the
|
|
804
|
-
same block runs `claude-*` / `gpt-*` / `grok-*` / `ollama/*` ids too.
|
|
1030
|
+
same block runs `claude-*` / `gpt-*` / `grok-*` / `kimi-*` / `ollama/*` ids too.
|
|
805
1031
|
|
|
806
1032
|
**Add a syndicate**: create `config/agents/<name>.yaml` — copy the
|
|
807
1033
|
closest starter-pack file from `config/agents/examples/` or start from
|
|
@@ -852,8 +1078,12 @@ by a person; `skills/README.md` records the procedure.
|
|
|
852
1078
|
optional `adk_telemetry` sink and `adk_agent_registry` with its version
|
|
853
1079
|
history (an unprotected registry is worst of all: agent definitions writable with the anon key
|
|
854
1080
|
means anyone can rewrite the instructions your server boots). The A2A
|
|
855
|
-
server verifies hardening at boot
|
|
856
|
-
|
|
1081
|
+
server verifies hardening at boot on both storage paths (`DATABASE_URL`
|
|
1082
|
+
and supabase-js) and is fatal on public deployments without it
|
|
1083
|
+
(`ALLOW_UNHARDENED_DB=true` accepts the risk). Over `DATABASE_URL` the check
|
|
1084
|
+
passes on its own when the database has no `anon` or `authenticated` role
|
|
1085
|
+
(plain Postgres: no API serves the tables) or when the tables live in a
|
|
1086
|
+
schema other than `public` (`MELCHIZEDEK_DB_SCHEMA`). Note `service_role` bypasses RLS by design — the hardening
|
|
857
1087
|
constrains the API surface, not the trusted server.
|
|
858
1088
|
- **A2A**: bearer auth, a failed-login limiter and a request rate limit
|
|
859
1089
|
are built in; without `A2A_SERVER_SECRET` the server binds loopback only.
|
package/QUICKSTART.md
CHANGED
|
@@ -14,7 +14,7 @@ fifteen. The full reference is [`DOCUMENTATION.md`](./DOCUMENTATION.md).
|
|
|
14
14
|
- Optional: **Ollama** ([ollama.com](https://ollama.com)) for the
|
|
15
15
|
open-weight local syndicates; provider keys only for the models you
|
|
16
16
|
actually declare — **Anthropic** (`claude-*`), **OpenAI** (`gpt-*`),
|
|
17
|
-
**xAI** (`grok-*`) — or one `MODEL_GATEWAY` key as the fallback for
|
|
17
|
+
**xAI** (`grok-*`), **Moonshot** (`kimi-*`) — or one `MODEL_GATEWAY` key as the fallback for
|
|
18
18
|
whatever direct key is missing (native search is lost on that path);
|
|
19
19
|
and a free **Supabase project** (only for persistent sessions / memory).
|
|
20
20
|
Not sure which you need? `npm run doctor` tells you, read-only.
|
|
@@ -243,7 +243,7 @@ console.log(result.text);
|
|
|
243
243
|
|---|---|
|
|
244
244
|
| `Gemini API Key is not configured` | `.env` missing or key not set — step 2. |
|
|
245
245
|
| `[400] Tool call context circulation is not enabled` | The agent's `model:` is too old for agent transfer. Use `gemini-3.8-flash` or newer (all shipped configs already do). |
|
|
246
|
-
| `Model not found` for `claude-*` / `gpt-*` / `grok-*` | The matching provider key (`ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `XAI_API_KEY`) is not set in `.env`, so the provider wasn't registered — `npm run doctor` names the variable. Or set `MODEL_GATEWAY` + `MODEL_GATEWAY_API_KEY` once for every cloud provider (native search is lost on that path). |
|
|
246
|
+
| `Model not found` for `claude-*` / `gpt-*` / `grok-*` / `kimi-*` | The matching provider key (`ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `XAI_API_KEY` / `MOONSHOT_API_KEY`) is not set in `.env`, so the provider wasn't registered — `npm run doctor` names the variable. Or set `MODEL_GATEWAY` + `MODEL_GATEWAY_API_KEY` once for every cloud provider (native search is lost on that path). |
|
|
247
247
|
| `GATEWAY_HTTP_ERROR … 404/400` | The gateway rejected the mapped model id. Fix the name once in `.env`: `MODEL_GATEWAY_MODEL_MAP=<your id>=<the gateway's id>`. `GATEWAY_KEY_MISSING` means `MODEL_GATEWAY` is set without `MODEL_GATEWAY_API_KEY`. |
|
|
248
248
|
| `OLLAMA_UNREACHABLE` for `ollama/*` | Ollama isn't running — start the app or `ollama serve`; then check the model is pulled (`ollama list`). |
|
|
249
249
|
| `Refusing to connect to private/loopback MCP host` | The SSRF guard is on (correctly). For the local demo set `ALLOW_PRIVATE_MCP=true` in `.env`. |
|