melchizedek-agents 1.0.3 → 1.2.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 +29 -2
- package/AGENT_SETUP.md +3 -1
- package/CHANGELOG.md +524 -0
- package/DOCUMENTATION.md +178 -43
- package/ONBOARDING.md +339 -0
- package/QUICKSTART.md +15 -2
- package/README.md +6 -2
- package/config/agents/README.md +2 -2
- package/config/agents/examples/ares.yaml +6 -10
- package/config/agents/examples/assistant.yaml +6 -6
- package/config/agents/examples/augustin.yaml +9 -15
- package/config/agents/examples/cartographers.yaml +6 -6
- package/config/agents/examples/council.yaml +7 -7
- package/config/agents/examples/critic.yaml +24 -24
- package/config/agents/examples/harness.yaml +4 -4
- package/config/agents/examples/image_production.yaml +37 -35
- package/config/agents/examples/librarian.yaml +4 -4
- package/config/agents/examples/patient_advocate.yaml +6 -10
- package/config/agents/examples/pipeline.yaml +20 -19
- package/config/agents/examples/research.yaml +36 -43
- package/config/agents/examples/scribe.yaml +31 -32
- package/config/agents/examples/scriptorium.yaml +6 -6
- package/config/agents/examples/structured_critic.yaml +82 -0
- package/config/agents/examples/style_council.yaml +9 -11
- package/config/agents/examples/syndicate.yaml +3 -5
- package/config/agents/examples/tutor.yaml +2 -2
- package/config/agents/examples/weather.yaml +2 -2
- package/config/agents/syndicate.schema.json +859 -13
- package/config/agents/syndicateSchema.yaml +122 -73
- package/config/agents/templates/README.md +1 -1
- package/config/agents/templates/account_memory.yaml +4 -4
- package/config/agents/templates/case_desk.yaml +6 -6
- package/config/agents/templates/conversational.yaml +2 -2
- package/config/agents/templates/draft_review.yaml +6 -6
- package/config/agents/templates/intake_extractor.yaml +64 -62
- package/config/agents/templates/research_brief.yaml +6 -6
- package/config/agents/templates/research_desk.yaml +6 -6
- package/config/agents/templates/review_panel.yaml +8 -8
- package/config/agents/templates/support_triage.yaml +30 -29
- package/config/agents/templates/systems_operator.yaml +24 -5
- package/db/migrations/0014_durable_runs.sql +31 -0
- package/dist/lib/a2a/app.d.ts +27 -0
- package/dist/lib/a2a/app.js +64 -5
- package/dist/lib/a2a/executor.d.ts +7 -1
- package/dist/lib/a2a/executor.js +43 -5
- package/dist/lib/a2a/oauthSetup.d.ts +106 -0
- package/dist/lib/a2a/oauthSetup.js +259 -0
- package/dist/lib/agentDialect.d.ts +76 -0
- package/dist/lib/agentDialect.js +151 -0
- package/dist/lib/compile.d.ts +42 -6
- package/dist/lib/compile.js +97 -45
- package/dist/lib/compileNative.d.ts +11 -3
- package/dist/lib/compileNative.js +20 -1
- package/dist/lib/dispatch.d.ts +10 -5
- package/dist/lib/dispatch.js +10 -5
- package/dist/lib/doctor.d.ts +110 -1
- package/dist/lib/doctor.js +205 -9
- package/dist/lib/loadSyndicate.d.ts +54 -2
- package/dist/lib/loadSyndicate.js +22 -2
- package/dist/lib/memory/namespace.d.ts +6 -5
- package/dist/lib/memory/namespace.js +6 -5
- package/dist/lib/memory/providers.js +3 -4
- package/dist/lib/models/capabilities.d.ts +25 -1
- package/dist/lib/models/capabilities.js +79 -1
- 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 +2 -1
- package/dist/lib/models/gateway.js +4 -2
- 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/observability/tracer.d.ts +8 -1
- package/dist/lib/observability/tracer.js +33 -5
- package/dist/lib/onboarding.d.ts +134 -0
- package/dist/lib/onboarding.js +539 -0
- package/dist/lib/runtime/approvals.d.ts +8 -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 +23 -3
- package/dist/lib/runtime/native/agentLoop.js +107 -11
- package/dist/lib/runtime/native/checkpoint.d.ts +171 -0
- package/dist/lib/runtime/native/checkpoint.js +359 -0
- package/dist/lib/runtime/native/delegate.d.ts +222 -18
- package/dist/lib/runtime/native/delegate.js +390 -39
- package/dist/lib/runtime/native/interrupts.d.ts +184 -2
- package/dist/lib/runtime/native/interrupts.js +391 -1
- package/dist/lib/runtime/native/request.d.ts +13 -3
- package/dist/lib/runtime/native/request.js +28 -4
- package/dist/lib/runtime/native/telemetry.js +10 -15
- package/dist/lib/runtime/questions.d.ts +7 -4
- package/dist/lib/runtime/sessions.d.ts +1 -1
- package/dist/lib/runtime/syndicateTurn.d.ts +11 -3
- package/dist/lib/runtime/syndicateTurn.js +353 -68
- package/dist/lib/runtime/turnControl.d.ts +56 -0
- package/dist/lib/runtime/turnControl.js +10 -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 +12 -2
- package/dist/lib/storage/postgres/index.js +8 -3
- package/dist/lib/storage/postgres/taskQueue.d.ts +20 -4
- package/dist/lib/storage/postgres/taskQueue.js +29 -5
- package/dist/lib/storage/postgres/taskStore.d.ts +26 -2
- package/dist/lib/storage/postgres/taskStore.js +74 -8
- package/dist/lib/syndicateSchema.d.ts +316 -1
- package/dist/lib/syndicateSchema.js +203 -29
- package/dist/lib/tools/auth.d.ts +20 -6
- package/dist/lib/tools/auth.js +21 -5
- package/dist/lib/tools/credentialEnv.d.ts +28 -0
- package/dist/lib/tools/credentialEnv.js +52 -0
- package/dist/lib/tools/credentialHosts.d.ts +88 -0
- package/dist/lib/tools/credentialHosts.js +165 -0
- package/dist/lib/tools/credentialStore.js +9 -2
- package/dist/lib/tools/credentialUses.d.ts +92 -0
- package/dist/lib/tools/credentialUses.js +112 -0
- package/dist/lib/tools/mcpToolFactory.d.ts +25 -2
- package/dist/lib/tools/mcpToolFactory.js +179 -21
- package/dist/lib/tools/oauthConsent.js +8 -1
- package/dist/lib/tools/oauthHosts.d.ts +73 -0
- package/dist/lib/tools/oauthHosts.js +154 -0
- package/dist/lib/tools/oauthTools.d.ts +242 -0
- package/dist/lib/tools/oauthTools.js +447 -0
- package/dist/lib/tools/openapiTools.d.ts +11 -6
- package/dist/lib/tools/openapiTools.js +88 -38
- package/dist/lib/tools/taskTools.d.ts +61 -6
- package/dist/lib/tools/taskTools.js +145 -21
- package/dist/lib/workflow/turn.d.ts +22 -3
- package/dist/lib/workflow/turn.js +151 -31
- package/dist/lib/workflow.d.ts +14 -7
- package/dist/lib/workflow.js +14 -7
- package/dist/lib/workflowConfig.d.ts +6 -0
- package/dist/scripts/a2a_server.d.ts +36 -0
- package/dist/scripts/a2a_server.js +131 -1
- package/dist/scripts/assistant_worker.d.ts +12 -0
- package/dist/scripts/assistant_worker.js +81 -22
- package/dist/scripts/doctor.d.ts +7 -2
- package/dist/scripts/doctor.js +9 -4
- package/dist/scripts/setup.d.ts +23 -0
- package/dist/scripts/setup.js +154 -0
- package/dist/scripts/skills_install.d.ts +3 -1
- package/dist/scripts/skills_install.js +17 -2
- package/dist/scripts/yaml_codemod.d.ts +62 -0
- package/dist/scripts/yaml_codemod.js +652 -0
- package/package.json +8 -2
- package/skills/README.md +11 -2
- package/skills/briefs/_shared.md +1 -1
- package/skills/briefs/melchizedek-author.md +7 -5
- package/skills/briefs/melchizedek-models.md +7 -4
- package/skills/briefs/melchizedek.md +1 -0
- package/skills/briefs/skills-readme.md +3 -1
- package/skills/melchizedek/SKILL.md +1 -0
- package/skills/melchizedek-author/SKILL.md +7 -4
- package/skills/melchizedek-author/assets/minimal.yaml +13 -13
- package/skills/melchizedek-models/SKILL.md +11 -4
- package/skills/melchizedek-onboard/SKILL.md +49 -0
- package/skills/melchizedek-onboard-cloud/SKILL.md +38 -0
- package/skills/melchizedek-onboard-keys/SKILL.md +35 -0
- package/skills/melchizedek-onboard-local/SKILL.md +41 -0
- package/skills/melchizedek-onboard-serve/SKILL.md +43 -0
package/DOCUMENTATION.md
CHANGED
|
@@ -106,8 +106,8 @@ orchestrator:
|
|
|
106
106
|
tools:
|
|
107
107
|
- "google_search" # names resolved via lib/toolRegistry.ts
|
|
108
108
|
reasoning: medium # none | low | medium | high, or { budget_tokens: 4096 }
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
sampling:
|
|
110
|
+
max_output_tokens: 4096
|
|
111
111
|
|
|
112
112
|
subagents:
|
|
113
113
|
- name: "Researcher"
|
|
@@ -135,20 +135,24 @@ 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. |
|
|
139
|
-
| `
|
|
140
|
-
| `
|
|
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
|
+
| `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
|
+
| `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
|
+
| `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
|
+
| `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)). |
|
|
141
144
|
| `yaml_reference` | subagent | Mount another syndicate file as a nested subagent. |
|
|
142
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. |
|
|
143
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`. |
|
|
144
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. |
|
|
145
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)). |
|
|
146
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)). |
|
|
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)). |
|
|
147
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. |
|
|
148
152
|
| `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`. |
|
|
149
153
|
| `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)). |
|
|
150
154
|
| `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. |
|
|
151
|
-
| `mode` | workflow node | `"task"`: the agent works with its tools until it calls `finish_task`, whose arguments (matching its `
|
|
155
|
+
| `mode` | workflow node | `"task"`: the agent works with its tools until it calls `finish_task`, whose arguments (matching its `output.schema`) become the node's output. Workflow nodes only. |
|
|
152
156
|
| `examples` | any agent | Few-shot exchanges, `[{ input, output }]` (up to 20), added to every request's instruction as a few-shot block, the one ADK's `ExampleTool` wrote (an Instruction tool, `lib/tools/examples.ts`); the model never calls it. Keeps worked examples out of the prose of `instruction`. |
|
|
153
157
|
| `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), with PATH, HOME, the temp directory and the locale but none of the server's keys, plus the variable names listed under `env:` (or `secret_env:` for a secret-shaped name, ADR 0086), and stdout and stderr each cut at 20,000 characters; `tools:` names registry tools a skill's `allowed-tools` may unlock once loaded. Worked example: `examples/harness.yaml`; engine: `lib/tools/skillToolset.ts` over `lib/tools/skills/`. |
|
|
154
158
|
|
|
@@ -181,8 +185,8 @@ function, and every adapter recognises it by marker
|
|
|
181
185
|
| `generate_image` | Contract | Calls the Gemini image model directly, saves the result under `outputs/`, returns the path. A function tool because binary `inlineData` cannot survive delegation, where a subagent returns only its final text. |
|
|
182
186
|
| `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`). |
|
|
183
187
|
| `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. |
|
|
184
|
-
| `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.
|
|
185
|
-
| `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. |
|
|
188
|
+
| `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). |
|
|
189
|
+
| `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. |
|
|
186
190
|
|
|
187
191
|
**MCP tools** are the exception to the registry: a subagent with
|
|
188
192
|
`mcp_server_url:` in its YAML gets its tools from a remote MCP server at
|
|
@@ -221,12 +225,26 @@ without `operations`, only GET operations become tools, so anything that
|
|
|
221
225
|
writes is exposed only by naming it, and a named operation can be listed
|
|
222
226
|
under `require_approval` (as written under `operations`) so a person
|
|
223
227
|
approves each call. `auth` names an environment variable, never a value
|
|
224
|
-
(`bearer_env`, or `api_key` with `in: header | query` and `name`)
|
|
225
|
-
|
|
228
|
+
(`bearer_env`, or `api_key` with `in: header | query` and `name`), or
|
|
229
|
+
declares an OAuth grant (`oauth2`: `provider`, `grant: authorization_code |
|
|
230
|
+
client_credentials`, `authorization_url`, `token_url`, `client_id` or
|
|
231
|
+
`client_id_env`, `client_secret_env`, `scopes`), whose token each call
|
|
232
|
+
fetches: the run's user's own through the consent pause, or the server's own
|
|
233
|
+
from the token endpoint ([ADR 0112](./wiki/decisions/0112-oauth-grants-declared-beside-the-tool.md)).
|
|
234
|
+
`oauthClientsFor(configs)` (`melchizedek-agents/tools/oauthTools`) builds the
|
|
235
|
+
consent step's clients from the same YAML, and `npm run doctor` lists every
|
|
236
|
+
tool that needs a grant. The hosts a grant's token goes to must be ones the
|
|
237
|
+
operator binds to its provider (`MELCHIZEDEK_OAUTH_HOSTS`, [ADR 0114](./wiki/decisions/0114-oauth-tokens-go-only-to-hosts-the-operator-binds.md));
|
|
238
|
+
with no allowlist, `authorization_code` is refused. An unset variable fails the compile, and so does one of the framework's own settings
|
|
226
239
|
(the database URL, a provider key, an `A2A_` secret: anything `.env.example`
|
|
227
240
|
documents), since the YAML chooses the host it goes to.
|
|
228
241
|
`OPENAPI_CREDENTIAL_ENVS`, when set, is the exact list of variables an `auth`
|
|
229
|
-
may name.
|
|
242
|
+
may name. `MELCHIZEDEK_CREDENTIAL_HOSTS` binds each such variable to the hosts
|
|
243
|
+
its value may reach (`TRACKER_TOKEN=api.tracker.example.com;…`, or
|
|
244
|
+
`createA2AApp({ credentialHosts })`, [ADR 0122](./wiki/decisions/0122-static-credentials-go-only-to-hosts-the-operator-binds.md)):
|
|
245
|
+
unset, a credential goes where the YAML says and the server and the doctor
|
|
246
|
+
name it as unbound; set, it is the whole list, checked when a syndicate
|
|
247
|
+
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,
|
|
230
248
|
never stored in session state. Every server must be http(s) and pass the
|
|
231
249
|
SSRF guard: its literal rules when the agent compiles, the full check with
|
|
232
250
|
DNS before each call; `ALLOW_PRIVATE_OPENAPI=true` permits private hosts for
|
|
@@ -365,7 +383,7 @@ The xAI adapter carries the deepest capability surface: `grok-4.5`
|
|
|
365
383
|
requests pin `reasoning.effort: "medium"` (`lib/config.ts`), SSE
|
|
366
384
|
streaming works end-to-end (`runConfig: { streamingMode: 'sse' }` —
|
|
367
385
|
partial delta events stream, one aggregated event persists with usage),
|
|
368
|
-
structured outputs ride `
|
|
386
|
+
structured outputs ride `output.schema` → `text.format`, and two
|
|
369
387
|
xAI-only tools — `x_search` and `collections_search` (§3) — turn on
|
|
370
388
|
live X search and hosted-document RAG. All verified live on grok-4.5.
|
|
371
389
|
|
|
@@ -414,11 +432,26 @@ reads every syndicate YAML, resolves each agent's model under your `.env`,
|
|
|
414
432
|
and prints one table — agent, model, provider, which declared server-side
|
|
415
433
|
tools that path runs natively (✓) or drops (✗), and whether the path is
|
|
416
434
|
funded — with one verdict per syndicate and the variables that would
|
|
417
|
-
unlock the most. Read-only; no key value is ever printed
|
|
435
|
+
unlock the most. Read-only; no key value is ever printed, and a
|
|
436
|
+
`MODEL_GATEWAY` it does not recognise is named as such, never repeated.
|
|
437
|
+
`--check` exits 1 on a blocked syndicate, a runtime problem, a problem
|
|
438
|
+
with the server's OAuth or credential settings, or a tool's OAuth grant
|
|
439
|
+
whose hosts `MELCHIZEDEK_OAUTH_HOSTS` refuses. Every
|
|
418
440
|
starter-pack file opens with a `# tier:` header (`keyless`, one provider
|
|
419
441
|
such as `gemini`, or `multi-provider`) the doctor checks against the
|
|
420
442
|
models. `GOOGLE_GENAI_API_KEY` alone runs fourteen of the nineteen examples.
|
|
421
443
|
|
|
444
|
+
**Not sure where to start? `npx melchizedek-setup`** (`npm run setup` in a
|
|
445
|
+
clone) opens a menu of authentication levels: no key (Ollama), one
|
|
446
|
+
provider's key, several, a gateway key, Vertex AI / Bedrock / Azure,
|
|
447
|
+
per-caller keys (BYOK), caller identities (`A2A_AUTH`), OAuth tool grants,
|
|
448
|
+
and an honest entry for ChatGPT / Codex, Claude.ai and Gemini CLI sign-ins,
|
|
449
|
+
which the engine does not use. Each prints a startup guide: variable names
|
|
450
|
+
and shapes, the doctor check, the shipped files that run there, and the
|
|
451
|
+
first commands. `--auto` reads the doctor's result and prints the guide
|
|
452
|
+
for the highest level it detects, names only. The same guides, generated
|
|
453
|
+
from the same code, are [`ONBOARDING.md`](./ONBOARDING.md).
|
|
454
|
+
|
|
422
455
|
**One key instead of several — the gateway fallback.** Direct adapters
|
|
423
456
|
are canonical: the native features above exist only on a provider's own
|
|
424
457
|
endpoint. But with `MODEL_GATEWAY=vercel` (or `openrouter`) and
|
|
@@ -486,7 +519,7 @@ runs as a one-agent syndicate through `runSyndicateTurn`.
|
|
|
486
519
|
|
|
487
520
|
Reasoning/thinking: scratchpads from every provider are surfaced as
|
|
488
521
|
dimmed THINKING output and kept out of session history. On Claude, any
|
|
489
|
-
`reasoning:` other than `none` (or the
|
|
522
|
+
`reasoning:` other than `none` (or the deprecated
|
|
490
523
|
`generateContentConfig.thinkingConfig.thinkingBudget`) enables Anthropic
|
|
491
524
|
extended thinking, tools included: the signed thinking blocks ride on the
|
|
492
525
|
response's parts as `providerState` and are replayed verbatim within the
|
|
@@ -787,12 +820,31 @@ without it. A message that is not an answer gets the same request back,
|
|
|
787
820
|
without a model call. The paused call and its arguments are pinned, so an
|
|
788
821
|
approval cannot run a different call.
|
|
789
822
|
|
|
790
|
-
Gates are allowed on the orchestrator
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
823
|
+
Gates are allowed on the orchestrator, on the subagents of a plan-dispatch
|
|
824
|
+
syndicate, on the agent nodes of a workflow (§6), and on a delegated
|
|
825
|
+
subagent, at any depth of nested delegate syndicates
|
|
826
|
+
([ADR 0110](./wiki/decisions/0110-pauses-inside-delegated-subagents-reach-the-turn.md)).
|
|
827
|
+
A delegated subagent's gate pauses the whole turn: the call that reached it
|
|
828
|
+
stays open, the request carries `path` (the agents from the turn's own down
|
|
829
|
+
to the one that asked, e.g. `["Desk", "Mailer"]`, in the data part and in
|
|
830
|
+
`approval.path`), and the decision goes back down to that subagent, which
|
|
831
|
+
runs or refuses the call and finishes before its caller continues. The same
|
|
832
|
+
holds inside nested syndicates
|
|
833
|
+
([ADR 0111](./wiki/decisions/0111-pauses-inside-nested-syndicates.md)): a
|
|
834
|
+
nested delegate syndicate's gates anywhere in it, a nested dispatch
|
|
835
|
+
syndicate's on its routes, which it classifies and runs as at the top
|
|
836
|
+
(`["Boss", "Team", "Ops"]`,
|
|
837
|
+
[ADR 0120](./wiki/decisions/0120-nested-dispatch-syndicates-route-as-at-the-top.md)),
|
|
838
|
+
and a nested workflow's gated agent nodes and
|
|
839
|
+
`ask_user` nodes when the workflow is a delegated subagent; the path then
|
|
840
|
+
ends at the node, e.g. `["Desk", "Writer", "Send"]`. A workflow run as a
|
|
841
|
+
dispatch route or as another workflow's node pauses the turn the same way
|
|
842
|
+
([ADR 0119](./wiki/decisions/0119-workflow-routes-and-nodes-pause-the-turn.md)),
|
|
843
|
+
the path running from the route or node down, e.g. `["Writer", "Send"]`.
|
|
844
|
+
Skill scripts (`skills.scripts: local`) on a delegated
|
|
845
|
+
subagent pause the same way, with `run_skill_script` as the tool
|
|
846
|
+
([ADR 0118](./wiki/decisions/0118-skill-scripts-and-oauth-consent-inside-delegated-subagents.md));
|
|
847
|
+
an agent a map node runs still may not carry them. Only function tools from the registry can be gated, not MCP tools
|
|
796
848
|
or native-search sentinels. In code, `runSyndicateTurn` returns
|
|
797
849
|
`status: 'input-required'` with `approval`, and the next turn's part
|
|
798
850
|
`approvalResponsePart(approval.id, approved)` answers it.
|
|
@@ -819,12 +871,14 @@ call's result, and the agent resumes its own tool loop where it asked. A
|
|
|
819
871
|
workflow's `ask_user` node (§6, Workflows) publishes the same data part and
|
|
820
872
|
is answered the same way, so a client handles both alike.
|
|
821
873
|
|
|
822
|
-
`ask_user` is allowed where an approval gate is: the orchestrator
|
|
874
|
+
`ask_user` is allowed where an approval gate is: the orchestrator, the
|
|
823
875
|
subagents of a plan-dispatch syndicate (a dispatch turn that answers goes
|
|
824
|
-
straight back to the route that asked, without the classifier)
|
|
825
|
-
subagent
|
|
876
|
+
straight back to the route that asked, without the classifier), and a
|
|
877
|
+
delegated subagent, whose question carries `path` and whose answer goes back
|
|
878
|
+
down to it (ADR 0110). A workflow node listing it is a load error. In code,
|
|
826
879
|
`runSyndicateTurn` returns `status: 'input-required'` with `input`
|
|
827
|
-
(`node`, `message`, `payload
|
|
880
|
+
(`node`, `message`, `payload`, and `path` for a delegated subagent's
|
|
881
|
+
question), and the next message's text answers it.
|
|
828
882
|
|
|
829
883
|
#### OAuth consent for tools
|
|
830
884
|
|
|
@@ -865,6 +919,13 @@ await createA2AApp({ /* … */ toolCredentials: { store, consent } });
|
|
|
865
919
|
call again. A message sent before the grant gets the same request back,
|
|
866
920
|
without a model call.
|
|
867
921
|
|
|
922
|
+
The call may sit in a delegated subagent, at any depth (the systems_operator
|
|
923
|
+
template's Systems, say): the request then carries `path`, the agents from
|
|
924
|
+
the turn's own down to the one that asked (in the data part and in
|
|
925
|
+
`consent.path`), the grant is stored under the conversation's app, and the
|
|
926
|
+
next message resumes that subagent's call before its caller continues
|
|
927
|
+
([ADR 0118](./wiki/decisions/0118-skill-scripts-and-oauth-consent-inside-delegated-subagents.md)).
|
|
928
|
+
|
|
868
929
|
The callback refuses these, and stores nothing:
|
|
869
930
|
|
|
870
931
|
- a state that is replayed, tampered with or older than ten minutes;
|
|
@@ -877,6 +938,48 @@ one replica, or route the callback to the instance that paused the call. In
|
|
|
877
938
|
code, `runSyndicateTurn({ …, toolCredentials })` returns
|
|
878
939
|
`status: 'input-required'` with `consent`.
|
|
879
940
|
|
|
941
|
+
**From the environment (`melchizedek-serve`).** The server binary builds the
|
|
942
|
+
store and the consent step itself, with the clients taken from the grants the
|
|
943
|
+
served syndicate files declare (`auth.oauth2`, `mcp_auth`; a registry row may
|
|
944
|
+
name a provider they declare, never define one):
|
|
945
|
+
|
|
946
|
+
| Variable | Shape | What it does |
|
|
947
|
+
|---|---|---|
|
|
948
|
+
| `MELCHIZEDEK_CREDENTIAL_KEY` | 32 random bytes, base64 or 64 hex (`openssl rand -base64 32`) | Seals each user's tokens. Unset: no store, and an authorization-code tool answers `unavailable`. Rows live in Postgres with `DATABASE_URL`, else in process memory |
|
|
949
|
+
| `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 |
|
|
950
|
+
| `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) |
|
|
951
|
+
| `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]` |
|
|
952
|
+
| `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)) |
|
|
953
|
+
|
|
954
|
+
**The host allowlist** ([ADR 0114](./wiki/decisions/0114-oauth-tokens-go-only-to-hosts-the-operator-binds.md))
|
|
955
|
+
is the operator's binding, never the YAML's: `MELCHIZEDEK_OAUTH_HOSTS`, or
|
|
956
|
+
`createA2AApp({ oauthHosts: { tracker: ['api.tracker.example.com'] } })`,
|
|
957
|
+
which wins. Unset, an `authorization_code` grant is refused (a user's token
|
|
958
|
+
never goes to a host nobody bound) and a `client_credentials` grant is
|
|
959
|
+
allowed (its secret is the operator's, as `bearer_env`'s is). Set, it is the
|
|
960
|
+
whole list: every grant's provider must be on it, and its API or MCP server,
|
|
961
|
+
token endpoint and authorization endpoint must be that provider's hosts. A
|
|
962
|
+
syndicate that breaks the rule is refused when the server loads it to serve,
|
|
963
|
+
again when its tools compile, and every call checks the host it sends a
|
|
964
|
+
token to. The server refuses to start on a malformed key, allowlist or
|
|
965
|
+
redirect URI, or on a redirect URI without a key; `npm run doctor` reports
|
|
966
|
+
each of these, and a declared grant with no key or redirect URI, by name.
|
|
967
|
+
|
|
968
|
+
**The credential host allowlist** ([ADR 0122](./wiki/decisions/0122-static-credentials-go-only-to-hosts-the-operator-binds.md))
|
|
969
|
+
does the same for the static credentials a YAML sends: `bearer_env` and
|
|
970
|
+
`api_key.env` to an OpenAPI server, and `client_secret_env` to a token
|
|
971
|
+
endpoint. `MELCHIZEDEK_CREDENTIAL_HOSTS`, or
|
|
972
|
+
`createA2AApp({ credentialHosts: { TRACKER_TOKEN: ['api.tracker.example.com'] } })`,
|
|
973
|
+
which wins. Unset, nothing changes: each credential goes to the host the
|
|
974
|
+
YAML names, and the server's boot and `npm run doctor` name every variable a
|
|
975
|
+
served YAML sends that no binding holds (names only). Set, it is the whole
|
|
976
|
+
list: a variable not on it is sent nowhere, and one on it only to its own
|
|
977
|
+
hosts. A syndicate that breaks the rule is refused when it loads, when it
|
|
978
|
+
compiles, and each call (an OpenAPI call, a client-credentials token
|
|
979
|
+
request, a consent code exchange, a refresh) checks again and sends nothing
|
|
980
|
+
when refused. `MCP_BEARER_TOKENS` is not covered: it already maps each token
|
|
981
|
+
to its host.
|
|
982
|
+
|
|
880
983
|
#### Limits
|
|
881
984
|
|
|
882
985
|
| Setting | Default |
|
|
@@ -1056,7 +1159,7 @@ user:
|
|
|
1056
1159
|
| | DELEGATE (default) | PLAN-DISPATCH (`dispatch:` present) |
|
|
1057
1160
|
|---|---|---|
|
|
1058
1161
|
| Subagents are | tools on the orchestrator | plain configs the server selects from |
|
|
1059
|
-
| Orchestrator holds | subagent tools, no `
|
|
1162
|
+
| Orchestrator holds | subagent tools, no `output.schema` | an `output.schema`, no subagent tools |
|
|
1060
1163
|
| Routing decision is | implicit in which tool it calls | an explicit value in code |
|
|
1061
1164
|
| The final answer comes from | the orchestrator re-emitting the answer | **the specialist itself** |
|
|
1062
1165
|
| LLM calls per request | classify + specialist + relay | classify + specialist |
|
|
@@ -1070,14 +1173,14 @@ orchestrator:
|
|
|
1070
1173
|
model: "gemini-3.5-flash-lite"
|
|
1071
1174
|
instruction: |
|
|
1072
1175
|
Name exactly ONE specialist for this message. ...
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1176
|
+
output:
|
|
1177
|
+
mime: "application/json"
|
|
1178
|
+
schema: # the router's schema; dispatch gives it no subagent tools
|
|
1179
|
+
type: "OBJECT"
|
|
1180
|
+
properties:
|
|
1181
|
+
route: { type: "STRING", description: "Exact specialist name" }
|
|
1182
|
+
reason: { type: "STRING", description: "≤8 plain words for the waiting user" }
|
|
1183
|
+
required: ["route"]
|
|
1081
1184
|
```
|
|
1082
1185
|
|
|
1083
1186
|
**Why it exists.** In DELEGATE mode the orchestrator receives the
|
|
@@ -1090,13 +1193,18 @@ Plan-dispatch has no relay turn to fail, and the classifier's output
|
|
|
1090
1193
|
shrinks from a whole relayed answer to ~15 tokens of JSON.
|
|
1091
1194
|
|
|
1092
1195
|
**Why the classifier is tool-less.** An agent that holds an
|
|
1093
|
-
`
|
|
1094
|
-
|
|
1095
|
-
whose header also tells how an orchestrator holding both deadlocked on
|
|
1096
|
-
ADK before 1.0.0).
|
|
1097
|
-
That constraint shapes the method: the classifier is a leaf, and the
|
|
1196
|
+
`output.schema` ends its turn on that JSON. The router's JSON names a
|
|
1197
|
+
route for code to run, so the router holds no subagent tools, and the
|
|
1098
1198
|
hand-off happens in code, where it can be logged, traced, and streamed
|
|
1099
|
-
to the user as progress.
|
|
1199
|
+
to the user as progress. This is a choice of method, not a limit of the
|
|
1200
|
+
engine: an orchestrator may hold an `output.schema` beside its subagents,
|
|
1201
|
+
delegate, and answer in the schema itself
|
|
1202
|
+
([ADR 0109](./wiki/decisions/0109-structured-output-beside-tools.md));
|
|
1203
|
+
under ADK, before 1.0.0, an orchestrator holding both deadlocked (see
|
|
1204
|
+
`config/agents/examples/critic.yaml`'s header). Choose plan-dispatch when
|
|
1205
|
+
the answer is the specialist's, and a schema on the orchestrator when the
|
|
1206
|
+
answer is its own structured judgment of what its team returned
|
|
1207
|
+
(`config/agents/examples/structured_critic.yaml`).
|
|
1100
1208
|
|
|
1101
1209
|
**Sessions.** Every *route* runs in the shared `<contextId>` session, so
|
|
1102
1210
|
one transcript accumulates across routes and long-term memory ingests
|
|
@@ -1165,6 +1273,27 @@ Implemented in `runSyndicateTurn`, so every surface honours it: the A2A
|
|
|
1165
1273
|
server, the CLI runner (`scripts/syndicate_chat.ts`), the worker and the
|
|
1166
1274
|
evals.
|
|
1167
1275
|
|
|
1276
|
+
**Nested.** A `yaml_reference` to a plan-dispatch syndicate runs as it
|
|
1277
|
+
runs at the top, as a turn of its own on its own conversation, wherever
|
|
1278
|
+
it appears
|
|
1279
|
+
([ADR 0120](./wiki/decisions/0120-nested-dispatch-syndicates-route-as-at-the-top.md)):
|
|
1280
|
+
its classifier picks one of its routes, and the route's final text is
|
|
1281
|
+
what its caller gets: the tool's answer as a DELEGATE subagent, the turn's
|
|
1282
|
+
answer as a plan-dispatch route, the node's output as a workflow node. The
|
|
1283
|
+
classifier's JSON never reaches its caller. It runs under the turn's
|
|
1284
|
+
`max_steps`, deadline and cancel; its guards run once, on the answer at the
|
|
1285
|
+
top; its OAuth grants are stored under the root's app. An approval, an
|
|
1286
|
+
`ask_user` question or an OAuth consent on one of its routes, or inside a
|
|
1287
|
+
route's own delegation, pauses the whole turn with the path (e.g.
|
|
1288
|
+
`["Boss", "Team", "Ops"]` delegated, `["Team", "Ops"]` as a route), and
|
|
1289
|
+
the answer resumes that route without classifying again. As a workflow
|
|
1290
|
+
node it cannot pause on a consent request: the node fails. Its
|
|
1291
|
+
conversation is filed at `<app>/<caller>/<entry>` when delegated, at
|
|
1292
|
+
`<app>/route:<route>` as a route and at `<app>/node:<node>` as a node. A
|
|
1293
|
+
route that carries the entry's own name, a `map` over one, and a reference
|
|
1294
|
+
chain below it that reaches itself are refused by name before any model
|
|
1295
|
+
call.
|
|
1296
|
+
|
|
1168
1297
|
### Workflows (`workflow:`) — the third orchestration method
|
|
1169
1298
|
|
|
1170
1299
|
A syndicate that declares a `workflow:` block is a **graph**. Its agents
|
|
@@ -1190,7 +1319,7 @@ name is an agent or a declared node; a list of names fans out (after one
|
|
|
1190
1319
|
node) or fans in (before one node); a map `{ <route>: <node or nodes>,
|
|
1191
1320
|
default: <node> }` ends a chain and routes on the output of the node
|
|
1192
1321
|
before it — the `route_key` property of a JSON output (an agent with an
|
|
1193
|
-
`
|
|
1322
|
+
`output.schema`), else the trimmed text — with `default` catching what no
|
|
1194
1323
|
key matched. Every node receives the previous node's output as its
|
|
1195
1324
|
message; a join hands on `{ <predecessor>: <output>, … }`.
|
|
1196
1325
|
|
|
@@ -1245,9 +1374,15 @@ graph, under the entry's name and description, wherever it appears: as a
|
|
|
1245
1374
|
DELEGATE subagent its last output is the tool's answer; as a plan-dispatch
|
|
1246
1375
|
route it is the turn's answer, and the conversation keeps the message and
|
|
1247
1376
|
that answer; as a node of another workflow it is the node's output. The
|
|
1248
|
-
graph's events are kept in the entry's own session,
|
|
1249
|
-
|
|
1250
|
-
|
|
1377
|
+
graph's events are kept in the entry's own session, filed under the agent
|
|
1378
|
+
path and its kind (`<app>/route:<route>`, `<app>/node:<node>`; a session
|
|
1379
|
+
an earlier release filed under `<app>/<entry>` or the entry's name alone is
|
|
1380
|
+
still continued). Wherever it
|
|
1381
|
+
runs, an `ask_user` node or a gated agent node inside it pauses the whole
|
|
1382
|
+
turn with the path down to that node, and the answer resumes the graph
|
|
1383
|
+
([ADR 0111](./wiki/decisions/0111-pauses-inside-nested-syndicates.md),
|
|
1384
|
+
[ADR 0119](./wiki/decisions/0119-workflow-routes-and-nodes-pause-the-turn.md)).
|
|
1385
|
+
A `map` over one is refused by name.
|
|
1251
1386
|
|
|
1252
1387
|
**Not yet.** Remote `a2a_agent_url` subagents are refused inside a
|
|
1253
1388
|
workflow by the schema. An `ask_user` tool on a node agent is refused
|