melchizedek-agents 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/.env.example +19 -2
  2. package/CHANGELOG.md +333 -0
  3. package/DOCUMENTATION.md +99 -39
  4. package/README.md +1 -0
  5. package/config/agents/README.md +2 -2
  6. package/config/agents/examples/ares.yaml +6 -10
  7. package/config/agents/examples/assistant.yaml +6 -6
  8. package/config/agents/examples/augustin.yaml +9 -15
  9. package/config/agents/examples/cartographers.yaml +6 -6
  10. package/config/agents/examples/council.yaml +6 -6
  11. package/config/agents/examples/critic.yaml +16 -16
  12. package/config/agents/examples/harness.yaml +4 -4
  13. package/config/agents/examples/image_production.yaml +33 -32
  14. package/config/agents/examples/librarian.yaml +4 -4
  15. package/config/agents/examples/patient_advocate.yaml +6 -10
  16. package/config/agents/examples/pipeline.yaml +20 -19
  17. package/config/agents/examples/research.yaml +36 -43
  18. package/config/agents/examples/scribe.yaml +31 -32
  19. package/config/agents/examples/scriptorium.yaml +6 -6
  20. package/config/agents/examples/structured_critic.yaml +82 -0
  21. package/config/agents/examples/style_council.yaml +9 -11
  22. package/config/agents/examples/syndicate.yaml +3 -5
  23. package/config/agents/examples/tutor.yaml +2 -2
  24. package/config/agents/examples/weather.yaml +2 -2
  25. package/config/agents/syndicate.schema.json +1979 -0
  26. package/config/agents/syndicateSchema.yaml +109 -72
  27. package/config/agents/templates/README.md +1 -1
  28. package/config/agents/templates/account_memory.yaml +4 -4
  29. package/config/agents/templates/case_desk.yaml +6 -6
  30. package/config/agents/templates/conversational.yaml +2 -2
  31. package/config/agents/templates/draft_review.yaml +6 -6
  32. package/config/agents/templates/intake_extractor.yaml +61 -60
  33. package/config/agents/templates/research_brief.yaml +6 -6
  34. package/config/agents/templates/research_desk.yaml +6 -6
  35. package/config/agents/templates/review_panel.yaml +8 -8
  36. package/config/agents/templates/support_triage.yaml +30 -29
  37. package/config/agents/templates/systems_operator.yaml +21 -5
  38. package/db/migrations/0014_durable_runs.sql +31 -0
  39. package/dist/lib/a2a/app.d.ts +15 -0
  40. package/dist/lib/a2a/app.js +53 -5
  41. package/dist/lib/a2a/executor.d.ts +7 -1
  42. package/dist/lib/a2a/executor.js +34 -4
  43. package/dist/lib/a2a/oauthSetup.d.ts +90 -0
  44. package/dist/lib/a2a/oauthSetup.js +205 -0
  45. package/dist/lib/agentDialect.d.ts +66 -0
  46. package/dist/lib/agentDialect.js +134 -0
  47. package/dist/lib/compile.d.ts +10 -5
  48. package/dist/lib/compile.js +69 -37
  49. package/dist/lib/compileNative.js +2 -0
  50. package/dist/lib/dispatch.d.ts +10 -5
  51. package/dist/lib/dispatch.js +10 -5
  52. package/dist/lib/doctor.d.ts +34 -1
  53. package/dist/lib/doctor.js +47 -3
  54. package/dist/lib/loadSyndicate.d.ts +45 -2
  55. package/dist/lib/loadSyndicate.js +22 -2
  56. package/dist/lib/memory/namespace.d.ts +6 -5
  57. package/dist/lib/memory/namespace.js +6 -5
  58. package/dist/lib/memory/providers.js +3 -4
  59. package/dist/lib/models/capabilities.d.ts +16 -1
  60. package/dist/lib/models/capabilities.js +55 -1
  61. package/dist/lib/observability/tracer.d.ts +8 -1
  62. package/dist/lib/observability/tracer.js +33 -5
  63. package/dist/lib/runtime/approvals.d.ts +6 -0
  64. package/dist/lib/runtime/native/agentLoop.d.ts +18 -1
  65. package/dist/lib/runtime/native/agentLoop.js +105 -11
  66. package/dist/lib/runtime/native/checkpoint.d.ts +164 -0
  67. package/dist/lib/runtime/native/checkpoint.js +365 -0
  68. package/dist/lib/runtime/native/delegate.d.ts +141 -18
  69. package/dist/lib/runtime/native/delegate.js +235 -39
  70. package/dist/lib/runtime/native/interrupts.d.ts +101 -2
  71. package/dist/lib/runtime/native/interrupts.js +201 -1
  72. package/dist/lib/runtime/native/request.d.ts +13 -3
  73. package/dist/lib/runtime/native/request.js +28 -4
  74. package/dist/lib/runtime/native/telemetry.js +10 -15
  75. package/dist/lib/runtime/questions.d.ts +7 -4
  76. package/dist/lib/runtime/sessions.d.ts +1 -1
  77. package/dist/lib/runtime/syndicateTurn.d.ts +7 -1
  78. package/dist/lib/runtime/syndicateTurn.js +74 -22
  79. package/dist/lib/runtime/turnControl.d.ts +15 -0
  80. package/dist/lib/runtime/turnControl.js +10 -0
  81. package/dist/lib/storage/postgres/index.d.ts +6 -1
  82. package/dist/lib/storage/postgres/index.js +7 -2
  83. package/dist/lib/storage/postgres/taskQueue.d.ts +14 -2
  84. package/dist/lib/storage/postgres/taskQueue.js +20 -3
  85. package/dist/lib/storage/postgres/taskStore.d.ts +26 -2
  86. package/dist/lib/storage/postgres/taskStore.js +74 -8
  87. package/dist/lib/syndicateSchema.d.ts +278 -0
  88. package/dist/lib/syndicateSchema.js +182 -15
  89. package/dist/lib/tools/auth.d.ts +7 -5
  90. package/dist/lib/tools/auth.js +12 -4
  91. package/dist/lib/tools/credentialEnv.d.ts +28 -0
  92. package/dist/lib/tools/credentialEnv.js +52 -0
  93. package/dist/lib/tools/credentialStore.js +9 -2
  94. package/dist/lib/tools/mcpToolFactory.d.ts +25 -2
  95. package/dist/lib/tools/mcpToolFactory.js +179 -21
  96. package/dist/lib/tools/oauthHosts.d.ts +71 -0
  97. package/dist/lib/tools/oauthHosts.js +154 -0
  98. package/dist/lib/tools/oauthTools.d.ts +233 -0
  99. package/dist/lib/tools/oauthTools.js +421 -0
  100. package/dist/lib/tools/openapiTools.d.ts +4 -6
  101. package/dist/lib/tools/openapiTools.js +40 -36
  102. package/dist/lib/tools/taskTools.d.ts +30 -5
  103. package/dist/lib/tools/taskTools.js +74 -10
  104. package/dist/lib/workflow.d.ts +8 -4
  105. package/dist/lib/workflow.js +8 -4
  106. package/dist/lib/workflowConfig.d.ts +6 -0
  107. package/dist/scripts/a2a_server.d.ts +36 -0
  108. package/dist/scripts/a2a_server.js +122 -1
  109. package/dist/scripts/assistant_worker.d.ts +10 -0
  110. package/dist/scripts/assistant_worker.js +69 -19
  111. package/dist/scripts/doctor.d.ts +4 -1
  112. package/dist/scripts/doctor.js +5 -2
  113. package/dist/scripts/yaml_codemod.d.ts +52 -0
  114. package/dist/scripts/yaml_codemod.js +570 -0
  115. package/package.json +11 -7
  116. package/skills/briefs/melchizedek-author.md +7 -5
  117. package/skills/briefs/melchizedek-models.md +4 -3
  118. package/skills/briefs/melchizedek.md +1 -0
  119. package/skills/melchizedek/SKILL.md +1 -0
  120. package/skills/melchizedek-author/SKILL.md +7 -4
  121. package/skills/melchizedek-author/assets/minimal.yaml +13 -13
  122. package/skills/melchizedek-models/SKILL.md +6 -3
