@foldrun/core 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +239 -0
  3. package/dist/index.d.ts +32 -0
  4. package/dist/index.js +43 -0
  5. package/dist/src/agent-tools.d.ts +31 -0
  6. package/dist/src/agent-tools.js +99 -0
  7. package/dist/src/agents-md.d.ts +9 -0
  8. package/dist/src/agents-md.js +83 -0
  9. package/dist/src/api-tools.d.ts +38 -0
  10. package/dist/src/api-tools.js +313 -0
  11. package/dist/src/approvals.d.ts +39 -0
  12. package/dist/src/approvals.js +103 -0
  13. package/dist/src/arrange.d.ts +8 -0
  14. package/dist/src/arrange.js +40 -0
  15. package/dist/src/catalog.d.ts +108 -0
  16. package/dist/src/catalog.js +255 -0
  17. package/dist/src/completions.d.ts +61 -0
  18. package/dist/src/completions.js +578 -0
  19. package/dist/src/confine.d.ts +31 -0
  20. package/dist/src/confine.js +298 -0
  21. package/dist/src/container.d.ts +30 -0
  22. package/dist/src/container.js +172 -0
  23. package/dist/src/context-tools.d.ts +81 -0
  24. package/dist/src/context-tools.js +347 -0
  25. package/dist/src/deploy.d.ts +100 -0
  26. package/dist/src/deploy.js +377 -0
  27. package/dist/src/egress.d.ts +75 -0
  28. package/dist/src/egress.js +133 -0
  29. package/dist/src/evals.d.ts +150 -0
  30. package/dist/src/evals.js +651 -0
  31. package/dist/src/flow-lint.d.ts +16 -0
  32. package/dist/src/flow-lint.js +177 -0
  33. package/dist/src/git-hook.d.ts +1 -0
  34. package/dist/src/git-hook.js +70 -0
  35. package/dist/src/git.d.ts +37 -0
  36. package/dist/src/git.js +101 -0
  37. package/dist/src/gitrepo.d.ts +95 -0
  38. package/dist/src/gitrepo.js +355 -0
  39. package/dist/src/history.d.ts +82 -0
  40. package/dist/src/history.js +259 -0
  41. package/dist/src/kinds.d.ts +95 -0
  42. package/dist/src/kinds.js +421 -0
  43. package/dist/src/library.d.ts +94 -0
  44. package/dist/src/library.js +341 -0
  45. package/dist/src/linkable.d.ts +20 -0
  46. package/dist/src/linkable.js +113 -0
  47. package/dist/src/model-probe.d.ts +21 -0
  48. package/dist/src/model-probe.js +81 -0
  49. package/dist/src/notify.d.ts +61 -0
  50. package/dist/src/notify.js +252 -0
  51. package/dist/src/oauth-presets.d.ts +13 -0
  52. package/dist/src/oauth-presets.js +39 -0
  53. package/dist/src/okf.d.ts +220 -0
  54. package/dist/src/okf.js +656 -0
  55. package/dist/src/openapi.d.ts +66 -0
  56. package/dist/src/openapi.js +342 -0
  57. package/dist/src/paths.d.ts +33 -0
  58. package/dist/src/paths.js +78 -0
  59. package/dist/src/platform.d.ts +32 -0
  60. package/dist/src/platform.js +48 -0
  61. package/dist/src/providers.d.ts +47 -0
  62. package/dist/src/providers.js +114 -0
  63. package/dist/src/refs.d.ts +20 -0
  64. package/dist/src/refs.js +78 -0
  65. package/dist/src/run-container.d.ts +227 -0
  66. package/dist/src/run-container.js +760 -0
  67. package/dist/src/runner.d.ts +241 -0
  68. package/dist/src/runner.js +3215 -0
  69. package/dist/src/runtime.d.ts +41 -0
  70. package/dist/src/runtime.js +357 -0
  71. package/dist/src/script-tools.d.ts +62 -0
  72. package/dist/src/script-tools.js +296 -0
  73. package/dist/src/secret-files.d.ts +12 -0
  74. package/dist/src/secret-files.js +92 -0
  75. package/dist/src/secrets.d.ts +147 -0
  76. package/dist/src/secrets.js +626 -0
  77. package/dist/src/starter.d.ts +21 -0
  78. package/dist/src/starter.js +399 -0
  79. package/dist/src/step-exec.d.ts +136 -0
  80. package/dist/src/step-exec.js +391 -0
  81. package/dist/src/storage.d.ts +123 -0
  82. package/dist/src/storage.js +640 -0
  83. package/dist/src/store.d.ts +1052 -0
  84. package/dist/src/store.js +2378 -0
  85. package/dist/src/tar.d.ts +30 -0
  86. package/dist/src/tar.js +111 -0
  87. package/dist/src/tool-names.d.ts +39 -0
  88. package/dist/src/tool-names.js +79 -0
  89. package/dist/src/tool-programs.d.ts +18 -0
  90. package/dist/src/tool-programs.js +52 -0
  91. package/dist/src/tool-test.d.ts +19 -0
  92. package/dist/src/tool-test.js +335 -0
  93. package/dist/src/translator.d.ts +159 -0
  94. package/dist/src/translator.js +986 -0
  95. package/dist/src/triggers.d.ts +60 -0
  96. package/dist/src/triggers.js +270 -0
  97. package/dist/src/webhook.d.ts +47 -0
  98. package/dist/src/webhook.js +148 -0
  99. package/index.ts +44 -0
  100. package/package.json +64 -0
  101. package/src/agent-tools.ts +138 -0
  102. package/src/agents-md.ts +81 -0
  103. package/src/api-tools.ts +389 -0
  104. package/src/approvals.ts +129 -0
  105. package/src/arrange.ts +42 -0
  106. package/src/catalog.ts +321 -0
  107. package/src/completions.ts +652 -0
  108. package/src/confine.ts +350 -0
  109. package/src/container.ts +220 -0
  110. package/src/context-tools.ts +435 -0
  111. package/src/deploy.ts +458 -0
  112. package/src/egress.ts +176 -0
  113. package/src/evals.ts +812 -0
  114. package/src/flow-lint.ts +215 -0
  115. package/src/git-hook.ts +73 -0
  116. package/src/git.ts +117 -0
  117. package/src/gitrepo.ts +391 -0
  118. package/src/history.ts +329 -0
  119. package/src/kinds.ts +507 -0
  120. package/src/library.ts +419 -0
  121. package/src/linkable.ts +123 -0
  122. package/src/model-probe.ts +112 -0
  123. package/src/notify.ts +281 -0
  124. package/src/oauth-presets.ts +54 -0
  125. package/src/okf.ts +768 -0
  126. package/src/openapi.ts +383 -0
  127. package/src/paths.ts +79 -0
  128. package/src/platform.ts +87 -0
  129. package/src/providers.ts +147 -0
  130. package/src/refs.ts +83 -0
  131. package/src/run-container.ts +883 -0
  132. package/src/runner.ts +3446 -0
  133. package/src/runtime.ts +404 -0
  134. package/src/script-tools.ts +383 -0
  135. package/src/secret-files.ts +101 -0
  136. package/src/secrets.ts +799 -0
  137. package/src/starter.ts +415 -0
  138. package/src/step-exec.ts +498 -0
  139. package/src/storage.ts +771 -0
  140. package/src/store.ts +2985 -0
  141. package/src/tar.ts +124 -0
  142. package/src/tool-names.ts +91 -0
  143. package/src/tool-programs.ts +65 -0
  144. package/src/tool-test.ts +401 -0
  145. package/src/translator.ts +1029 -0
  146. package/src/triggers.ts +287 -0
  147. package/src/webhook.ts +183 -0
  148. package/templates/blog-desk/AGENTS.md +13 -0
  149. package/templates/blog-desk/agents/researcher/agent.md +11 -0
  150. package/templates/blog-desk/agents/writer/agent.md +8 -0
  151. package/templates/blog-desk/evals/writer-quality.md +10 -0
  152. package/templates/blog-desk/flows/publish.md +10 -0
  153. package/templates/blog-desk/knowledge/house-style.md +9 -0
  154. package/templates/blog-desk/knowledge/index.md +9 -0
  155. package/templates/hello/AGENTS.md +18 -0
  156. package/templates/hello/agents/curator/agent.md +32 -0
  157. package/templates/hello/agents/notetaker/agent.md +24 -0
  158. package/templates/hello/evals/house-style.md +16 -0
  159. package/templates/hello/flows/memory-review.md +7 -0
  160. package/templates/hello/flows/note.md +7 -0
  161. package/templates/hello/knowledge/house-style.md +15 -0
  162. package/templates/hello/knowledge/index.md +9 -0
  163. package/templates/hello/skills/plain-english/SKILL.md +11 -0
  164. package/templates/hello/state/publishing.md +7 -0
  165. package/templates/hello/tools/wordcount/run.py +13 -0
  166. package/templates/hello/tools/wordcount/tool.md +22 -0
