melchizedek-agents 1.1.0 → 1.3.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 +19 -0
- package/AGENT_SETUP.md +3 -1
- package/CHANGELOG.md +373 -0
- package/DOCUMENTATION.md +193 -21
- package/ONBOARDING.md +378 -0
- package/QUICKSTART.md +15 -2
- package/README.md +26 -3
- package/config/agents/examples/connectors.yaml +82 -0
- package/config/agents/examples/council.yaml +1 -1
- package/config/agents/examples/critic.yaml +8 -8
- package/config/agents/examples/image_production.yaml +4 -3
- package/config/agents/syndicate.schema.json +389 -33
- package/config/agents/syndicateSchema.yaml +18 -6
- package/config/agents/templates/intake_extractor.yaml +3 -2
- package/config/agents/templates/systems_operator.yaml +5 -2
- package/dist/lib/a2a/app.d.ts +12 -0
- package/dist/lib/a2a/app.js +25 -11
- package/dist/lib/a2a/executor.js +14 -6
- package/dist/lib/a2a/oauthSetup.d.ts +16 -0
- package/dist/lib/a2a/oauthSetup.js +77 -8
- package/dist/lib/agentDialect.d.ts +18 -8
- package/dist/lib/agentDialect.js +36 -19
- package/dist/lib/chatgpt/adapter.d.ts +50 -0
- package/dist/lib/chatgpt/adapter.js +107 -0
- package/dist/lib/chatgpt/cli.d.ts +23 -0
- package/dist/lib/chatgpt/cli.js +77 -0
- package/dist/lib/chatgpt/oauth.d.ts +103 -0
- package/dist/lib/chatgpt/oauth.js +448 -0
- package/dist/lib/chatgpt/state.d.ts +98 -0
- package/dist/lib/chatgpt/state.js +145 -0
- package/dist/lib/chatgpt/store.d.ts +24 -0
- package/dist/lib/chatgpt/store.js +94 -0
- package/dist/lib/compile.d.ts +38 -7
- package/dist/lib/compile.js +97 -62
- package/dist/lib/compileNative.d.ts +11 -3
- package/dist/lib/compileNative.js +18 -1
- package/dist/lib/dispatch.js +6 -2
- package/dist/lib/doctor.d.ts +107 -0
- package/dist/lib/doctor.js +217 -7
- package/dist/lib/loadSyndicate.d.ts +15 -0
- package/dist/lib/mcp/server.d.ts +195 -0
- package/dist/lib/mcp/server.js +564 -0
- package/dist/lib/memory/supabaseMemoryService.js +2 -2
- package/dist/lib/models/adapterResolver.js +6 -1
- package/dist/lib/models/capabilities.d.ts +9 -0
- package/dist/lib/models/capabilities.js +24 -0
- package/dist/lib/models/chatCompletionsAdapter.d.ts +11 -3
- package/dist/lib/models/chatCompletionsAdapter.js +19 -5
- package/dist/lib/models/claudeAdapter.js +4 -0
- package/dist/lib/models/contract.d.ts +5 -2
- package/dist/lib/models/gateway.d.ts +3 -2
- package/dist/lib/models/gateway.js +8 -4
- package/dist/lib/models/gatewayAdapter.d.ts +7 -1
- package/dist/lib/models/gatewayAdapter.js +8 -0
- package/dist/lib/models/geminiAdapter.js +8 -2
- package/dist/lib/models/genaiMapping.d.ts +8 -4
- package/dist/lib/models/genaiMapping.js +23 -9
- package/dist/lib/models/gptAdapter.d.ts +4 -2
- package/dist/lib/models/gptAdapter.js +11 -4
- package/dist/lib/models/grokAdapter.d.ts +3 -1
- package/dist/lib/models/grokAdapter.js +11 -3
- package/dist/lib/models/kimiAdapter.d.ts +2 -1
- package/dist/lib/models/kimiAdapter.js +3 -3
- package/dist/lib/models/reasoning.d.ts +30 -2
- package/dist/lib/models/reasoning.js +56 -5
- package/dist/lib/onboarding.d.ts +135 -0
- package/dist/lib/onboarding.js +578 -0
- package/dist/lib/runtime/approvals.d.ts +2 -0
- package/dist/lib/runtime/approvals.js +4 -3
- package/dist/lib/runtime/credentials.d.ts +6 -0
- package/dist/lib/runtime/native/agentLoop.d.ts +5 -2
- package/dist/lib/runtime/native/agentLoop.js +2 -0
- package/dist/lib/runtime/native/checkpoint.d.ts +12 -5
- package/dist/lib/runtime/native/checkpoint.js +13 -19
- package/dist/lib/runtime/native/delegate.d.ts +82 -1
- package/dist/lib/runtime/native/delegate.js +157 -2
- package/dist/lib/runtime/native/interrupts.d.ts +88 -5
- package/dist/lib/runtime/native/interrupts.js +220 -30
- package/dist/lib/runtime/syndicateTurn.d.ts +4 -2
- package/dist/lib/runtime/syndicateTurn.js +324 -77
- package/dist/lib/runtime/turnControl.d.ts +41 -0
- package/dist/lib/session/transcript.d.ts +9 -0
- package/dist/lib/session/transcript.js +20 -1
- package/dist/lib/skills.d.ts +18 -0
- package/dist/lib/skills.js +73 -1
- package/dist/lib/storage/postgres/index.d.ts +6 -1
- package/dist/lib/storage/postgres/index.js +1 -1
- package/dist/lib/storage/postgres/taskQueue.d.ts +7 -3
- package/dist/lib/storage/postgres/taskQueue.js +10 -3
- package/dist/lib/syndicateSchema.d.ts +184 -9
- package/dist/lib/syndicateSchema.js +143 -29
- package/dist/lib/tools/auth.d.ts +13 -1
- package/dist/lib/tools/auth.js +10 -2
- package/dist/lib/tools/credentialHosts.d.ts +88 -0
- package/dist/lib/tools/credentialHosts.js +165 -0
- package/dist/lib/tools/credentialUses.d.ts +98 -0
- package/dist/lib/tools/credentialUses.js +120 -0
- package/dist/lib/tools/generateImageTool.js +2 -1
- package/dist/lib/tools/inspectImageTool.js +1 -1
- package/dist/lib/tools/mcpToolFactory.d.ts +29 -1
- package/dist/lib/tools/mcpToolFactory.js +72 -26
- package/dist/lib/tools/mcpTransports.d.ts +12 -0
- package/dist/lib/tools/mcpTransports.js +11 -0
- package/dist/lib/tools/oauthConsent.d.ts +18 -3
- package/dist/lib/tools/oauthConsent.js +57 -15
- package/dist/lib/tools/oauthDiscovery.d.ts +202 -0
- package/dist/lib/tools/oauthDiscovery.js +616 -0
- package/dist/lib/tools/oauthHosts.d.ts +2 -0
- package/dist/lib/tools/oauthHosts.js +2 -2
- package/dist/lib/tools/oauthTools.d.ts +54 -13
- package/dist/lib/tools/oauthTools.js +106 -23
- package/dist/lib/tools/openapiTools.d.ts +7 -0
- package/dist/lib/tools/openapiTools.js +49 -3
- package/dist/lib/tools/taskTools.d.ts +32 -2
- package/dist/lib/tools/taskTools.js +93 -33
- package/dist/lib/tools/xApiSearchTool.js +2 -2
- package/dist/lib/workflow/turn.d.ts +22 -3
- package/dist/lib/workflow/turn.js +151 -31
- package/dist/lib/workflow.d.ts +10 -7
- package/dist/lib/workflow.js +10 -7
- package/dist/scripts/a2a_server.d.ts +2 -2
- package/dist/scripts/a2a_server.js +19 -2
- package/dist/scripts/assistant_worker.d.ts +2 -0
- package/dist/scripts/assistant_worker.js +24 -5
- package/dist/scripts/doctor.d.ts +4 -2
- package/dist/scripts/doctor.js +6 -4
- package/dist/scripts/mcp_server.d.ts +12 -0
- package/dist/scripts/mcp_server.js +345 -0
- package/dist/scripts/setup.d.ts +25 -0
- package/dist/scripts/setup.js +181 -0
- package/dist/scripts/skills_install.d.ts +3 -1
- package/dist/scripts/skills_install.js +17 -2
- package/dist/scripts/syndicate_chat.js +12 -3
- package/dist/scripts/yaml_codemod.d.ts +12 -2
- package/dist/scripts/yaml_codemod.js +115 -33
- package/package.json +8 -2
- package/skills/README.md +11 -2
- package/skills/briefs/_shared.md +1 -1
- package/skills/briefs/melchizedek-models.md +4 -2
- package/skills/briefs/skills-readme.md +3 -1
- package/skills/melchizedek-models/SKILL.md +6 -2
- package/skills/melchizedek-onboard/SKILL.md +71 -0
- package/skills/melchizedek-onboard-cloud/SKILL.md +38 -0
- package/skills/melchizedek-onboard-keys/SKILL.md +47 -0
- package/skills/melchizedek-onboard-local/SKILL.md +41 -0
- package/skills/melchizedek-onboard-serve/SKILL.md +43 -0
package/DOCUMENTATION.md
CHANGED
|
@@ -135,19 +135,22 @@ Field reference:
|
|
|
135
135
|
| `name` / `model` / `instruction` | agent | The agent triple. Any Gemini id, `claude-*`, or `ollama/*` for open-weight local models (see §5). |
|
|
136
136
|
| `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…"). |
|
|
137
137
|
| `tools` | agent | Names resolved by the tool registry (§3). Long-term memory agents add `preload_memory` / `load_memory`. |
|
|
138
|
-
| `reasoning` | agent | How hard the agent reasons, on any provider: `none`, `low`, `medium`, `high`, or `{ budget_tokens: <int> }`. The compiler sends each provider the field it reads: a thinking level on Gemini 3, a thinking budget on Claude 4.6 and earlier (2,048 / 8,192 / 16,384 tokens for low / medium / high), adaptive thinking with that effort on later Claude models ([ADR 0049](./wiki/decisions/0049-claude-requests-by-model-generation.md)), an effort word on GPT, Grok, Kimi, Ollama and the gateway ([ADR 0047](./wiki/decisions/0047-provider-neutral-reasoning-key.md)). Unset, each adapter keeps its own default. |
|
|
138
|
+
| `reasoning` | agent | How hard the agent reasons, on any provider: `none`, `low`, `medium`, `high`, `xhigh`, `max`, or `{ budget_tokens: <int> }`. The compiler sends each provider the field it reads: a thinking level on Gemini 3, a thinking budget on Claude 4.6 and earlier (2,048 / 8,192 / 16,384 tokens for low / medium / high), adaptive thinking with that effort on later Claude models ([ADR 0049](./wiki/decisions/0049-claude-requests-by-model-generation.md)), an effort word on GPT, Grok, Kimi, Ollama and the gateway ([ADR 0047](./wiki/decisions/0047-provider-neutral-reasoning-key.md)). `xhigh` and `max` reach the models that take them (both on Claude from Opus 4.7 and Sonnet 5, `max` on Kimi K3, `xhigh` on GPT-5.2 and later and grok-4.7); elsewhere the model's highest setting is sent and the span carries `llm.reasoning.weakened` (`npm run doctor -- --matrix` lists each path, [ADR 0117](./wiki/decisions/0117-tool-choice-and-effort-above-high-in-v2.md)). Unset, each adapter keeps its own default. |
|
|
139
139
|
| `sampling` | agent | `{ temperature, top_p, max_output_tokens, stop }`, the fields the engine sends to every provider; each adapter sends the ones its API takes ([ADR 0115](./wiki/decisions/0115-yaml-schema-v2-provider-neutral-keys.md)). |
|
|
140
140
|
| `output` | agent | `{ schema, mime }`. `mime` is `application/json` (JSON mode, with or without a schema) or `text/plain` (the default). `schema` is the structured-JSON contract: the agent ends its turn on one JSON object matching it. An orchestrator may hold one beside its subagents: it delegates first, then answers in the schema itself. The schema travels in the provider's own structured-output field beside the tools on Claude from Opus 4.8, Sonnet 5 and Haiku 5.5, on OpenAI, and on Gemini 2+ through Vertex AI, and as a `set_model_response` tool elsewhere (the capability matrix's `structured_output_with_tools`, [ADR 0109](./wiki/decisions/0109-structured-output-beside-tools.md)). Worked example: `examples/structured_critic.yaml`. A leaf that holds the schema (`critic.yaml`) and plan-dispatch remain the choices when the answer should be a specialist's. |
|
|
141
141
|
| `model_overrides` | agent | A prompt nuance for one provider: keyed by `gemini`, `anthropic`, `openai`, `xai`, `moonshot` or `ollama`, each entry holds `instruction` (replaces the agent's instruction on that provider) or `instruction_append` (added after a blank line). The entry for the provider of the agent's `model` applies, before the skills index is appended; a `fallback_model` gets the primary's instruction. |
|
|
142
|
-
| `
|
|
142
|
+
| `tool_choice` | agent | Which tools the model may call: `auto` (the default), `none`, `required` (some tool), or `{ name: <tool> }` (that tool). A provider that rejects forcing sends a weaker choice and marks the span `llm.tool_choice.weakened` ([ADR 0117](./wiki/decisions/0117-tool-choice-and-effort-above-high-in-v2.md)). |
|
|
143
|
+
| `generateContentConfig` / `outputSchema` | agent | The deprecated v1 spelling of `sampling`, `output.mime`, `reasoning` (as `thinkingConfig` / `reasoningEffort`), `tool_choice` (as `toolConfig.functionCallingConfig`) and `output.schema`. Both still load and behave the same; a load that finds either prints one deprecation line per file per process (`LoadSyndicateOptions.onWarning`, default `console.warn`), and a v2 key beside its v1 spelling on one agent is a load error. Only `toolConfig.functionCallingConfig.mode: VALIDATED` and Gemini's dynamic budget (`thinkingBudget: -1`) have no v2 form; `topK`, `seed`, the penalties, `candidateCount`, `safetySettings` and `includeThoughts` reach no provider, and the codemod drops `includeThoughts`. `npx melchizedek-codemod [--check] <file|dir>` rewrites a v1 file to v2, comments kept ([ADR 0115](./wiki/decisions/0115-yaml-schema-v2-provider-neutral-keys.md)). |
|
|
143
144
|
| `yaml_reference` | subagent | Mount another syndicate file as a nested subagent. |
|
|
144
145
|
| `a2a_agent_url` | subagent | A REMOTE agent over A2A (§6): the orchestrator delegates to it with one `request` argument; in plan-dispatch it can be a route. No `model`/`instruction` — the remote agent has its own. Credentials come from `A2A_AGENT_TOKENS`, never YAML. |
|
|
145
146
|
| `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`. |
|
|
146
147
|
| `includeContents` / `outputKey` / `globalInstruction` / `disallowTransferToParent` / `disallowTransferToPeers` | agent | Passed through to the agent (spelled after ADK's `LlmAgent` fields). `includeContents: none` makes an agent see only the current message. |
|
|
147
148
|
| `fallback_model` | any agent | A model, ideally on another provider, that answers when this agent's model fails provider-side (5xx, 429, a connection reset, after its own retries) before producing any output, or while that provider's circuit is open: after `MODEL_BREAKER_THRESHOLD` consecutive provider failures (default 5; 0 disables) the provider is skipped for `MODEL_BREAKER_COOLDOWN_MS` (default 30 s). A 4xx and a canceled turn are never redirected, and a stream that already produced text is never replayed elsewhere ([ADR 0044](./wiki/decisions/0044-fallback-model-and-circuit-breaker.md)). |
|
|
148
149
|
| `mcp_tools` | subagent | The MCP server's tools this agent may use; any other tool the server lists is not exposed. On a dispatch route `require_approval` may name them ([ADR 0041](./wiki/decisions/0041-tool-vendors-get-least-privilege.md)). |
|
|
149
|
-
| `mcp_auth` | subagent | `{ oauth2 }`: the OAuth grant the MCP server takes, the same block as an OpenAPI `auth.oauth2`. `client_credentials` sends the server's own token; `authorization_code` sends each user's own token on their own connection (the consent pause asks for it) and needs `mcp_tools` ([ADR 0112](./wiki/decisions/0112-oauth-grants-declared-beside-the-tool.md)). |
|
|
150
|
+
| `mcp_auth` | subagent | `{ oauth2 }`: the OAuth grant the MCP server takes, the same block as an OpenAPI `auth.oauth2`. `client_credentials` sends the server's own token; `authorization_code` sends each user's own token on their own connection (the consent pause asks for it) and needs `mcp_tools` ([ADR 0112](./wiki/decisions/0112-oauth-grants-declared-beside-the-tool.md)). With `client_registration: dynamic` and no client id or endpoints, the authorization server is discovered from the MCP server and this deployment registers itself as a client, every discovered host held to `MELCHIZEDEK_OAUTH_HOSTS` (ADR 0124). |
|
|
150
151
|
| `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. |
|
|
152
|
+
| `mcp_transport` | subagent | How `mcp_server_url` is reached: `auto` (default: Streamable HTTP, falling back to the legacy SSE transport on the spec's signal), `streamable_http` or `sse` ([ADR 0124](./wiki/decisions/0124-mcp-client-streamable-http-several-servers-and-dynamic-registration.md)). |
|
|
153
|
+
| `mcp_servers` | any agent | Several MCP servers: a list of `{ name, url, tools, auth?, transport? }`. `tools` is required on each; a tool name on two servers, or shared with the agent's own tools, is refused at load. `require_approval` may name any server's tool. Not with `mcp_server_url` (ADR 0124). |
|
|
151
154
|
| `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`. |
|
|
152
155
|
| `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)). |
|
|
153
156
|
| `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. |
|
|
@@ -185,14 +188,18 @@ function, and every adapter recognises it by marker
|
|
|
185
188
|
| `inspect_image` | Contract | **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`). |
|
|
186
189
|
| `task_add` / `task_list` / `task_get` / `task_update` | Contract | 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. |
|
|
187
190
|
| `ask_user` | Contract, long-running | 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. Any agent but a workflow node's; inside a delegated subagent the question reaches the person with the agent path (§6, Questions). |
|
|
188
|
-
| `task_queue` | Contract | 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. A run is durable (ADR 0113): it is checkpointed beside the job at every step boundary (migration 0014 on Postgres), a job claimed again resumes from its last checkpoint, SIGTERM re-queues the job in hand, and `task_update` can cancel a running job. |
|
|
191
|
+
| `task_queue` | Contract | 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. A run is durable (ADR 0113): it is checkpointed beside the job at every step boundary (migration 0014 on Postgres), a job claimed again resumes from its last checkpoint (inside a dispatch route too, ADR 0121), a checkpoint above `MELCHIZEDEK_CHECKPOINT_MAX_BYTES` (default 5 MiB of JSON; `TaskBackendOptions.checkpointMaxBytes`, `postgresStorage({ taskQueue })`) is skipped and the previous one kept, SIGTERM re-queues the job in hand, and `task_update` can cancel a running job. |
|
|
189
192
|
|
|
190
193
|
**MCP tools** are the exception to the registry: a subagent with
|
|
191
|
-
`mcp_server_url:`
|
|
192
|
-
|
|
193
|
-
|
|
194
|
+
`mcp_server_url:` (or several servers under `mcp_servers:`) in its YAML
|
|
195
|
+
gets its tools from remote MCP servers at load time.
|
|
196
|
+
`lib/tools/mcpToolFactory.ts` dials each server over Streamable HTTP,
|
|
197
|
+
falling back to the legacy SSE transport when the server answers with
|
|
198
|
+
the spec's signal, lists its tools, and makes each one an own Tool (`loadMcpTools`) — the
|
|
194
199
|
agent's reach is decided by the server, not compiled in.
|
|
195
|
-
`config/agents/examples/
|
|
200
|
+
`config/agents/examples/connectors.yaml` reaches two servers at once
|
|
201
|
+
and shows, commented, an official remote server with dynamic client
|
|
202
|
+
registration. `config/agents/examples/librarian.yaml` plus the demo catalog server
|
|
196
203
|
(`npm run mcp:demo`, `scripts/demo_mcp_server.ts`) are the worked
|
|
197
204
|
example: read tools *and* write tools, so the agent demonstrably
|
|
198
205
|
modifies data on the far side of the protocol. The factory refuses
|
|
@@ -238,7 +245,12 @@ with no allowlist, `authorization_code` is refused. An unset variable fails the
|
|
|
238
245
|
(the database URL, a provider key, an `A2A_` secret: anything `.env.example`
|
|
239
246
|
documents), since the YAML chooses the host it goes to.
|
|
240
247
|
`OPENAPI_CREDENTIAL_ENVS`, when set, is the exact list of variables an `auth`
|
|
241
|
-
may name.
|
|
248
|
+
may name. `MELCHIZEDEK_CREDENTIAL_HOSTS` binds each such variable to the hosts
|
|
249
|
+
its value may reach (`TRACKER_TOKEN=api.tracker.example.com;…`, or
|
|
250
|
+
`createA2AApp({ credentialHosts })`, [ADR 0122](./wiki/decisions/0122-static-credentials-go-only-to-hosts-the-operator-binds.md)):
|
|
251
|
+
unset, a credential goes where the YAML says and the server and the doctor
|
|
252
|
+
name it as unbound; set, it is the whole list, checked when a syndicate
|
|
253
|
+
loads, when it compiles and before each call. A refused or unset variable fails the compile, and a static token is applied to the request,
|
|
242
254
|
never stored in session state. Every server must be http(s) and pass the
|
|
243
255
|
SSRF guard: its literal rules when the agent compiles, the full check with
|
|
244
256
|
DNS before each call; `ALLOW_PRIVATE_OPENAPI=true` permits private hosts for
|
|
@@ -426,11 +438,28 @@ reads every syndicate YAML, resolves each agent's model under your `.env`,
|
|
|
426
438
|
and prints one table — agent, model, provider, which declared server-side
|
|
427
439
|
tools that path runs natively (✓) or drops (✗), and whether the path is
|
|
428
440
|
funded — with one verdict per syndicate and the variables that would
|
|
429
|
-
unlock the most. Read-only; no key value is ever printed
|
|
441
|
+
unlock the most. Read-only; no key value is ever printed, and a
|
|
442
|
+
`MODEL_GATEWAY` it does not recognise is named as such, never repeated.
|
|
443
|
+
`--check` exits 1 on a blocked syndicate, a runtime problem, an
|
|
444
|
+
unrecognised `MODEL_GATEWAY`, a problem with the server's OAuth or
|
|
445
|
+
credential settings, or a tool's OAuth grant whose hosts
|
|
446
|
+
`MELCHIZEDEK_OAUTH_HOSTS` refuses. Every
|
|
430
447
|
starter-pack file opens with a `# tier:` header (`keyless`, one provider
|
|
431
448
|
such as `gemini`, or `multi-provider`) the doctor checks against the
|
|
432
449
|
models. `GOOGLE_GENAI_API_KEY` alone runs fourteen of the nineteen examples.
|
|
433
450
|
|
|
451
|
+
**Not sure where to start? `npx melchizedek-setup`** (`npm run setup` in a
|
|
452
|
+
clone) opens a menu of authentication levels: no key (Ollama), one
|
|
453
|
+
provider's key, several, a gateway key, Vertex AI / Bedrock / Azure,
|
|
454
|
+
per-caller keys (BYOK), caller identities (`A2A_AUTH`), OAuth tool grants,
|
|
455
|
+
Sign in with ChatGPT on your own machine (local only, ADR 0126), and an
|
|
456
|
+
honest entry for Codex CLI, Claude.ai and Gemini CLI sign-ins, which the
|
|
457
|
+
engine does not use. Each prints a startup guide: variable names
|
|
458
|
+
and shapes, the doctor check, the shipped files that run there, and the
|
|
459
|
+
first commands. `--auto` reads the doctor's result and prints the guide
|
|
460
|
+
for the highest level it detects, names only. The same guides, generated
|
|
461
|
+
from the same code, are [`ONBOARDING.md`](./ONBOARDING.md).
|
|
462
|
+
|
|
434
463
|
**One key instead of several — the gateway fallback.** Direct adapters
|
|
435
464
|
are canonical: the native features above exist only on a provider's own
|
|
436
465
|
endpoint. But with `MODEL_GATEWAY=vercel` (or `openrouter`) and
|
|
@@ -811,12 +840,19 @@ runs or refuses the call and finishes before its caller continues. The same
|
|
|
811
840
|
holds inside nested syndicates
|
|
812
841
|
([ADR 0111](./wiki/decisions/0111-pauses-inside-nested-syndicates.md)): a
|
|
813
842
|
nested delegate syndicate's gates anywhere in it, a nested dispatch
|
|
814
|
-
syndicate's on its
|
|
815
|
-
|
|
843
|
+
syndicate's on its routes, which it classifies and runs as at the top
|
|
844
|
+
(`["Boss", "Team", "Ops"]`,
|
|
845
|
+
[ADR 0120](./wiki/decisions/0120-nested-dispatch-syndicates-route-as-at-the-top.md)),
|
|
846
|
+
and a nested workflow's gated agent nodes and
|
|
816
847
|
`ask_user` nodes when the workflow is a delegated subagent; the path then
|
|
817
|
-
ends at the node, e.g. `["Desk", "Writer", "Send"]`. A
|
|
818
|
-
|
|
819
|
-
|
|
848
|
+
ends at the node, e.g. `["Desk", "Writer", "Send"]`. A workflow run as a
|
|
849
|
+
dispatch route or as another workflow's node pauses the turn the same way
|
|
850
|
+
([ADR 0119](./wiki/decisions/0119-workflow-routes-and-nodes-pause-the-turn.md)),
|
|
851
|
+
the path running from the route or node down, e.g. `["Writer", "Send"]`.
|
|
852
|
+
Skill scripts (`skills.scripts: local`) on a delegated
|
|
853
|
+
subagent pause the same way, with `run_skill_script` as the tool
|
|
854
|
+
([ADR 0118](./wiki/decisions/0118-skill-scripts-and-oauth-consent-inside-delegated-subagents.md));
|
|
855
|
+
an agent a map node runs still may not carry them. Only function tools from the registry can be gated, not MCP tools
|
|
820
856
|
or native-search sentinels. In code, `runSyndicateTurn` returns
|
|
821
857
|
`status: 'input-required'` with `approval`, and the next turn's part
|
|
822
858
|
`approvalResponsePart(approval.id, approved)` answers it.
|
|
@@ -891,6 +927,13 @@ await createA2AApp({ /* … */ toolCredentials: { store, consent } });
|
|
|
891
927
|
call again. A message sent before the grant gets the same request back,
|
|
892
928
|
without a model call.
|
|
893
929
|
|
|
930
|
+
The call may sit in a delegated subagent, at any depth (the systems_operator
|
|
931
|
+
template's Systems, say): the request then carries `path`, the agents from
|
|
932
|
+
the turn's own down to the one that asked (in the data part and in
|
|
933
|
+
`consent.path`), the grant is stored under the conversation's app, and the
|
|
934
|
+
next message resumes that subagent's call before its caller continues
|
|
935
|
+
([ADR 0118](./wiki/decisions/0118-skill-scripts-and-oauth-consent-inside-delegated-subagents.md)).
|
|
936
|
+
|
|
894
937
|
The callback refuses these, and stores nothing:
|
|
895
938
|
|
|
896
939
|
- a state that is replayed, tampered with or older than ten minutes;
|
|
@@ -914,6 +957,7 @@ name a provider they declare, never define one):
|
|
|
914
957
|
| `OAUTH_REDIRECT_URI` | `https://agents.example.com/oauth/callback` (no query) | The callback, as registered at every provider. Mounts it and lets a run ask a user to connect. Needs the key |
|
|
915
958
|
| `OAUTH_CALLBACK_IDENTITY` | `required` (default) or `state` | `required`: the browser that completes a grant carries the flow's user's identity, which only `A2A_AUTH=header` behind a gateway gives it. `state`: the single-use state alone binds the flow (a forwarded link can connect the wrong account) |
|
|
916
959
|
| `MELCHIZEDEK_OAUTH_HOSTS` | `provider=host,host;provider=host` | Which hosts each provider's tokens and client secret may be sent to. A host is a hostname, `*.domain` (subdomains only), IPv4 or `[IPv6]` |
|
|
960
|
+
| `MELCHIZEDEK_CREDENTIAL_HOSTS` | `VARIABLE=host,host;VARIABLE=host` | Which hosts each static credential variable a YAML sends (`bearer_env`, `api_key.env`, `client_secret_env`) may be sent to. Unset: as the YAML says, each named as unbound. Set: the whole list ([ADR 0122](./wiki/decisions/0122-static-credentials-go-only-to-hosts-the-operator-binds.md)) |
|
|
917
961
|
|
|
918
962
|
**The host allowlist** ([ADR 0114](./wiki/decisions/0114-oauth-tokens-go-only-to-hosts-the-operator-binds.md))
|
|
919
963
|
is the operator's binding, never the YAML's: `MELCHIZEDEK_OAUTH_HOSTS`, or
|
|
@@ -929,6 +973,21 @@ token to. The server refuses to start on a malformed key, allowlist or
|
|
|
929
973
|
redirect URI, or on a redirect URI without a key; `npm run doctor` reports
|
|
930
974
|
each of these, and a declared grant with no key or redirect URI, by name.
|
|
931
975
|
|
|
976
|
+
**The credential host allowlist** ([ADR 0122](./wiki/decisions/0122-static-credentials-go-only-to-hosts-the-operator-binds.md))
|
|
977
|
+
does the same for the static credentials a YAML sends: `bearer_env` and
|
|
978
|
+
`api_key.env` to an OpenAPI server, and `client_secret_env` to a token
|
|
979
|
+
endpoint. `MELCHIZEDEK_CREDENTIAL_HOSTS`, or
|
|
980
|
+
`createA2AApp({ credentialHosts: { TRACKER_TOKEN: ['api.tracker.example.com'] } })`,
|
|
981
|
+
which wins. Unset, nothing changes: each credential goes to the host the
|
|
982
|
+
YAML names, and the server's boot and `npm run doctor` name every variable a
|
|
983
|
+
served YAML sends that no binding holds (names only). Set, it is the whole
|
|
984
|
+
list: a variable not on it is sent nowhere, and one on it only to its own
|
|
985
|
+
hosts. A syndicate that breaks the rule is refused when it loads, when it
|
|
986
|
+
compiles, and each call (an OpenAPI call, a client-credentials token
|
|
987
|
+
request, a consent code exchange, a refresh) checks again and sends nothing
|
|
988
|
+
when refused. `MCP_BEARER_TOKENS` is not covered: it already maps each token
|
|
989
|
+
to its host.
|
|
990
|
+
|
|
932
991
|
#### Limits
|
|
933
992
|
|
|
934
993
|
| Setting | Default |
|
|
@@ -1099,6 +1158,95 @@ and the parent changes only when it is published again. `--no-bundle` stores
|
|
|
1099
1158
|
the references bare, to resolve from files at run time; a version published
|
|
1100
1159
|
before bundling existed behaves that way too.
|
|
1101
1160
|
|
|
1161
|
+
### MCP server mode (`melchizedek-mcp`)
|
|
1162
|
+
|
|
1163
|
+
`npx melchizedek-mcp` (clone: `npm run mcp:serve --`) serves syndicates as
|
|
1164
|
+
MCP tools, so Claude Code, Codex or any MCP client asks a syndicate the way
|
|
1165
|
+
it calls any other tool ([ADR 0125](./wiki/decisions/0125-syndicates-as-mcp-tools.md)).
|
|
1166
|
+
Each syndicate is one tool named after its file (`research_desk.yaml` is
|
|
1167
|
+
`research_desk`), described by its orchestrator's `description`, taking
|
|
1168
|
+
`{ message, session_id? }`. The result is the turn's answer, then a line with
|
|
1169
|
+
the `session_id`; pass it back to continue the conversation, omit it to start
|
|
1170
|
+
a new one. An answer declared by an output schema (or JSON mode) is also
|
|
1171
|
+
returned as `structuredContent.output`. One more tool, `melch_resume
|
|
1172
|
+
{ session_id, approve?, answer? }`, answers a turn that paused.
|
|
1173
|
+
|
|
1174
|
+
Which syndicates: `--syndicate <id>` (repeatable; a bare id also finds the
|
|
1175
|
+
shipped `examples/` and `templates/`), else every YAML at the agents
|
|
1176
|
+
directory's root (`--agents-dir`, `MELCHIZEDEK_AGENTS_DIR`, else
|
|
1177
|
+
`./config/agents`).
|
|
1178
|
+
|
|
1179
|
+
**Claude Code** (stdio, run from the project, which holds `.env` and
|
|
1180
|
+
`config/agents/`):
|
|
1181
|
+
|
|
1182
|
+
```bash
|
|
1183
|
+
claude mcp add melch -- npx melchizedek-mcp --syndicate research_desk
|
|
1184
|
+
# not installed in the project:
|
|
1185
|
+
claude mcp add melch -- npx -y -p melchizedek-agents melchizedek-mcp --syndicate research_desk
|
|
1186
|
+
# a remote server over Streamable HTTP:
|
|
1187
|
+
claude mcp add --transport http melch https://mcp.example.com/mcp --header "Authorization: Bearer $MCP_SERVER_SECRET"
|
|
1188
|
+
```
|
|
1189
|
+
|
|
1190
|
+
A turn can run for minutes: raise Claude Code's tool timeout with
|
|
1191
|
+
`MCP_TOOL_TIMEOUT` (ms) when a syndicate needs longer than its default. The
|
|
1192
|
+
server sends progress notifications while a turn works.
|
|
1193
|
+
|
|
1194
|
+
**Codex** (`~/.codex/config.toml`):
|
|
1195
|
+
|
|
1196
|
+
```toml
|
|
1197
|
+
[mcp_servers.melch]
|
|
1198
|
+
command = "npx"
|
|
1199
|
+
args = ["-y", "-p", "melchizedek-agents", "melchizedek-mcp", "--syndicate", "research_desk"]
|
|
1200
|
+
cwd = "/path/to/your/project"
|
|
1201
|
+
tool_timeout_sec = 900
|
|
1202
|
+
|
|
1203
|
+
# or a running HTTP server:
|
|
1204
|
+
# [mcp_servers.melch]
|
|
1205
|
+
# url = "http://127.0.0.1:4100/mcp"
|
|
1206
|
+
# bearer_token_env_var = "MCP_SERVER_SECRET"
|
|
1207
|
+
```
|
|
1208
|
+
|
|
1209
|
+
**Pauses.** A turn that stops for a person returns `status: input-required`
|
|
1210
|
+
with what is waiting and the `session_id`, and nothing has run:
|
|
1211
|
+
|
|
1212
|
+
| Waiting for | The result says | Resume with |
|
|
1213
|
+
|---|---|---|
|
|
1214
|
+
| an approval (`require_approval`) | the agent, the tool, its arguments | `melch_resume { session_id, approve: true \| false }` |
|
|
1215
|
+
| a question (`ask_user`) | the question and its options | `melch_resume { session_id, answer }` |
|
|
1216
|
+
| an OAuth grant | the provider and the authorization URL | `melch_resume { session_id }` once the person has granted it |
|
|
1217
|
+
|
|
1218
|
+
An approval is answered only through `melch_resume`'s explicit `approve`: a
|
|
1219
|
+
message that reads "approve" on the syndicate's own tool repeats the request.
|
|
1220
|
+
The server never approves anything itself. Resumes run through the same path
|
|
1221
|
+
an A2A answer does, so a call paused inside a delegated subagent, a dispatch
|
|
1222
|
+
route or a workflow node resumes where it paused.
|
|
1223
|
+
|
|
1224
|
+
**Transports.** stdio is the default; every log line goes to stderr, so stdout
|
|
1225
|
+
carries only the protocol. `--http` serves Streamable HTTP at `/mcp`
|
|
1226
|
+
(stateful sessions, `Mcp-Session-Id`), on `127.0.0.1:4100` (`--host`,
|
|
1227
|
+
`--port`, `MCP_HOST`, `MCP_PORT`). On loopback the Host header must name
|
|
1228
|
+
loopback (DNS rebinding). Beyond loopback the bin refuses to start without
|
|
1229
|
+
`MCP_SERVER_SECRET` (32 characters or more), which every request then
|
|
1230
|
+
presents as `Authorization: Bearer`; `MCP_ALLOWED_HOSTS` names the Host
|
|
1231
|
+
values it answers, `MCP_TRUST_PROXY` the proxies in front. Requests are
|
|
1232
|
+
limited to `MCP_RATE_LIMIT_PER_MINUTE` (240) per IP, and failed
|
|
1233
|
+
authentications to 30 per 15 minutes per IP. `X-User-Id` scopes an HTTP
|
|
1234
|
+
caller's sessions and memory as on the A2A server; a stdio client's scope is
|
|
1235
|
+
`MCP_USER_ID` (default `default`).
|
|
1236
|
+
|
|
1237
|
+
**The same turn as A2A.** Each tool call is one task through the A2A
|
|
1238
|
+
executor: budgets (`A2A_BUDGETS`), the concurrency caps
|
|
1239
|
+
(`A2A_MAX_CONCURRENT_*`), one turn at a time per conversation, the deadline
|
|
1240
|
+
(`A2A_TASK_TIMEOUT_MS`), guards, the ledger, the task record and memory
|
|
1241
|
+
ingestion. MCP's `notifications/cancelled` cancels the turn. `DATABASE_URL`
|
|
1242
|
+
keeps sessions, memory, turn locks, the audit trail and tool credentials in
|
|
1243
|
+
Postgres; without it they live in process memory. The OAuth consent callback
|
|
1244
|
+
is mounted on `--http` when `OAUTH_REDIRECT_URI` is set; stdio serves none.
|
|
1245
|
+
|
|
1246
|
+
In your own process: `createMcpServer({ syndicates, ... })` from
|
|
1247
|
+
`melchizedek-agents/mcp`, then `serveMcpStdio(mcp)` or
|
|
1248
|
+
`mcpHttpApp(mcp, { secret })`.
|
|
1249
|
+
|
|
1102
1250
|
### Plan-dispatch routing (`dispatch:`) — the second orchestration method
|
|
1103
1251
|
|
|
1104
1252
|
A syndicate that declares a `dispatch:` block stops delegating and
|
|
@@ -1222,6 +1370,27 @@ Implemented in `runSyndicateTurn`, so every surface honours it: the A2A
|
|
|
1222
1370
|
server, the CLI runner (`scripts/syndicate_chat.ts`), the worker and the
|
|
1223
1371
|
evals.
|
|
1224
1372
|
|
|
1373
|
+
**Nested.** A `yaml_reference` to a plan-dispatch syndicate runs as it
|
|
1374
|
+
runs at the top, as a turn of its own on its own conversation, wherever
|
|
1375
|
+
it appears
|
|
1376
|
+
([ADR 0120](./wiki/decisions/0120-nested-dispatch-syndicates-route-as-at-the-top.md)):
|
|
1377
|
+
its classifier picks one of its routes, and the route's final text is
|
|
1378
|
+
what its caller gets: the tool's answer as a DELEGATE subagent, the turn's
|
|
1379
|
+
answer as a plan-dispatch route, the node's output as a workflow node. The
|
|
1380
|
+
classifier's JSON never reaches its caller. It runs under the turn's
|
|
1381
|
+
`max_steps`, deadline and cancel; its guards run once, on the answer at the
|
|
1382
|
+
top; its OAuth grants are stored under the root's app. An approval, an
|
|
1383
|
+
`ask_user` question or an OAuth consent on one of its routes, or inside a
|
|
1384
|
+
route's own delegation, pauses the whole turn with the path (e.g.
|
|
1385
|
+
`["Boss", "Team", "Ops"]` delegated, `["Team", "Ops"]` as a route), and
|
|
1386
|
+
the answer resumes that route without classifying again. As a workflow
|
|
1387
|
+
node it cannot pause on a consent request: the node fails. Its
|
|
1388
|
+
conversation is filed at `<app>/<caller>/<entry>` when delegated, at
|
|
1389
|
+
`<app>/route:<route>` as a route and at `<app>/node:<node>` as a node. A
|
|
1390
|
+
route that carries the entry's own name, a `map` over one, and a reference
|
|
1391
|
+
chain below it that reaches itself are refused by name before any model
|
|
1392
|
+
call.
|
|
1393
|
+
|
|
1225
1394
|
### Workflows (`workflow:`) — the third orchestration method
|
|
1226
1395
|
|
|
1227
1396
|
A syndicate that declares a `workflow:` block is a **graph**. Its agents
|
|
@@ -1302,12 +1471,15 @@ graph, under the entry's name and description, wherever it appears: as a
|
|
|
1302
1471
|
DELEGATE subagent its last output is the tool's answer; as a plan-dispatch
|
|
1303
1472
|
route it is the turn's answer, and the conversation keeps the message and
|
|
1304
1473
|
that answer; as a node of another workflow it is the node's output. The
|
|
1305
|
-
graph's events are kept in the entry's own session,
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1474
|
+
graph's events are kept in the entry's own session, filed under the agent
|
|
1475
|
+
path and its kind (`<app>/route:<route>`, `<app>/node:<node>`; a session
|
|
1476
|
+
an earlier release filed under `<app>/<entry>` or the entry's name alone is
|
|
1477
|
+
still continued). Wherever it
|
|
1478
|
+
runs, an `ask_user` node or a gated agent node inside it pauses the whole
|
|
1479
|
+
turn with the path down to that node, and the answer resumes the graph
|
|
1480
|
+
([ADR 0111](./wiki/decisions/0111-pauses-inside-nested-syndicates.md),
|
|
1481
|
+
[ADR 0119](./wiki/decisions/0119-workflow-routes-and-nodes-pause-the-turn.md)).
|
|
1482
|
+
A `map` over one is refused by name.
|
|
1311
1483
|
|
|
1312
1484
|
**Not yet.** Remote `a2a_agent_url` subagents are refused inside a
|
|
1313
1485
|
workflow by the schema. An `ask_user` tool on a node agent is refused
|