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.
Files changed (82) hide show
  1. package/.env.example +17 -0
  2. package/AGENT_SETUP.md +2 -1
  3. package/CHANGELOG.md +142 -0
  4. package/DOCUMENTATION.md +237 -7
  5. package/QUICKSTART.md +2 -2
  6. package/README.md +125 -149
  7. package/config/agents/examples/harness.yaml +135 -0
  8. package/config/agents/examples/model_zoo.yaml +23 -9
  9. package/config/agents/examples/pipeline.yaml +120 -0
  10. package/config/agents/examples/specs/open-meteo-forecast.json +77 -0
  11. package/config/agents/examples/specs/open-meteo-geocoding.json +53 -0
  12. package/config/agents/examples/weather.yaml +59 -0
  13. package/config/agents/syndicateSchema.yaml +112 -1
  14. package/db/migrations/0011_erase_expired.sql +198 -0
  15. package/dist/lib/a2a/app.d.ts +7 -0
  16. package/dist/lib/a2a/app.js +7 -1
  17. package/dist/lib/a2a/executor.js +41 -0
  18. package/dist/lib/compile.d.ts +40 -1
  19. package/dist/lib/compile.js +132 -14
  20. package/dist/lib/config.d.ts +17 -0
  21. package/dist/lib/config.js +17 -0
  22. package/dist/lib/doctor.js +17 -4
  23. package/dist/lib/index.d.ts +2 -0
  24. package/dist/lib/index.js +1 -0
  25. package/dist/lib/loadSyndicate.d.ts +46 -0
  26. package/dist/lib/loadSyndicate.js +19 -3
  27. package/dist/lib/memory/erase.d.ts +5 -0
  28. package/dist/lib/memory/erase.js +5 -0
  29. package/dist/lib/models/capabilities.js +10 -0
  30. package/dist/lib/models/endpoints.d.ts +1 -1
  31. package/dist/lib/models/endpoints.js +4 -1
  32. package/dist/lib/models/gateway.d.ts +2 -2
  33. package/dist/lib/models/gateway.js +4 -4
  34. package/dist/lib/models/kimiLlm.d.ts +123 -0
  35. package/dist/lib/models/kimiLlm.js +171 -0
  36. package/dist/lib/models/providerMap.d.ts +2 -2
  37. package/dist/lib/models/providerMap.js +4 -1
  38. package/dist/lib/models/registry.d.ts +1 -1
  39. package/dist/lib/models/registry.js +11 -3
  40. package/dist/lib/net/redirects.d.ts +14 -0
  41. package/dist/lib/net/redirects.js +94 -0
  42. package/dist/lib/observability/tracer.d.ts +1 -1
  43. package/dist/lib/persistence/supabaseProvider.d.ts +2 -6
  44. package/dist/lib/persistence/supabaseProvider.js +2 -31
  45. package/dist/lib/runtime/questions.d.ts +55 -0
  46. package/dist/lib/runtime/questions.js +107 -0
  47. package/dist/lib/runtime/syndicateTurn.d.ts +42 -4
  48. package/dist/lib/runtime/syndicateTurn.js +162 -16
  49. package/dist/lib/storage/postgres/index.d.ts +6 -0
  50. package/dist/lib/storage/postgres/index.js +11 -0
  51. package/dist/lib/storage/rlsStatus.d.ts +45 -0
  52. package/dist/lib/storage/rlsStatus.js +69 -0
  53. package/dist/lib/syndicateSchema.d.ts +169 -0
  54. package/dist/lib/syndicateSchema.js +327 -2
  55. package/dist/lib/toolRegistry.js +9 -0
  56. package/dist/lib/tools/openapiTools.d.ts +46 -0
  57. package/dist/lib/tools/openapiTools.js +195 -0
  58. package/dist/lib/tools/skillToolset.d.ts +135 -0
  59. package/dist/lib/tools/skillToolset.js +247 -0
  60. package/dist/lib/tools/urlContextTool.d.ts +29 -0
  61. package/dist/lib/tools/urlContextTool.js +39 -0
  62. package/dist/lib/workflow.d.ts +72 -0
  63. package/dist/lib/workflow.js +180 -0
  64. package/dist/lib/workflowConfig.d.ts +95 -0
  65. package/dist/lib/workflowConfig.js +93 -0
  66. package/dist/scripts/syndicate_chat.js +39 -5
  67. package/package.json +4 -1
  68. package/skills/briefs/_shared.md +1 -1
  69. package/skills/briefs/melchizedek-author.md +8 -4
  70. package/skills/briefs/melchizedek-memory.md +1 -1
  71. package/skills/briefs/melchizedek-models.md +1 -1
  72. package/skills/briefs/melchizedek-scribe.md +4 -4
  73. package/skills/briefs/melchizedek-serve.md +1 -1
  74. package/skills/briefs/melchizedek.md +4 -1
  75. package/skills/melchizedek/SKILL.md +4 -1
  76. package/skills/melchizedek-author/SKILL.md +7 -3
  77. package/skills/melchizedek-memory/SKILL.md +1 -1
  78. package/skills/melchizedek-models/SKILL.md +1 -1
  79. package/skills/melchizedek-scribe/SKILL.md +3 -3
  80. package/skills/melchizedek-serve/SKILL.md +1 -1
  81. /package/skills/melchizedek-author/{templates → assets}/minimal.yaml +0 -0
  82. /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). Not sure which
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 Grok.
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 ledger rows, with per-store counts; `?all=1` covers every memory namespace |
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 and is fatal on public deployments
856
- without it. Note `service_role` bypasses RLS by design — the hardening
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`. |