@@ -0,0 +1,399 @@
1
+ // The starter workspace, defined once.
2
+ //
3
+ // Two things used to build one: `foldrun init` had its own copy and the
4
+ // dashboard's "new workspace" button had another. They drifted exactly as you
5
+ // would expect — different flow names, one with evals and knowledge and one
6
+ // without — and when structural documents moved from `type:` to `kind:`, one
7
+ // copy was migrated and the other silently kept writing the old spelling.
8
+ //
9
+ // This is the same failure the consistency tests exist to catch: a list in two
10
+ // places, one updated, nothing erroring. So there is one definition here and
11
+ // both callers read it.
12
+ //
13
+ // Not to be confused with KINDS[kind].template(), which makes *one* document
14
+ // of a kind with a placeholder name — that is what the New button uses inside
15
+ // an existing workspace. This is the set of files a brand-new workspace starts
16
+ // with, and they reference each other on purpose: the flow names the agents,
17
+ // the eval names the writer, so `foldrun check` passes and `foldrun run
18
+ // publish` works before anything has been edited.
19
+ /**
20
+ * The account root's own files — one scope up from any workspace.
21
+ *
22
+ * The account AGENTS.md was readable from the day `workspaceFrontmatter` and
23
+ * `sharedInstructions` learned to look for it, and nothing ever wrote one. So
24
+ * the outer half of "nearest-wins" existed only for people who had read the
25
+ * spec closely enough to know the file could exist at all, which is the same
26
+ * failure as prose that reached no model: a feature nobody is told about.
27
+ *
28
+ * Deliberately near-empty. This file's frontmatter governs *every* workspace
29
+ * under it, so the scaffold ships the shape and a commented example rather
30
+ * than a live `provider:` — a starter that silently routed every future
31
+ * workspace somewhere would be worse than no starter.
32
+ */
33
+ export function accountFiles(account) {
34
+ return [
35
+ {
36
+ path: "AGENTS.md",
37
+ content: `---
38
+ foldrun_version: "0.1"
39
+ # Config here applies to every workspace under this account, and a workspace
40
+ # that declares the same key replaces it *whole* — base_url and token never
41
+ # merge across scopes, so a provider block belongs entirely at one level.
42
+ #
43
+ # provider:
44
+ # base_url: https://openrouter.ai/api
45
+ # token: \${OPENROUTER_API_KEY}
46
+ # models: # what this gateway calls each tier
47
+ # fast: google/gemini-2.5-flash
48
+ # max: anthropic/claude-opus-4.1
49
+ # headers: # anything else the gateway wants
50
+ # X-Title: foldrun
51
+ #
52
+ # The token is sent as a bearer credential. A gateway that wants it as a key
53
+ # header asks for that by name: headers: { x-api-key: \${THE_SECRET} }.
54
+ ---
55
+
56
+ # ${account}
57
+
58
+ Context every agent in every workspace here shares.
59
+
60
+ Unlike the frontmatter above, this prose **accumulates**: a workspace's
61
+ AGENTS.md is added to it, not swapped for it, so a rule written here cannot be
62
+ dropped further down.
63
+ `,
64
+ },
65
+ ];
66
+ }
67
+ export function starterFiles(workspace) {
68
+ return [
69
+ {
70
+ // AGENTS.md, not project.md — the file was renamed to the Linux
71
+ // Foundation standard and this scaffold was missed once already, so
72
+ // every workspace made from the dashboard started with a file nothing
73
+ // reads.
74
+ path: "AGENTS.md",
75
+ content: `---
76
+ name: ${workspace}
77
+ description: A starter workspace — edit the agents, then run the flow.
78
+ foldrun_version: "0.1"
79
+ ---
80
+
81
+ # ${workspace}
82
+
83
+ Context every agent here shares. Prices, rules, anything they should all know.
84
+ `,
85
+ },
86
+ {
87
+ // A workspace is meant to be a git repository — that is the whole pitch,
88
+ // that you can diff and review what an agent is. So it has to arrive
89
+ // knowing which of its own files must never be committed. The decisive
90
+ // one is `.foldrun/.secret-key`: the CLI writes the key that decrypts
91
+ // every secret *inside the workspace*, and without this file the first
92
+ // `git add -A` after setting a secret commits it.
93
+ path: ".gitignore",
94
+ content: `# The key that decrypts every secret in this workspace, plus the
95
+ # local run store. Never commit these.
96
+ .foldrun/
97
+
98
+ # Written by runs, not by you.
99
+ runs/
100
+ outputs/
101
+
102
+ .env
103
+ .env.local
104
+ .DS_Store
105
+ `,
106
+ },
107
+ {
108
+ path: "agents/researcher/agent.md",
109
+ content: `---
110
+ name: researcher
111
+ description: Finds one thing worth writing about, and says why.
112
+ model: fast
113
+ effort: high
114
+ tools:
115
+ - web
116
+ - read
117
+ ---
118
+
119
+ Pick exactly one topic, in a short paragraph, and say who it helps and why now.
120
+ Do not write the article.
121
+ `,
122
+ },
123
+ {
124
+ path: "agents/writer/agent.md",
125
+ content: `---
126
+ name: writer
127
+ description: Turns a brief into a short draft.
128
+ tools:
129
+ - files
130
+ ---
131
+
132
+ Write a short draft from the brief you were given. Save it to outputs/draft.md.
133
+ `,
134
+ },
135
+ {
136
+ path: "flows/publish.md",
137
+ content: `---
138
+ name: publish
139
+ trigger: manual
140
+ ---
141
+
142
+ Read the numbers, not the order of the lines.
143
+ Same number = at the same time. Different number = one after the other.
144
+
145
+ 1. [[researcher]] — find one topic worth writing about
146
+ 2. [[writer]] — draft it
147
+ `,
148
+ },
149
+ {
150
+ // knowledge/ is an OKF bundle, so this one declares itself in `type:`.
151
+ path: "knowledge/house-style.md",
152
+ content: `---
153
+ type: Reference
154
+ title: How we write
155
+ description: How we write. Agents read this; they never rewrite it.
156
+ status: stable
157
+ ---
158
+
159
+ - Short sentences. One idea each.
160
+ - Say the number, not "significant growth".
161
+ `,
162
+ },
163
+ {
164
+ // The other half of OKF, and the half the starter never showed. Same
165
+ // format as knowledge/, different write permission: an agent may add
166
+ // here, and when it does the runtime stamps `generated:` so a fact it
167
+ // worked out never looks like one you gave it.
168
+ path: "memory/what-worked.md",
169
+ content: `---
170
+ type: Fact
171
+ title: What worked last time
172
+ description: Something learned from a run. Agents add files here themselves.
173
+ status: stable
174
+ # generated: is stamped automatically when an agent writes a memory.
175
+ # Add verified: with a \`human:\` actor once you have checked one yourself —
176
+ # that is what moves it from unverified to human-reviewed in the index.
177
+ ---
178
+
179
+ Short drafts got edited less than long ones. Prefer four paragraphs to eight.
180
+ `,
181
+ },
182
+ {
183
+ // For the CODING TOOL the author is using, not for the platform — this is
184
+ // the one file here that foldrun itself never reads. Claude Code and its
185
+ // peers look for AGENTS.md/CLAUDE.md at the root of whatever they open,
186
+ // and without one they infer conventions from the tree, which for a
187
+ // workspace of loosely-structured markdown means guessing frontmatter
188
+ // fields that do not exist and putting agents in the wrong place.
189
+ //
190
+ // The platform already imports .claude/agents/*.md as agents, so a
191
+ // subagent authored in Claude Code deploys as a foldrun agent with no
192
+ // conversion. That path is much more useful when the tool knows it
193
+ // exists, which is most of what this file is for.
194
+ path: "CLAUDE.md",
195
+ content: `# Working on this foldrun workspace
196
+
197
+ This is a foldrun workspace: agents, flows and knowledge as markdown. There is
198
+ no build — the files ARE the program — so the loop is edit, check, run, deploy.
199
+
200
+ ## The loop
201
+
202
+ \`\`\`sh
203
+ foldrun check # validate everything, offline, no model calls
204
+ foldrun run <flow> # run it locally against real models
205
+ foldrun eval # run the evals
206
+ foldrun deploy --url <server> --token $FOLDRUN_TOKEN
207
+ \`\`\`
208
+
209
+ \`check\` is cheap and catches a broken flow before a schedule fires it at 3am.
210
+ Run it after edits, the way you would run a typecheck.
211
+
212
+ ## Where things go
213
+
214
+ | Path | What |
215
+ |---|---|
216
+ | \`AGENTS.md\` | shared context every agent in this workspace sees |
217
+ | \`agents/<name>/agent.md\` | one role: its persona, model, tools |
218
+ | \`flows/<name>.md\` | steps, in order, naming the agents that run them |
219
+ | \`knowledge/\` | reference material agents read |
220
+ | \`memory/\` | what past runs learned — agents write here themselves |
221
+ | \`evals/\` | tests for agents, same idea as unit tests |
222
+ | \`skills/\` | a procedure an agent can follow, named in \`skills:\` |
223
+ | \`tools/\` | a folder tool: its definition and its code together |
224
+ | \`scripts/\` | code the agents call, referenced by \`scripts:\` |
225
+ | \`state/\` | what runs accumulate. Yours to read, never deployed over |
226
+ | \`.claude/agents/<name>.md\` | a Claude Code subagent — deploys AS an agent |
227
+
228
+ \`knowledge/\` is what you tell the agents; \`memory/\` is what they worked out.
229
+ Regenerated every run means a file; accumulated across runs means \`state/\`.
230
+
231
+ That last row is worth knowing: a subagent you write in Claude Code is imported
232
+ as \`agents/<name>/agent.md\` at deploy, no conversion. Write it either way.
233
+
234
+ ## What a deploy does NOT touch
235
+
236
+ \`runs/\`, \`state/\`, \`secrets.json\`, and any memory an agent wrote that your
237
+ push does not mention. Those belong to the platform, not to git — a deploy that
238
+ reverted what an agent learned would make every run a little dumber.
239
+
240
+ Secrets never go in these files. \`foldrun secrets\` puts them in the vault, and
241
+ agents reference them by name.
242
+
243
+ ## Talking to a deployed workspace
244
+
245
+ The CLI is local except for \`deploy\`. To inspect or drive a workspace running
246
+ on a server, use the HTTP API — every route takes the same API key
247
+ (\`Authorization: Bearer $FOLDRUN_TOKEN\`, from Settings → API keys) or a browser
248
+ session, and refuses without one.
249
+
250
+ \`\`\`sh
251
+ export FOLDRUN_TOKEN=... FOLDRUN_URL=https://your-server WS=my-workspace
252
+ api() { p=$1; shift; curl -sS -H "authorization: Bearer $FOLDRUN_TOKEN" "$FOLDRUN_URL/api/$p" "$@"; }
253
+
254
+ api workspaces/$WS/runs # recent runs, newest first
255
+ api workspaces/$WS/runs/<id> # the full trail of one
256
+ api workspaces/$WS/runs/<id>/stream # follow a live one (SSE)
257
+ api workspaces/$WS/flows/<flow>/run -X POST # start one
258
+ \`\`\`
259
+
260
+ All of it, \`/api/workspaces/<workspace>\` unless noted:
261
+
262
+ | Route | Methods | For |
263
+ |---|---|---|
264
+ | \`/\` | GET DELETE PATCH | the workspace itself |
265
+ | \`/deploy\` | POST | what \`foldrun deploy\` calls |
266
+ | \`/source\` | GET PUT PATCH DELETE | read and write single files |
267
+ | \`/runs\` | GET | list runs |
268
+ | \`/runs/<id>\` | GET DELETE | one run, every step |
269
+ | \`/runs/<id>/stream\` | GET | live events, server-sent |
270
+ | \`/runs/<id>/stop\` | POST | kill it — destroys the sandbox too |
271
+ | \`/runs/<id>/rerun\` | POST | again, or from a chosen step |
272
+ | \`/runs/<id>/approve\` | POST | release a run waiting on a human |
273
+ | \`/flows\` · \`/flows/<f>\` | GET POST · POST DELETE PATCH | list, create, edit |
274
+ | \`/flows/<f>/run\` | POST | start a flow |
275
+ | \`/agents\` · \`/agents/<a>/run\` | GET POST · POST | list agents, run one |
276
+ | \`/evals\` · \`/evals/<e>/run\` | GET POST · POST | list evals, run one |
277
+ | \`/tools/<t>/test\` | POST | exercise one tool alone |
278
+ | \`/storage\` · \`/storage/download\` · \`/storage/upload-url\` | GET POST PUT DELETE · GET · POST | workspace files |
279
+ | \`/assets\` | POST | upload an asset |
280
+ | \`/hooks/<f>/rotate\` | POST | new webhook token for a flow |
281
+ | \`/vocabulary\` | GET | what this workspace's documents may say |
282
+
283
+ Account-level, outside \`/workspaces\`: \`/api/secrets\`, \`/api/keys\`, \`/api/schedule\`,
284
+ \`/api/approvals\`, \`/api/usage\`, \`/api/library/<kind>\`, \`/api/team\`, \`/api/billing\`,
285
+ \`/api/healthz\` (open) and \`/api/metrics\` (Prometheus).
286
+
287
+ Debugging a failed run is usually: \`runs\` to find it, \`runs/<id>\` to read which
288
+ step failed and what it cost, fix the markdown, \`deploy\`, then \`rerun\`.
289
+
290
+ Every route, with request and response shapes, is in \`docs/api.md\` in the
291
+ foldrun repository — including secrets, keys, billing, OAuth, webhooks and the
292
+ git push endpoint.
293
+
294
+ ## Writing a flow
295
+
296
+ A numbered list of steps. The number is the GROUP: same number means those
297
+ steps run in parallel, and the flow waits for all of them before the next.
298
+
299
+ \`\`\`markdown
300
+ ---
301
+ trigger: manual # manual | schedule | webhook
302
+ schedule: "0 9 * * 1" # cron, when trigger: schedule
303
+ timezone: Australia/Sydney
304
+ model: default # a default for every step here
305
+ effort: low
306
+ overlap: skip # skip | queue — what a second run does while one is live
307
+ ---
308
+
309
+ 1. [[researcher]] — Find three sources on {{topic}}.
310
+ 2. [[writer]] — Draft from what the researcher found.
311
+ model: max
312
+ verify: the draft cites every source
313
+ 3?. [[editor]] — Tighten it.
314
+ 4!. [[publisher]] — Publish it.
315
+ \`\`\`
316
+
317
+ \`3?.\` is optional — it may be skipped. \`4!.\` needs a human to approve before it
318
+ runs. \`[[flow:other-flow]]\` in place of an agent runs another flow as a step.
319
+
320
+ Indented under a step, any of:
321
+
322
+ | | |
323
+ |---|---|
324
+ | \`approve: true\` | stop for a human (\`!\` is shorthand for this) |
325
+ | \`when: <condition>\` | run only if it holds |
326
+ | \`case: <value>\` / \`else: true\` | branch on the previous step's result |
327
+ | \`retry: 2\` | re-run on failure, up to 5 |
328
+ | \`timeout: 900\` | seconds before the step is cut off |
329
+ | \`verify: <claim>\` | the step fails unless this is true of its output |
330
+ | \`model:\` / \`effort:\` | override the flow's default for this step |
331
+ | \`loop: 3\` / \`until: <condition>\` | repeat until it holds, at most 5 |
332
+ | \`each: lines\` | run once per line of the previous result |
333
+ | \`each: rows of <path>\` | run once per CSV row |
334
+ | \`max: 10\` | cap how many iterations \`each:\` produces |
335
+ | \`on-fail: [[agent]]\` | who handles it when this step fails |
336
+ | \`wait: 30m\` | pause before running — \`90s\`, \`30m\`, \`4h\`, \`3d\` |
337
+ | \`wait: event\` | hold until something POSTs the run's event URL; the body reaches the step |
338
+ | \`ask: <question>\` | ask a human, use the answer |
339
+ | \`preview: draft/*.mdx\` | what the approval box shows — paths under \`storage/\`, globs allowed |
340
+ | \`delegate: [[a]], [[b]]\` | hand the step to whichever fits, up to 5 |
341
+ | \`output: json\` | the reply ends with one JSON value; the next step gets it as data, \`each: items\` fans out over it |
342
+ | \`each: items\` | run once per element of the previous step's JSON array |
343
+
344
+ ## Writing an agent
345
+
346
+ Frontmatter then prose. The prose IS the system prompt, so write it to be read
347
+ by a model: what this role is for, what it must not do, what good looks like.
348
+
349
+ \`\`\`markdown
350
+ ---
351
+ name: researcher
352
+ description: Finds and summarises sources.
353
+ model: default # fast | default | max
354
+ effort: low # how hard to think about it
355
+ size: large # small | large | heavy — the sandbox it rents
356
+ tools: [WebSearch, Read, my-folder-tool] # built-ins and your own tools/, one list
357
+ disallowedTools: [Bash] # subtract from what it would otherwise have
358
+ skills: [house-style] # from skills/
359
+ scripts: [summarise.py] # from scripts/, each becomes a callable tool
360
+ apis: [{ name: crm }] # an HTTP API, as a tool
361
+ mcpServers: {} # an MCP server, as a tool
362
+ agents: [writer] # colleagues this one may consult
363
+ secrets: [CRM_TOKEN] # from the vault — never written in these files
364
+ provider: {} # BYOK: your own model credential
365
+ permissionMode: plan # plan first, act once approved
366
+ runtime: # only if scripts need packages
367
+ packages: [requests]
368
+ ---
369
+
370
+ You research a topic and report what you found...
371
+ \`\`\`
372
+
373
+ Only \`name\` and the prose are required. Every other field widens or narrows
374
+ what the agent can reach, and the default is narrow.
375
+
376
+ \`runtime:\` installs pip/npm packages in the sandbox each agent runs in. Pin
377
+ versions the normal way (\`pandas>=2\`, \`lodash@^4\`). Python and Node are
378
+ supported; anything else should ship as a committed binary.
379
+
380
+ Link documents to each other with \`[[wikilinks]]\` — the name, not the path.
381
+ `,
382
+ },
383
+ {
384
+ path: "evals/writer-quality.md",
385
+ content: `---
386
+ name: writer-quality
387
+ agent: writer
388
+ model: fast
389
+ effort: low
390
+ ---
391
+
392
+ ## writes something
393
+ task: Write two sentences about rain.
394
+ expect:
395
+ - judge: it is two sentences and mentions rain
396
+ `,
397
+ },
398
+ ];
399
+ }
@@ -0,0 +1,136 @@
1
+ import type { McpServerConfig } from "@anthropic-ai/claude-agent-sdk";
2
+ import type { Effort } from "./store.ts";
3
+ export interface ExecOutcome {
4
+ status: "completed" | "failed";
5
+ result: string | null;
6
+ /** The LAST text block, which is the model's answer. `result` is every
7
+ * block joined, and the first of those is usually narration before a tool
8
+ * call — so anything wanting "what did this conclude" must read this. */
9
+ conclusion: string | null;
10
+ /** The JSON an `output: json` step returned — parsed, so the next step
11
+ * gets the value and not a re-extraction of it from prose. Undefined for
12
+ * a step that declared no output shape. */
13
+ data?: unknown;
14
+ costUsd: number | null;
15
+ /** Token counts off the SDK's result message. costUsd is priced from
16
+ * Anthropic's table, which is wrong for a routed model — these are the
17
+ * raw numbers a caller with a gateway's own prices can reprice from. */
18
+ usage: {
19
+ inputTokens: number;
20
+ outputTokens: number;
21
+ } | null;
22
+ }
23
+ export interface ExecOptions {
24
+ agentDir: string;
25
+ workspaceRoot: string;
26
+ libraryRoot: string;
27
+ prompt: string;
28
+ model: string;
29
+ /** How hard the model thinks before answering — orthogonal to which model
30
+ * it is. Null leaves it to the SDK's own default rather than guessing a
31
+ * level on the author's behalf. */
32
+ effort?: Effort | null;
33
+ systemPrompt: string;
34
+ /** Exact SDK tool names the agent may use. */
35
+ allowed: string[];
36
+ /** MCP server names the agent was granted — tools from these pass. */
37
+ mcpNames: string[];
38
+ mcpServers: Record<string, McpServerConfig>;
39
+ /** The child environment: process env + secrets + provider. */
40
+ env: Record<string, string | undefined>;
41
+ timeoutSec?: number;
42
+ /** The most this step may spend, in USD, before it is stopped mid-turn.
43
+ * The runner derives it from the flow's `budget:` and what the run has
44
+ * already spent; absent means no ceiling on this step. */
45
+ budgetUsd?: number | null;
46
+ /** Price per token for the model, when the catalogue knows it, so spend
47
+ * can be counted turn by turn instead of learned at the end. Without it
48
+ * the step prices its turns from the SDK's own running total. */
49
+ price?: {
50
+ input: number;
51
+ output: number;
52
+ } | null;
53
+ /** A check the step must pass to count as done: a shell command that must
54
+ * exit 0, or an eval-style assertion (`contains: x`, `not-contains: x`,
55
+ * `matches: re`, `file: path`, `judge: sentence`) — see checkVerify. */
56
+ verify?: string;
57
+ verifyEnv?: Record<string, string>;
58
+ /** `output: json` — the reply must carry one JSON value; extracting it is
59
+ * part of finishing the step, and failing to is failing the step. */
60
+ output?: "json";
61
+ /** false when the caller is already an isolation boundary (a run
62
+ * container): the SDK's bash sandbox is then redundant and would block
63
+ * declared network use. Default (undefined/true) keeps it on. */
64
+ sandboxBash?: boolean;
65
+ emit: (type: "text" | "tool" | "info" | "error", text: string, extra?: EventExtra) => void;
66
+ }
67
+ /** The pairing fields on a tool event — see RunEvent in store.ts. */
68
+ export type EventExtra = {
69
+ call?: string;
70
+ ms?: number;
71
+ err?: boolean;
72
+ };
73
+ /** Conservative per-token rates for a model the catalogue cannot price —
74
+ * Opus-class — so a ceiling still means something on an unknown gateway. */
75
+ export declare const FALLBACK_PRICE: {
76
+ input: number;
77
+ output: number;
78
+ };
79
+ /**
80
+ * What one assistant turn cost. Cache traffic is counted as input at the
81
+ * input rate, which over-approximates on gateways with cheaper cache reads
82
+ * — on the money side, the honest direction to be wrong in. Pure.
83
+ */
84
+ export declare function priceTurn(u: Record<string, number | undefined>, price: {
85
+ input: number;
86
+ output: number;
87
+ } | null): number;
88
+ /**
89
+ * Each step launched together gets an equal share of what is left of the
90
+ * run's budget: the shares sum to the remainder, so a group cannot end
91
+ * over the cap however many steps it fans out to. No budget, no ceiling.
92
+ */
93
+ export declare function stepCeiling(budgetUsd: number | null | undefined, spentUsd: number, launching: number): number | null;
94
+ export declare function executeStep(opts: ExecOptions): Promise<ExecOutcome>;
95
+ /**
96
+ * The one JSON value a reply carries. A ```json fence wins — it is what the
97
+ * step was asked to write — and the LAST one at that, because a model that
98
+ * shows its working often quotes an earlier draft first. Without a fence,
99
+ * the reply's trailing `{…}` or `[…]` is tried, so a model that answered
100
+ * with bare JSON is not failed for good behaviour.
101
+ */
102
+ export declare function extractJson(result: string | null): {
103
+ ok: true;
104
+ value: unknown;
105
+ } | {
106
+ ok: false;
107
+ reason: string;
108
+ };
109
+ export interface VerifyVerdict {
110
+ ok: boolean;
111
+ /** One line for the trace: "exit 0", "found", "PASS"… */
112
+ headline: string;
113
+ detail: string;
114
+ }
115
+ /**
116
+ * Decide a `verify:`. Two dialects, one key: an eval assertion, when the
117
+ * value starts with one of the eval file's assertion words, or a shell
118
+ * command. The assertion form exists so that a flow and an eval share a
119
+ * vocabulary — "the output must mention the price" is the same sentence in
120
+ * both places — and so that the commonest checks need no shell at all.
121
+ */
122
+ export declare function checkVerify(agentDir: string, verify: string, ctx: {
123
+ env: Record<string, string>;
124
+ result: string | null;
125
+ /** The final turn — what the step concluded. `contains:`, `not-contains:`
126
+ * and `matches:` test this, because it is also what the run's headline
127
+ * is read from: a reporter that narrated between tool calls ("Now let
128
+ * me write the report…") failed `matches: ^BAD\b` with a correct
129
+ * headline while the check read every turn joined. `judge:` still grades
130
+ * the whole result. */
131
+ conclusion?: string | null;
132
+ data?: unknown;
133
+ /** The step's own model environment, for `judge:` — it grades on the
134
+ * fast tier through the same credential the step rode. */
135
+ modelEnv?: Record<string, string | undefined>;
136
+ }): Promise<VerifyVerdict>;