@dogpile/sdk 0.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 (88) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/LICENSE +16 -0
  3. package/README.md +842 -0
  4. package/dist/browser/index.d.ts +8 -0
  5. package/dist/browser/index.d.ts.map +1 -0
  6. package/dist/browser/index.js +4493 -0
  7. package/dist/browser/index.js.map +1 -0
  8. package/dist/index.d.ts +17 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +14 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/providers/openai-compatible.d.ts +44 -0
  13. package/dist/providers/openai-compatible.d.ts.map +1 -0
  14. package/dist/providers/openai-compatible.js +305 -0
  15. package/dist/providers/openai-compatible.js.map +1 -0
  16. package/dist/runtime/broadcast.d.ts +18 -0
  17. package/dist/runtime/broadcast.d.ts.map +1 -0
  18. package/dist/runtime/broadcast.js +335 -0
  19. package/dist/runtime/broadcast.js.map +1 -0
  20. package/dist/runtime/cancellation.d.ts +6 -0
  21. package/dist/runtime/cancellation.d.ts.map +1 -0
  22. package/dist/runtime/cancellation.js +35 -0
  23. package/dist/runtime/cancellation.js.map +1 -0
  24. package/dist/runtime/coordinator.d.ts +18 -0
  25. package/dist/runtime/coordinator.d.ts.map +1 -0
  26. package/dist/runtime/coordinator.js +434 -0
  27. package/dist/runtime/coordinator.js.map +1 -0
  28. package/dist/runtime/decisions.d.ts +5 -0
  29. package/dist/runtime/decisions.d.ts.map +1 -0
  30. package/dist/runtime/decisions.js +31 -0
  31. package/dist/runtime/decisions.js.map +1 -0
  32. package/dist/runtime/defaults.d.ts +63 -0
  33. package/dist/runtime/defaults.d.ts.map +1 -0
  34. package/dist/runtime/defaults.js +426 -0
  35. package/dist/runtime/defaults.js.map +1 -0
  36. package/dist/runtime/engine.d.ts +79 -0
  37. package/dist/runtime/engine.d.ts.map +1 -0
  38. package/dist/runtime/engine.js +723 -0
  39. package/dist/runtime/engine.js.map +1 -0
  40. package/dist/runtime/model.d.ts +14 -0
  41. package/dist/runtime/model.d.ts.map +1 -0
  42. package/dist/runtime/model.js +82 -0
  43. package/dist/runtime/model.js.map +1 -0
  44. package/dist/runtime/sequential.d.ts +18 -0
  45. package/dist/runtime/sequential.d.ts.map +1 -0
  46. package/dist/runtime/sequential.js +277 -0
  47. package/dist/runtime/sequential.js.map +1 -0
  48. package/dist/runtime/shared.d.ts +18 -0
  49. package/dist/runtime/shared.d.ts.map +1 -0
  50. package/dist/runtime/shared.js +288 -0
  51. package/dist/runtime/shared.js.map +1 -0
  52. package/dist/runtime/termination.d.ts +77 -0
  53. package/dist/runtime/termination.d.ts.map +1 -0
  54. package/dist/runtime/termination.js +355 -0
  55. package/dist/runtime/termination.js.map +1 -0
  56. package/dist/runtime/tools.d.ts +314 -0
  57. package/dist/runtime/tools.d.ts.map +1 -0
  58. package/dist/runtime/tools.js +969 -0
  59. package/dist/runtime/tools.js.map +1 -0
  60. package/dist/runtime/validation.d.ts +23 -0
  61. package/dist/runtime/validation.d.ts.map +1 -0
  62. package/dist/runtime/validation.js +656 -0
  63. package/dist/runtime/validation.js.map +1 -0
  64. package/dist/types.d.ts +2434 -0
  65. package/dist/types.d.ts.map +1 -0
  66. package/dist/types.js +81 -0
  67. package/dist/types.js.map +1 -0
  68. package/package.json +157 -0
  69. package/src/browser/index.ts +7 -0
  70. package/src/index.ts +195 -0
  71. package/src/providers/openai-compatible.ts +406 -0
  72. package/src/runtime/broadcast.test.ts +355 -0
  73. package/src/runtime/broadcast.ts +428 -0
  74. package/src/runtime/cancellation.ts +40 -0
  75. package/src/runtime/coordinator.test.ts +468 -0
  76. package/src/runtime/coordinator.ts +581 -0
  77. package/src/runtime/decisions.ts +38 -0
  78. package/src/runtime/defaults.ts +547 -0
  79. package/src/runtime/engine.ts +880 -0
  80. package/src/runtime/model.ts +117 -0
  81. package/src/runtime/sequential.test.ts +262 -0
  82. package/src/runtime/sequential.ts +357 -0
  83. package/src/runtime/shared.test.ts +265 -0
  84. package/src/runtime/shared.ts +367 -0
  85. package/src/runtime/termination.ts +463 -0
  86. package/src/runtime/tools.ts +1518 -0
  87. package/src/runtime/validation.ts +771 -0
  88. package/src/types.ts +2729 -0