@@ -21,6 +21,16 @@
21
21
  * instead of abandoning it while it keeps spending. Any syndicate and any
22
22
  * subagent in it can serve as the worker.
23
23
  *
24
+ * A run is durable (ADR 0113): runDurableTurn (lib/runtime/native/
25
+ * checkpoint.ts) checkpoints the run's sessions at every step boundary
26
+ * beside the job (TaskBackend.saveCheckpoint), and a job claimed again
27
+ * after its worker stopped resumes from its last checkpoint instead of
28
+ * starting over. SIGTERM aborts the step in flight and puts the job back
29
+ * in the queue with its checkpoint; the next worker finishes it. A job
30
+ * cancelled while it runs (task_update) loses its claim: the next lease
31
+ * renewal or checkpoint save sees it, and the run is aborted without
32
+ * writing a result over the cancellation.
33
+ *
24
34
  * Usage:
25
35
  * npm run assistant:worker poll every 30 s until Ctrl-C
26
36
  * npm run assistant:worker -- --once drain the queue, then exit (cron)
@@ -34,12 +44,11 @@
34
44
  import { randomUUID } from 'node:crypto';
35
45
  import { loadEnv } from '../lib/loadEnv.js';
36
46
  import { loadSyndicate } from '../lib/loadSyndicate.js';
