melchizedek-agents 0.18.0 → 0.19.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 +12 -0
- package/CHANGELOG.md +825 -0
- package/DOCUMENTATION.md +217 -59
- package/README.md +42 -1
- package/config/agents/examples/harness.yaml +3 -1
- package/config/agents/examples/model_zoo.yaml +10 -9
- package/config/agents/syndicateSchema.yaml +50 -16
- package/config/agents/templates/account_memory.yaml +2 -6
- package/config/agents/templates/case_desk.yaml +3 -9
- package/config/agents/templates/draft_review.yaml +3 -9
- package/config/agents/templates/intake_extractor.yaml +1 -3
- package/config/agents/templates/research_brief.yaml +3 -9
- package/config/agents/templates/research_desk.yaml +3 -9
- package/config/agents/templates/review_panel.yaml +4 -12
- package/config/agents/templates/support_triage.yaml +4 -12
- package/config/agents/templates/systems_operator.yaml +2 -6
- package/db/hardening.sql +7 -1
- package/db/migrations/0013_tool_credentials.sql +260 -0
- package/dist/lib/a2a/app.d.ts +70 -6
- package/dist/lib/a2a/app.js +107 -2
- package/dist/lib/a2a/executor.d.ts +20 -3
- package/dist/lib/a2a/executor.js +66 -7
- package/dist/lib/a2a/remoteAgent.d.ts +24 -9
- package/dist/lib/a2a/remoteAgent.js +31 -14
- package/dist/lib/compile.d.ts +121 -30
- package/dist/lib/compile.js +137 -124
- package/dist/lib/compileAdk.d.ts +29 -0
- package/dist/lib/compileAdk.js +132 -0
- package/dist/lib/compileNative.d.ts +85 -0
- package/dist/lib/compileNative.js +222 -0
- package/dist/lib/config.d.ts +6 -5
- package/dist/lib/config.js +6 -5
- package/dist/lib/doctor.js +1 -0
- package/dist/lib/index.d.ts +2 -1
- package/dist/lib/index.js +2 -1
- package/dist/lib/loadSyndicate.d.ts +18 -0
- package/dist/lib/memory/erase.d.ts +5 -3
- package/dist/lib/memory/erase.js +5 -2
- package/dist/lib/memory/namespace.d.ts +8 -1
- package/dist/lib/memory/namespace.js +12 -6
- package/dist/lib/memory/providers.js +2 -1
- package/dist/lib/memory/supabaseMemoryService.d.ts +37 -3
- package/dist/lib/memory/supabaseMemoryService.js +42 -2
- package/dist/lib/model.d.ts +47 -0
- package/dist/lib/model.js +37 -0
- package/dist/lib/models/adapterResolver.d.ts +113 -0
- package/dist/lib/models/adapterResolver.js +136 -0
- package/dist/lib/models/adkGeminiAdapter.d.ts +90 -0
- package/dist/lib/models/adkGeminiAdapter.js +367 -0
- package/dist/lib/models/adkShim.d.ts +111 -0
- package/dist/lib/models/adkShim.js +136 -0
- package/dist/lib/models/capabilities.d.ts +3 -0
- package/dist/lib/models/capabilities.js +20 -13
- package/dist/lib/models/chatCompletionsAdapter.d.ts +279 -0
- package/dist/lib/models/chatCompletionsAdapter.js +854 -0
- package/dist/lib/models/circuitBreaker.d.ts +42 -0
- package/dist/lib/models/circuitBreaker.js +70 -0
- package/dist/lib/models/claudeAdapter.d.ts +103 -0
- package/dist/lib/models/claudeAdapter.js +669 -0
- package/dist/lib/models/claudeLlm.d.ts +39 -62
- package/dist/lib/models/claudeLlm.js +44 -349
- package/dist/lib/models/claudeModels.d.ts +127 -0
- package/dist/lib/models/claudeModels.js +237 -0
- package/dist/lib/models/contract.d.ts +395 -0
- package/dist/lib/models/contract.js +39 -0
- package/dist/lib/models/endpoints.js +2 -1
- package/dist/lib/models/errorResponse.d.ts +50 -0
- package/dist/lib/models/errorResponse.js +48 -0
- package/dist/lib/models/fallback.d.ts +17 -10
- package/dist/lib/models/fallback.js +53 -42
- package/dist/lib/models/fallbackAdapter.d.ts +55 -0
- package/dist/lib/models/fallbackAdapter.js +62 -0
- package/dist/lib/models/gateway.js +2 -1
- package/dist/lib/models/gatewayAdapter.d.ts +51 -0
- package/dist/lib/models/gatewayAdapter.js +81 -0
- package/dist/lib/models/gatewayLlm.d.ts +12 -33
- package/dist/lib/models/gatewayLlm.js +14 -75
- package/dist/lib/models/geminiAdapter.d.ts +123 -0
- package/dist/lib/models/geminiAdapter.js +831 -0
- package/dist/lib/models/geminiState.d.ts +25 -0
- package/dist/lib/models/geminiState.js +25 -0
- package/dist/lib/models/genaiMapping.d.ts +263 -0
- package/dist/lib/models/genaiMapping.js +862 -0
- package/dist/lib/models/gptAdapter.d.ts +240 -0
- package/dist/lib/models/gptAdapter.js +804 -0
- package/dist/lib/models/gptLlm.d.ts +63 -123
- package/dist/lib/models/gptLlm.js +88 -602
- package/dist/lib/models/grokAdapter.d.ts +86 -0
- package/dist/lib/models/grokAdapter.js +142 -0
- package/dist/lib/models/grokLlm.d.ts +22 -45
- package/dist/lib/models/grokLlm.js +22 -67
- package/dist/lib/models/kimiAdapter.d.ts +157 -0
- package/dist/lib/models/kimiAdapter.js +203 -0
- package/dist/lib/models/kimiLlm.d.ts +15 -107
- package/dist/lib/models/kimiLlm.js +17 -153
- package/dist/lib/models/ollamaAdapter.d.ts +125 -0
- package/dist/lib/models/ollamaAdapter.js +155 -0
- package/dist/lib/models/ollamaLlm.d.ts +11 -107
- package/dist/lib/models/ollamaLlm.js +13 -151
- package/dist/lib/models/openAiCompatibleLlm.d.ts +73 -216
- package/dist/lib/models/openAiCompatibleLlm.js +109 -671
- package/dist/lib/models/providerState.d.ts +63 -0
- package/dist/lib/models/providerState.js +61 -0
- package/dist/lib/models/reasoning.d.ts +26 -0
- package/dist/lib/models/reasoning.js +59 -0
- package/dist/lib/models/registry.d.ts +58 -35
- package/dist/lib/models/registry.js +89 -118
- package/dist/lib/models/retry.d.ts +2 -1
- package/dist/lib/models/retry.js +3 -2
- package/dist/lib/models/schemaNormalize.d.ts +98 -0
- package/dist/lib/models/schemaNormalize.js +321 -1
- package/dist/lib/models/tracedGemini.d.ts +41 -0
- package/dist/lib/models/tracedGemini.js +98 -0
- package/dist/lib/models/urls.d.ts +11 -0
- package/dist/lib/models/urls.js +16 -0
- package/dist/lib/net/redirects.d.ts +21 -2
- package/dist/lib/net/redirects.js +11 -34
- package/dist/lib/observability/audit.d.ts +5 -1
- package/dist/lib/observability/lineage.d.ts +14 -0
- package/dist/lib/observability/lineage.js +33 -0
- package/dist/lib/observability/supabaseSpanExporter.d.ts +10 -4
- package/dist/lib/observability/supabaseSpanExporter.js +17 -9
- package/dist/lib/observability/tracer.d.ts +26 -8
- package/dist/lib/observability/tracer.js +43 -18
- package/dist/lib/runtime/adkMemoryBridge.d.ts +46 -0
- package/dist/lib/runtime/adkMemoryBridge.js +68 -0
- package/dist/lib/runtime/adkSessionBridge.d.ts +78 -0
- package/dist/lib/runtime/adkSessionBridge.js +176 -0
- package/dist/lib/runtime/credentials.d.ts +59 -0
- package/dist/lib/runtime/credentials.js +62 -0
- package/dist/lib/runtime/events.d.ts +229 -0
- package/dist/lib/runtime/events.js +274 -0
- package/dist/lib/runtime/logging.d.ts +32 -0
- package/dist/lib/runtime/logging.js +55 -0
- package/dist/lib/runtime/memoryService.d.ts +72 -0
- package/dist/lib/runtime/memoryService.js +25 -0
- package/dist/lib/runtime/native/agentLoop.d.ts +203 -0
- package/dist/lib/runtime/native/agentLoop.js +687 -0
- package/dist/lib/runtime/native/compaction.d.ts +103 -0
- package/dist/lib/runtime/native/compaction.js +191 -0
- package/dist/lib/runtime/native/delegate.d.ts +98 -0
- package/dist/lib/runtime/native/delegate.js +173 -0
- package/dist/lib/runtime/native/history.d.ts +83 -0
- package/dist/lib/runtime/native/history.js +312 -0
- package/dist/lib/runtime/native/interrupts.d.ts +126 -0
- package/dist/lib/runtime/native/interrupts.js +291 -0
- package/dist/lib/runtime/native/request.d.ts +215 -0
- package/dist/lib/runtime/native/request.js +585 -0
- package/dist/lib/runtime/native/selfCorrection.d.ts +106 -0
- package/dist/lib/runtime/native/selfCorrection.js +253 -0
- package/dist/lib/runtime/native/step.d.ts +148 -0
- package/dist/lib/runtime/native/step.js +239 -0
- package/dist/lib/runtime/native/taskMode.d.ts +64 -0
- package/dist/lib/runtime/native/taskMode.js +126 -0
- package/dist/lib/runtime/native/telemetry.d.ts +75 -0
- package/dist/lib/runtime/native/telemetry.js +299 -0
- package/dist/lib/runtime/native/tempState.d.ts +31 -0
- package/dist/lib/runtime/native/tempState.js +56 -0
- package/dist/lib/runtime/nativeTurn.d.ts +86 -0
- package/dist/lib/runtime/nativeTurn.js +107 -0
- package/dist/lib/runtime/questions.d.ts +29 -7
- package/dist/lib/runtime/questions.js +47 -32
- package/dist/lib/runtime/runtimeFlag.d.ts +30 -0
- package/dist/lib/runtime/runtimeFlag.js +50 -0
- package/dist/lib/runtime/sessions.d.ts +166 -0
- package/dist/lib/runtime/sessions.js +201 -0
- package/dist/lib/runtime/syndicateTurn.d.ts +41 -5
- package/dist/lib/runtime/syndicateTurn.js +202 -32
- package/dist/lib/session/supabaseSessionService.d.ts +64 -17
- package/dist/lib/session/supabaseSessionService.js +148 -105
- package/dist/lib/session/transcript.d.ts +68 -22
- package/dist/lib/session/transcript.js +102 -28
- package/dist/lib/skills.js +33 -7
- package/dist/lib/storage/postgres/credentialStore.d.ts +20 -0
- package/dist/lib/storage/postgres/credentialStore.js +82 -0
- package/dist/lib/storage/postgres/index.d.ts +12 -0
- package/dist/lib/storage/postgres/index.js +6 -1
- package/dist/lib/storage/postgres/sessionService.d.ts +43 -15
- package/dist/lib/storage/postgres/sessionService.js +128 -66
- package/dist/lib/syndicateSchema.d.ts +47 -0
- package/dist/lib/syndicateSchema.js +72 -3
- package/dist/lib/toolRegistry.d.ts +15 -2
- package/dist/lib/toolRegistry.js +33 -17
- package/dist/lib/tools/adkTool.d.ts +81 -0
- package/dist/lib/tools/adkTool.js +268 -0
- package/dist/lib/tools/auth.d.ts +118 -0
- package/dist/lib/tools/auth.js +89 -0
- package/dist/lib/tools/collectionsSearchTool.d.ts +7 -6
- package/dist/lib/tools/collectionsSearchTool.js +11 -26
- package/dist/lib/tools/credentialCipher.d.ts +58 -0
- package/dist/lib/tools/credentialCipher.js +116 -0
- package/dist/lib/tools/credentialStore.d.ts +77 -0
- package/dist/lib/tools/credentialStore.js +246 -0
- package/dist/lib/tools/examples.d.ts +43 -0
- package/dist/lib/tools/examples.js +62 -0
- package/dist/lib/tools/generateImageTool.d.ts +15 -6
- package/dist/lib/tools/generateImageTool.js +19 -29
- package/dist/lib/tools/inspectImageTool.d.ts +9 -6
- package/dist/lib/tools/inspectImageTool.js +13 -17
- package/dist/lib/tools/mcpToolFactory.d.ts +42 -1
- package/dist/lib/tools/mcpToolFactory.js +46 -47
- package/dist/lib/tools/memoryTools.d.ts +61 -0
- package/dist/lib/tools/memoryTools.js +119 -0
- package/dist/lib/tools/nativeTools.d.ts +38 -0
- package/dist/lib/tools/nativeTools.js +43 -0
- package/dist/lib/tools/oauthConsent.d.ts +181 -0
- package/dist/lib/tools/oauthConsent.js +330 -0
- package/dist/lib/tools/openapi/call.d.ts +108 -0
- package/dist/lib/tools/openapi/call.js +295 -0
- package/dist/lib/tools/openapi/parse.d.ts +147 -0
- package/dist/lib/tools/openapi/parse.js +570 -0
- package/dist/lib/tools/openapiTools.d.ts +25 -17
- package/dist/lib/tools/openapiTools.js +124 -121
- package/dist/lib/tools/scienceTools.d.ts +14 -14
- package/dist/lib/tools/skillToolset.d.ts +58 -87
- package/dist/lib/tools/skillToolset.js +61 -142
- package/dist/lib/tools/skills/env.d.ts +58 -0
- package/dist/lib/tools/skills/env.js +165 -0
- package/dist/lib/tools/skills/executor.d.ts +114 -0
- package/dist/lib/tools/skills/executor.js +331 -0
- package/dist/lib/tools/skills/frontmatter.d.ts +79 -0
- package/dist/lib/tools/skills/frontmatter.js +215 -0
- package/dist/lib/tools/skills/loader.d.ts +91 -0
- package/dist/lib/tools/skills/loader.js +257 -0
- package/dist/lib/tools/skills/tools.d.ts +187 -0
- package/dist/lib/tools/skills/tools.js +386 -0
- package/dist/lib/tools/taskTools.d.ts +15 -15
- package/dist/lib/tools/tool.d.ts +354 -0
- package/dist/lib/tools/tool.js +304 -0
- package/dist/lib/tools/toolContract.d.ts +89 -33
- package/dist/lib/tools/toolContract.js +104 -82
- package/dist/lib/tools/urlContextTool.d.ts +3 -0
- package/dist/lib/tools/urlContextTool.js +5 -1
- package/dist/lib/tools/webExtractTool.d.ts +2 -2
- package/dist/lib/tools/webExtractTool.js +2 -1
- package/dist/lib/tools/webSearchTool.d.ts +6 -9
- package/dist/lib/tools/webSearchTool.js +14 -59
- package/dist/lib/tools/wikiTools.d.ts +19 -19
- package/dist/lib/tools/wikiTools.js +13 -9
- package/dist/lib/tools/xApiSearchTool.d.ts +2 -2
- package/dist/lib/tools/xApiSearchTool.js +2 -1
- package/dist/lib/tools/xSearchTool.d.ts +7 -9
- package/dist/lib/tools/xSearchTool.js +11 -62
- package/dist/lib/tools/xaiSearchParams.d.ts +41 -0
- package/dist/lib/tools/xaiSearchParams.js +126 -0
- package/dist/lib/wiki/agentRun.d.ts +12 -0
- package/dist/lib/wiki/agentRun.js +72 -34
- package/dist/lib/wiki/entities.d.ts +12 -0
- package/dist/lib/wiki/entities.js +45 -3
- package/dist/lib/workflow/agentNode.d.ts +136 -0
- package/dist/lib/workflow/agentNode.js +274 -0
- package/dist/lib/workflow/graph.d.ts +177 -0
- package/dist/lib/workflow/graph.js +458 -0
- package/dist/lib/workflow/nodeEvents.d.ts +50 -0
- package/dist/lib/workflow/nodeEvents.js +101 -0
- package/dist/lib/workflow/pause.d.ts +122 -0
- package/dist/lib/workflow/pause.js +180 -0
- package/dist/lib/workflow/resume.d.ts +165 -0
- package/dist/lib/workflow/resume.js +440 -0
- package/dist/lib/workflow/route.d.ts +58 -0
- package/dist/lib/workflow/route.js +69 -0
- package/dist/lib/workflow/scheduler.d.ts +415 -0
- package/dist/lib/workflow/scheduler.js +743 -0
- package/dist/lib/workflow/toolNode.d.ts +127 -0
- package/dist/lib/workflow/toolNode.js +236 -0
- package/dist/lib/workflow/turn.d.ts +118 -0
- package/dist/lib/workflow/turn.js +216 -0
- package/dist/lib/workflowConfig.d.ts +3 -2
- package/dist/lib/workflowConfig.js +3 -12
- package/dist/scripts/a2a_server.js +4 -3
- package/dist/scripts/assistant_worker.d.ts +2 -1
- package/dist/scripts/assistant_worker.js +9 -4
- package/dist/scripts/syndicate_chat.js +19 -10
- package/package.json +4 -1
- package/skills/briefs/melchizedek-author.md +3 -2
- package/skills/briefs/melchizedek-models.md +13 -9
- package/skills/briefs/melchizedek.md +3 -3
- package/skills/melchizedek/SKILL.md +2 -1
- package/skills/melchizedek-author/SKILL.md +2 -1
- package/skills/melchizedek-author/assets/minimal.yaml +2 -0
- package/skills/melchizedek-models/SKILL.md +23 -6
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/workflow/turn.ts — one turn of a workflow syndicate on the native
|
|
3
|
+
* runtime: the message stored, the graph walked by the engine's scheduler,
|
|
4
|
+
* and every event the walk stores yielded in the order it was stored, the
|
|
5
|
+
* node inputs aside, as ADK's Runner yields them (ADR 0095).
|
|
6
|
+
*
|
|
7
|
+
* WHY this file exists:
|
|
8
|
+
* runSyndicateTurn (lib/runtime/syndicateTurn.ts) runs a workflow on ADK
|
|
9
|
+
* by handing ADK's Workflow to its Runner and draining the events the
|
|
10
|
+
* Runner yields (drainAgentStream). On the native runtime it hands this
|
|
11
|
+
* generator to the same drain, under the same root span, so the progress
|
|
12
|
+
* lines, onProgress calls, node errors, the paused input and the ledger
|
|
13
|
+
* rows come out of one reader on both runtimes. Everything a node does is
|
|
14
|
+
* the WS4 modules': the scheduler (scheduler.ts), agent nodes on the
|
|
15
|
+
* native loop (agentNode.ts), tool nodes (toolNode.ts), ask_user nodes and
|
|
16
|
+
* the workflow's pause record (pause.ts), and the resume rebuilt from the
|
|
17
|
+
* session (resume.ts). This file wires them for one turn, as ADK's Runner
|
|
18
|
+
* and Workflow wire theirs:
|
|
19
|
+
*
|
|
20
|
+
* 1. THE MESSAGE. Stored as the user's event under a new `e-` invocation
|
|
21
|
+
* id, before the walk, as the Runner stores it (runNativeAgent does the
|
|
22
|
+
* same for one agent). A turn whose signal aborted first stores nothing.
|
|
23
|
+
* 2. THE START. workflowResume rebuilds every node's prior runs and the
|
|
24
|
+
* answers from the session, as ADK's rehydration does on every message:
|
|
25
|
+
* with nothing paused every node runs fresh. The walk's input is the
|
|
26
|
+
* message's text, not its content, as ADK hands its root workflow, and
|
|
27
|
+
* that text is what the workflow's pause record keeps. A pause only ADK
|
|
28
|
+
* can resume (UnsupportedWorkflowResumeError) fails the turn; it never
|
|
29
|
+
* walks afresh and asks the person again.
|
|
30
|
+
* 3. THE WALK. runWorkflowGraph with the runners chained (ask_user, tool,
|
|
31
|
+
* then agentNodeRuntime for agent nodes and map items), under the
|
|
32
|
+
* turn's signal. Every event a runner stores goes through the agent
|
|
33
|
+
* node runtime's one queue, in walk order: node inputs, the agents'
|
|
34
|
+
* events, tool and ask_user events, route steps, joins and maps, and
|
|
35
|
+
* the node-error event ADK writes for a node that gave up
|
|
36
|
+
* (nodeErrorEvent, on the scheduler's `workflow` node_error). A node's
|
|
37
|
+
* input turn is stored and not yielded, as ADK's Runner never yields
|
|
38
|
+
* it; a node agent's partial (streamed) event is yielded and not
|
|
39
|
+
* stored, as the Runner yields it.
|
|
40
|
+
* 4. THE END. A paused walk stores the workflow's own record
|
|
41
|
+
* (workflowPauseEvent) after every node's event. A walk the turn
|
|
42
|
+
* stopped (InvocationAbortedError, or any failure once the signal
|
|
43
|
+
* fired) ends quietly, as ADK's Runner ends an aborted run: the turn
|
|
44
|
+
* runner reads the stop reason from its control. A node that gave up
|
|
45
|
+
* rethrows its error once every event is stored and yielded, as ADK's
|
|
46
|
+
* Runner throws it.
|
|
47
|
+
* 5. THE SPANS. `workflow.invoke <name>` around the walk, `node.execute
|
|
48
|
+
* <name>` around each node run (the scheduler's traceNode hook), and
|
|
49
|
+
* `tool.execute <name>` around a tool node's call
|
|
50
|
+
* (lib/runtime/native/telemetry.ts). An agent node's `agent.invoke`
|
|
51
|
+
* opens inside its node's span, so each model call is attributed to
|
|
52
|
+
* its node's agent in the ledger.
|
|
53
|
+
*
|
|
54
|
+
* WHAT IT REFUSES before any model call: refuseUnrunnableNodes throws, for
|
|
55
|
+
* a tool node, ADK's compile-time refusals (an unregistered tool, a
|
|
56
|
+
* long-running one). A pause raised inside an agent node is refused by
|
|
57
|
+
* runAgentNode by name.
|
|
58
|
+
*
|
|
59
|
+
* It imports nothing from ADK: the caller passes the tool lookup (the
|
|
60
|
+
* registry builds ADK's tools) and the store as the engine's interface.
|
|
61
|
+
* The message, the answers and every stored event are data the walk
|
|
62
|
+
* carries, never instructions this module acts on.
|
|
63
|
+
*/
|
|
64
|
+
import { randomUUID } from 'node:crypto';
|
|
65
|
+
import { createTurnEvent } from '../runtime/events.js';
|
|
66
|
+
import { traceNodeExecution, traceToolNodeCall, traceWorkflowInvocation } from '../runtime/native/telemetry.js';
|
|
67
|
+
import { agentNodeRuntime } from './agentNode.js';
|
|
68
|
+
import { askUserNodeRunner, workflowPauseEvent } from './pause.js';
|
|
69
|
+
import { workflowResume } from './resume.js';
|
|
70
|
+
import { InvocationAbortedError, nodeErrorEvent, runWorkflowGraph } from './scheduler.js';
|
|
71
|
+
import { resolveToolNode, toolNodeRunner } from './toolNode.js';
|
|
72
|
+
/**
|
|
73
|
+
* Throws, before any model call, what ADK's compileWorkflow throws for a
|
|
74
|
+
* tool node: an unregistered tool, or a long-running one (ADR 0091).
|
|
75
|
+
*/
|
|
76
|
+
export function refuseUnrunnableNodes(graph, resolveTool) {
|
|
77
|
+
for (const node of graph.nodes.values())
|
|
78
|
+
if (node.kind === 'tool')
|
|
79
|
+
resolveToolNode(node, { resolveTool });
|
|
80
|
+
}
|
|
81
|
+
const isAborted = (error) => error instanceof InvocationAbortedError || error?.name === 'InvocationAbortedError';
|
|
82
|
+
/**
|
|
83
|
+
* Runs one turn of the workflow: stores the message, walks the graph (a
|
|
84
|
+
* resume when the session holds a paused walk), and yields every event the
|
|
85
|
+
* walk stores, in the order stored, but the node inputs (ADK's Runner
|
|
86
|
+
* yields none of them). Returns how the walk ended, or
|
|
87
|
+
* undefined when the signal fired before the message was stored. Throws
|
|
88
|
+
* the error a node gave up with (after its events), ADK's message for a
|
|
89
|
+
* reply that answers nothing, and UnsupportedWorkflowResumeError.
|
|
90
|
+
*/
|
|
91
|
+
export async function* runNativeWorkflow(params) {
|
|
92
|
+
const { graph, sessions, appName, userId, sessionId, signal } = params;
|
|
93
|
+
const session = await sessions.get({ appName, userId, sessionId });
|
|
94
|
+
if (!session)
|
|
95
|
+
throw new Error(`Session not found: ${sessionId} (appName=${appName}, userId=${userId})`);
|
|
96
|
+
if (signal?.aborted)
|
|
97
|
+
return undefined;
|
|
98
|
+
if (params.userParts.length === 0)
|
|
99
|
+
throw new Error('No parts in the newMessage.');
|
|
100
|
+
// 1. The message, as the Runner stores it.
|
|
101
|
+
const invocationId = `e-${randomUUID()}`;
|
|
102
|
+
const userContent = { role: 'user', parts: params.userParts };
|
|
103
|
+
await sessions.append(session, createTurnEvent({ invocationId, author: 'user', content: userContent }));
|
|
104
|
+
if (signal?.aborted)
|
|
105
|
+
return { stopped: true };
|
|
106
|
+
// 2. The start: the message's text as the input, and every node's prior runs.
|
|
107
|
+
const start = workflowResume({ events: session.events, invocationId, userContent, workflowPath: graph.name });
|
|
108
|
+
// 3. The walk, its events handed on in the order they are stored.
|
|
109
|
+
const queue = [];
|
|
110
|
+
let wake;
|
|
111
|
+
const runtime = agentNodeRuntime({
|
|
112
|
+
agents: params.agents,
|
|
113
|
+
session,
|
|
114
|
+
sessions,
|
|
115
|
+
invocationId,
|
|
116
|
+
userContent,
|
|
117
|
+
loop: {
|
|
118
|
+
adapterFor: params.adapterFor,
|
|
119
|
+
stream: params.stream ?? false,
|
|
120
|
+
...(params.memory ? { memory: params.memory } : {}),
|
|
121
|
+
...(params.log ? { log: params.log } : {}),
|
|
122
|
+
...(params.selfCorrection ? { selfCorrection: params.selfCorrection } : {}),
|
|
123
|
+
...(params.credentials ? { credentials: params.credentials } : {}),
|
|
124
|
+
},
|
|
125
|
+
onEvent: (event) => {
|
|
126
|
+
// A node's input turn is appended straight to the session, as ADK's runLlmAgentAsNode appends it: stored, never yielded.
|
|
127
|
+
if (event.author === 'user')
|
|
128
|
+
return;
|
|
129
|
+
queue.push(event);
|
|
130
|
+
wake?.();
|
|
131
|
+
},
|
|
132
|
+
// A node agent's streamed text, yielded as ADK's Runner yields its partial events.
|
|
133
|
+
onPartial: (event) => {
|
|
134
|
+
queue.push(event);
|
|
135
|
+
wake?.();
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
// Every event handed over is queued a microtask later (agentNodeRuntime.store); `stored` waits for all of them.
|
|
139
|
+
const handed = new Set();
|
|
140
|
+
const store = (event) => {
|
|
141
|
+
const queued = runtime.store(event).catch(() => { }); // a store error surfaces from settled()
|
|
142
|
+
handed.add(queued);
|
|
143
|
+
void queued.finally(() => handed.delete(queued));
|
|
144
|
+
};
|
|
145
|
+
const stored = async () => {
|
|
146
|
+
while (handed.size > 0)
|
|
147
|
+
await Promise.all([...handed]);
|
|
148
|
+
await runtime.settled();
|
|
149
|
+
};
|
|
150
|
+
const runNode = askUserNodeRunner({ invocationId, onEvent: store }, toolNodeRunner({
|
|
151
|
+
invocationId,
|
|
152
|
+
appName,
|
|
153
|
+
userId,
|
|
154
|
+
sessionId,
|
|
155
|
+
userContent,
|
|
156
|
+
resolveTool: params.resolveTool,
|
|
157
|
+
state: () => session.state,
|
|
158
|
+
...(params.memory ? { memory: params.memory } : {}),
|
|
159
|
+
...(params.credentials ? { credentials: params.credentials } : {}),
|
|
160
|
+
onEvent: store,
|
|
161
|
+
traceCall: traceToolNodeCall,
|
|
162
|
+
}, runtime.runNode));
|
|
163
|
+
const onEvent = (event) => {
|
|
164
|
+
runtime.onEvent(event);
|
|
165
|
+
// ADK's reportNodeError: the workflow's own event for a node that gave up.
|
|
166
|
+
if (event.type === 'node_error' && event.source === 'workflow')
|
|
167
|
+
store(nodeErrorEvent(event, invocationId));
|
|
168
|
+
};
|
|
169
|
+
let end;
|
|
170
|
+
let failure;
|
|
171
|
+
let finished = false;
|
|
172
|
+
const walk = traceWorkflowInvocation({ name: graph.name, path: graph.name }, { sessionId, invocationId }, async () => {
|
|
173
|
+
try {
|
|
174
|
+
const run = await runWorkflowGraph(graph, {
|
|
175
|
+
input: start.input,
|
|
176
|
+
resume: start.resume,
|
|
177
|
+
runNode,
|
|
178
|
+
onEvent,
|
|
179
|
+
traceNode: traceNodeExecution,
|
|
180
|
+
...(signal ? { signal } : {}),
|
|
181
|
+
});
|
|
182
|
+
// 4. A paused walk: the workflow's own record, after every node's event.
|
|
183
|
+
if (run.interruptIds.length > 0)
|
|
184
|
+
store(workflowPauseEvent({ name: graph.name, invocationId, input: start.input, interruptIds: run.interruptIds }));
|
|
185
|
+
await stored();
|
|
186
|
+
return run;
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
await stored().catch(() => { });
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
}).then((run) => {
|
|
193
|
+
end = { run };
|
|
194
|
+
}, (error) => {
|
|
195
|
+
// ADK's Runner ends an aborted run without an error; the turn's control holds why.
|
|
196
|
+
if (isAborted(error) || signal?.aborted)
|
|
197
|
+
end = { stopped: true };
|
|
198
|
+
else
|
|
199
|
+
failure = { error };
|
|
200
|
+
}).finally(() => {
|
|
201
|
+
finished = true;
|
|
202
|
+
wake?.();
|
|
203
|
+
});
|
|
204
|
+
for (;;) {
|
|
205
|
+
while (queue.length > 0)
|
|
206
|
+
yield queue.shift();
|
|
207
|
+
if (finished)
|
|
208
|
+
break;
|
|
209
|
+
await new Promise((resolve) => (wake = resolve));
|
|
210
|
+
wake = undefined;
|
|
211
|
+
}
|
|
212
|
+
await walk;
|
|
213
|
+
if (failure)
|
|
214
|
+
throw failure.error;
|
|
215
|
+
return end;
|
|
216
|
+
}
|
|
@@ -69,9 +69,10 @@ export declare function nodeSettings(node: WorkflowNodeYaml | undefined): {
|
|
|
69
69
|
/**
|
|
70
70
|
* The route an output names: the `routeKey` property of an object (a parsed
|
|
71
71
|
* JSON output), else the trimmed text. Never undefined, so an edge with a
|
|
72
|
-
* `default` key always has something to fall back from.
|
|
72
|
+
* `default` key always has something to fall back from. One function for
|
|
73
|
+
* both runtimes; it lives in lib/workflow/route.ts.
|
|
73
74
|
*/
|
|
74
|
-
export
|
|
75
|
+
export { routeOf } from './workflow/route.ts';
|
|
75
76
|
/** A pause raised by an `ask_user` node, read from the run's events (`adk_request_input`). */
|
|
76
77
|
export interface PendingInput {
|
|
77
78
|
/** The interrupt id. A plain-text next message answers it. */
|
|
@@ -57,19 +57,10 @@ export function nodeSettings(node) {
|
|
|
57
57
|
/**
|
|
58
58
|
* The route an output names: the `routeKey` property of an object (a parsed
|
|
59
59
|
* JSON output), else the trimmed text. Never undefined, so an edge with a
|
|
60
|
-
* `default` key always has something to fall back from.
|
|
60
|
+
* `default` key always has something to fall back from. One function for
|
|
61
|
+
* both runtimes; it lives in lib/workflow/route.ts.
|
|
61
62
|
*/
|
|
62
|
-
export
|
|
63
|
-
if (output && typeof output === 'object' && !Array.isArray(output)) {
|
|
64
|
-
const value = output[routeKey];
|
|
65
|
-
if (value === undefined || value === null)
|
|
66
|
-
return '';
|
|
67
|
-
return String(value).trim();
|
|
68
|
-
}
|
|
69
|
-
if (typeof output === 'string')
|
|
70
|
-
return output.trim();
|
|
71
|
-
return output === undefined || output === null ? '' : String(output).trim();
|
|
72
|
-
}
|
|
63
|
+
export { routeOf } from './workflow/route.js';
|
|
73
64
|
/** ADK's function-call name for a workflow input request. */
|
|
74
65
|
export const INPUT_REQUEST = 'adk_request_input';
|
|
75
66
|
/** The request an `adk_request_input` call carries, or undefined for another call. */
|
|
@@ -79,7 +79,6 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
79
79
|
import { randomBytes } from 'node:crypto';
|
|
80
80
|
import { realpathSync, writeFileSync } from 'node:fs';
|
|
81
81
|
import { pathToFileURL } from 'node:url';
|
|
82
|
-
import { setLogLevel, LogLevel } from '@google/adk';
|
|
83
82
|
import { createA2AApp } from '../lib/a2a/app.js';
|
|
84
83
|
import { callerTokens, firstOf, hashCallerToken, jwtIdentity, parseCallers, sharedSecret, trustedHeader, } from '../lib/a2a/identity.js';
|
|
85
84
|
import { budgets, memoryUsageStore, parseBudgets, postgresUsageStore, supabaseUsageStore } from '../lib/a2a/policy.js';
|
|
@@ -90,6 +89,7 @@ import { dbSchema } from '../lib/storage/schema.js';
|
|
|
90
89
|
import { postgresStorage } from '../lib/storage/postgres/index.js';
|
|
91
90
|
import { isPlaceholderValue, loadEnv } from '../lib/loadEnv.js';
|
|
92
91
|
import { flushTracing } from '../lib/observability/tracer.js';
|
|
92
|
+
import { setLogLevel } from '../lib/runtime/logging.js';
|
|
93
93
|
// Re-exported for existing importers (tests/failNarration.test.ts).
|
|
94
94
|
export { describeTurnError } from '../lib/a2a/executor.js';
|
|
95
95
|
/** Minimum bearer-secret length below which the server warns. */
|
|
@@ -138,8 +138,9 @@ export async function startServer(syndicateName = 'syndicate.yaml', extensions =
|
|
|
138
138
|
// the root span carries the full user message and answer.
|
|
139
139
|
if (process.env.OTEL_CONSOLE_SPANS === undefined)
|
|
140
140
|
process.env.OTEL_CONSOLE_SPANS = 'false';
|
|
141
|
-
//
|
|
142
|
-
|
|
141
|
+
// The engine's level, which ADK's logger follows: ADK's INFO/DEBUG logs
|
|
142
|
+
// carry raw event JSON, so keep them quiet.
|
|
143
|
+
setLogLevel('warn');
|
|
143
144
|
const publicUrl = process.env.PUBLIC_URL?.trim() || undefined;
|
|
144
145
|
const port = envInt('PORT', 4000);
|
|
145
146
|
// ── Authentication posture ─────────────────────────────────────────────────
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
* and writes the result (or the error) back to the store, where
|
|
14
14
|
* `task_get` reads it in a later conversation.
|
|
15
15
|
*
|
|
16
|
-
* A job runs through lib/runtime/syndicateTurn.ts
|
|
16
|
+
* A job runs through lib/runtime/syndicateTurn.ts (on the runtime
|
|
17
|
+
* MELCHIZEDEK_RUNTIME names, ADK by default), the same runtime the
|
|
17
18
|
* A2A server, the REPL and the observatory use, so it runs the exact agent
|
|
18
19
|
* the conversation would have called directly — with the same step cap —
|
|
19
20
|
* and the job timeout ABORTS the run (the model call in flight included)
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
* and writes the result (or the error) back to the store, where
|
|
14
14
|
* `task_get` reads it in a later conversation.
|
|
15
15
|
*
|
|
16
|
-
* A job runs through lib/runtime/syndicateTurn.ts
|
|
16
|
+
* A job runs through lib/runtime/syndicateTurn.ts (on the runtime
|
|
17
|
+
* MELCHIZEDEK_RUNTIME names, ADK by default), the same runtime the
|
|
17
18
|
* A2A server, the REPL and the observatory use, so it runs the exact agent
|
|
18
19
|
* the conversation would have called directly — with the same step cap —
|
|
19
20
|
* and the job timeout ABORTS the run (the model call in flight included)
|
|
@@ -31,17 +32,19 @@
|
|
|
31
32
|
* and must match the chat process's value. Run ONE worker per store.
|
|
32
33
|
*/
|
|
33
34
|
import { randomUUID } from 'node:crypto';
|
|
34
|
-
import { InMemorySessionService, LogLevel, setLogLevel } from '@google/adk';
|
|
35
35
|
import { loadEnv } from '../lib/loadEnv.js';
|
|
36
36
|
import { loadSyndicate } from '../lib/loadSyndicate.js';
|
|
37
37
|
import { runSyndicateTurn } from '../lib/runtime/syndicateTurn.js';
|
|
38
|
+
import { asAdkSessionService } from '../lib/runtime/adkSessionBridge.js';
|
|
39
|
+
import { setLogLevel } from '../lib/runtime/logging.js';
|
|
40
|
+
import { InProcessSessionService } from '../lib/runtime/sessions.js';
|
|
38
41
|
import { PROVIDERS, providerForModel, providerKeyPresent, registerAvailableProviders, } from '../lib/models/registry.js';
|
|
39
42
|
import { getTaskBackend, setTaskBackend, taskStorePath } from '../lib/tools/taskTools.js';
|
|
40
43
|
import { postgresStorage } from '../lib/storage/postgres/index.js';
|
|
41
44
|
import { dbSchema } from '../lib/storage/schema.js';
|
|
42
45
|
import { hostname } from 'node:os';
|
|
43
46
|
loadEnv(import.meta.url);
|
|
44
|
-
setLogLevel(
|
|
47
|
+
setLogLevel('warn');
|
|
45
48
|
registerAvailableProviders();
|
|
46
49
|
// ── CLI ──────────────────────────────────────────────────────────────────────
|
|
47
50
|
const argv = process.argv.slice(2).filter((a) => a !== '--');
|
|
@@ -111,7 +114,9 @@ async function runJob(job) {
|
|
|
111
114
|
appName: 'assistant-worker',
|
|
112
115
|
userId: 'local-user',
|
|
113
116
|
sessionId: randomUUID(),
|
|
114
|
-
|
|
117
|
+
// A fresh in-process store per job, as the turn runner's signature
|
|
118
|
+
// names it (ADR 0080). The runtime follows MELCHIZEDEK_RUNTIME.
|
|
119
|
+
sessionService: asAdkSessionService(new InProcessSessionService()),
|
|
115
120
|
compile: { log, onUnknownTool: (n) => log(`unknown tool '${n}' skipped`) },
|
|
116
121
|
signal: current.signal,
|
|
117
122
|
deadlineMs: JOB_TIMEOUT_MS,
|
|
@@ -5,7 +5,11 @@ import { join } from 'node:path';
|
|
|
5
5
|
import { loadSyndicate, parseCliBindings } from '../lib/loadSyndicate.js';
|
|
6
6
|
import { isDispatchSyndicate } from '../lib/dispatch.js';
|
|
7
7
|
import { approvalResponsePart, describeApproval, describeInput, ingestTurnMemory, runSyndicateTurn } from '../lib/runtime/syndicateTurn.js';
|
|
8
|
-
import {
|
|
8
|
+
import { getFunctionCalls, getFunctionResponses } from '../lib/runtime/events.js';
|
|
9
|
+
import { InProcessSessionService } from '../lib/runtime/sessions.js';
|
|
10
|
+
import { asAdkSessionService, asSessionService } from '../lib/runtime/adkSessionBridge.js';
|
|
11
|
+
import { asAdkMemoryService } from '../lib/runtime/adkMemoryBridge.js';
|
|
12
|
+
import { setLogLevel } from '../lib/runtime/logging.js';
|
|
9
13
|
import { randomUUID } from 'node:crypto';
|
|
10
14
|
import { loadEnv } from '../lib/loadEnv.js';
|
|
11
15
|
// ── LLM Provider Registration ─────────────────────────────────────────────────
|
|
@@ -18,8 +22,8 @@ import { registerAvailableProviders, providerForModel, providerKeyPresent, PROVI
|
|
|
18
22
|
// ── Persistence Factory ───────────────────────────────────────────────────────
|
|
19
23
|
// Supabase-specific initialization lives in supabaseProvider.ts.
|
|
20
24
|
import { hasSupabaseCredentials, createSupabaseServices, } from '../lib/persistence/supabaseProvider.js';
|
|
21
|
-
//
|
|
22
|
-
setLogLevel(
|
|
25
|
+
// The engine's level, which ADK's logger follows: no INFO chatter in the chat.
|
|
26
|
+
setLogLevel('warn');
|
|
23
27
|
const c = {
|
|
24
28
|
reset: '\x1b[0m',
|
|
25
29
|
bold: '\x1b[1m',
|
|
@@ -175,7 +179,10 @@ async function main() {
|
|
|
175
179
|
// The same services the server would use for this syndicate's
|
|
176
180
|
// memory_system: Supabase sessions (and the memory service for
|
|
177
181
|
// long-term) when configured, process memory otherwise.
|
|
178
|
-
|
|
182
|
+
// The engine's stores (ADR 0080): the in-process one, or Supabase's,
|
|
183
|
+
// which has both faces. The turn runner takes ADK's face (its signature
|
|
184
|
+
// is fixed), and follows MELCHIZEDEK_RUNTIME for the runtime.
|
|
185
|
+
let sessionService = new InProcessSessionService();
|
|
179
186
|
let memoryService;
|
|
180
187
|
if (persistence.sessionService === 'supabase') {
|
|
181
188
|
const services = await createSupabaseServices({
|
|
@@ -188,7 +195,9 @@ async function main() {
|
|
|
188
195
|
memoryService = services.memoryService;
|
|
189
196
|
}
|
|
190
197
|
const appName = config.syndicate_name || 'melchizedek-syndicate';
|
|
191
|
-
await sessionService.
|
|
198
|
+
await asSessionService(sessionService).create({ appName, userId: SESSION_USER_ID, sessionId: SESSION_ID, state: {} });
|
|
199
|
+
const turnSessions = asAdkSessionService(sessionService);
|
|
200
|
+
const turnMemory = memoryService ? asAdkMemoryService(memoryService) : undefined;
|
|
192
201
|
banner(config, persistence, mergedBindings, SESSION_ID);
|
|
193
202
|
// Plan-dispatch, nested yaml_reference syndicates, guards and the
|
|
194
203
|
// max_steps cap run here exactly as the A2A server runs them: both call
|
|
@@ -227,8 +236,8 @@ async function main() {
|
|
|
227
236
|
appName,
|
|
228
237
|
userId: SESSION_USER_ID,
|
|
229
238
|
sessionId: SESSION_ID,
|
|
230
|
-
sessionService,
|
|
231
|
-
memoryService,
|
|
239
|
+
sessionService: turnSessions,
|
|
240
|
+
memoryService: turnMemory,
|
|
232
241
|
compile: {
|
|
233
242
|
onUnknownTool: (name) => console.warn(`${c.yellow}⚠ Unknown tool: '${name}' — skipping.${c.reset}`),
|
|
234
243
|
log: (message) => console.log(`${c.dim} ${message}${c.reset}`),
|
|
@@ -276,12 +285,12 @@ async function main() {
|
|
|
276
285
|
}
|
|
277
286
|
}
|
|
278
287
|
const ingest = async () => {
|
|
279
|
-
if (!
|
|
288
|
+
if (!turnMemory)
|
|
280
289
|
return;
|
|
281
290
|
try {
|
|
282
291
|
const ingested = await ingestTurnMemory({
|
|
283
|
-
memoryService,
|
|
284
|
-
sessionService,
|
|
292
|
+
memoryService: turnMemory,
|
|
293
|
+
sessionService: turnSessions,
|
|
285
294
|
appName,
|
|
286
295
|
userId: SESSION_USER_ID,
|
|
287
296
|
sessionId: SESSION_ID,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "melchizedek-agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "A multi-model, multi-agent orchestration framework on Google ADK — YAML-defined syndicates, Supabase session persistence, pgvector long-term memory. Companion repo to the lyceumagents.com curriculum.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"./storage/postgres": "./dist/lib/storage/postgres/index.js",
|
|
36
36
|
"./grounding": "./dist/lib/grounding.js",
|
|
37
37
|
"./config": "./dist/lib/config.js",
|
|
38
|
+
"./model": "./dist/lib/model.js",
|
|
38
39
|
"./models/*": "./dist/lib/models/*.js",
|
|
39
40
|
"./tools": "./dist/lib/toolRegistry.js",
|
|
40
41
|
"./tools/*": "./dist/lib/tools/*.js",
|
|
@@ -102,6 +103,7 @@
|
|
|
102
103
|
"assistant:worker": "node --disable-warning=DEP0040 --experimental-strip-types scripts/assistant_worker.ts",
|
|
103
104
|
"demo:models": "node --disable-warning=DEP0040 --experimental-strip-types scripts/demo_model_optionality.ts",
|
|
104
105
|
"demo:direct": "node --disable-warning=DEP0040 --experimental-strip-types scripts/direct_call.ts",
|
|
106
|
+
"parity": "node --disable-warning=DEP0040 --experimental-strip-types scripts/parity_check.ts",
|
|
105
107
|
"mcp:demo": "node --disable-warning=DEP0040 --experimental-strip-types scripts/demo_mcp_server.ts",
|
|
106
108
|
"mcp:wiki": "node --disable-warning=DEP0040 --experimental-strip-types scripts/wiki/mcp_server.ts",
|
|
107
109
|
"mcp:science": "node --disable-warning=DEP0040 --experimental-strip-types scripts/science_mcp_server.ts",
|
|
@@ -111,6 +113,7 @@
|
|
|
111
113
|
"schema:gen": "node --disable-warning=DEP0040 --experimental-strip-types scripts/gen_syndicate_schema.ts",
|
|
112
114
|
"start:a2a": "node --disable-warning=DEP0040 --experimental-strip-types scripts/a2a_server.ts",
|
|
113
115
|
"test": "node --disable-warning=DEP0040 --experimental-strip-types --test tests/**/*.test.ts",
|
|
116
|
+
"fixtures:sessions:check": "node --disable-warning=DEP0040 --experimental-strip-types tests/fixtures/sessions/generate.ts --check",
|
|
114
117
|
"init": "node --disable-warning=DEP0040 --experimental-strip-types scripts/init.ts",
|
|
115
118
|
"db": "node --disable-warning=DEP0040 --experimental-strip-types scripts/db.ts",
|
|
116
119
|
"registry": "node --disable-warning=DEP0040 --experimental-strip-types scripts/registry.ts",
|
|
@@ -38,7 +38,8 @@ Agent block keys (orchestrator and each subagent):
|
|
|
38
38
|
- `code_execution: gemini` lets a Gemini agent write and run Python in Google's sandbox (nothing runs on your machine). `context: { compact_after_tokens: 60000 }` on a delegate orchestrator summarizes a long conversation instead of overflowing it. `mode: task` on a workflow node makes its output its `finish_task` arguments.
|
|
39
39
|
- `openapi:` a list of OpenAPI 3 spec files (relative to the syndicate file) whose operations become the agent's tools; only GET operations unless `operations` names others; `auth` reads `bearer_env` or `api_key: {env, in, name}` from the environment. Read `weather.yaml` for the worked example.
|
|
40
40
|
- `mcp_server_url:` on a subagent: tools discovered from that MCP server at start (the melchizedek-serve skill).
|
|
41
|
-
- `
|
|
41
|
+
- `reasoning:` how hard the agent reasons, on any provider: `none`, `low`, `medium`, `high`, or `{ budget_tokens: 4096 }`. The compiler sends each provider the field it reads, so the setting survives a change to the `model:` line; unset keeps the provider's default. The templates in `config/agents/templates/` give a gathering or routing agent `none` and a synthesizing agent `low` or `medium`. What each level becomes per provider and per Claude model generation belongs to the melchizedek-models skill.
|
|
42
|
+
- `generateContentConfig:` per agent: `temperature`, `maxOutputTokens`, `responseMimeType: "application/json"` for structured output. Thinking tokens count against `maxOutputTokens`, so a reasoning agent with long output needs a high ceiling (the Scribe uses 24576). `thinkingConfig` and `reasoningEffort` inside this block are the older, provider-specific spelling of `reasoning:`: some starter-pack examples still use them and they still load, but an agent that sets either beside `reasoning:` fails to load. New agents use `reasoning:`.
|
|
42
43
|
- `outputSchema:` a JSON schema (types in upper case: `OBJECT`, `STRING`, `INTEGER`, `BOOLEAN`, `ARRAY`) the agent's reply must satisfy.
|
|
43
44
|
- Subagents receive one string argument, `query`, and see nothing else of the conversation. The orchestrator's instruction must say what to pass them, in full, every time.
|
|
44
45
|
|
|
@@ -60,5 +61,5 @@ Validate and run:
|
|
|
60
61
|
Editing a syndicate that is already serving:
|
|
61
62
|
- `melchizedek-serve` compiles a syndicate when it is first requested and caches it for the life of the process; an edited file changes nothing until the server restarts. A deployment that boots agents from the Supabase agent registry (`registry:<id>`) serves the registry row, not the file, until the row is republished.
|
|
62
63
|
|
|
63
|
-
IDENTIFIERS (verbatim): config/agents/, syndicateSchema.yaml, assets/minimal.yaml, syndicate_name, memory_system, internal-only, session-only, long-term, variables:, {{current_date}}, memory_extraction_rules:, dispatch:, default_route, route_overrides, workflow:, edges, nodes, ask_user, join, map, START, pipeline.yaml, orchestrator:, subagents:, description, model, instruction, tools:, mcp_server_url:, generateContentConfig:, outputSchema:, responseMimeType, thinkingConfig, maxOutputTokens, # tier:, npm test, npx melchizedek-doctor, npx melchizedek-chat --syndicate, loadSyndicate, gemini-3.8-flash, gemini-3.5-flash-lite, critic.yaml, scribe.yaml, tutor.yaml, patient_advocate.yaml
|
|
64
|
+
IDENTIFIERS (verbatim): config/agents/, syndicateSchema.yaml, assets/minimal.yaml, syndicate_name, memory_system, internal-only, session-only, long-term, variables:, {{current_date}}, memory_extraction_rules:, dispatch:, default_route, route_overrides, workflow:, edges, nodes, ask_user, join, map, START, pipeline.yaml, orchestrator:, subagents:, description, model, instruction, tools:, mcp_server_url:, reasoning:, budget_tokens, config/agents/templates/, generateContentConfig:, outputSchema:, responseMimeType, thinkingConfig, reasoningEffort, maxOutputTokens, # tier:, npm test, npx melchizedek-doctor, npx melchizedek-chat --syndicate, loadSyndicate, gemini-3.8-flash, gemini-3.5-flash-lite, critic.yaml, scribe.yaml, tutor.yaml, patient_advocate.yaml
|
|
64
65
|
LIMITS: the Node one-liner goes in a fenced bash block exactly as given. Body under 170 lines.
|
|
@@ -6,19 +6,19 @@ STRUCTURE (exact):
|
|
|
6
6
|
Frontmatter, verbatim:
|
|
7
7
|
---
|
|
8
8
|
name: melchizedek-models
|
|
9
|
-
description: "Choose and wire a model for a Melchizedek agent: how a model id routes to Gemini, Claude, GPT, Grok, or local Ollama, which environment variable each needs, the gateway fallback, the doctor, and per-agent
|
|
9
|
+
description: "Choose and wire a model for a Melchizedek agent: how a model id routes to Gemini, Claude, GPT, Grok, Kimi, or local Ollama, which environment variable each needs, the gateway fallback, the doctor, and per-agent settings such as how hard an agent reasons. Use when the user changes a model line, adds a provider key, wants an agent to think more or less, sees Model not found or a gateway error, or asks which keys a syndicate needs."
|
|
10
10
|
---
|
|
11
11
|
Then `##` sections in this order: "How a model id routes", "Which key unlocks what", "Run with no key at all", "One key for every cloud provider", "Settings per agent", "Mixing providers in one syndicate", "Reading the errors".
|
|
12
12
|
|
|
13
13
|
FACTS:
|
|
14
14
|
How a model id routes:
|
|
15
|
-
- The `model:` string's prefix names the provider: `claude-*` to Anthropic, `gpt-*` and `o<digit>*` to OpenAI, `grok-*` to xAI, `ollama/<model>` to a local Ollama, and everything else to Gemini (the ADK-native default).
|
|
15
|
+
- The `model:` string's prefix names the provider: `claude-*` to Anthropic, `gpt-*` and `o<digit>*` to OpenAI, `grok-*` to xAI, `kimi-*` to Moonshot AI (Kimi), `ollama/<model>` to a local Ollama, and everything else to Gemini (the ADK-native default).
|
|
16
16
|
- There is no allowlist in the engine: any id a provider currently serves works as written. A new model is a one-line YAML change.
|
|
17
|
-
- Ids verified in this deployment: `gemini-3.8-flash` (production), `gemini-3.5-flash-lite` (subagents, cost), `claude-sonnet-4-6`, `claude-opus-4-6`, `claude-haiku-4-5-20251001`, `gpt-5-mini`, `gpt-5`, `grok-4.7`, `ollama/qwen3:8b`.
|
|
17
|
+
- Ids verified in this deployment: `gemini-3.8-flash` (production), `gemini-3.5-flash-lite` (subagents, cost), `claude-sonnet-4-6`, `claude-opus-4-6`, `claude-haiku-4-5-20251001`, `gpt-5-mini`, `gpt-5`, `grok-4.7`, `kimi-k3`, `ollama/qwen3:8b`.
|
|
18
18
|
- `gemini-2.5-flash` returns a 400 about tool call context circulation with the server-side tool flag the framework sets; use `gemini-3.8-flash` or newer.
|
|
19
19
|
- Subagents inherit the orchestrator's `model` when they set none.
|
|
20
20
|
Which key unlocks what:
|
|
21
|
-
- `GOOGLE_GENAI_API_KEY` for Gemini (also the embedding model behind long-term memory); `ANTHROPIC_API_KEY`; `OPENAI_API_KEY`; `XAI_API_KEY`; no key for `ollama/*`.
|
|
21
|
+
- `GOOGLE_GENAI_API_KEY` for Gemini (also the embedding model behind long-term memory); `ANTHROPIC_API_KEY`; `OPENAI_API_KEY`; `XAI_API_KEY`; `MOONSHOT_API_KEY` for Kimi; no key for `ollama/*`.
|
|
22
22
|
- A provider registers only when its key is present; a missing key logs the provider as disabled, and an agent on that provider fails with `Model not found`.
|
|
23
23
|
- `npx melchizedek-doctor` (clone: `npm run doctor`) reads every syndicate, resolves each agent's model under the current `.env`, and prints per agent: model, provider, which server-side search tools the path keeps or drops, and whether it is funded; per syndicate a verdict; and the variables that would unlock the most. `--json`, `--check` (exit 1 when any syndicate is blocked). Read-only, no key value printed.
|
|
24
24
|
- The `# tier:` first line of every starter-pack file states its cost class (`keyless`, one provider name, or `multi-provider`); the doctor checks the claim against the models.
|
|
@@ -32,16 +32,20 @@ One key for every cloud provider (the gateway fallback):
|
|
|
32
32
|
- Dials: `MODEL_GATEWAY_MODEL_MAP=<your id>=<the gateway's id>` renames an id the gateway rejects; `MODEL_GATEWAY_BASE_URL` points at a self-hosted proxy.
|
|
33
33
|
- Telemetry attributes the call to the upstream provider and records the transport separately.
|
|
34
34
|
Settings per agent:
|
|
35
|
-
- `
|
|
36
|
-
- The
|
|
35
|
+
- `reasoning:` on the agent block, beside `model:`, sets how hard the agent reasons: `none`, `low`, `medium`, `high`, or `{ budget_tokens: 4096 }`. It reads the same on every provider; unset keeps the provider's default. The compiler sends each provider the field it reads: Gemini 3 and later get a thinking level (`none` is `MINIMAL`, then `LOW`, `MEDIUM`, `HIGH`); Gemini 2.x, and Claude 4.6 or older, get a thinking budget of 0, 2048, 8192 or 16384 tokens; every later Claude model gets adaptive thinking at an effort of `low`, `medium` or `high`, and `none` is that model's own off switch, sent at `low` effort; every other provider, and the gateway, gets an effort word, and where the provider lacks that word, its nearest setting above.
|
|
36
|
+
- A `budget_tokens` value goes as written to Gemini and to Claude 4.6 or older, and as the smallest level that covers it everywhere else, later Claude models included. The effort word is always sent too, because the gateway can serve any cloud id; a direct provider ignores the field it does not read. Changing the `model:` line keeps the setting. Unset, a later Claude model keeps its own default: it thinks, except Opus 4.7 and 4.8, which think only when asked.
|
|
37
|
+
- Four provider limits: on Claude 4.6 or older, the adapter raises a budget under 1024 to 1024, the least Anthropic takes; on every Claude model, the Anthropic SDK refuses a non-streaming request whose output ceiling passes about 21000 tokens, and the adapter sets that ceiling to at least the thinking budget plus 2048, so a `budget_tokens` above about 19000 fails a non-streaming turn, while the levels stay under it; on Claude Opus 5 and 5.5, Fable and Mythos, `none` is adaptive thinking at `low` effort, so a little thinking remains, because Opus 5.5, Fable and Mythos cannot turn thinking off and Opus 5's off switch can write a tool call as text that never runs; Gemini 2.5 Pro rejects a budget of 0, so `none` fails on it.
|
|
38
|
+
- `generateContentConfig:` on any agent: `temperature` (where the provider still exposes it; never sent to Claude), `maxOutputTokens`. Thinking tokens count against `maxOutputTokens`, so a reasoning agent with long output needs room; the Claude adapter raises its own ceiling to the thinking budget plus 2048 tokens, on every Claude model. A level counts as its budget (2048, 8192 or 16384), so `high`, or a later Claude model that thinks by default, gets at least 18432.
|
|
39
|
+
- `thinkingConfig` and `reasoningEffort` inside `generateContentConfig:` are the older, provider-specific spelling of `reasoning:`. They still load, but each reaches only the providers that read it (a `thinkingLevel` written for Gemini does nothing on Claude). An agent that sets either beside `reasoning:` fails to load. `thinkingConfig` stays only for `includeThoughts: true`, which streams Gemini's thought trace and has no `reasoning:` form; such an agent sets its level inside `thinkingConfig` too (`thinkingLevel`) and leaves `reasoning:` out.
|
|
40
|
+
- The framework's own pattern: data-gathering subagents on a lite model with `reasoning: none` and a tight output cap; synthesis on the stronger model with `reasoning: low` or higher and room to reason. The templates in `config/agents/templates/` follow it.
|
|
37
41
|
Mixing providers in one syndicate:
|
|
38
42
|
- Any agent in a graph can run on a different provider; `model_zoo.yaml` declares one lightweight agent per provider, and in a clone `npm run demo:models` sends one prompt through each.
|
|
39
43
|
- In code, `registerAvailableProviders()` from `melchizedek-agents` registers every provider whose key is present so a plain ADK `LlmAgent` can take any of these ids; in a clone `npm run demo:direct -- --model ollama/qwen3:8b hello` runs one agent with no YAML.
|
|
40
44
|
Reading the errors:
|
|
41
|
-
- `Model not found` for a `claude-*`, `gpt-*` or `
|
|
45
|
+
- `Model not found` for a `claude-*`, `gpt-*`, `grok-*` or `kimi-*` id: the provider's key is unset. Set it, or set the gateway pair.
|
|
42
46
|
- `GATEWAY_HTTP_ERROR ... 404/400`: the gateway rejected the mapped id; fix the name with `MODEL_GATEWAY_MODEL_MAP`.
|
|
43
47
|
- `GATEWAY_KEY_MISSING`: `MODEL_GATEWAY` is set without `MODEL_GATEWAY_API_KEY`.
|
|
44
48
|
- `OLLAMA_UNREACHABLE`: Ollama is not running (`ollama serve`) or the model is not pulled (`ollama list`).
|
|
45
49
|
|
|
46
|
-
IDENTIFIERS (verbatim): model:, claude-*, gpt-*, grok-*, ollama/, gemini-3.8-flash, gemini-3.5-flash-lite, gemini-2.5-flash, ollama/qwen3:8b, GOOGLE_GENAI_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY, XAI_API_KEY, npx melchizedek-doctor, --json, --check, # tier:, ollama pull qwen3:8b, http://localhost:11434/v1, MODEL_GATEWAY, MODEL_GATEWAY_API_KEY, MODEL_GATEWAY_MODEL_MAP, MODEL_GATEWAY_BASE_URL, X-API-Key, generateContentConfig:, maxOutputTokens, thinkingConfig, model_zoo.yaml, npm run demo:models, registerAvailableProviders, GATEWAY_HTTP_ERROR, GATEWAY_KEY_MISSING, OLLAMA_UNREACHABLE, Model not found
|
|
47
|
-
LIMITS: the routing prefixes may be a short table. Body under 160 lines.
|
|
50
|
+
IDENTIFIERS (verbatim): model:, claude-*, gpt-*, grok-*, kimi-*, ollama/, gemini-3.8-flash, gemini-3.5-flash-lite, gemini-2.5-flash, kimi-k3, ollama/qwen3:8b, GOOGLE_GENAI_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY, XAI_API_KEY, MOONSHOT_API_KEY, npx melchizedek-doctor, --json, --check, # tier:, ollama pull qwen3:8b, http://localhost:11434/v1, MODEL_GATEWAY, MODEL_GATEWAY_API_KEY, MODEL_GATEWAY_MODEL_MAP, MODEL_GATEWAY_BASE_URL, X-API-Key, reasoning:, budget_tokens, generateContentConfig:, maxOutputTokens, thinkingConfig, reasoningEffort, thinkingLevel, includeThoughts, config/agents/templates/, model_zoo.yaml, npm run demo:models, registerAvailableProviders, GATEWAY_HTTP_ERROR, GATEWAY_KEY_MISSING, OLLAMA_UNREACHABLE, Model not found
|
|
51
|
+
LIMITS: the routing prefixes and what `reasoning:` becomes per provider may each be a short table. Body under 160 lines.
|
|
@@ -43,7 +43,7 @@ The starter pack (file → name → what it does → tier / extra requirement):
|
|
|
43
43
|
Check the keys:
|
|
44
44
|
- `npx melchizedek-doctor` (clone: `npm run doctor`) reads every syndicate the loader can see, resolves each agent's model to a provider under the current `.env`, and prints one table: agent, model, provider, which server-side tools the path keeps or drops, and whether the path is funded, with one verdict per syndicate and the variables that would unlock the most. Read-only; nothing sent, nothing written, no key value printed.
|
|
45
45
|
- `--json` for machine-readable output; `--check` exits 1 when any syndicate is blocked.
|
|
46
|
-
- Which key: GOOGLE_GENAI_API_KEY for gemini-* ids, ANTHROPIC_API_KEY for claude-*, OPENAI_API_KEY for gpt-*, XAI_API_KEY for grok-*, none for ollama/* (Ollama must be running, model pulled). Details and the gateway fallback: the melchizedek-models skill.
|
|
46
|
+
- Which key: GOOGLE_GENAI_API_KEY for gemini-* ids, ANTHROPIC_API_KEY for claude-*, OPENAI_API_KEY for gpt-*, XAI_API_KEY for grok-*, MOONSHOT_API_KEY for kimi-*, none for ollama/* (Ollama must be running, model pulled). Details and the gateway fallback: the melchizedek-models skill.
|
|
47
47
|
|
|
48
48
|
Run one:
|
|
49
49
|
- Interactive: `npx melchizedek-chat --syndicate <name>` (clone: `npm run chat:syndicate -- --syndicate <name>`; every example also has an alias such as `npm run syndicate:tutor`). A banner shows the syndicate, orchestrator, subagents, and whether sessions and memory are in Supabase or in memory. Type `exit` to end the session; that is also when a long-term syndicate writes its memory.
|
|
@@ -60,7 +60,7 @@ Delegate a task from this agent:
|
|
|
60
60
|
|
|
61
61
|
When something fails:
|
|
62
62
|
- `Gemini API Key is not configured`: `.env` missing or GOOGLE_GENAI_API_KEY unset.
|
|
63
|
-
- `Model not found` for a claude-*, gpt-* or
|
|
63
|
+
- `Model not found` for a claude-*, gpt-*, grok-* or kimi-* id: that provider's key is not set, so the provider was not registered; the doctor names the variable.
|
|
64
64
|
- `OLLAMA_UNREACHABLE` for ollama/* ids: Ollama is not running, or the model is not pulled (`ollama list`).
|
|
65
65
|
- `[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.
|
|
66
66
|
- `Refusing to connect to private/loopback MCP host`: the SSRF guard; for a local MCP server set `ALLOW_PRIVATE_MCP=true` in `.env`.
|
|
@@ -68,5 +68,5 @@ When something fails:
|
|
|
68
68
|
|
|
69
69
|
The other skills: melchizedek-author (design or edit a syndicate file), melchizedek-serve (A2A server and MCP in both directions), melchizedek-memory (Supabase sessions and long-term memory), melchizedek-models (model ids, providers, keys, the gateway), melchizedek-scribe (write documents from a brief with the Scribe).
|
|
70
70
|
|
|
71
|
-
IDENTIFIERS (verbatim): config/agents/, config/agents/examples/, node_modules/melchizedek-agents/config/agents/examples/, MELCHIZEDEK_AGENTS_DIR, npx melchizedek-doctor, --json, --check, npx melchizedek-chat --syndicate, CHAT_STREAMING=false, --bind, --bindings, {{current_date}}, GOOGLE_GENAI_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY, XAI_API_KEY, SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, ALLOW_PRIVATE_MCP=true, X_BEARER_TOKEN, scribe.yaml, tutor.yaml, augustin.yaml, syndicate.yaml
|
|
71
|
+
IDENTIFIERS (verbatim): config/agents/, config/agents/examples/, node_modules/melchizedek-agents/config/agents/examples/, MELCHIZEDEK_AGENTS_DIR, npx melchizedek-doctor, --json, --check, npx melchizedek-chat --syndicate, CHAT_STREAMING=false, --bind, --bindings, {{current_date}}, GOOGLE_GENAI_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY, XAI_API_KEY, MOONSHOT_API_KEY, SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, ALLOW_PRIVATE_MCP=true, X_BEARER_TOKEN, scribe.yaml, tutor.yaml, augustin.yaml, syndicate.yaml
|
|
72
72
|
LIMITS: the starter-pack section is a bulleted list, one line per file, file name in backticks first. Body under 170 lines.
|
|
@@ -78,6 +78,7 @@ Provide keys in `.env` or in the environment:
|
|
|
78
78
|
- `ANTHROPIC_API_KEY` for claude-* ids.
|
|
79
79
|
- `OPENAI_API_KEY` for gpt-* ids.
|
|
80
80
|
- `XAI_API_KEY` for grok-* ids.
|
|
81
|
+
- `MOONSHOT_API_KEY` for kimi-* ids.
|
|
81
82
|
- No key for ollama/* ids; start Ollama and pull the model.
|
|
82
83
|
|
|
83
84
|
The `melchizedek-models` skill covers provider details and the gateway fallback.
|
|
@@ -147,7 +148,7 @@ CHAT_STREAMING=false npx melchizedek-chat --syndicate scribe -- "$(cat brief.md)
|
|
|
147
148
|
## When something fails
|
|
148
149
|
|
|
149
150
|
- `Gemini API Key is not configured`: `.env` is missing or `GOOGLE_GENAI_API_KEY` is unset.
|
|
150
|
-
- `Model not found` for a claude-*, gpt-*, or
|
|
151
|
+
- `Model not found` for a claude-*, gpt-*, grok-*, or kimi-* id: you did not set that provider's key, so the framework did not register the provider; run `npx melchizedek-doctor` to see the missing variable.
|
|
151
152
|
- `OLLAMA_UNREACHABLE` for ollama/* ids: Ollama is not running, or you have not pulled the target model (`ollama list`).
|
|
152
153
|
- `[400] Tool call context circulation is not enabled`: the agent uses a `model:` too old for agent transfer; update the agent to `gemini-3.8-flash` or newer.
|
|
153
154
|
- `Refusing to connect to private/loopback MCP host`: the SSRF guard blocked the connection; for a local MCP server, set `ALLOW_PRIVATE_MCP=true` in `.env`.
|
|
@@ -45,7 +45,8 @@ Agent block keys for the orchestrator and each subagent:
|
|
|
45
45
|
- `code_execution: gemini` lets a Gemini agent write and run Python in Google's sandbox (nothing runs on your machine). `context: { compact_after_tokens: 60000 }` on a delegate orchestrator summarizes a long conversation instead of overflowing it. `mode: task` on a workflow node makes its output its `finish_task` arguments.
|
|
46
46
|
- `openapi:` a list of OpenAPI 3 spec files (relative to the syndicate file) whose operations become the agent's tools; only GET operations unless `operations` names others; `auth` reads `bearer_env` or `api_key: {env, in, name}` from the environment. Read `weather.yaml` for the worked example.
|
|
47
47
|
- `mcp_server_url:`: URL on a subagent to discover MCP tools at start. The melchizedek-serve skill covers MCP servers.
|
|
48
|
-
- `
|
|
48
|
+
- `reasoning:`: how hard the agent reasons, on any provider: `none`, `low`, `medium`, `high`, or `{ budget_tokens: 4096 }`. The compiler sends each provider the field it reads, so the setting survives a change to the `model:` line. Leave it unset to keep the provider's default. The templates in `config/agents/templates/` give a gathering or routing agent `none` and a synthesizing agent `low` or `medium`. The melchizedek-models skill covers what each level becomes per provider and per Claude model generation.
|
|
49
|
+
- `generateContentConfig:`: per-agent options: `temperature`, `maxOutputTokens`, `responseMimeType: "application/json"`. Thinking tokens count against `maxOutputTokens`, so a reasoning agent with long output needs a high ceiling. The Scribe uses 24576 for `maxOutputTokens`. `thinkingConfig` and `reasoningEffort` inside this block are the older, provider-specific spelling of `reasoning:`. Some starter-pack examples still use them and they still load, but an agent that sets either beside `reasoning:` fails to load. Write new agents with `reasoning:`.
|
|
49
50
|
- `outputSchema:`: a JSON schema using uppercase types (`OBJECT`, `STRING`, `INTEGER`, `BOOLEAN`, `ARRAY`) that the agent reply must satisfy.
|
|
50
51
|
|
|
51
52
|
Subagents receive one string argument, `query`, and see nothing else of the conversation. The orchestrator instruction must state what to pass them in full every time.
|
|
@@ -34,6 +34,7 @@ orchestrator:
|
|
|
34
34
|
3. <Compose the reply from what came back.>
|
|
35
35
|
</execution_framework>
|
|
36
36
|
</prompt_instructions>
|
|
37
|
+
reasoning: low # none | low | medium | high, on any provider (melchizedek-models)
|
|
37
38
|
generateContentConfig:
|
|
38
39
|
maxOutputTokens: 4096
|
|
39
40
|
|
|
@@ -47,6 +48,7 @@ subagents:
|
|
|
47
48
|
instruction: |
|
|
48
49
|
You are Worker. <The one job, its inputs, and the exact shape of the
|
|
49
50
|
report you return.>
|
|
51
|
+
reasoning: none # a gathering agent; raise it when the job is judgment
|
|
50
52
|
generateContentConfig:
|
|
51
53
|
maxOutputTokens: 2048
|
|
52
54
|
# For structured output put the schema HERE, on a leaf with no tools —
|