package/README.md ADDED
@@ -0,0 +1,842 @@
1
+ # Dogpile
2
+
3
+ Dogpile is a strict TypeScript SDK for turning one mission into a replayable multi-agent coordination run. It is inspired by arXiv 2603.28990v1, "Drop the Hierarchy and Roles", but it is packaged like an application SDK: bring your own model provider, pick a protocol, stream events if you need them, and persist the trace wherever your product already stores work.
4
+
5
+ The useful bit is the contract. Dogpile does not own your credentials, pricing table, storage, queue, or UI. It owns the coordination loop: agent turns, protocol events, transcripts, cost aggregation, cancellation, termination policy, typed errors, and replayable result shapes.
6
+
7
+ ```ts
8
+ import { Dogpile } from "@dogpile/sdk";
9
+
10
+ const result = await Dogpile.pile({
11
+ intent: "Stress-test this release plan before it ships.",
12
+ model,
13
+ protocol: "broadcast",
14
+ tier: "quality"
15
+ });
16
+
17
+ console.log(result.output);
18
+ console.log(result.trace.events);
19
+ ```
20
+
21
+ ## Research Basis
22
+
23
+ Dogpile's protocol vocabulary and paper-faithfulness examples are based on:
24
+
25
+ Dochkina, V. (2026). *Drop the Hierarchy and Roles: How Self-Organizing LLM Agents Outperform Designed Structures*. arXiv:2603.28990 [cs.AI]. https://doi.org/10.48550/arXiv.2603.28990
26
+
27
+ ```bibtex
28
+ @misc{dochkina2026drop,
29
+ title = {Drop the Hierarchy and Roles: How Self-Organizing LLM Agents Outperform Designed Structures},
30
+ author = {Dochkina, Victoria},
31
+ year = {2026},
32
+ eprint = {2603.28990},
33
+ archivePrefix = {arXiv},
34
+ primaryClass = {cs.AI},
35
+ doi = {10.48550/arXiv.2603.28990},
36
+ url = {https://arxiv.org/abs/2603.28990}
37
+ }
38
+ ```
39
+
40
+ ## Why Dogpile
41
+
42
+ - **Provider-neutral by default.** Any object with `id` and `generate(request)` can be the model boundary.
43
+ - **Four first-party protocols.** Use `sequential`, `broadcast`, `shared`, or `coordinator` without changing the result contract.
44
+ - **Live when you need it.** `Dogpile.stream()` yields the same event shapes that land in the final trace.
45
+ - **Replayable by construction.** Completed runs return a JSON-serializable trace with inputs, events, provider calls, transcript, accounting, and final output.
46
+ - **Application-owned effects.** Runtime tools, web search, code execution, credentials, and persistence stay under caller policy.
47
+ - **Small public surface.** The v1 package exports the SDK root, browser entrypoint, runtime subpaths, type subpath, and OpenAI-compatible provider adapter.
48
+
49
+ ## Documentation
50
+
51
+ - [Developer usage guide](https://github.com/zakkeown/dogpile/blob/main/docs/developer-usage.md): API choices, providers, protocols, streaming, termination, tools, replay, errors, browser usage, and repo commands.
52
+ - [Examples](examples/README.md): repeatable protocol comparison and live OpenAI-compatible execution.
53
+ - [Changelog](CHANGELOG.md): v1 release notes and public-surface changes.
54
+
55
+ ## Choose Your Entry Point
56
+
57
+ | Need | Start here |
58
+ | --- | --- |
59
+ | One application workflow | `Dogpile.pile({ intent, model })` |
60
+ | Functional API without the namespace | `run({ intent, model })` |
61
+ | Live UI or logs | `Dogpile.stream({ intent, model })` |
62
+ | Repeated controlled runs | `Dogpile.createEngine({ protocol, tier, model })` |
63
+ | Load a saved trace | `Dogpile.replay(trace)` |
64
+ | Direct compatible HTTP endpoint | `createOpenAICompatibleProvider(options)` |
65
+
66
+ ## Install
67
+
68
+ Dogpile ships to npm as `@dogpile/sdk`, a pure TypeScript package with its own provider-neutral model interface. The package root has no provider SDK peer dependency: pass any object that implements `ConfiguredModelProvider`, or use the built-in dependency-free OpenAI-compatible adapter for direct HTTP calls.
69
+
70
+ ```sh
71
+ pnpm add @dogpile/sdk
72
+ ```
73
+
74
+ ```sh
75
+ npm install @dogpile/sdk
76
+ ```
77
+
78
+ ```sh
79
+ yarn add @dogpile/sdk
80
+ ```
81
+
82
+ ```sh
83
+ bun add @dogpile/sdk
84
+ ```
85
+
86
+ Dogpile itself does not read API keys or any other environment variables. Your provider object owns credentials, routing, pricing, retries, and any vendor SDKs.
87
+
88
+ The SDK supports only Node.js LTS 22 / 24, Bun latest, and browser ESM runtimes. Core APIs are stateless and do not require filesystem access, a database, or a session store. Browser-aware bundlers can use the package root's `browser` export condition, and direct browser ESM consumers can import the bundled entrypoint from `@dogpile/sdk/browser`.
89
+
90
+ ### Packed Tarball Quickstart Setup
91
+
92
+ Use the packed-tarball path when validating the exact package that will be
93
+ published. Packing from this repository requires Node.js LTS 22 or 24 and
94
+ pnpm 10.33.0. Running the quickstart from a consumer project requires one of
95
+ the supported runtimes: Node.js LTS 22 / 24 or Bun latest.
96
+
97
+ From the Dogpile repository, build and pack the SDK:
98
+
99
+ ```sh
100
+ pnpm install
101
+ pnpm run build
102
+ pnpm pack --pack-destination ./packed
103
+ ```
104
+
105
+ The local tarball is named `dogpile-sdk-1.0.0.tgz` for the scoped package
106
+ `@dogpile/sdk@1.0.0`. Install that tarball into a fresh consumer project:
107
+
108
+ ```sh
109
+ mkdir ../dogpile-quickstart
110
+ cd ../dogpile-quickstart
111
+ pnpm init
112
+ pnpm add ../dogpile/packed/dogpile-sdk-1.0.0.tgz
113
+ ```
114
+
115
+ Equivalent install commands for other supported package managers are:
116
+
117
+ ```sh
118
+ npm install ../dogpile/packed/dogpile-sdk-1.0.0.tgz
119
+ yarn add ../dogpile/packed/dogpile-sdk-1.0.0.tgz
120
+ bun add ../dogpile/packed/dogpile-sdk-1.0.0.tgz
121
+ ```
122
+
123
+ ## Versioning and Stability
124
+
125
+ Dogpile follows semantic versioning for published packages:
126
+
127
+ - Patch releases fix bugs, tighten docs, or add tests without changing public behavior.
128
+ - Minor releases add backward-compatible APIs, protocols, event fields, or runtime support.
129
+ - Major releases may change public contracts, remove deprecated APIs, or alter protocol semantics.
130
+
131
+ The v1.0.0 stable surface includes the package root exports, high-level `Dogpile.pile()`, `run()`, `stream()`, `createEngine()`, the dependency-free OpenAI-compatible provider adapter, protocol and tier discriminated unions, event unions, trace/result types, and runtime portability guarantees for Node.js LTS 22 / 24, Bun latest, and browser ESM runtimes.
132
+
133
+ Dogpile treats documented `dist` entrypoints, their runtime implementation dependencies, JavaScript source maps, declaration maps, original TypeScript sources for shipped runtime/browser/provider files, `README.md`, `CHANGELOG.md`, and `LICENSE` as the publishable package payload. Demo, benchmark, deterministic testing, and internal helper files are repository-only and stay out of the npm tarball. Core runtime code must remain pure TypeScript, storage-free, and free of Node-only dependencies so the same package can run across the supported Node.js, Bun, and browser ESM runtimes.
134
+
135
+ ## Release Verification
136
+
137
+ Before publishing, run the local package gates:
138
+
139
+ ```sh
140
+ pnpm run package:identity
141
+ pnpm run package:artifacts
142
+ pnpm run browser:smoke
143
+ pnpm run quickstart:smoke
144
+ pnpm run verify
145
+ pnpm run pack:check
146
+ pnpm run publish:check
147
+ ```
148
+
149
+ `package:identity` asserts the scoped npm package name `@dogpile/sdk`, the v1 release identity, required package metadata (license, repository, keywords, publish access, and package manager), and scans release-facing source, docs, tests, and CI files for stale unscoped package install/import references. `package:artifacts` verifies that every runtime JavaScript file and TypeScript declaration file referenced by package metadata has been emitted by the build and is covered by `package.json` `files` before any pack or publish dry run. `browser:smoke` rebuilds the browser ESM bundle and runs the focused smoke test that imports `@dogpile/sdk` through the `browser` condition. `quickstart:smoke` builds the SDK, runs the package artifact guard, creates a real `pnpm pack` tarball, installs that tarball into a fresh temporary project without provider SDK peer fixtures, asserts the consumer dependency and lockfile resolve `@dogpile/sdk` from the `.tgz` instead of `workspace:` or `link:` metadata, verifies installed package entrypoints and `dist` imports do not resolve through local source imports, imports `@dogpile/sdk` from the consumer, imports every public package subpath from the installed tarball, extracts the marked quickstart from the installed package README, executes that documented provider-neutral `Dogpile.pile()` example end to end, writes a downstream TypeScript fixture that imports the package root and public subpaths, runs `tsc --noEmit` from the consumer project to prove declaration and export-map type resolution, verifies private helper files are absent from the installed tarball, and proves private helper subpaths remain blocked by package exports. `consumer:smoke` is kept as the same packed-tarball quickstart smoke command for compatibility. `verify` rebuilds `dist`, runs the package artifact guard, runs the packed-tarball quickstart smoke against that build, runs strict typecheck, then runs the test suite so declaration export checks, downstream type-resolution smoke tests, public API type-level assertions, the tarball install path, and package identity checks all fail the same local or CI gate. `pack:check` runs package identity, rebuilds `dist`, runs the package artifact guard before the packed-tarball quickstart smoke creates its `pnpm pack` tarball, then runs the packed JavaScript source-map and declaration-map guard and `npm pack --dry-run` so both the actual tarball install path and the npm package payload are checked. The source-map guard extracts the packed tarball, verifies every packaged `dist/**/*.js` and `dist/**/*.d.ts` file has its map, resolves packaged JavaScript and declaration `sourceMappingURL` references to map files present in the tarball, and confirms package-owned source references in those maps resolve to files present in the tarball. `publish:check` runs `verify`, reruns the package artifact guard, and then runs `npm publish --dry-run` so the package metadata, export map, and publishable files are checked without publishing.
150
+
151
+ The release identity is `@dogpile/sdk@1.0.0`. A real `pnpm pack` or `npm pack` for this scoped package produces the local tarball `dogpile-sdk-1.0.0.tgz`; the dry-run package gate must report that tarball filename and the scoped npm package name before publish. See `CHANGELOG.md` for v1.0.0 release notes and breaking-change documentation.
152
+
153
+ The browser ESM target is emitted at `dist/browser/index.js` with `dist/browser/index.js.map`; both the package root `browser` condition and the explicit `@dogpile/sdk/browser` subpath resolve to that bundled artifact.
154
+
155
+ ### Required CI Status Checks
156
+
157
+ Before publishing from `main` or a `release/**` branch, GitHub branch protection or release review must require these `Release Validation` workflow checks to pass:
158
+
159
+ - `Release Validation / Required Node.js 22 full suite`
160
+ - `Release Validation / Required Node.js 24 full suite`
161
+ - `Release Validation / Required Bun latest full suite`
162
+ - `Release Validation / Required browser bundle smoke`
163
+ - `Release Validation / Required packed-tarball quickstart smoke`
164
+ - `Release Validation / Required pack:check package artifact`
165
+
166
+ Do not publish `@dogpile/sdk` unless all Node LTS matrix entries, the Bun latest suite, the browser bundle smoke job, the packed-tarball quickstart smoke job, and the dependent `pack:check` package artifact job are green.
167
+
168
+ ### Automated npm Publishing
169
+
170
+ `.github/workflows/npm-publish.yml` publishes `@dogpile/sdk` from GitHub Actions
171
+ when a GitHub Release is published. It can also be started manually with
172
+ `workflow_dispatch`; the manual path defaults to a dry run.
173
+
174
+ The publish workflow uses npm Trusted Publishing/OIDC rather than a long-lived
175
+ npm automation token. Configure the package's trusted publisher on npmjs.com
176
+ with:
177
+
178
+ - Organization or user: `zakkeown`
179
+ - Repository: `dogpile`
180
+ - Workflow filename: `npm-publish.yml`
181
+ - Environment name: `npm`
182
+
183
+ Before the first automated release, publish the initial package version from an
184
+ npm account with owner access to the `@dogpile` scope:
185
+
186
+ ```sh
187
+ npm publish --access public
188
+ ```
189
+
190
+ That first manual publish creates the `@dogpile/sdk` package settings page on
191
+ npmjs.com. After it exists, add the Trusted Publisher fields above, then use
192
+ GitHub Releases or the manual workflow dry-run path for future releases. The
193
+ local safety check for the first publish is:
194
+
195
+ ```sh
196
+ pnpm run publish:check
197
+ ```
198
+
199
+ The workflow grants `id-token: write`, runs on a GitHub-hosted Node.js 24
200
+ runner, installs the latest npm CLI, runs `pnpm run publish:check`, verifies the
201
+ target version is not already published, and then runs
202
+ `npm publish --access public`. Keep the `npm` GitHub environment protected for
203
+ release review if this repository should require human approval before a
204
+ published GitHub Release can reach the npm registry.
205
+
206
+ ## Import
207
+
208
+ Use the branded high-level API for application code:
209
+
210
+ ```ts
211
+ import { Dogpile } from "@dogpile/sdk";
212
+
213
+ const result = await Dogpile.pile({
214
+ intent: "Draft a migration plan for an SDK release.",
215
+ model
216
+ });
217
+ ```
218
+
219
+ Dogpile also exports direct helpers and public types from the package root:
220
+
221
+ ```ts
222
+ import {
223
+ createOpenAICompatibleProvider,
224
+ createEngine,
225
+ run,
226
+ stream,
227
+ type ConfiguredModelProvider,
228
+ type DogpileOptions,
229
+ type Protocol,
230
+ type RunAccounting,
231
+ type RunEventLog,
232
+ type RunEvent,
233
+ type RunMetadata,
234
+ type RunResult,
235
+ type RunUsage,
236
+ type Tier
237
+ } from "@dogpile/sdk";
238
+ ```
239
+
240
+ The `model` option is a `ConfiguredModelProvider`: a small caller-owned object
241
+ with a stable `id` and a `generate(request)` function. That is Dogpile's
242
+ provider boundary. Save this complete script as `quickstart.mjs` in the
243
+ consumer project:
244
+
245
+ <!-- dogpile-consumer-quickstart-smoke:start -->
246
+ ```ts
247
+ import { Dogpile } from "@dogpile/sdk";
248
+
249
+ const pricing = {
250
+ inputPerMillionTokens: 0.15,
251
+ outputPerMillionTokens: 0.6
252
+ };
253
+ let turn = 0;
254
+
255
+ const provider = {
256
+ id: "quickstart-provider",
257
+ async generate() {
258
+ turn += 1;
259
+ const usage = {
260
+ inputTokens: 10,
261
+ outputTokens: 4,
262
+ totalTokens: 14
263
+ };
264
+
265
+ return {
266
+ text: `quickstart turn ${turn} completed`,
267
+ usage,
268
+ costUsd:
269
+ (usage.inputTokens * pricing.inputPerMillionTokens +
270
+ usage.outputTokens * pricing.outputPerMillionTokens) /
271
+ 1_000_000
272
+ };
273
+ }
274
+ };
275
+
276
+ const result = await Dogpile.pile({
277
+ intent: "Draft a migration plan for an SDK release.",
278
+ model: provider
279
+ });
280
+
281
+ console.log("Dogpile quickstart complete");
282
+ console.log(`protocol=${result.metadata.protocol}`);
283
+ console.log(`tier=${result.metadata.tier}`);
284
+ console.log(`provider=${result.metadata.modelProviderId}`);
285
+ console.log(`turns=${result.transcript.length}`);
286
+ console.log(`costUsd=${result.usage.usd}`);
287
+ console.log(`output=${result.output}`);
288
+ ```
289
+ <!-- dogpile-consumer-quickstart-smoke:end -->
290
+
291
+ Run it from the consumer project:
292
+
293
+ ```sh
294
+ node quickstart.mjs
295
+ ```
296
+
297
+ Expected observable output:
298
+
299
+ ```text
300
+ Dogpile quickstart complete
301
+ protocol=sequential
302
+ tier=balanced
303
+ provider=quickstart-provider
304
+ turns=3
305
+ costUsd=<estimated from provider token usage and your pricing table>
306
+ output=<model response text>
307
+ ```
308
+
309
+ For direct OpenAI or OpenAI-compatible HTTP endpoints, use Dogpile's built-in
310
+ adapter. It uses `fetch` and the endpoint you provide; it does not route through
311
+ any gateway unless your `baseURL` points at one.
312
+
313
+ ```ts
314
+ import { createOpenAICompatibleProvider, run } from "@dogpile/sdk";
315
+
316
+ const provider = createOpenAICompatibleProvider({
317
+ model: "gpt-4.1-mini",
318
+ apiKey: process.env.OPENAI_API_KEY,
319
+ costEstimator({ usage }) {
320
+ return usage ? usage.totalTokens * 0.0000003 : undefined;
321
+ }
322
+ });
323
+
324
+ const result = await run({
325
+ intent: "Compare the release risks for a TypeScript SDK.",
326
+ protocol: "sequential",
327
+ tier: "balanced",
328
+ model: provider
329
+ });
330
+
331
+ console.log(result.output);
332
+ ```
333
+
334
+ You can point the same adapter at another compatible server by setting
335
+ `baseURL`, `path`, and headers explicitly:
336
+
337
+ ```ts
338
+ const provider = createOpenAICompatibleProvider({
339
+ id: "local-openai-compatible",
340
+ model: "local-model",
341
+ baseURL: "http://127.0.0.1:8080/v1",
342
+ headers: {
343
+ "x-workspace": "dogpile-live-test"
344
+ },
345
+ maxOutputTokens: 1_024
346
+ });
347
+ ```
348
+
349
+ ### Provider Boundary
350
+
351
+ `ConfiguredModelProvider` is the only model contract Dogpile core needs:
352
+
353
+ ```ts
354
+ const provider = {
355
+ id: "my-provider",
356
+ async generate(request) {
357
+ const response = await myModelClient.complete({
358
+ messages: request.messages,
359
+ temperature: request.temperature,
360
+ signal: request.signal
361
+ });
362
+
363
+ return {
364
+ text: response.text,
365
+ usage: response.usage,
366
+ costUsd: response.costUsd
367
+ };
368
+ }
369
+ };
370
+ ```
371
+
372
+ The provider owns vendor SDKs, credentials, model names, retries, routing,
373
+ pricing, and telemetry. Dogpile owns protocol orchestration, events, traces,
374
+ termination policy, and replayable result shapes.
375
+
376
+ ### OpenAI-Compatible Provider Configuration
377
+
378
+ `createOpenAICompatibleProvider()` returns Dogpile's
379
+ `ConfiguredModelProvider`, which is the value passed to `Dogpile.pile()`,
380
+ `run()`, `stream()`, or `createEngine()`.
381
+
382
+ ```ts
383
+ const provider = createOpenAICompatibleProvider({
384
+ id: "openai:gpt-4.1-mini",
385
+ model: "gpt-4.1-mini",
386
+ apiKey: process.env.OPENAI_API_KEY,
387
+ maxOutputTokens: 1_024,
388
+ extraBody: {
389
+ reasoning_effort: "low"
390
+ },
391
+ costEstimator({ usage }) {
392
+ return usage ? usage.totalTokens * 0.0000003 : undefined;
393
+ }
394
+ });
395
+ ```
396
+
397
+ The configuration object supports:
398
+
399
+ - `model`: required model id sent to the compatible chat-completions endpoint.
400
+ - `apiKey`: optional bearer token. Dogpile does not read environment variables;
401
+ pass the credential explicitly when the endpoint requires one.
402
+ - `baseURL`: endpoint root; defaults to `https://api.openai.com/v1`.
403
+ - `path`: request path under `baseURL`; defaults to `/chat/completions`.
404
+ - `id`: stable provider id stored in events, traces, and errors; when omitted,
405
+ Dogpile uses `openai-compatible:<model>`.
406
+ - `fetch`: optional fetch-compatible implementation for tests, custom runtimes,
407
+ proxies, or instrumentation.
408
+ - `costEstimator`: caller-owned usage-to-USD function. Dogpile passes
409
+ `{ providerId, request, response, usage }` and records the returned number as
410
+ `costUsd`; Dogpile does not bundle model pricing.
411
+ - `headers`: optional headers merged into the request. `authorization` is set
412
+ from `apiKey` unless you provide it yourself.
413
+ - `maxOutputTokens`: optional positive integer sent as `max_tokens`.
414
+ - `extraBody`: optional JSON object merged into the request body before
415
+ Dogpile sets `model`, `messages`, `temperature`, and `max_tokens`.
416
+
417
+ During each model turn, Dogpile supplies a provider-neutral `ModelRequest` and
418
+ the adapter builds an OpenAI-compatible chat-completions request with this
419
+ mapping:
420
+
421
+ | Dogpile field | Request field | Behavior |
422
+ | --- | --- | --- |
423
+ | `request.messages[].role` and `request.messages[].content` | `messages[].role` and `messages[].content` | Preserves message order and role/content values. |
424
+ | `request.temperature` | `temperature` | Forwards the protocol-selected sampling temperature. |
425
+ | `request.signal` | `signal` | Passes caller cancellation through to `fetch`. |
426
+ | `request.metadata` | Not forwarded | Remains Dogpile trace/protocol metadata and is available to `costEstimator` through `request`. |
427
+ | `model` | `model` | Sends the configured model id. |
428
+ | `maxOutputTokens` | `max_tokens` | Sends the configured output cap when present. |
429
+ | `extraBody` | request body | Merges caller-owned provider options before canonical Dogpile fields are written. |
430
+
431
+ OpenAI-compatible responses are normalized back into Dogpile's stable public
432
+ provider types with this mapping:
433
+
434
+ | Response field | Dogpile field | Behavior |
435
+ | --- | --- | --- |
436
+ | `choices[0].message.content` | `ModelResponse.text` | Becomes the completed model-turn text. String content and text parts are supported. |
437
+ | `choices[0].finish_reason` | `finishReason` | Maps `stop`, `length`, content-filter, and tool-call finish reasons to Dogpile's provider-neutral finish reason union. |
438
+ | `usage.prompt_tokens` / `usage.completion_tokens` / `usage.total_tokens` | `usage` | Maps input/output/total tokens when all counts are available. |
439
+ | Caller `costEstimator` return value | `costUsd` | Calls `costEstimator({ providerId, request, response, usage })`; Dogpile does not ship or infer a pricing table. |
440
+ | `id`, `object`, `created`, `model`, and `usage` | `metadata.openAICompatible` | Stores JSON-compatible response metadata. |
441
+ | HTTP/provider failures | `DogpileError` | Wraps failures with stable string codes such as `provider-rate-limited`, `provider-authentication`, `provider-invalid-request`, and `provider-unavailable`. |
442
+
443
+ ## Runtime Tool Input Validation
444
+
445
+ Runtime tools can define an optional `validateInput(input)` hook when their
446
+ JSON schema is not enough to enforce the executable contract. Dogpile validates
447
+ the tool definition at registration time, before protocol execution begins; if
448
+ `validateInput` is present, it must be callable. Invalid registrations fail with
449
+ `DogpileError` code `"invalid-configuration"` and a `detail.path` such as
450
+ `tools[0].validateInput`.
451
+
452
+ For each registered tool call, Dogpile emits the `tool-call` event, builds the
453
+ `RuntimeToolExecutionContext`, then calls `validateInput()` with the normalized
454
+ JSON object input immediately before `execute()`. If the hook is omitted, the
455
+ input is treated as valid. If the hook returns `{ type: "invalid", issues }`,
456
+ Dogpile does not call `execute()`. Instead it returns and emits a
457
+ `RuntimeToolErrorResult` with `error.code: "invalid-input"`, `retryable: false`,
458
+ and `error.detail.issues` copied from the validation result. This is tool-result
459
+ data, not a thrown `DogpileError`, so traces and transcripts remain replayable.
460
+
461
+ Tool authors should keep `inputSchema` as the model-visible JSON contract and
462
+ use `validateInput()` for runtime-only checks such as cross-field constraints,
463
+ caller policy, adapter limits, or stricter type narrowing before side effects.
464
+ Return serializable `RuntimeToolValidationIssue` objects for ordinary bad model
465
+ or caller input instead of throwing. Keep the hook deterministic and side-effect
466
+ free because it runs on every execution of that tool; put network calls,
467
+ filesystem work, sandbox execution, and other effects inside `execute()` after
468
+ validation has passed.
469
+
470
+ ```ts
471
+ import { type RuntimeTool } from "@dogpile/sdk";
472
+
473
+ interface LookupInput {
474
+ readonly query?: string;
475
+ }
476
+
477
+ const lookupTool: RuntimeTool<LookupInput> = {
478
+ identity: {
479
+ id: "app.tools.lookup",
480
+ name: "lookup",
481
+ description: "Look up release context."
482
+ },
483
+ inputSchema: {
484
+ kind: "json-schema",
485
+ schema: {
486
+ type: "object",
487
+ properties: {
488
+ query: { type: "string", minLength: 1 }
489
+ },
490
+ required: ["query"],
491
+ additionalProperties: false
492
+ }
493
+ },
494
+ validateInput(input) {
495
+ return typeof input.query === "string" && input.query.trim().length > 0
496
+ ? { type: "valid" }
497
+ : {
498
+ type: "invalid",
499
+ issues: [
500
+ {
501
+ code: "missing-field",
502
+ path: "query",
503
+ message: "query is required."
504
+ }
505
+ ]
506
+ };
507
+ },
508
+ execute(input, context) {
509
+ return {
510
+ type: "success",
511
+ toolCallId: context.toolCallId,
512
+ tool: this.identity,
513
+ output: {
514
+ answer: `result for ${input.query}`
515
+ }
516
+ };
517
+ }
518
+ };
519
+ ```
520
+
521
+ ## DogpileError Codes
522
+
523
+ `DogpileError` and `DogpileErrorCode` are exported from `@dogpile/sdk`. The
524
+ string `code` values below are the stable v1 contract for JavaScript callers,
525
+ TypeScript discriminated-union handling, retry policy, and observability. When
526
+ `retryable` is present, prefer it over a hard-coded policy; the handling column
527
+ describes the default caller posture when provider metadata does not override
528
+ it.
529
+
530
+ | Code | When Dogpile emits it | Caller handling |
531
+ | --- | --- | --- |
532
+ | `invalid-configuration` | Public entrypoint or adapter validation fails before a model turn starts, including malformed `run()`, `stream()`, `createEngine()`, runtime tool, provider registration, or OpenAI-compatible adapter options. `detail.path` points at the failing input. | Treat as a caller/configuration bug. Do not retry the same request; fix the option named by `detail.path`. |
533
+ | `aborted` | A caller `AbortSignal`, `StreamHandle.cancel()`, or provider-facing abort failure cancels an active run or stream. | Treat as intentional cancellation. Stop consuming the run, clean up UI/state, and start a new request only if the user asks. |
534
+ | `timeout` | Dogpile's own `budget.timeoutMs` deadline expires and Dogpile aborts the active provider-facing request. | Safe to retry with a larger timeout, smaller workload, cheaper/faster tier, or different provider. |
535
+ | `provider-authentication` | The configured provider reports authentication or authorization failure, including HTTP 401/403 or API key loading errors. | Do not blindly retry. Refresh credentials, permissions, provider configuration, or account state. |
536
+ | `provider-invalid-request` | The provider SDK rejects the model request shape, prompt, tool choice/input, argument set, or type validation before a valid provider response is produced. | Treat as a request-construction bug. Fix the prompt/tool/options payload before retrying. |
537
+ | `provider-invalid-response` | The provider returns an empty, unparsable, schema-invalid, or no-output response that the configured adapter cannot normalize. | Usually safe to retry once or fail over; log `detail` because repeated failures may indicate provider drift or an unsupported response shape. |
538
+ | `provider-not-found` | The provider SDK reports a missing provider, model, route, or resource, including HTTP 404. | Do not retry unchanged. Verify the configured model id, provider id, deployment, and account access. |
539
+ | `provider-rate-limited` | The provider indicates quota, contention, or rate limiting, including HTTP 409/429. | Retry with backoff or switch providers; surface quota pressure when retries are exhausted. |
540
+ | `provider-timeout` | The provider or upstream gateway times out the request, including HTTP 408/504. | Retry with backoff, reduce prompt/work size, or fail over to another provider. |
541
+ | `provider-unavailable` | The provider is temporarily unavailable, including HTTP 5xx responses or retry exhaustion. | Retry with backoff or fail over. Preserve the original `providerId` and `detail` for incident correlation. |
542
+ | `provider-unsupported` | The provider/model does not support a requested function or model version. | Do not retry unchanged. Disable the unsupported feature or choose a model that supports it. |
543
+ | `provider-error` | The configured adapter reports a generic provider failure that Dogpile cannot map to a narrower provider code. | Check `retryable` and `detail.statusCode` when present. Retry transient status codes; otherwise inspect provider diagnostics. |
544
+ | `unknown` | Dogpile catches an unrecognized provider or adapter failure with no stable mapping. | Treat conservatively: log `detail`, avoid assuming retry safety unless `retryable` is true, and add handling once the underlying failure is understood. |
545
+
546
+ Use `stream()` or `Dogpile.stream()` when you need a live event log, and `createEngine()` when a research harness needs reusable low-level protocol settings across many missions.
547
+
548
+ ## Benchmark Artifacts
549
+
550
+ Benchmark runners and deterministic provider fixtures remain repository test
551
+ harnesses in v1. They are intentionally not exported from `@dogpile/sdk`.
552
+ Repository tests and benchmark harnesses that need those helpers import them
553
+ from the source-only internal path `../internal.js`, which resolves to
554
+ `src/internal.ts` in the TypeScript source tree.
555
+ Consumer applications should build reproducibility artifacts from the public
556
+ `RunResult`, `Trace`, `transcript`, `eventLog`, and cost summary returned by
557
+ `run()`, `stream()`, or `Dogpile.pile()`.
558
+
559
+ ## Single-Call Workflow Contract
560
+
561
+ Application code starts with `Dogpile.pile()`: provide the mission and a model
562
+ provider adapter. When `protocol`, `tier`, and `budget` are omitted, Dogpile
563
+ uses the default application flow: Sequential coordination, the `balanced`
564
+ cost/quality tier, and no hard budget caps beyond the tier preset.
565
+
566
+ ```ts
567
+ const result = await Dogpile.pile({
568
+ intent: "Plan the safest SDK v1 release sequence.",
569
+ model: provider
570
+ });
571
+
572
+ console.log(result.output);
573
+ console.log(result.trace.events);
574
+ console.log(result.transcript);
575
+ ```
576
+
577
+ Pass explicit controls when the application needs a non-default protocol,
578
+ cost tier, or hard budget cap:
579
+
580
+ ```ts
581
+ const result = await Dogpile.pile({
582
+ intent: "Plan the safest SDK v1 release sequence.",
583
+ protocol: "sequential",
584
+ tier: "balanced",
585
+ model: provider,
586
+ budget: { maxTokens: 20_000 }
587
+ });
588
+ ```
589
+
590
+ Use an explicit protocol configuration when the workflow needs custom
591
+ coordination limits, and pair it with an explicit cost tier plus hard caps:
592
+
593
+ ```ts
594
+ import { Dogpile, type Budget, type ProtocolConfig } from "@dogpile/sdk";
595
+
596
+ const protocol = {
597
+ kind: "broadcast",
598
+ maxRounds: 2
599
+ } satisfies ProtocolConfig;
600
+
601
+ const budget = {
602
+ tier: "quality",
603
+ maxUsd: 0.5,
604
+ maxTokens: 24_000,
605
+ qualityWeight: 0.85
606
+ } satisfies Budget;
607
+
608
+ const result = await Dogpile.pile({
609
+ intent: "Produce a release-risk brief with independent reviewer opinions.",
610
+ protocol,
611
+ tier: budget.tier,
612
+ model: provider,
613
+ budget: {
614
+ maxUsd: budget.maxUsd,
615
+ maxTokens: budget.maxTokens,
616
+ qualityWeight: budget.qualityWeight
617
+ }
618
+ });
619
+
620
+ console.log(result.output);
621
+ console.log(result.trace.protocol);
622
+ console.log(result.trace.events);
623
+ ```
624
+
625
+ The required inputs are:
626
+
627
+ - `intent`: the mission the agent collective should solve.
628
+ - `model`: a caller-owned `ConfiguredModelProvider`, backed by your direct provider client, a compatible HTTP endpoint, or a test fixture.
629
+
630
+ Optional inputs refine the run without changing the core contract:
631
+
632
+ - `protocol`: `"coordinator"`, `"sequential"`, `"broadcast"`, or `"shared"`, or an explicit protocol config such as `{ kind: "broadcast", maxRounds: 2 }`; omitted protocols default to `"sequential"`. Named broadcast runs default to two rounds: an intention broadcast followed by final decisions. Shared runs may include `organizationalMemory` so every agent sees the same prior-memory snapshot without seeing current-run peer updates.
633
+ - `tier`: `"fast"`, `"balanced"`, or `"quality"`; omitted tiers default to `"balanced"`.
634
+ - `budget`: hard caps layered over the tier, for example `{ maxUsd: 0.25, maxTokens: 20_000, timeoutMs: 60_000, qualityWeight: 0.7 }`. When `timeoutMs` expires, Dogpile aborts the active provider-facing request and rejects with `DogpileError` code `"timeout"`.
635
+ - `agents`: an explicit roster of `{ id, role, instructions? }` participants.
636
+ - `temperature`: an override for the tier-selected default sampling temperature.
637
+
638
+ Invalid caller configuration is rejected before any protocol turn starts with
639
+ `DogpileError` code `"invalid-configuration"` and `retryable: false`. The
640
+ error `detail` includes `kind: "configuration-validation"`, the failing
641
+ `path`, the `rule`, and the expected shape. Validation covers required
642
+ `intent`, provider registrations, protocol and tier enums, positive turn/round
643
+ limits, non-negative budget caps, normalized `qualityWeight`, agent and tool
644
+ shapes, termination policies, `AbortSignal` inputs, and built-in provider
645
+ adapter options.
646
+
647
+ Every completed call returns the same result shape:
648
+
649
+ ```ts
650
+ type RunResult = {
651
+ output: string;
652
+ eventLog: RunEventLog;
653
+ trace: Trace;
654
+ transcript: readonly TranscriptEntry[];
655
+ usage: RunUsage;
656
+ metadata: RunMetadata;
657
+ accounting: RunAccounting;
658
+ cost: CostSummary;
659
+ quality?: number;
660
+ };
661
+ ```
662
+
663
+ - `output` is the final synthesized answer.
664
+ - `eventLog` is the complete non-streaming event log with ordered event types, count, and events.
665
+ - `trace` is a JSON-serializable replay artifact containing the run id, protocol, tier, model provider id, agents used, ordered event log, and transcript.
666
+ - `trace.events` is the full event log for coordination moments: `role-assignment`, `agent-turn`, `broadcast`, and `final`. Agent-turn and broadcast records include `decision` when model output follows Dogpile's structured `role_selected`, `participation`, `rationale`, and `contribution` labels.
667
+ - `transcript` is the ordered list of model-visible agent turns with `agentId`, `role`, `input`, `output`, and optional parsed `decision`; it matches `trace.transcript` for ergonomic access.
668
+ - `usage` reports aggregate USD and token accounting.
669
+ - `metadata` exposes run id, protocol, tier, model provider id, participating agents, and start/completion timestamps.
670
+ - `accounting` bundles the selected tier, optional budget caps, termination policy, final usage/cost, budget state snapshots, and cap utilization.
671
+ - `cost` is retained as a compatibility alias for `usage`.
672
+
673
+ ## Replay Trace Contract
674
+
675
+ `result.trace` is the canonical replay artifact for a completed run. It is
676
+ versioned by `schemaVersion`, JSON-serializable, and contains all SDK-owned
677
+ state required to inspect or reproduce the coordination path without Dogpile
678
+ storage. Callers own persistence and may save the trace as JSON, NDJSON-derived
679
+ records, object storage, or database rows.
680
+
681
+ The required trace sections are:
682
+
683
+ - `inputs`: normalized mission, protocol config, tier, model provider id,
684
+ agent roster, and temperature.
685
+ - `budget`: selected tier, caller caps, and the serializable termination policy
686
+ used by the run.
687
+ - `seed`: caller seed metadata, or an explicit `source: "none"` record when no
688
+ seed was supplied.
689
+ - `events`: the ordered event log emitted during execution.
690
+ - `protocolDecisions`: one replay decision per event, with `eventIndex`
691
+ pointing at the corresponding `events[eventIndex]`.
692
+ - `providerCalls`: every provider request and provider response, ordered by
693
+ execution and keyed by stable `callId` values such as
694
+ `${runId}:provider-call:1`.
695
+ - `budgetStateChanges`: cumulative cost snapshots derived from cost-bearing
696
+ events.
697
+ - `transcript`: ordered model-visible prompt/output turns.
698
+ - `finalOutput`: terminal output, final cost, completion timestamp, and
699
+ transcript link.
700
+
701
+ Event ordering is authoritative. `trace.events`, `result.eventLog.events`, and
702
+ the live events yielded by `stream()` use the same order for completed runs.
703
+ `result.eventLog.eventTypes` is exactly `trace.events.map(event => event.type)`,
704
+ and `protocolDecisions[n].eventIndex === n` for each recorded coordination
705
+ moment. The terminal event is always `final` for a successful run, and
706
+ `trace.finalOutput.output` matches `result.output`.
707
+
708
+ Provider responses are preserved in `trace.providerCalls`, not inferred from
709
+ final text. Each provider call stores the exact provider-neutral `ModelRequest`
710
+ handed to the configured adapter and the exact `ModelResponse` returned by that
711
+ adapter, including optional usage and USD cost. For current protocol runners,
712
+ provider calls are ordered one-to-one with transcript entries and completed
713
+ `agent-turn` events: the `n`th provider response text is the `n`th transcript
714
+ output, and the `n`th transcript input is the final user message in the `n`th
715
+ provider request. Streaming providers may additionally emit
716
+ `model-output-chunk` events before the completed `agent-turn`; the completed
717
+ provider response remains the replay source for the final turn text.
718
+
719
+ Use `Dogpile.stream()` when the UI or harness needs the event log as it happens.
720
+ The stream yields the same `RunEvent` values that will later appear in
721
+ `result.trace.events`, and the final result is available through
722
+ `handle.result`. Pass `signal` to propagate caller cancellation through
723
+ streamed provider requests, or call `handle.cancel()` to abort the active
724
+ provider-facing request, close the stream iterator, mark `handle.status` as
725
+ `"cancelled"`, and reject `handle.result` with `DogpileError` code
726
+ `"aborted"`.
727
+
728
+ ```ts
729
+ const abortController = new AbortController();
730
+ const handle = Dogpile.stream({
731
+ intent: "Compare release risks across protocol variants.",
732
+ protocol: "broadcast",
733
+ tier: "quality",
734
+ model: provider,
735
+ budget: { maxTokens: 12_000 },
736
+ signal: abortController.signal
737
+ });
738
+
739
+ for await (const event of handle) {
740
+ if (event.type === "agent-turn") {
741
+ renderTurn(event.agentId, event.output);
742
+ }
743
+ }
744
+
745
+ // Later, if the user leaves the view or stops the workflow:
746
+ // handle.cancel();
747
+
748
+ const result = await handle.result;
749
+ ```
750
+
751
+ Use `replay()` / `Dogpile.replay()` or `replayStream()` /
752
+ `Dogpile.replayStream()` when loading a trace artifact you already persisted.
753
+ The replay entrypoints accept the saved `Trace` object and reconstruct the same
754
+ public `RunResult`, event stream, and transcript without calling a model,
755
+ reading disk, or using SDK-managed storage:
756
+
757
+ ```ts
758
+ import { Dogpile, replay, replayStream, type Trace } from "@dogpile/sdk";
759
+
760
+ const trace = await loadJson<Trace>("run-trace.json");
761
+
762
+ const result = replay(trace);
763
+ const sameResult = Dogpile.replay(trace);
764
+ const replayHandle = replayStream(trace);
765
+
766
+ for await (const event of replayHandle) {
767
+ renderReplayEvent(event);
768
+ }
769
+
770
+ console.log(result.output);
771
+ console.log(result.eventLog.events);
772
+ console.log(sameResult.transcript);
773
+ console.log((await replayHandle.result).output);
774
+ ```
775
+
776
+ Researchers can drop below the single-call surface with `createEngine()` while
777
+ keeping the same stateless result contract:
778
+
779
+ ```ts
780
+ const engine = Dogpile.createEngine({
781
+ protocol: { kind: "sequential", maxTurns: 4 },
782
+ tier: "balanced",
783
+ model: provider,
784
+ agents
785
+ });
786
+
787
+ const reproductionRun = await engine.run("Reproduce the paper triage task.");
788
+ ```
789
+
790
+ For a lower-level reproduction harness, keep protocol settings and agents fixed
791
+ on the engine, capture only the streaming events you need for live analysis, and
792
+ persist the completed trace/transcript yourself:
793
+
794
+ ```ts
795
+ import {
796
+ Dogpile,
797
+ type AgentSpec,
798
+ type ProtocolConfig,
799
+ type RunEvent,
800
+ type TranscriptEntry
801
+ } from "@dogpile/sdk";
802
+
803
+ const protocol = {
804
+ kind: "shared",
805
+ maxTurns: 4
806
+ } satisfies ProtocolConfig;
807
+
808
+ const agents = [
809
+ { id: "a", role: "solver", instructions: "Solve the task directly." },
810
+ { id: "b", role: "auditor", instructions: "Challenge weak assumptions." },
811
+ { id: "c", role: "editor", instructions: "Merge the strongest answer." }
812
+ ] satisfies readonly AgentSpec[];
813
+
814
+ const engine = Dogpile.createEngine({
815
+ protocol,
816
+ tier: "quality",
817
+ model: provider,
818
+ agents,
819
+ temperature: 0.1,
820
+ budget: { maxTokens: 16_000, qualityWeight: 0.9 }
821
+ });
822
+
823
+ const handle = engine.stream("Re-run the L3 release readiness triage fixture.");
824
+ const eventLog: RunEvent[] = [];
825
+
826
+ for await (const event of handle) {
827
+ if (event.type === "agent-turn" || event.type === "broadcast") {
828
+ eventLog.push(event);
829
+ }
830
+ }
831
+
832
+ const result = await handle.result;
833
+ const transcript: readonly TranscriptEntry[] = result.transcript;
834
+ const replayArtifact = {
835
+ output: result.output,
836
+ eventLog,
837
+ transcript,
838
+ trace: result.trace
839
+ };
840
+
841
+ await saveJson(replayArtifact);
842
+ ```