37
- import { runSyndicateTurn } from '../lib/runtime/syndicateTurn.js';
47
+ import { runDurableTurn } from '../lib/runtime/native/checkpoint.js';
38
48
  import { runtimeSetting } from '../lib/runtime/runtimeFlag.js';
39
49
  import { setLogLevel } from '../lib/runtime/logging.js';
40
- import { InProcessSessionService } from '../lib/runtime/sessions.js';
41
50
  import { PROVIDERS, providerForModel, providerKeyPresent, } from '../lib/models/registry.js';
42
- import { getTaskBackend, setTaskBackend, taskStorePath } from '../lib/tools/taskTools.js';
51
+ import { applyInterrupted, getTaskBackend, setTaskBackend, taskStorePath } from '../lib/tools/taskTools.js';
43
52
  import { postgresStorage } from '../lib/storage/postgres/index.js';
44
53
  import { dbSchema } from '../lib/storage/schema.js';
45
54
  import { hostname } from 'node:os';
@@ -105,24 +114,43 @@ const workerConfig = !sub
105
114
  };
106
115
  /** The job in hand, so a signal can cancel it. */
107
116
  let current;
108
- // ── One job = one fresh single-turn session ─────────────────────────────────
117
+ /** Set by SIGTERM: the job in hand goes back to the queue with its checkpoint. */
118
+ let interrupted = false;
119
+ /** The run lost its claim on the job (cancelled while it ran, or taken by another worker). */
120
+ class ClaimLost extends Error {
121
+ }
122
+ /** SIGTERM stopped the run; the job is re-queued, not failed. */
123
+ class Interrupted extends Error {
124
+ }
125
+ // ── One job = one durable single-turn run (ADR 0113) ────────────────────────
109
126
  async function runJob(job) {
110
- current = new AbortController();
127
+ const controller = new AbortController();
128
+ current = controller;
111
129
  try {
112
- const result = await runSyndicateTurn({
130
+ // A fresh in-process store per attempt, restored from the job's last
131
+ // checkpoint when an earlier attempt left one (ADR 0080, ADR 0113).
132
+ const result = await runDurableTurn({
113
133
  config: workerConfig,
114
134
  parts: [{ text: job.instruction ?? job.title }],
115
135
  appName: 'assistant-worker',
116
136
  userId: 'local-user',
117
- sessionId: randomUUID(),
118
- // A fresh in-process store per job, as the turn runner's signature
119
- // names it (ADR 0080). The runtime follows MELCHIZEDEK_RUNTIME.
120
- sessionService: new InProcessSessionService(),
137
+ runId: `${job.owner}:${job.id}`,
138
+ checkpoints: {
139
+ load: async () => ((await backend.loadCheckpoint?.(job)) ?? null),
140
+ save: async (checkpoint) => (backend.saveCheckpoint ? backend.saveCheckpoint(lease, job, checkpoint) : true),
141
+ },
142
+ onSaveError: (e) => log(`checkpoint not saved: ${e instanceof Error ? e.message : String(e)}`),
121
143
  compile: { log, onUnknownTool: (n) => log(`unknown tool '${n}' skipped`) },
122
- signal: current.signal,
144
+ signal: controller.signal,
123
145
  deadlineMs: JOB_TIMEOUT_MS,
124
146
  events: { warn: (m) => log(`⚠ ${m}`) },
125
147
  });
148
+ if (result.resumedFromStep > 0)
149
+ log(`${job.id} resumed from step ${result.resumedFromStep}`);
150
+ if (result.lostClaim || controller.signal.reason instanceof ClaimLost)
151
+ throw new ClaimLost('the job was cancelled or claimed elsewhere');
152
+ if (interrupted)
153
+ throw new Interrupted('stopped by SIGTERM');
126
154
  if (result.status !== 'completed') {
127
155
  throw new Error(`${result.error?.code ?? 'ERROR'}: ${result.error?.message ?? ''}`.trim());
128
156
  }
@@ -142,10 +170,17 @@ async function drain() {
142
170
  const started = Date.now();
143
171
  const label = job.owner ? `${job.id} (${job.owner})` : job.id;
144
172
  log(`${label} started: ${job.title}`);
145
- // Renew the lease while the job runs, so no other worker takes it back.
173
+ // Renew the lease while the job runs, so no other worker takes it back;
174
+ // a renewal that finds the claim gone (the job was cancelled) stops the run.
146
175
  const held = job;
147
176
  const heartbeat = setInterval(() => {
148
- backend.renew(lease, held).catch((e) => log(`lease renewal failed: ${e instanceof Error ? e.message : e}`));
177
+ backend
178
+ .renew(lease, held)
179
+ .then((kept) => {
180
+ if (kept === false)
181
+ current?.abort(new ClaimLost('claim lost'));
182
+ })
183
+ .catch((e) => log(`lease renewal failed: ${e instanceof Error ? e.message : e}`));
149
184
  }, Math.floor(lease.leaseMs / 3));
150
185
  try {
151
186
  const result = await runJob(job);
@@ -153,8 +188,22 @@ async function drain() {
153
188
  log(`${label} done in ${Math.round((Date.now() - started) / 1000)} s`);
154
189
  }
155
190
  catch (error) {
156
- await backend.finish(job, { error: String(error?.message ?? error) });
157
- log(`${label} failed: ${error?.message ?? error}`);
191
+ if (error instanceof ClaimLost) {
192
+ // Cancelled while it ran: the record already says so; nothing is written over it.
193
+ log(`${label} stopped: ${error.message}`);
194
+ }
195
+ else if (error instanceof Interrupted) {
196
+ // Back to the queue with its checkpoint, or failed after MAX_ATTEMPTS interruptions.
197
+ const outcome = await backend.mutate(job.owner, (store) => {
198
+ const t = store.tasks.find((r) => r.id === held.id);
199
+ return t && t.status === 'running' ? applyInterrupted(t) : 'left as is';
200
+ });
201
+ log(`${label} interrupted: ${outcome}`);
202
+ }
203
+ else {
204
+ await backend.finish(job, { error: String(error?.message ?? error) });
205
+ log(`${label} failed: ${error?.message ?? error}`);
206
+ }
158
207
  }
159
208
  finally {
160
209
  clearInterval(heartbeat);
@@ -183,12 +232,13 @@ process.on('SIGINT', () => {
183
232
  stopping = true;
184
233
  log('stopping after the current job (Ctrl-C again to quit now)');
185
234
  });
186
- // A container stop: cancel the job in hand (it is recorded as failed and the
187
- // store stays consistent), then exit.
235
+ // A container stop: abort the step in flight and put the job back in the
236
+ // queue with its checkpoint, so the next worker resumes it (ADR 0113).
188
237
  process.on('SIGTERM', () => {
189
238
  stopping = true;
190
- log('SIGTERM — canceling the current job');
191
- current?.abort();
239
+ interrupted = true;
240
+ log('SIGTERM — stopping the current job; it resumes from its last checkpoint');
241
+ current?.abort(new Interrupted('SIGTERM'));
192
242
  });
193
243
  while (!stopping) {
194
244
  await drain();
@@ -8,7 +8,10 @@
8
8
  * gateway stand-in, or local), which declared server-side tools it keeps or
9
9
  * drops, and one verdict per syndicate. Says first which runtime a turn runs
10
10
  * on (native; a leftover MELCHIZEDEK_RUNTIME=adk is a problem, since 1.0.0
11
- * removed that runtime). Closes with the env vars that would
11
+ * removed that runtime). Lists the tools that need an OAuth grant (an
12
+ * `auth: { oauth2 }` or `mcp_auth`, ADR 0112): each user's consent, or the
13
+ * server's own client credentials and whether their variables are set
14
+ * (names only). Closes with the env vars that would
12
15
  * unlock the most, where to get each, and the first command to try.
13
16
  *
14
17
  * Read-only: never edits .env, never sends a request, never prints a key
@@ -8,7 +8,10 @@
8
8
  * gateway stand-in, or local), which declared server-side tools it keeps or
9
9
  * drops, and one verdict per syndicate. Says first which runtime a turn runs
10
10
  * on (native; a leftover MELCHIZEDEK_RUNTIME=adk is a problem, since 1.0.0
11
- * removed that runtime). Closes with the env vars that would
11
+ * removed that runtime). Lists the tools that need an OAuth grant (an
12
+ * `auth: { oauth2 }` or `mcp_auth`, ADR 0112): each user's consent, or the
13
+ * server's own client credentials and whether their variables are set
14
+ * (names only). Closes with the env vars that would
12
15
  * unlock the most, where to get each, and the first command to try.
13
16
  *
14
17
  * Read-only: never edits .env, never sends a request, never prints a key
@@ -88,5 +91,5 @@ if (json) {
88
91
  else {
89
92
  console.log(renderDoctor(result, { color: !noColor }));
90
93
  }
91
- if (check && (result.counts.blocked > 0 || result.runtime.problem))
94
+ if (check && (result.counts.blocked > 0 || result.runtime.problem || result.oauth?.problems.length))
92
95
  process.exit(1);
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * scripts/yaml_codemod.ts — the `melchizedek-codemod` bin and `npm run yaml:codemod`.
4
+ *
5
+ * Rewrites an agent's v1 spellings into the v2 keys (ADR 0115):
6
+ *
7
+ * npm run yaml:codemod -- config/agents/my_syndicate.yaml # rewrite in place
8
+ * npm run yaml:codemod -- config/agents # every *.yaml below it
9
+ * npm run yaml:codemod -- --check config/agents # exit 1 if any file would change
10
+ *
11
+ * Per agent (the orchestrator and every inline subagent):
12
+ *
13
+ * outputSchema → output.schema
14
+ * generateContentConfig.responseMimeType → output.mime (application/json | text/plain)
15
+ * generateContentConfig.temperature → sampling.temperature
16
+ * generateContentConfig.topP → sampling.top_p
17
+ * generateContentConfig.maxOutputTokens → sampling.max_output_tokens
18
+ * generateContentConfig.stopSequences → sampling.stop
19
+ * generateContentConfig.thinkingConfig / reasoningEffort → reasoning:, ONLY
20
+ * when the engine reads the same ReasoningSetting before and after for
21
+ * the agent's model (reasoningOf over the old config equals reasoningOf
22
+ * over what reasoningConfig makes of the new key).
23
+ * generateContentConfig.thinkingConfig.includeThoughts: false → dropped (the default; read by nothing)
24
+ *
25
+ * Anything else stays under generateContentConfig with a note naming its key
26
+ * path (never its value); an emptied generateContentConfig is removed. An
27
+ * agent that already has the v2 key for a concept is left alone for that
28
+ * concept, with a note: the loader refuses an overlap.
29
+ *
30
+ * HOW: the file is parsed with the `yaml` package's Document API, which
31
+ * locates every key and value in the source. The edit splices whole lines:
32
+ * a moved key travels with its own lines (its leading and inline comments,
33
+ * its indentation and quoting); the comments of a key that is converted or
34
+ * dropped are kept as comment lines where the new key goes. Every line the
35
+ * codemod does not move is left byte for byte. The result is parsed again and
36
+ * must read back as the planned data, or the file is refused. Running it
37
+ * twice changes nothing the second time.
38
+ */
39
+ export interface MigrateResult {
40
+ /** The migrated text; the input itself when nothing changed. */
41
+ text: string;
42
+ changed: boolean;
43
+ /** One line per key left in a v1 spelling, naming its key path (never a value). */
44
+ notes: string[];
45
+ }
46
+ /**
47
+ * Rewrites one YAML text's v1 agent keys into v2 (see the header). Throws on
48
+ * text that is not one YAML document, and on an edit that would not read
49
+ * back as planned (a bug, never a silent change).
50
+ */
51
+ export declare function migrateYaml(text: string): MigrateResult;
52
+ export declare function main(argv: string[]): number;