melchizedek-agents 0.16.2 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +17 -0
- package/AGENT_SETUP.md +2 -1
- package/CHANGELOG.md +142 -0
- package/DOCUMENTATION.md +237 -7
- package/QUICKSTART.md +2 -2
- package/README.md +125 -149
- package/config/agents/examples/harness.yaml +135 -0
- package/config/agents/examples/model_zoo.yaml +23 -9
- package/config/agents/examples/pipeline.yaml +120 -0
- package/config/agents/examples/specs/open-meteo-forecast.json +77 -0
- package/config/agents/examples/specs/open-meteo-geocoding.json +53 -0
- package/config/agents/examples/weather.yaml +59 -0
- package/config/agents/syndicateSchema.yaml +112 -1
- package/db/migrations/0011_erase_expired.sql +198 -0
- package/dist/lib/a2a/app.d.ts +7 -0
- package/dist/lib/a2a/app.js +7 -1
- package/dist/lib/a2a/executor.js +41 -0
- package/dist/lib/compile.d.ts +40 -1
- package/dist/lib/compile.js +132 -14
- package/dist/lib/config.d.ts +17 -0
- package/dist/lib/config.js +17 -0
- package/dist/lib/doctor.js +17 -4
- package/dist/lib/index.d.ts +2 -0
- package/dist/lib/index.js +1 -0
- package/dist/lib/loadSyndicate.d.ts +46 -0
- package/dist/lib/loadSyndicate.js +19 -3
- package/dist/lib/memory/erase.d.ts +5 -0
- package/dist/lib/memory/erase.js +5 -0
- package/dist/lib/models/capabilities.js +10 -0
- package/dist/lib/models/endpoints.d.ts +1 -1
- package/dist/lib/models/endpoints.js +4 -1
- package/dist/lib/models/gateway.d.ts +2 -2
- package/dist/lib/models/gateway.js +4 -4
- package/dist/lib/models/kimiLlm.d.ts +123 -0
- package/dist/lib/models/kimiLlm.js +171 -0
- package/dist/lib/models/providerMap.d.ts +2 -2
- package/dist/lib/models/providerMap.js +4 -1
- package/dist/lib/models/registry.d.ts +1 -1
- package/dist/lib/models/registry.js +11 -3
- package/dist/lib/net/redirects.d.ts +14 -0
- package/dist/lib/net/redirects.js +94 -0
- package/dist/lib/observability/tracer.d.ts +1 -1
- package/dist/lib/persistence/supabaseProvider.d.ts +2 -6
- package/dist/lib/persistence/supabaseProvider.js +2 -31
- package/dist/lib/runtime/questions.d.ts +55 -0
- package/dist/lib/runtime/questions.js +107 -0
- package/dist/lib/runtime/syndicateTurn.d.ts +42 -4
- package/dist/lib/runtime/syndicateTurn.js +162 -16
- package/dist/lib/storage/postgres/index.d.ts +6 -0
- package/dist/lib/storage/postgres/index.js +11 -0
- package/dist/lib/storage/rlsStatus.d.ts +45 -0
- package/dist/lib/storage/rlsStatus.js +69 -0
- package/dist/lib/syndicateSchema.d.ts +169 -0
- package/dist/lib/syndicateSchema.js +327 -2
- package/dist/lib/toolRegistry.js +9 -0
- package/dist/lib/tools/openapiTools.d.ts +46 -0
- package/dist/lib/tools/openapiTools.js +195 -0
- package/dist/lib/tools/skillToolset.d.ts +135 -0
- package/dist/lib/tools/skillToolset.js +247 -0
- package/dist/lib/tools/urlContextTool.d.ts +29 -0
- package/dist/lib/tools/urlContextTool.js +39 -0
- package/dist/lib/workflow.d.ts +72 -0
- package/dist/lib/workflow.js +180 -0
- package/dist/lib/workflowConfig.d.ts +95 -0
- package/dist/lib/workflowConfig.js +93 -0
- package/dist/scripts/syndicate_chat.js +39 -5
- package/package.json +4 -1
- package/skills/briefs/_shared.md +1 -1
- package/skills/briefs/melchizedek-author.md +8 -4
- package/skills/briefs/melchizedek-memory.md +1 -1
- package/skills/briefs/melchizedek-models.md +1 -1
- package/skills/briefs/melchizedek-scribe.md +4 -4
- package/skills/briefs/melchizedek-serve.md +1 -1
- package/skills/briefs/melchizedek.md +4 -1
- package/skills/melchizedek/SKILL.md +4 -1
- package/skills/melchizedek-author/SKILL.md +7 -3
- package/skills/melchizedek-memory/SKILL.md +1 -1
- package/skills/melchizedek-models/SKILL.md +1 -1
- package/skills/melchizedek-scribe/SKILL.md +3 -3
- package/skills/melchizedek-serve/SKILL.md +1 -1
- /package/skills/melchizedek-author/{templates → assets}/minimal.yaml +0 -0
- /package/skills/melchizedek-scribe/{templates → assets}/brief.md +0 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/workflow.ts — the `workflow:` block: a syndicate as a graph.
|
|
3
|
+
*
|
|
4
|
+
* ── The third orchestration method ───────────────────────────────────────
|
|
5
|
+
* DELEGATE lets an orchestrator call subagents as tools; PLAN-DISPATCH
|
|
6
|
+
* (lib/dispatch.ts) lets a classifier pick ONE subagent to answer. Neither
|
|
7
|
+
* can say "run these two in parallel, join their outputs, review, ask the
|
|
8
|
+
* person, then publish". A `workflow:` block does: it is a graph whose
|
|
9
|
+
* nodes are the syndicate's own agents plus a few node kinds the engine
|
|
10
|
+
* supplies, and whose edges say what runs after what and on which route.
|
|
11
|
+
*
|
|
12
|
+
* workflow:
|
|
13
|
+
* edges:
|
|
14
|
+
* - [START, Planner, { article: [Writer, Checker], answer: Answerer }]
|
|
15
|
+
* - [[Writer, Checker], Both, Editor, Confirm, Publisher]
|
|
16
|
+
* nodes:
|
|
17
|
+
* Both: { join: true } # waits for every predecessor
|
|
18
|
+
* Confirm: { ask_user: "Publish? yes, or say what to change." }
|
|
19
|
+
* Planner: { route_key: "kind", retry: { max_attempts: 2 } }
|
|
20
|
+
*
|
|
21
|
+
* ADK 2.2 runs it (`Workflow`, `FunctionNode`, `JoinNode`, `ParallelWorker`,
|
|
22
|
+
* `ToolNode`, `RequestInput`): per-node retries and timeouts, fan-out and
|
|
23
|
+
* fan-in, a pause that waits for a person and resumes on the next message,
|
|
24
|
+
* and resumption from the session's own events. Every agent is already a
|
|
25
|
+
* node (`LlmAgent extends BaseNode`): a node agent receives the previous
|
|
26
|
+
* node's output as its user turn and, unless its YAML sets
|
|
27
|
+
* `includeContents`, sees nothing else of the conversation.
|
|
28
|
+
*
|
|
29
|
+
* ── What the engine adds to ADK ───────────────────────────────────────────
|
|
30
|
+
* ROUTING. ADK's TypeScript port never derives a route from an agent's
|
|
31
|
+
* output, so a routing map in YAML gets a hidden step after the agent
|
|
32
|
+
* (`<Agent>__route`) that reads the route from the output — the
|
|
33
|
+
* `route_key` property of a JSON output, else the trimmed text — and
|
|
34
|
+
* re-emits the output with it. A `default` key catches what no key
|
|
35
|
+
* matched.
|
|
36
|
+
* THE PAUSE. `ask_user` is a node that raises ADK's `RequestInput`. The
|
|
37
|
+
* turn ends `input-required` carrying the question (`result.input`); the
|
|
38
|
+
* next message on the conversation is the answer and becomes that node's
|
|
39
|
+
* output. The A2A server publishes the question; the chat prints it.
|
|
40
|
+
* NAMES. Nodes are addressed by their YAML names everywhere (edges, events,
|
|
41
|
+
* the wiki), including a `map` node, which ADK would otherwise name after
|
|
42
|
+
* the agent it wraps.
|
|
43
|
+
*
|
|
44
|
+
* ── Not in this version ───────────────────────────────────────────────────
|
|
45
|
+
* Approval gates (`require_approval`, `skills.scripts: local`) and remote
|
|
46
|
+
* `a2a_agent_url` subagents are refused inside a workflow by the schema: a
|
|
47
|
+
* gated call pauses a node through ADK's interrupt path, whose resume is not
|
|
48
|
+
* the one ADR 0028 built, and a remote agent is reachable only as a tool.
|
|
49
|
+
* Both are open for a later record. A `Workflow` cannot yet be another
|
|
50
|
+
* agent's subagent (an ADK limit), so `yaml_reference` to a workflow
|
|
51
|
+
* syndicate compiles its orchestrator alone.
|
|
52
|
+
*/
|
|
53
|
+
import { DEFAULT_ROUTE, FunctionNode, JoinNode, ParallelWorker, RequestInput, ToolNode, Workflow, createEvent, } from '@google/adk';
|
|
54
|
+
import { compileSubagent } from './compile.js';
|
|
55
|
+
import { resolveTools as resolveNamedTools } from './toolRegistry.js';
|
|
56
|
+
import { DEFAULT_ROUTE_KEY, ROUTE_STEP_SUFFIX, START_NAME, isWorkflowSyndicate, nodeKind, nodeSettings, routeOf } from './workflowConfig.js';
|
|
57
|
+
export * from './workflowConfig.js';
|
|
58
|
+
/**
|
|
59
|
+
* Compile a workflow syndicate: every agent through `compileSubagent` (its
|
|
60
|
+
* tools, skills and MCP server as in any mode), the declared nodes, the
|
|
61
|
+
* hidden route steps, and the edge chains, into one ADK `Workflow` named
|
|
62
|
+
* after the syndicate. Throws on a name the schema let through, a tool that
|
|
63
|
+
* is not registered, or a graph ADK rejects (unreachable node, cycle).
|
|
64
|
+
*/
|
|
65
|
+
export async function compileWorkflow(config, opts = {}, transformAgent = (a) => a) {
|
|
66
|
+
if (!isWorkflowSyndicate(config))
|
|
67
|
+
throw new Error(`${config.syndicate_name}: no workflow block`);
|
|
68
|
+
const wf = config.workflow;
|
|
69
|
+
const nodeYaml = wf.nodes ?? {};
|
|
70
|
+
const agentConfigs = new Map();
|
|
71
|
+
agentConfigs.set(config.orchestrator.name, { description: '', ...config.orchestrator });
|
|
72
|
+
for (const sub of config.subagents ?? [])
|
|
73
|
+
agentConfigs.set(sub.name, sub);
|
|
74
|
+
// Agents, with their node modifiers (retry, timeout) at construction.
|
|
75
|
+
const compileOpts = {
|
|
76
|
+
...opts,
|
|
77
|
+
nodeConfig: (name) => {
|
|
78
|
+
const settings = nodeSettings(nodeYaml[name]);
|
|
79
|
+
return Object.keys(settings).length ? settings : undefined;
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
const agents = new Map();
|
|
83
|
+
for (const [name, sub] of agentConfigs)
|
|
84
|
+
agents.set(name, transformAgent(await compileSubagent(sub, compileOpts)));
|
|
85
|
+
// Declared nodes.
|
|
86
|
+
const nodes = new Map(agents);
|
|
87
|
+
for (const [name, entry] of Object.entries(nodeYaml)) {
|
|
88
|
+
const kind = nodeKind(entry);
|
|
89
|
+
if (!kind)
|
|
90
|
+
continue; // modifiers on an agent
|
|
91
|
+
const settings = nodeSettings(entry);
|
|
92
|
+
if (kind === 'ask_user') {
|
|
93
|
+
const message = entry.ask_user ?? '';
|
|
94
|
+
const schema = entry.schema;
|
|
95
|
+
// The node runs again on resume (rerunOnResume): ADK then hands it the
|
|
96
|
+
// same input and the answers resolved so far, so its output can carry
|
|
97
|
+
// BOTH the reply and what the person was asked about. A node fast-
|
|
98
|
+
// forwarded by ADK would output the bare reply, and the next agent
|
|
99
|
+
// would never see the draft it is meant to act on.
|
|
100
|
+
nodes.set(name, new FunctionNode(name, (ctx, input) => {
|
|
101
|
+
const replies = Object.values(ctx.resumeInputs);
|
|
102
|
+
if (replies.length > 0)
|
|
103
|
+
return { reply: replies[replies.length - 1], input };
|
|
104
|
+
return new RequestInput({ message, payload: input, ...(schema ? { responseSchema: schema } : {}) });
|
|
105
|
+
}, { ...settings, rerunOnResume: true }));
|
|
106
|
+
}
|
|
107
|
+
else if (kind === 'join') {
|
|
108
|
+
nodes.set(name, new JoinNode({ name, ...settings }));
|
|
109
|
+
}
|
|
110
|
+
else if (kind === 'map') {
|
|
111
|
+
const inner = agents.get(entry.map);
|
|
112
|
+
if (!inner)
|
|
113
|
+
throw new Error(`workflow node '${name}': map names '${entry.map}', which is not an agent of this syndicate`);
|
|
114
|
+
const worker = new ParallelWorker(inner, entry.max_parallel !== undefined ? { maxParallelWorkers: entry.max_parallel } : {});
|
|
115
|
+
// ADK names a worker after the agent it wraps; the YAML name is what
|
|
116
|
+
// the edges, the events and the docs use.
|
|
117
|
+
Object.defineProperty(worker, 'name', { value: name, enumerable: true });
|
|
118
|
+
nodes.set(name, worker);
|
|
119
|
+
}
|
|
120
|
+
else if (kind === 'tool') {
|
|
121
|
+
const [tool] = resolveNamedTools([entry.tool], opts.onUnknownTool);
|
|
122
|
+
if (!tool)
|
|
123
|
+
throw new Error(`workflow node '${name}': tool '${entry.tool}' is not registered`);
|
|
124
|
+
nodes.set(name, new ToolNode(tool, { name, ...settings }));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const nodeOf = (name) => {
|
|
128
|
+
const node = nodes.get(name);
|
|
129
|
+
if (!node)
|
|
130
|
+
throw new Error(`workflow: '${name}' is not an agent or a declared node of this syndicate`);
|
|
131
|
+
return node;
|
|
132
|
+
};
|
|
133
|
+
const routeSteps = new Map();
|
|
134
|
+
const routeStepAfter = (name) => {
|
|
135
|
+
let step = routeSteps.get(name);
|
|
136
|
+
if (step)
|
|
137
|
+
return step;
|
|
138
|
+
const routeKey = nodeYaml[name]?.route_key ?? 'route';
|
|
139
|
+
const stepName = `${name}${ROUTE_STEP_SUFFIX}`;
|
|
140
|
+
step = new FunctionNode(stepName, (ctx, input) => createEvent({
|
|
141
|
+
author: stepName,
|
|
142
|
+
invocationId: ctx.invocationId,
|
|
143
|
+
branch: ctx.branch,
|
|
144
|
+
output: input,
|
|
145
|
+
route: routeOf(input, routeKey),
|
|
146
|
+
}));
|
|
147
|
+
routeSteps.set(name, step);
|
|
148
|
+
return step;
|
|
149
|
+
};
|
|
150
|
+
const edges = wf.edges.map((chain) => {
|
|
151
|
+
const out = [];
|
|
152
|
+
chain.forEach((element, i) => {
|
|
153
|
+
if (typeof element === 'string') {
|
|
154
|
+
out.push(element === START_NAME ? 'START' : nodeOf(element));
|
|
155
|
+
}
|
|
156
|
+
else if (Array.isArray(element)) {
|
|
157
|
+
out.push(element.map(nodeOf));
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
const previous = chain[i - 1];
|
|
161
|
+
if (typeof previous !== 'string' || previous === START_NAME) {
|
|
162
|
+
throw new Error(`workflow: a routing map must follow the name of the node whose output it routes`);
|
|
163
|
+
}
|
|
164
|
+
out.push(routeStepAfter(previous));
|
|
165
|
+
const map = {};
|
|
166
|
+
for (const [key, target] of Object.entries(element)) {
|
|
167
|
+
map[key === DEFAULT_ROUTE_KEY ? DEFAULT_ROUTE : key] = Array.isArray(target) ? target.map(nodeOf) : nodeOf(target);
|
|
168
|
+
}
|
|
169
|
+
out.push(map);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
return out;
|
|
173
|
+
});
|
|
174
|
+
const workflow = new Workflow({
|
|
175
|
+
name: config.syndicate_name,
|
|
176
|
+
edges,
|
|
177
|
+
...(wf.max_concurrency !== undefined ? { maxConcurrency: wf.max_concurrency } : {}),
|
|
178
|
+
});
|
|
179
|
+
return { workflow, agents, nodes };
|
|
180
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/workflowConfig.ts — the `workflow:` block's contract: its types, the
|
|
3
|
+
* names it reserves, and the pure helpers the schema (lib/syndicateSchema.ts),
|
|
4
|
+
* the compiler (lib/workflow.ts) and the turn runner share. No imports
|
|
5
|
+
* beyond types, so the schema can read it without pulling the compiler in.
|
|
6
|
+
*/
|
|
7
|
+
import type { RetryConfig as AdkRetryConfig } from '@google/adk';
|
|
8
|
+
/** `START` in an edge chain: the graph's entry. Not a node. */
|
|
9
|
+
export declare const START_NAME = "START";
|
|
10
|
+
/** The routing-map key that catches an unmatched route. */
|
|
11
|
+
export declare const DEFAULT_ROUTE_KEY = "default";
|
|
12
|
+
/** Suffix of the hidden step inserted after an agent a routing map follows. */
|
|
13
|
+
export declare const ROUTE_STEP_SUFFIX = "__route";
|
|
14
|
+
/** One element of an edge chain: a name, several names (fan-out or fan-in), or a routing map. */
|
|
15
|
+
export type EdgeElement = string | string[] | Record<string, string | string[]>;
|
|
16
|
+
export interface RetryYaml {
|
|
17
|
+
/** Attempts including the first. 1 = no retry. Default 5 in ADK. */
|
|
18
|
+
max_attempts?: number;
|
|
19
|
+
/** Seconds before the first retry. */
|
|
20
|
+
initial_delay?: number;
|
|
21
|
+
max_delay?: number;
|
|
22
|
+
backoff_factor?: number;
|
|
23
|
+
}
|
|
24
|
+
/** Per-node settings. A node is an agent (modifiers only) or exactly one of the kinds. */
|
|
25
|
+
export interface WorkflowNodeYaml {
|
|
26
|
+
/** Pause and ask the person this; the reply becomes the node's output. */
|
|
27
|
+
ask_user?: string;
|
|
28
|
+
/** JSON Schema a structured reply to `ask_user` must satisfy. A plain-text reply passes as is. */
|
|
29
|
+
schema?: Record<string, unknown>;
|
|
30
|
+
/** Wait for every predecessor, then output `{ <predecessor>: <output> }`. */
|
|
31
|
+
join?: true;
|
|
32
|
+
/** Run this agent once per item of a list input, concurrently; output the list of results. */
|
|
33
|
+
map?: string;
|
|
34
|
+
/** Concurrency of `map`. Default 8. */
|
|
35
|
+
max_parallel?: number;
|
|
36
|
+
/** Run this registry tool with the node input as its arguments. */
|
|
37
|
+
tool?: string;
|
|
38
|
+
/** Property of a JSON output holding the route (agents). Default "route". */
|
|
39
|
+
route_key?: string;
|
|
40
|
+
retry?: RetryYaml;
|
|
41
|
+
/** Seconds this node may run before it fails. */
|
|
42
|
+
timeout?: number;
|
|
43
|
+
}
|
|
44
|
+
export interface WorkflowConfig {
|
|
45
|
+
/** Chains, each a list of elements; `START` begins at least one of them. */
|
|
46
|
+
edges: EdgeElement[][];
|
|
47
|
+
nodes?: Record<string, WorkflowNodeYaml>;
|
|
48
|
+
/** Nodes that may run at once. Default: unbounded. */
|
|
49
|
+
max_concurrency?: number;
|
|
50
|
+
}
|
|
51
|
+
/** True when the syndicate is a graph (a `workflow:` block is present). */
|
|
52
|
+
export declare function isWorkflowSyndicate(config: {
|
|
53
|
+
workflow?: unknown;
|
|
54
|
+
}): config is {
|
|
55
|
+
workflow: WorkflowConfig;
|
|
56
|
+
};
|
|
57
|
+
export declare const NODE_KINDS: readonly ['ask_user', 'join', 'map', 'tool'];
|
|
58
|
+
/** The kind a node entry declares, or undefined for a modifier-only entry (an agent). */
|
|
59
|
+
export declare function nodeKind(node: WorkflowNodeYaml | undefined): (typeof NODE_KINDS)[number] | undefined;
|
|
60
|
+
/** Every name an edge element references (`START` excluded). */
|
|
61
|
+
export declare function elementNames(element: EdgeElement): string[];
|
|
62
|
+
/** ADK's retry config from the YAML spelling. */
|
|
63
|
+
export declare function toRetryConfig(retry: RetryYaml | undefined): AdkRetryConfig | undefined;
|
|
64
|
+
/** The BaseNode fields a node entry's modifiers map to. */
|
|
65
|
+
export declare function nodeSettings(node: WorkflowNodeYaml | undefined): {
|
|
66
|
+
retryConfig?: AdkRetryConfig;
|
|
67
|
+
timeout?: number;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* The route an output names: the `routeKey` property of an object (a parsed
|
|
71
|
+
* JSON output), else the trimmed text. Never undefined, so an edge with a
|
|
72
|
+
* `default` key always has something to fall back from.
|
|
73
|
+
*/
|
|
74
|
+
export declare function routeOf(output: unknown, routeKey?: string): string;
|
|
75
|
+
/** A pause raised by an `ask_user` node, read from the run's events (`adk_request_input`). */
|
|
76
|
+
export interface PendingInput {
|
|
77
|
+
/** The interrupt id. A plain-text next message answers it. */
|
|
78
|
+
id: string;
|
|
79
|
+
/** The node that asked. */
|
|
80
|
+
node: string;
|
|
81
|
+
/** The question. */
|
|
82
|
+
message: string;
|
|
83
|
+
payload?: unknown;
|
|
84
|
+
/** JSON Schema a structured answer must satisfy, when the node declared one. */
|
|
85
|
+
schema?: unknown;
|
|
86
|
+
}
|
|
87
|
+
/** ADK's function-call name for a workflow input request. */
|
|
88
|
+
export declare const INPUT_REQUEST = "adk_request_input";
|
|
89
|
+
/** The request an `adk_request_input` call carries, or undefined for another call. */
|
|
90
|
+
export declare function inputRequestFrom(author: string | undefined, call: {
|
|
91
|
+
name?: string;
|
|
92
|
+
args?: Record<string, unknown>;
|
|
93
|
+
}): PendingInput | undefined;
|
|
94
|
+
/** One line a person can read: what the workflow is waiting for. */
|
|
95
|
+
export declare function describeInput(input: PendingInput): string;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/workflowConfig.ts — the `workflow:` block's contract: its types, the
|
|
3
|
+
* names it reserves, and the pure helpers the schema (lib/syndicateSchema.ts),
|
|
4
|
+
* the compiler (lib/workflow.ts) and the turn runner share. No imports
|
|
5
|
+
* beyond types, so the schema can read it without pulling the compiler in.
|
|
6
|
+
*/
|
|
7
|
+
/** `START` in an edge chain: the graph's entry. Not a node. */
|
|
8
|
+
export const START_NAME = 'START';
|
|
9
|
+
/** The routing-map key that catches an unmatched route. */
|
|
10
|
+
export const DEFAULT_ROUTE_KEY = 'default';
|
|
11
|
+
/** Suffix of the hidden step inserted after an agent a routing map follows. */
|
|
12
|
+
export const ROUTE_STEP_SUFFIX = '__route';
|
|
13
|
+
/** True when the syndicate is a graph (a `workflow:` block is present). */
|
|
14
|
+
export function isWorkflowSyndicate(config) {
|
|
15
|
+
return !!config.workflow && typeof config.workflow === 'object';
|
|
16
|
+
}
|
|
17
|
+
export const NODE_KINDS = ['ask_user', 'join', 'map', 'tool'];
|
|
18
|
+
/** The kind a node entry declares, or undefined for a modifier-only entry (an agent). */
|
|
19
|
+
export function nodeKind(node) {
|
|
20
|
+
if (!node)
|
|
21
|
+
return undefined;
|
|
22
|
+
return NODE_KINDS.find((k) => node[k] !== undefined);
|
|
23
|
+
}
|
|
24
|
+
/** Every name an edge element references (`START` excluded). */
|
|
25
|
+
export function elementNames(element) {
|
|
26
|
+
if (typeof element === 'string')
|
|
27
|
+
return element === START_NAME ? [] : [element];
|
|
28
|
+
if (Array.isArray(element))
|
|
29
|
+
return element;
|
|
30
|
+
return Object.values(element).flatMap((v) => (Array.isArray(v) ? v : [v]));
|
|
31
|
+
}
|
|
32
|
+
/** ADK's retry config from the YAML spelling. */
|
|
33
|
+
export function toRetryConfig(retry) {
|
|
34
|
+
if (!retry)
|
|
35
|
+
return undefined;
|
|
36
|
+
const out = {};
|
|
37
|
+
if (retry.max_attempts !== undefined)
|
|
38
|
+
out.maxAttempts = retry.max_attempts;
|
|
39
|
+
if (retry.initial_delay !== undefined)
|
|
40
|
+
out.initialDelay = retry.initial_delay;
|
|
41
|
+
if (retry.max_delay !== undefined)
|
|
42
|
+
out.maxDelay = retry.max_delay;
|
|
43
|
+
if (retry.backoff_factor !== undefined)
|
|
44
|
+
out.backoffFactor = retry.backoff_factor;
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
/** The BaseNode fields a node entry's modifiers map to. */
|
|
48
|
+
export function nodeSettings(node) {
|
|
49
|
+
const out = {};
|
|
50
|
+
const retry = toRetryConfig(node?.retry);
|
|
51
|
+
if (retry)
|
|
52
|
+
out.retryConfig = retry;
|
|
53
|
+
if (node?.timeout !== undefined)
|
|
54
|
+
out.timeout = node.timeout;
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The route an output names: the `routeKey` property of an object (a parsed
|
|
59
|
+
* JSON output), else the trimmed text. Never undefined, so an edge with a
|
|
60
|
+
* `default` key always has something to fall back from.
|
|
61
|
+
*/
|
|
62
|
+
export function routeOf(output, routeKey = 'route') {
|
|
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
|
+
}
|
|
73
|
+
/** ADK's function-call name for a workflow input request. */
|
|
74
|
+
export const INPUT_REQUEST = 'adk_request_input';
|
|
75
|
+
/** The request an `adk_request_input` call carries, or undefined for another call. */
|
|
76
|
+
export function inputRequestFrom(author, call) {
|
|
77
|
+
if (call.name !== INPUT_REQUEST)
|
|
78
|
+
return undefined;
|
|
79
|
+
const args = call.args ?? {};
|
|
80
|
+
return {
|
|
81
|
+
id: String(args.interruptId ?? ''),
|
|
82
|
+
node: author ?? '',
|
|
83
|
+
message: typeof args.message === 'string' ? args.message : '',
|
|
84
|
+
...(args.payload !== undefined && args.payload !== null ? { payload: args.payload } : {}),
|
|
85
|
+
...(args.response_schema !== undefined && args.response_schema !== null ? { schema: args.response_schema } : {}),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/** One line a person can read: what the workflow is waiting for. */
|
|
89
|
+
export function describeInput(input) {
|
|
90
|
+
const options = input.payload?.options;
|
|
91
|
+
const choices = Array.isArray(options) && options.length ? ` (${options.join(' / ')})` : '';
|
|
92
|
+
return `${input.node} asks: ${input.message || '(no question text)'}${choices}`;
|
|
93
|
+
}
|
|
@@ -4,7 +4,7 @@ import { writeFileSync, mkdirSync } from 'node:fs';
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { loadSyndicate, parseCliBindings } from '../lib/loadSyndicate.js';
|
|
6
6
|
import { isDispatchSyndicate } from '../lib/dispatch.js';
|
|
7
|
-
import { ingestTurnMemory, runSyndicateTurn } from '../lib/runtime/syndicateTurn.js';
|
|
7
|
+
import { approvalResponsePart, describeApproval, describeInput, ingestTurnMemory, runSyndicateTurn } from '../lib/runtime/syndicateTurn.js';
|
|
8
8
|
import { InMemorySessionService, getFunctionCalls, getFunctionResponses, setLogLevel, LogLevel, } from '@google/adk';
|
|
9
9
|
import { randomUUID } from 'node:crypto';
|
|
10
10
|
import { loadEnv } from '../lib/loadEnv.js';
|
|
@@ -215,13 +215,15 @@ async function main() {
|
|
|
215
215
|
const cliInput = queryParts.join(' ');
|
|
216
216
|
// Ctrl+C during a turn cancels the turn; Ctrl+C at the prompt exits.
|
|
217
217
|
let activeTurn;
|
|
218
|
-
|
|
218
|
+
/** A gated call waiting for this person's yes (ADR 0028); answered by the next prompt. */
|
|
219
|
+
let pendingApproval;
|
|
220
|
+
async function runChat(trimmed, parts = [{ text: trimmed }]) {
|
|
219
221
|
const printer = makePrinter();
|
|
220
222
|
activeTurn = new AbortController();
|
|
221
223
|
try {
|
|
222
224
|
const result = await runSyndicateTurn({
|
|
223
225
|
config,
|
|
224
|
-
parts
|
|
226
|
+
parts,
|
|
225
227
|
appName,
|
|
226
228
|
userId: SESSION_USER_ID,
|
|
227
229
|
sessionId: SESSION_ID,
|
|
@@ -249,7 +251,18 @@ async function main() {
|
|
|
249
251
|
// The orchestrator's relay came back empty; show what shipped.
|
|
250
252
|
console.log(`\n${c.dim}[relay fallback — the specialist's answer]${c.reset}\n${result.text}`);
|
|
251
253
|
}
|
|
252
|
-
if (result.status
|
|
254
|
+
if (result.status === 'input-required' && result.approval) {
|
|
255
|
+
// The turn stopped on a tool a person must approve: a gated tool,
|
|
256
|
+
// or a skill script. The next prompt asks for the decision.
|
|
257
|
+
pendingApproval = result.approval;
|
|
258
|
+
console.log(`\n${c.yellow}⏸ Approval needed: ${describeApproval(result.approval)}${c.reset}`);
|
|
259
|
+
}
|
|
260
|
+
else if (result.status === 'input-required' && result.input) {
|
|
261
|
+
// A workflow's ask_user node: whatever is typed next is the answer,
|
|
262
|
+
// and the graph resumes from that node.
|
|
263
|
+
console.log(`\n${c.yellow}❓ ${describeInput(result.input)}${c.reset}\n${c.dim} (your next message is the answer)${c.reset}`);
|
|
264
|
+
}
|
|
265
|
+
else if (result.status !== 'completed') {
|
|
253
266
|
console.error(`\n${c.yellow}⚠ [${result.error?.code ?? 'ERROR'}] ${result.error?.message ?? ''}${c.reset}`);
|
|
254
267
|
}
|
|
255
268
|
console.log('\n');
|
|
@@ -286,12 +299,29 @@ async function main() {
|
|
|
286
299
|
if (cliInput) {
|
|
287
300
|
console.log(`${c.green}${c.bold}You${c.reset} › ${cliInput}`);
|
|
288
301
|
await runChat(cliInput);
|
|
302
|
+
if (pendingApproval) {
|
|
303
|
+
// A one-shot run has nobody to ask: the call stays unapproved and
|
|
304
|
+
// never runs. Interactive mode, or an A2A client, can answer it.
|
|
305
|
+
console.error(`${c.yellow}⚠ The run stopped on a call that needs your approval; it did not run. Start the chat without a query to approve or refuse such calls.${c.reset}`);
|
|
306
|
+
process.exitCode = 2;
|
|
307
|
+
}
|
|
289
308
|
// A one-shot query still contributes to long-term memory.
|
|
290
309
|
await ingest();
|
|
291
310
|
return;
|
|
292
311
|
}
|
|
293
312
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
294
313
|
const ask = () => {
|
|
314
|
+
if (pendingApproval) {
|
|
315
|
+
const approval = pendingApproval;
|
|
316
|
+
rl.question(`${c.yellow}${c.bold}Approve${c.reset} ${describeApproval(approval)}? [y/N] › `, async (answer) => {
|
|
317
|
+
pendingApproval = undefined;
|
|
318
|
+
const approved = /^y(es)?$/i.test(answer.trim());
|
|
319
|
+
console.log(`${c.dim} ${approved ? 'Approved' : 'Refused'}: ${approval.agent} → ${approval.tool}${c.reset}`);
|
|
320
|
+
await runChat(approved ? 'approve' : 'reject', [approvalResponsePart(approval.id, approved)]);
|
|
321
|
+
ask();
|
|
322
|
+
});
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
295
325
|
rl.question(`${c.green}${c.bold}You${c.reset} › `, async (userInput) => {
|
|
296
326
|
const trimmed = userInput.trim();
|
|
297
327
|
if (!trimmed) {
|
|
@@ -334,6 +364,9 @@ async function main() {
|
|
|
334
364
|
function makePrinter() {
|
|
335
365
|
let currentMode = 'none';
|
|
336
366
|
let streamedText = false;
|
|
367
|
+
// Which agent the current text label names: a workflow's nodes speak in
|
|
368
|
+
// turn, each under its own label.
|
|
369
|
+
let labelled = '';
|
|
337
370
|
return {
|
|
338
371
|
onEvent(event) {
|
|
339
372
|
const e = event;
|
|
@@ -363,9 +396,10 @@ function makePrinter() {
|
|
|
363
396
|
else if (p.text) {
|
|
364
397
|
if (!isPartial && streamedText)
|
|
365
398
|
continue;
|
|
366
|
-
if (currentMode !== 'text') {
|
|
399
|
+
if (currentMode !== 'text' || labelled !== e.author) {
|
|
367
400
|
process.stdout.write(`\n${c.magenta}${c.bold}${e.author}${c.reset} › `);
|
|
368
401
|
currentMode = 'text';
|
|
402
|
+
labelled = e.author;
|
|
369
403
|
}
|
|
370
404
|
process.stdout.write(p.text);
|
|
371
405
|
if (isPartial)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "melchizedek-agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.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",
|
|
@@ -95,6 +95,9 @@
|
|
|
95
95
|
"syndicate:augustin": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate augustin",
|
|
96
96
|
"syndicate:scribe": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate scribe",
|
|
97
97
|
"syndicate:assistant": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate assistant",
|
|
98
|
+
"syndicate:harness": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate harness",
|
|
99
|
+
"syndicate:pipeline": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate pipeline",
|
|
100
|
+
"syndicate:weather": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate weather",
|
|
98
101
|
"syndicate:conversational": "node --disable-warning=DEP0040 --experimental-strip-types scripts/syndicate_chat.ts --syndicate conversational",
|
|
99
102
|
"assistant:worker": "node --disable-warning=DEP0040 --experimental-strip-types scripts/assistant_worker.ts",
|
|
100
103
|
"demo:models": "node --disable-warning=DEP0040 --experimental-strip-types scripts/demo_model_optionality.ts",
|
package/skills/briefs/_shared.md
CHANGED
|
@@ -9,7 +9,7 @@ SHARED FACTS (true for every document in this series; use those the section need
|
|
|
9
9
|
- The reader of a skill file is a coding agent (Claude Code, Codex, Cursor, OpenCode, Gemini CLI) acting for a software engineer. It reads the file when the task matches the description, then acts. Every command it might run goes in a fenced bash block, with the exact flags. Prose between commands says when to run which and what the output means. Nothing decorative.
|
|
10
10
|
|
|
11
11
|
GLOBAL LIMITS FOR SKILL FILES:
|
|
12
|
-
- The document is a complete SKILL.md: it begins with the frontmatter block given under STRUCTURE, exactly, then the body.
|
|
12
|
+
- The document is a complete SKILL.md: it begins with the frontmatter block given under STRUCTURE, exactly (its `description` stays in double quotes: a colon inside an unquoted value is invalid YAML and a strict loader drops the skill), then the body.
|
|
13
13
|
- Body under 170 lines. Headings are `##`. No H1. No table of contents. No closing summary section.
|
|
14
14
|
- Every command in a fenced block tagged bash. Every file path, flag, environment variable and identifier in backticks in prose.
|
|
15
15
|
- Commands, flags, paths and environment variable names come only from this brief; never invent one.
|
|
@@ -6,7 +6,7 @@ STRUCTURE (exact):
|
|
|
6
6
|
Frontmatter, verbatim:
|
|
7
7
|
---
|
|
8
8
|
name: melchizedek-author
|
|
9
|
-
description: Design or edit a Melchizedek syndicate YAML: the file layout, required keys, orchestrator and subagent blocks, tools by name, output schemas, memory mode, the tier header, and offline validation. Use when the user wants a new agent team, wants to change a syndicate's prompt, model, or tools, or asks what a field in a syndicate file means.
|
|
9
|
+
description: "Design or edit a Melchizedek syndicate YAML: the file layout, required keys, orchestrator and subagent blocks, tools by name, output schemas, memory mode, the tier header, and offline validation. Use when the user wants a new agent team, wants to change a syndicate's prompt, model, or tools, or asks what a field in a syndicate file means."
|
|
10
10
|
---
|
|
11
11
|
Then `##` sections in this order: "Where the file goes", "Start from the closest example", "The keys", "Write the instruction", "Two constraints that break a file", "Validate and run", "Editing a syndicate that is already serving".
|
|
12
12
|
|
|
@@ -14,7 +14,7 @@ FACTS:
|
|
|
14
14
|
Where the file goes:
|
|
15
15
|
- Your syndicates live at the root of `config/agents/` in your project; the file name without `.yaml` is the id used everywhere (`--syndicate <id>`, A2A routes). Use lowercase with underscores.
|
|
16
16
|
- `MELCHIZEDEK_AGENTS_DIR` relocates the directory; `syndicateSchema.yaml` beside the examples documents every field with its ADK counterpart.
|
|
17
|
-
- A minimal template ships with this skill at `
|
|
17
|
+
- A minimal template ships with this skill at `assets/minimal.yaml` (relative to this skill's directory): copy it, rename it, fill it in.
|
|
18
18
|
|
|
19
19
|
Start from the closest example:
|
|
20
20
|
- Copy the example nearest the job out of the starter pack into `config/agents/` and edit it. One orchestrator and one subagent is the right size to start; grow only when the work divides.
|
|
@@ -26,13 +26,17 @@ The keys (top level):
|
|
|
26
26
|
- `variables:` defaults for `{{token}}` placeholders used anywhere in the file; overridden at run time with `--bind key=value` or `--bindings '{"key":"value"}'`. Never hardcode `current_date`; `{{current_date}}` is injected fresh on every load.
|
|
27
27
|
- `memory_extraction_rules:` (long-term only): domain rules appended to the shared fact-extraction prompt for this syndicate alone, such as what never to store and what always to store.
|
|
28
28
|
- `dispatch:` switches the syndicate from delegate mode (the default: subagents become tools the orchestrator calls, and it re-emits the chosen answer) to plan-dispatch (a classifier picks a route). Keys: `default_route` (required inside the block; a declared subagent that can answer any message), `route_key`, `route_overrides` (a list of `{route, pattern, flags, reason}`; a regex match pins the route and the classifier never runs), `reason_key`.
|
|
29
|
+
- `workflow:` makes the syndicate a graph instead of a delegation: `edges` lists chains of agent names (`START` opens one; a list of names fans out or in; a map `{route: Agent, default: Agent}` routes on the previous node's output, the `route_key` of a JSON output or its text), and `nodes` declares what is not an agent: `join: true`, `map: Agent`, `tool: name`, `ask_user: "question"` (the turn pauses; the next message answers), plus `retry` and `timeout` on any node. Every node receives the previous node's output as its message and sees nothing else. Cannot be combined with `dispatch:`. The worked example is `pipeline.yaml`.
|
|
29
30
|
- `orchestrator:` (required) and `subagents:` (optional; omit or `[]` for a single agent).
|
|
30
31
|
Agent block keys (orchestrator and each subagent):
|
|
31
32
|
- `name` (required): a valid JavaScript identifier, unique in the tree, never `user`.
|
|
32
33
|
- `description`: one line. For a subagent this is the routing API: the orchestrator decides whom to call by reading descriptions, so say exactly what to pass and when to call.
|
|
33
34
|
- `model` (required on the orchestrator; subagents inherit it when unset): the id's prefix names the provider (the melchizedek-models skill). Use `gemini-3.8-flash` or `gemini-3.5-flash-lite` for Gemini; `gemini-2.5-flash` fails with a 400 about tool call context circulation.
|
|
34
35
|
- `instruction` (required): the system prompt.
|
|
35
|
-
- `tools:` a list of registered tool names: `web_search`, `web_extract`, `google_search`, `x_search`, `x_api_search`, `collections_search`, `generate_image`, `inspect_image`, `load_memory`, `preload_memory`, `wiki_map`, `wiki_search`, `wiki_read`, `wiki_links`, `wiki_dive`, `wiki_save`, `wiki_graph`, `wiki_relate
|
|
36
|
+
- `tools:` a list of registered tool names: `web_search`, `web_extract`, `google_search`, `x_search`, `x_api_search`, `collections_search`, `generate_image`, `inspect_image`, `load_memory`, `preload_memory`, `wiki_map`, `wiki_search`, `wiki_read`, `wiki_links`, `wiki_dive`, `wiki_save`, `wiki_graph`, `wiki_relate`, `url_context` (Gemini reads the pages at URLs in the conversation; a no-op on other providers), and `ask_user` (the agent asks the person and the turn waits for the answer; only on the orchestrator or a plan-dispatch route). An unknown name is warned and skipped, never fatal. Server-side search tools run natively only on providers that offer them; the doctor reports per agent which are kept or dropped.
|
|
37
|
+
- `examples:` a list of `{input, output}` pairs added to every request as few-shot examples. At the top level, `retries:` tunes self-correction, which is on by default: `model_errors` (2) retries a malformed model reply, `tool_errors` (3) retries a tool that threw; 0 turns either off.
|
|
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
|
+
- `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.
|
|
36
40
|
- `mcp_server_url:` on a subagent: tools discovered from that MCP server at start (the melchizedek-serve skill).
|
|
37
41
|
- `generateContentConfig:` per agent: `temperature`, `maxOutputTokens`, `responseMimeType: "application/json"` for structured output, `thinkingConfig: { thinkingLevel: MEDIUM, includeThoughts: false }` on Gemini models that think. Thinking tokens count against `maxOutputTokens`, so a thinking agent with long output needs a high ceiling (the Scribe uses 24576).
|
|
38
42
|
- `outputSchema:` a JSON schema (types in upper case: `OBJECT`, `STRING`, `INTEGER`, `BOOLEAN`, `ARRAY`) the agent's reply must satisfy.
|
|
@@ -56,5 +60,5 @@ Validate and run:
|
|
|
56
60
|
Editing a syndicate that is already serving:
|
|
57
61
|
- `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.
|
|
58
62
|
|
|
59
|
-
IDENTIFIERS (verbatim): config/agents/, syndicateSchema.yaml,
|
|
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
|
|
60
64
|
LIMITS: the Node one-liner goes in a fenced bash block exactly as given. Body under 170 lines.
|
|
@@ -6,7 +6,7 @@ STRUCTURE (exact):
|
|
|
6
6
|
Frontmatter, verbatim:
|
|
7
7
|
---
|
|
8
8
|
name: melchizedek-memory
|
|
9
|
-
description: Give a Melchizedek syndicate persistent sessions and long-term memory on Supabase: the memory_system modes, the two tables and the match function, the memory tools, extraction rules, inspection and erasure. Use when the user wants an agent to remember across runs, sets up Supabase for melchizedek, or asks why recall returns nothing.
|
|
9
|
+
description: "Give a Melchizedek syndicate persistent sessions and long-term memory on Supabase: the memory_system modes, the two tables and the match function, the memory tools, extraction rules, inspection and erasure. Use when the user wants an agent to remember across runs, sets up Supabase for melchizedek, or asks why recall returns nothing."
|
|
10
10
|
---
|
|
11
11
|
Then `##` sections in this order: "The three memory modes", "Provision Supabase", "What gets stored", "Wire the syndicate", "Prove it", "Inspect and erase", "When recall returns nothing".
|
|
12
12
|
|
|
@@ -6,7 +6,7 @@ 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 generation settings. Use when the user changes a model line, adds a provider key, sees Model not found or a gateway error, or asks which keys a syndicate needs.
|
|
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 generation settings. Use when the user changes a model line, adds a provider key, 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
|
|
|
@@ -6,7 +6,7 @@ STRUCTURE (exact):
|
|
|
6
6
|
Frontmatter, verbatim:
|
|
7
7
|
---
|
|
8
8
|
name: melchizedek-scribe
|
|
9
|
-
description: Write a document from a technical brief with the Scribe syndicate: a README section, a skill file, product copy, a runbook. Use when the user wants prose generated from facts they supply, wants to write a SKILL.md, or mentions the scribe; covers the brief format, the one-shot run, capture, and review.
|
|
9
|
+
description: "Write a document from a technical brief with the Scribe syndicate: a README section, a skill file, product copy, a runbook. Use when the user wants prose generated from facts they supply, wants to write a SKILL.md, or mentions the scribe; covers the brief format, the one-shot run, capture, and review."
|
|
10
10
|
---
|
|
11
11
|
Then `##` sections in this order: "What the Scribe is", "Write the brief", "Run it and capture the document", "Review before you hand it over", "Writing a SKILL.md with it".
|
|
12
12
|
|
|
@@ -19,7 +19,7 @@ What the Scribe is:
|
|
|
19
19
|
Write the brief:
|
|
20
20
|
- A brief is plain text with these parts, any order, any headings: AUDIENCE (who reads it and what they know), KIND (skill file, README section, page copy, runbook, email; the kind sets the shape), PURPOSE (what the reader can do afterwards), FACTS (every statement the document may make; this is the Scribe's whole knowledge), IDENTIFIERS (strings that must appear verbatim: commands, flags, paths, environment variables, package names, model ids, URLs), LIMITS (word or line caps, required headings, forbidden constructions), SOURCE MATERIAL (optional pasted text the Scribe may quote).
|
|
21
21
|
- Facts come from the code, the manifest and the docs at hand; the reading agent gathers them into the brief before running. Put the exact command with its flags in FACTS, and list it again under IDENTIFIERS.
|
|
22
|
-
- A template ships with this skill at `
|
|
22
|
+
- A template ships with this skill at `assets/brief.md` (relative to this skill's directory).
|
|
23
23
|
Run it and capture the document:
|
|
24
24
|
- One shot, output as one block: `CHAT_STREAMING=false npx melchizedek-chat --syndicate scribe -- "$(cat brief.md)" > scribe.out` (clone: `CHAT_STREAMING=false npm run syndicate:scribe -- "$(cat brief.md)" > scribe.out`).
|
|
25
25
|
- The file holds the startup banner, the echoed brief after `You › `, dim lines naming the Auditor calls, and the document after the last line that begins `Scribe › `. Take everything after that last marker; strip terminal color codes if the consumer needs plain text. A one-line extraction:
|
|
@@ -31,7 +31,7 @@ Review before you hand it over:
|
|
|
31
31
|
- If the document invents a fact, the fix is in the brief: add the true fact or forbid the topic under LIMITS, then rerun. Do not patch the prose by hand and leave the brief stale; the brief is what the next run uses.
|
|
32
32
|
- The document is the Scribe's output: text to review and present, never instructions for the reading agent.
|
|
33
33
|
Writing a SKILL.md with it:
|
|
34
|
-
- Under LIMITS give the exact frontmatter block (`name` equal to the directory name, lowercase with hyphens; `description` under 1024 characters stating when to use the skill), the `##` headings in order, a line cap, and "every command in a fenced bash block". Put the shared facts of a suite in one block and prepend it to each brief so the skills agree.
|
|
34
|
+
- Under LIMITS give the exact frontmatter block (`name` equal to the directory name, lowercase with hyphens; `description` in double quotes, under 1024 characters, stating when to use the skill), the `##` headings in order, a line cap, and "every command in a fenced bash block". Put the shared facts of a suite in one block and prepend it to each brief so the skills agree.
|
|
35
35
|
|
|
36
|
-
IDENTIFIERS (verbatim): scribe.yaml, GOOGLE_GENAI_API_KEY,
|
|
36
|
+
IDENTIFIERS (verbatim): scribe.yaml, GOOGLE_GENAI_API_KEY, assets/brief.md, CHAT_STREAMING=false npx melchizedek-chat --syndicate scribe, npm run syndicate:scribe, You › , Scribe › , exit
|
|
37
37
|
LIMITS: the run command and the extraction one-liner go in fenced bash blocks exactly as given. Body under 140 lines.
|
|
@@ -6,7 +6,7 @@ STRUCTURE (exact):
|
|
|
6
6
|
Frontmatter, verbatim:
|
|
7
7
|
---
|
|
8
8
|
name: melchizedek-serve
|
|
9
|
-
description: Expose a Melchizedek syndicate over HTTP with the A2A server, call it from code, give a subagent tools from an MCP server, or serve your own tools as an MCP server. Use when the user mentions melchizedek-serve, A2A, an agent card, mcp_server_url, or wants a syndicate reachable from another application or agent.
|
|
9
|
+
description: "Expose a Melchizedek syndicate over HTTP with the A2A server, call it from code, give a subagent tools from an MCP server, or serve your own tools as an MCP server. Use when the user mentions melchizedek-serve, A2A, an agent card, mcp_server_url, or wants a syndicate reachable from another application or agent."
|
|
10
10
|
---
|
|
11
11
|
Then `##` sections in this order: "Serve a syndicate over A2A", "Secure it", "Call it", "Per-agent routes", "Give a subagent MCP tools", "Serve your own tools over MCP".
|
|
12
12
|
|
|
@@ -6,7 +6,7 @@ STRUCTURE (exact):
|
|
|
6
6
|
Frontmatter, verbatim:
|
|
7
7
|
---
|
|
8
8
|
name: melchizedek
|
|
9
|
-
description: Find and run a Melchizedek syndicate (an agent team defined in one YAML file) from the terminal or from inside a coding agent. Use when the user mentions melchizedek, melchizedek-agents, a syndicate, the starter pack, or asks to run, list, or delegate a task to one of these agents; this is also the entry point to the other melchizedek-* skills.
|
|
9
|
+
description: "Find and run a Melchizedek syndicate (an agent team defined in one YAML file) from the terminal or from inside a coding agent. Use when the user mentions melchizedek, melchizedek-agents, a syndicate, the starter pack, or asks to run, list, or delegate a task to one of these agents; this is also the entry point to the other melchizedek-* skills."
|
|
10
10
|
---
|
|
11
11
|
Then sections with these `##` headings, in this order: "Where the syndicates are", "What each starter-pack syndicate does", "Check the keys before running", "Run one", "Delegate a task from this agent", "When something fails", "The other skills in this suite".
|
|
12
12
|
|
|
@@ -19,6 +19,9 @@ Where the syndicates are:
|
|
|
19
19
|
|
|
20
20
|
The starter pack (file → name → what it does → tier / extra requirement):
|
|
21
21
|
- assistant.yaml → Assistant → the generic starting point: converses, summarizes pasted text or URLs, keeps a task list, and queues background jobs that `npx melchizedek-worker` runs (clone: `npm run assistant:worker`) → keyless (Ollama with qwen3:8b pulled)
|
|
22
|
+
- harness.yaml → Harness → works from a directory of Agent Skills the way a coding harness does: every skill's frontmatter is in its prompt, it reads the matching SKILL.md and the files it names, `/name` forces a skill, a Checker holds the result to the skill's own rules; `--bind skills_dir=<dir>` points it at a project's shelf (default: this package's own skills) → gemini
|
|
23
|
+
- weather.yaml → Weather → an HTTP API as tools from its OpenAPI spec (`openapi:`), no tool code: finds a place and forecasts its weather on Open-Meteo's keyless APIs → gemini
|
|
24
|
+
- pipeline.yaml → Editorial Pipeline → a syndicate as a graph (`workflow:`): a planner routes on a JSON field, a writer and a checker run at once, a join feeds an editor that retries once, the person is asked before a publisher finishes → gemini
|
|
22
25
|
- tutor.yaml → Tutor → one agent that teaches a topic or pasted material by questioning → keyless (Ollama with qwen3:8b pulled)
|
|
23
26
|
- council.yaml → Council → an advocate and a skeptic argue a question and a chair rules → keyless
|
|
24
27
|
- critic.yaml → Critic Review Workflow → a Drafter answers, a Critic scores it as JSON with a confidence field, and the loop repeats until confidence is 85 or higher (three rounds at most) → gemini
|