anchor-ai-sdk 0.1.6 → 0.1.7

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 (54) hide show
  1. package/README.md +337 -102
  2. package/dist/engine/background.d.ts +48 -0
  3. package/dist/engine/background.d.ts.map +1 -0
  4. package/dist/engine/background.js +98 -0
  5. package/dist/engine/background.js.map +1 -0
  6. package/dist/engine/dispatch.d.ts +54 -0
  7. package/dist/engine/dispatch.d.ts.map +1 -0
  8. package/dist/engine/dispatch.js +185 -0
  9. package/dist/engine/dispatch.js.map +1 -0
  10. package/dist/engine/events.d.ts +21 -1
  11. package/dist/engine/events.d.ts.map +1 -1
  12. package/dist/engine/guardrails.d.ts +54 -0
  13. package/dist/engine/guardrails.d.ts.map +1 -0
  14. package/dist/engine/guardrails.js +91 -0
  15. package/dist/engine/guardrails.js.map +1 -0
  16. package/dist/engine/inbox.d.ts +109 -0
  17. package/dist/engine/inbox.d.ts.map +1 -0
  18. package/dist/engine/inbox.js +94 -0
  19. package/dist/engine/inbox.js.map +1 -0
  20. package/dist/engine/loop.d.ts.map +1 -1
  21. package/dist/engine/loop.js +296 -241
  22. package/dist/engine/loop.js.map +1 -1
  23. package/dist/engine/types.d.ts +66 -6
  24. package/dist/engine/types.d.ts.map +1 -1
  25. package/dist/index.d.ts +1 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +1 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/llm/types.d.ts +7 -2
  30. package/dist/llm/types.d.ts.map +1 -1
  31. package/dist/sandbox/docker/index.d.ts +8 -0
  32. package/dist/sandbox/docker/index.d.ts.map +1 -1
  33. package/dist/sandbox/docker/index.js +34 -1
  34. package/dist/sandbox/docker/index.js.map +1 -1
  35. package/dist/sandbox/e2b/index.d.ts.map +1 -1
  36. package/dist/sandbox/e2b/index.js +3 -0
  37. package/dist/sandbox/e2b/index.js.map +1 -1
  38. package/dist/sandbox/heyo/index.d.ts.map +1 -1
  39. package/dist/sandbox/heyo/index.js +3 -0
  40. package/dist/sandbox/heyo/index.js.map +1 -1
  41. package/dist/sandbox/local/index.d.ts.map +1 -1
  42. package/dist/sandbox/local/index.js +3 -0
  43. package/dist/sandbox/local/index.js.map +1 -1
  44. package/dist/sandbox/types.d.ts +9 -0
  45. package/dist/sandbox/types.d.ts.map +1 -1
  46. package/dist/sandbox/types.js.map +1 -1
  47. package/dist/tools/bash.d.ts.map +1 -1
  48. package/dist/tools/bash.js +5 -13
  49. package/dist/tools/bash.js.map +1 -1
  50. package/dist/tools/types.d.ts +25 -1
  51. package/dist/tools/types.d.ts.map +1 -1
  52. package/dist/tools/types.js +26 -1
  53. package/dist/tools/types.js.map +1 -1
  54. package/package.json +1 -1
package/README.md CHANGED
@@ -1,147 +1,382 @@
1
1
  <p align="center">
2
2
  <picture>
3
- <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/hrithikkoduri/anchor-ai-sdk/main/assets/logo-dark.svg" />
4
- <img src="https://raw.githubusercontent.com/hrithikkoduri/anchor-ai-sdk/main/assets/logo.svg" alt="Anchor" width="96" />
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/hrithikkoduri/anchor-ai-sdk/d4e34e41ccb6fbb83d4c54824dd3383b5e84adc4/assets/readme-hero-dark.svg" />
4
+ <img src="https://raw.githubusercontent.com/hrithikkoduri/anchor-ai-sdk/d4e34e41ccb6fbb83d4c54824dd3383b5e84adc4/assets/readme-hero.svg" alt="Anchor. Your agent. Your machine. One runtime to wire them together." width="1200" />
5
5
  </picture>
6
6
  </p>
7
7
 
8
- <h1 align="center">Anchor</h1>
8
+ <p align="center">
9
+ <strong>A TypeScript agent harness SDK.</strong><br />
10
+ Define the behavior. Choose the machine. Let the shared runtime handle the turns.
11
+ </p>
9
12
 
10
13
  <p align="center">
11
- <strong>Loop-first, sandbox-first agent harness SDK.</strong><br />
12
- Give it a task and a machine; it reasons, runs tools, checks its own work, and keeps the receipts.
14
+ <a href="https://www.npmjs.com/package/anchor-ai-sdk"><img src="https://img.shields.io/npm/v/anchor-ai-sdk?style=flat-square&color=c2410c" alt="npm version" /></a>
15
+ <a href="https://github.com/hrithikkoduri/anchor-ai-sdk/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/hrithikkoduri/anchor-ai-sdk/ci.yml?style=flat-square&label=ci&color=111114" alt="CI status" /></a>
16
+ <a href="https://github.com/hrithikkoduri/anchor-ai-sdk/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-111114?style=flat-square" alt="MIT license" /></a>
17
+ <img src="https://img.shields.io/badge/node-%E2%89%A520-111114?style=flat-square" alt="Node.js 20 or newer" />
13
18
  </p>
14
19
 
15
20
  <p align="center">
16
- <a href="https://www.npmjs.com/package/anchor-ai-sdk"><img src="https://img.shields.io/npm/v/anchor-ai-sdk?style=flat-square&color=0f1115" alt="npm" /></a>
17
- <a href="https://github.com/hrithikkoduri/anchor-ai-sdk/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/hrithikkoduri/anchor-ai-sdk/ci.yml?style=flat-square&label=ci&color=0f1115" alt="CI" /></a>
18
- <a href="https://github.com/hrithikkoduri/anchor-ai-sdk/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/anchor-ai-sdk?style=flat-square&color=0f1115" alt="license" /></a>
19
- <img src="https://img.shields.io/node/v/anchor-ai-sdk?style=flat-square&color=0f1115" alt="node" />
21
+ <a href="https://anchor-sdk.dev/docs.html"><strong>Documentation</strong></a> ·
22
+ <a href="#quickstart">Quickstart</a> ·
23
+ <a href="#one-sandbox-interface">Sandboxes</a> ·
24
+ <a href="#make-the-agent-yours">Custom agents</a> ·
25
+ <a href="https://anchor-sdk.dev/benchmarks.html">Benchmarks</a>
20
26
  </p>
21
27
 
22
28
  ---
23
29
 
24
- Most agent harnesses give you a loop and leave the hard parts to you: a
25
- context that grows until it dies, a trace you can't replay, a "done" you can't
26
- trust. Anchor ships the hard parts.
27
-
28
- - **Evidence, never vibes.** Every tool result is recorded on
29
- `AgentResult.evidence`; `run_tests` stamps the tree hash it ran against and
30
- `latestEvidenceForTree` ties a green run to the *exact* bytes (fail-closed
31
- when the tree can't be inspected). The engine carries evidence and never
32
- reads success out of a confident sentence; what to gate on it is your
33
- policy, not a hidden wall.
34
- - **Durable by construction.** Conversation, workspace, and the agent's own
35
- notes survive folds, restarts, and budget deaths; the SDK ships the
36
- resumption primitives (thread store, checkpoints, phase pauses, revival
37
- planning), so resuming is wiring, not research.
38
- - **Sandbox-first.** The machine is part of the design, not an afterthought:
39
- one provider contract, adapters accepted by an executable conformance
40
- suite.
41
-
42
- ## Out of the box
43
-
44
- | | |
30
+ Anchor runs the **model → tools → observations → model** loop around your agent.
31
+ It combines configurable guardrails, context compaction, continuation primitives,
32
+ and structured events with a sandbox layer that can also be used on its own.
33
+
34
+ **Your app keeps control of the model, prompts, tools, storage, credentials, and
35
+ release policy.** There is no requirement to turn an agent into a workflow graph.
36
+
37
+ | Build around | What Anchor supplies |
45
38
  | --- | --- |
46
- | **Agent loop with dials** | Budgets (steps / tokens / USD), repeated-call detection, silent-tool and verbose-turn advisories, a per-call output cap (16k, nudge at 8k; a capped prose turn continues once instead of ending), and assistant-text elision (20k replay, 12k head-only when capped, spilled and pointed at); guardrails on by default, each with an off switch. Phases describe the approach, never a second gated execution engine. |
47
- | **Streaming by default** | One wire path: every model call streams. Text and reasoning deltas, tool calls, compaction, checkpoints, and finishes all arrive as typed events. |
48
- | **Durable memory** | `ThreadStore` carries conversation across runs; compaction is derived from the model's *real* context window; reasoning is journaled but never re-sent. |
49
- | **Notes & spill storage** | A reserved `.anchor/notes.md` (git-excluded, checkpoint-carried) is the agent's durable memory across folds; oversized tool output spills to sandbox files and stays addressable instead of vanishing. |
50
- | **Revival** | A dying run leaves a handoff (`request_revival`); `planRevival` + `ThreadBudget` decide how to fund the next run; **same task, phase, and budget**, and when the thread's caps are spent, a human gets the note, quoting the agent's own words on what remains. |
51
- | **Snapshots & resume** | A pluggable snapshot hook (git-backed by default, push optional), phase-boundary pauses, heartbeats and cross-process cancel polling owned by the engine. |
52
- | **Traces you can replay** | One fanout: one sequence number, redaction applied before *every* sink; NDJSON file, database rows, and a live projector share the same ordering. |
53
- | **Sandbox plugins** | One `SandboxProvider` contract: Heyo microVMs, Docker, local. Capabilities are promises; the conformance suite holds adapters to them. Store-backed sessions give adopt-and-fallback continuity for free. |
54
- | **Typed handoffs** | `finish({ handoff })` is shaped by *your* JSON schema; the engine declares the parameter, carries the object, and never interprets it. |
55
- | **Priced & windowed models** | `llmFromCatalog` reads live prices and the genuine context window from the provider catalog; budgets and compaction configure themselves correctly. |
56
-
57
- ## Ten lines to a working agent
39
+ | **Your agent** | Chat and coding bundles, or your own strategy and toolset. Phases label the approach; they are not permissions. |
40
+ | **Your machine** | One `SandboxProvider` / `Sandbox` surface for Heyo Computer, E2B, Docker, and local development. |
41
+ | **Longer tasks** | Conversation compaction, thread-store interfaces, working notes, checkpoints, and opt-in revival planning. |
42
+ | **Observable execution** | Streaming events, tool-result metadata, NDJSON/custom trace sinks, and replay helpers. |
43
+
44
+ ## Quickstart
58
45
 
59
46
  ```bash
60
47
  npm install anchor-ai-sdk
61
48
  ```
62
49
 
63
- ```ts
50
+ Save as `agent.mjs`. This is ordinary JavaScript with ES modules; `.js` also works
51
+ when your `package.json` contains `"type": "module"`.
52
+
53
+ ```js
64
54
  import { chatAgent, llmFromCatalog, runAgent } from "anchor-ai-sdk";
65
55
 
66
- const { llm } = await llmFromCatalog({
56
+ const { llm, compaction } = await llmFromCatalog({
67
57
  baseUrl: "https://openrouter.ai/api/v1",
68
- apiKey: process.env.OPENROUTER_KEY!,
69
- model: "z-ai/glm-5.3",
58
+ apiKey: process.env.OPENROUTER_KEY,
59
+ model: process.env.MODEL ?? "z-ai/glm-5.3",
70
60
  });
71
61
 
72
62
  const result = await runAgent(
73
- { task: "Explain what this repository does" },
74
- { llm, agent: chatAgent },
63
+ { task: "Explain exponential backoff in plain English" },
64
+ { llm, compaction, agent: chatAgent },
75
65
  );
66
+
76
67
  console.log(result.summary);
77
68
  ```
78
69
 
79
- ## Give it a machine
70
+ ```bash
71
+ export OPENROUTER_KEY="your-key"
72
+ node agent.mjs
73
+ ```
74
+
75
+ No sandbox is needed for this chat example. Model calls may incur cost.
76
+ `llmFromCatalog` uses provider pricing and context-window metadata when available;
77
+ pass its returned `compaction` settings to the engine as shown. Catalog lookup is
78
+ best-effort; configure overrides and fallbacks for endpoints without this metadata.
79
+
80
+ ## One sandbox interface
81
+
82
+ **Configure the provider once. Keep the agent-facing machine interface the same.**
83
+ The same layer works with the built-in coding tools, custom agents, or direct
84
+ `sandbox.exec()` calls without an agent.
85
+
86
+ | Provider | Import | Boot configuration |
87
+ | --- | --- | --- |
88
+ | **Heyo Computer** | `anchor-ai-sdk/sandbox/heyo` | Image artifact/store on the provider; host-built image kinds are also supported. |
89
+ | **E2B** | `anchor-ai-sdk/sandbox/e2b` | A prepared template name or ID, **not** an arbitrary Docker image at create time. |
90
+ | **Docker** | `anchor-ai-sdk/sandbox/docker` | An explicit container image, Docker CLI, and running daemon. |
91
+ | **Local** | `anchor-ai-sdk/sandbox/local` | Host processes and a working directory. **No isolation; trusted dev/CI only.** |
92
+
93
+ Choose one of the configurations below. Each defines `provider`; the common
94
+ runner that follows uses the `llm` and `compaction` from the quickstart.
95
+
96
+ <details open>
97
+ <summary><strong>Heyo Computer: choose an image and machine size</strong></summary>
98
+
99
+ ```bash
100
+ npm install @heyocomputer/hws
101
+ ```
102
+
103
+ ```js
104
+ import { heyo } from "anchor-ai-sdk/sandbox/heyo";
105
+
106
+ const provider = heyo({
107
+ token: requiredEnv("HEYO_TOKEN"),
108
+ server: requiredEnv("HEYO_SERVER"),
109
+ namespace: requiredEnv("HEYO_NAMESPACE"),
110
+ image: requiredEnv("HEYO_IMAGE"),
111
+ imageKind: "artifact",
112
+ imageStore: requiredEnv("HEYO_IMAGE_STORE"),
113
+ sizeClass: "small",
114
+ diskSizeGb: 10,
115
+ });
116
+ ```
117
+
118
+ Use an artifact/store you can access and a toolchain suitable for the task.
119
+ Heyo configures its image on the provider and rejects per-sandbox `image` and
120
+ `workspacePath` overrides. `requiredEnv` is defined in the shared runner below.
121
+
122
+ </details>
123
+
124
+ <details>
125
+ <summary><strong>E2B: choose a prepared template</strong></summary>
126
+
127
+ ```bash
128
+ npm install 'e2b@^2.56'
129
+ ```
130
+
131
+ ```js
132
+ import { e2b } from "anchor-ai-sdk/sandbox/e2b";
133
+
134
+ const provider = e2b({
135
+ apiKey: requiredEnv("E2B_API_KEY"),
136
+ template: requiredEnv("E2B_TEMPLATE"),
137
+ timeoutMs: 60 * 60_000, // sandbox lifetime, not a command timeout
138
+ });
139
+ ```
140
+
141
+ Build a template with `bash`, `git`, coreutils (including `timeout`), and the
142
+ language tools your task needs. `spec.image` can override the template per run;
143
+ `workspacePath` points to a Git workspace already prepared inside it. Neither
144
+ option turns a Docker image name into an E2B template automatically.
145
+
146
+ </details>
80
147
 
81
- ```ts
82
- import { codingAgent, runAgent } from "anchor-ai-sdk";
148
+ <details>
149
+ <summary><strong>Docker: supply your container image</strong></summary>
150
+
151
+ ```js
83
152
  import { docker } from "anchor-ai-sdk/sandbox/docker";
84
153
 
85
- const sandbox = docker().create({
86
- repoUrl: "https://github.com/you/your-repo",
87
- ref: "main",
154
+ const provider = docker({
155
+ image: requiredEnv("ANCHOR_DOCKER_IMAGE"),
156
+ });
157
+ ```
158
+
159
+ The image must already contain the required tools; Anchor does not infer or
160
+ install your repository's dependencies. `spec.image` overrides the default.
161
+ Use `workspacePath` only when the image already contains the Git workspace.
162
+
163
+ </details>
164
+
165
+ ### Pass the machine to an agent
166
+
167
+ Combine **one** provider configuration above with this runner and the model setup
168
+ from the quickstart. Merge imports when composing an existing application.
169
+
170
+ ```js
171
+ import { codingAgent } from "anchor-ai-sdk";
172
+
173
+ function requiredEnv(name) {
174
+ const value = process.env[name];
175
+ if (!value) throw new Error(`Set ${name} before running this example.`);
176
+ return value;
177
+ }
178
+
179
+ const sandbox = provider.create({
180
+ repoUrl: requiredEnv("REPO_URL"),
181
+ ref: process.env.REPO_REF ?? "main",
88
182
  });
89
- await sandbox.start();
183
+
90
184
  try {
185
+ await sandbox.start();
91
186
  const result = await runAgent(
92
- { task: "Run the tests and fix the failing case" },
93
- { llm, agent: codingAgent, sandbox },
187
+ { task: "Run the tests and report the results" },
188
+ { llm, compaction, agent: codingAgent, sandbox },
94
189
  );
95
- console.log(result.summary, result.checkpointRef);
190
+ console.log(result.summary, result.reason, result.evidence);
96
191
  } finally {
97
192
  await sandbox.destroy();
98
193
  }
99
194
  ```
100
195
 
101
- Runnable versions of both snippets live in [`examples/`](https://github.com/hrithikkoduri/anchor-ai-sdk/tree/main/examples), including
102
- a non-coding agent (its own strategy and tool, no sandbox).
196
+ Provider boot settings and optional capabilities differ. Retention needs a cleanup
197
+ policy; remote machines and model calls can cost money. Private HTTPS repositories
198
+ can use `spec.git.token`, a caller-supplied credential callback.
199
+
200
+ The latest Docker adapter also supports `spec.egress: "none"`: it disconnects
201
+ container networks after `start()` setup. Other adapters reject that setting.
202
+ This is not a claim that all providers offer equivalent isolation.
203
+
204
+ **[Configuration examples](https://anchor-sdk.dev/docs.html#machine)** ·
205
+ **[Provider contract and image guide](https://github.com/hrithikkoduri/anchor-ai-sdk/blob/main/src/sandbox/README.md)**
206
+
207
+ ## Make the agent yours
208
+
209
+ ```text
210
+ Agent = name + strategy + tools
211
+ Strategy = system prompt + phases + optional context preparation
212
+ Tool = model-visible JSON Schema + your handler
213
+ runAgent = task + agent + model + optional sandbox + runtime settings
214
+ ```
215
+
216
+ Use `chatAgent` or `codingAgent`, compose a bundle, or pass `strategy` and `tools`
217
+ separately. A custom agent is not required to be a coding agent or use a machine.
218
+
219
+ <details>
220
+ <summary><strong>Example: a small policy assistant with its own tool</strong></summary>
221
+
222
+ This fragment uses `llm` and `compaction` from the quickstart. The policy is demo
223
+ data; a real service needs your authorization, validation, and output limits.
224
+
225
+ ```js
226
+ import { defineTool, requireString } from "anchor-ai-sdk";
227
+
228
+ const policies = new Map([
229
+ ["refunds", "Refunds are available within 30 days with the original receipt."],
230
+ ]);
231
+
232
+ const lookupPolicy = defineTool(
233
+ {
234
+ name: "lookup_policy",
235
+ description: "Look up a policy by topic.",
236
+ parameters: {
237
+ type: "object",
238
+ properties: { topic: { type: "string" } },
239
+ required: ["topic"],
240
+ },
241
+ },
242
+ async (input) => {
243
+ const policy = policies.get(requireString(input, "topic").trim().toLowerCase());
244
+ return { ok: policy !== undefined, content: policy ?? "No policy found." };
245
+ },
246
+ );
247
+
248
+ const policyAgent = {
249
+ name: "policy-assistant",
250
+ strategy: {
251
+ name: "policy",
252
+ phases: [{ id: "answer", goal: "Answer using the retrieved policy." }],
253
+ requiredTools: ["lookup_policy"],
254
+ systemPrompt: () =>
255
+ "Look up policy before making policy claims. Say when it is unknown.",
256
+ },
257
+ tools: [lookupPolicy],
258
+ };
259
+
260
+ const answer = await runAgent(
261
+ { task: "Can I get a refund after 20 days?" },
262
+ { llm, compaction, agent: policyAgent },
263
+ );
264
+ console.log(answer.summary);
265
+ ```
266
+
267
+ The schema is declared to the model; `defineTool` does not perform full JSON
268
+ Schema validation. It catches thrown handler errors as failed tool results.
269
+ Validate inputs and enforce permissions in your implementation.
270
+
271
+ </details>
272
+
273
+ **[Tools and schemas](https://anchor-sdk.dev/docs.html#tools)** ·
274
+ **[Runnable SDK examples](https://github.com/hrithikkoduri/anchor-ai-sdk/tree/main/examples)**
275
+
276
+ ## Configure the runtime, not another loop
277
+
278
+ | Capability | Your controls |
279
+ | --- | --- |
280
+ | **Budgets and correction** | Step/token/known-dollar budgets, repeated-call detection, silent-tool and visible-verbosity nudges, prose checkpoints, and per-call output requests. |
281
+ | **Context and memory** | Catalog-derived or explicit compaction, supplied thread stores, agent-authored notes, and best-effort output spill. |
282
+ | **Revival and resume** | Opt-in budget warnings, `request_revival`, `planRevival`, and `ThreadBudget`. Your app persists state and decides whether to fund or enqueue another run. |
283
+ | **Snapshots and lifecycle** | Git-backed or custom snapshot hooks, optional push, phase-boundary pauses, heartbeats, and cancellation. |
284
+ | **Streaming and traces** | Raw `onEvent` updates; sequenced, pattern-redacted `traceSinks`; NDJSON files, custom sinks, live projection, and replay. |
285
+ | **Execution evidence** | Tool names/status/metadata plus recorded test-command outcomes and optional post-run Git freshness checks. Your app defines verification and release gates. |
103
286
 
104
- ## Providers
287
+ <details>
288
+ <summary><strong>Example: limits, nudges, compaction, and a recorded trace</strong></summary>
105
289
 
106
- | subpath | backend | status |
290
+ Runtime options for a model and started sandbox configured by your host:
291
+
292
+ ```js
293
+ import { JsonlTrace } from "anchor-ai-sdk";
294
+ import { randomUUID } from "node:crypto";
295
+
296
+ const tracePath = "./traces/" + randomUUID() + ".ndjson";
297
+
298
+ const result = await runAgent({ task: "Investigate the failing test" }, {
299
+ llm, sandbox, agent: codingAgent,
300
+ budgets: { maxSteps: 60, maxUsd: 3 },
301
+ compaction: { maxEstimatedTokens: 60_000, keepRecentTokens: 15_000 },
302
+ guardrails: {
303
+ repeatedToolCallLimit: 3,
304
+ toolOnlyNudgeLimit: 8,
305
+ verboseTurnLimit: 8_000,
306
+ proseCheckpoint: true,
307
+ },
308
+ maxOutputTokens: 16_000,
309
+ traceSinks: [new JsonlTrace(tracePath)],
310
+ onEvent(event) {
311
+ if (event.type === "llm_text_delta") process.stdout.write(event.text);
312
+ },
313
+ });
314
+ ```
315
+
316
+ Run usage is checked between calls and can overshoot a budget. Output limits are
317
+ requested from the provider. Compaction is lossy summarization using estimated
318
+ token sizes; persistence and spill require working storage. Trace redaction is
319
+ pattern-based, not a guarantee that all secrets are removed; raw `onEvent` events
320
+ are not redacted. Use distinct trace files/IDs per run in your application.
321
+
322
+ </details>
323
+
324
+ **[Compaction](https://anchor-sdk.dev/docs.html#compaction)** ·
325
+ **[Course correction](https://anchor-sdk.dev/docs.html#correction)** ·
326
+ **[Revival and host-side resume](https://anchor-sdk.dev/docs.html#revival)** ·
327
+ **[Streaming and detailed traces](https://anchor-sdk.dev/docs.html#traces)**
328
+
329
+ > **A completed run is not a correctness certificate.** `run_tests` records a
330
+ > command outcome and, when available, a Git hash captured **after** the command.
331
+ > `latestEvidenceForTree` can compare it with the current tree, but does not prove
332
+ > an immutable tested snapshot, require a full suite, or inspect assertions.
333
+
334
+ ## Test the machine contract
335
+
336
+ `runSandboxConformance(provider)` from `anchor-ai-sdk/testing` exercises the
337
+ public sandbox contract: command results, deadlines, environment handling, Git
338
+ checkpoints, retention/adoption, and declared optional capabilities.
339
+
340
+ The repository configures local and Docker conformance in CI. Heyo and E2B live
341
+ suites are opt-in and credential-gated; having a suite is not certification that
342
+ every account, image, or template has passed it. Custom providers can use the
343
+ same contract and tests without rewriting the agent engine.
344
+
345
+ **[Conformance setup](https://anchor-sdk.dev/docs.html#conformance)** ·
346
+ **[Test source](https://github.com/hrithikkoduri/anchor-ai-sdk/tree/main/tests)**
347
+
348
+ ## Evaluated, with the limits visible
349
+
350
+ The completed **Parasail GLM 5.3 / SWE-bench Lite-10** records show:
351
+
352
+ | | Anchor | mini-swe-agent |
107
353
  | --- | --- | --- |
108
- | `anchor-ai-sdk/sandbox/heyo` | Heyo Cloud microVMs (Firecracker) | production path; conformance + live tests |
109
- | `anchor-ai-sdk/sandbox/docker` | containers on the local daemon | conformance-tested in CI |
110
- | `anchor-ai-sdk/sandbox/e2b` | E2B cloud sandboxes (remote) | conformance-tested live (`base` template); opt-in in CI |
111
- | `anchor-ai-sdk/sandbox/local` | this host, **no isolation** | dev / CI only |
112
- | n/a | Daytona | planned |
113
-
114
- E2B boots a **template**, not an arbitrary Docker image at create time: pass
115
- `template` (or let `SandboxSpec.image` name one) and build it with the tools
116
- the task needs. Pause/resume powers retain/adopt, and native sandbox forking
117
- powers `fork()`.
118
-
119
- A backend is "done" when `runSandboxConformance(provider)` from
120
- `anchor-ai-sdk/testing` passes: exec fidelity, kill deadlines, env secrets,
121
- checkpoints, retain/adopt, git identity, pushes, and `fork` where declared.
122
- See [`src/sandbox/README.md`](https://github.com/hrithikkoduri/anchor-ai-sdk/blob/main/src/sandbox/README.md) for the provider guide.
123
-
124
- ## Proof, not promises
125
-
126
- - **Tests:** the engine, tools, trace layer, and every provider scenario run
127
- in CI: local conformance always, Docker conformance on a purpose-built
128
- image. Live microVM conformance is opt-in with your own Heyo credentials
129
- (the suite skips without them).
130
- - **Genericity guards:** executable rules keep the package consumer-neutral:
131
- no product nouns, no env reads, SDKs behind subpaths. They run in the same
132
- suite as everything else.
133
- - **Evaluation:** Anchor vs mini-swe-agent on SWE-bench Lite (same
134
- container, same model, same official grader), resolved the same 7/10 at
135
- roughly **one-third of the cost**
136
- ([`evals/benchmarks/swe-bench/results/comparison.md`](https://github.com/hrithikkoduri/anchor-ai-sdk/blob/main/evals/benchmarks/swe-bench/results/comparison.md)).
137
-
138
- ## Status
139
-
140
- **Early alpha (0.x).** The API will move before 1.0, so pin exact versions. The
141
- [`DECISIONS.md`](https://github.com/hrithikkoduri/anchor-ai-sdk/blob/main/DECISIONS.md) is the engineering log: every API has a written
142
- reason, and the reasons moved with the code when this repo was extracted from
143
- its monorepo.
144
-
145
- - Repo / issues: [github.com/hrithikkoduri/anchor-ai-sdk](https://github.com/hrithikkoduri/anchor-ai-sdk)
146
- - Docs: [anchor-sdk.dev](https://anchor-sdk.dev) (coming)
147
- - License: MIT
354
+ | **Resolved** | **9 / 10** | **9 / 10** |
355
+ | Remaining task | Sympy patch unresolved | Matplotlib patch extraction failed; empty patch |
356
+ | Recorded usage | 11,363,962 tokens; $4.3162 | Not reported; no cost ratio claimed |
357
+ | Published limits | 120 steps / 25M tokens | Its own 250-step / $3 loop |
358
+
359
+ Both records name `zai-org/GLM-5.3` on Parasail, temperature 0, seed 42, and
360
+ Docker-backed tasks. This is a selected ten-task sample with different budgets
361
+ and an extraction failure, not the full benchmark or a general superiority
362
+ claim. The older 7/10 OpenRouter cost comparison is separate historical context.
363
+
364
+ **[Benchmark explorer](https://anchor-sdk.dev/benchmarks.html)** ·
365
+ **[Anchor artifacts](https://github.com/hrithikkoduri/anchor-ai-sdk/tree/main/evals/benchmarks/swe-bench/results/glm53-parasail)** ·
366
+ **[Mini artifacts](https://github.com/hrithikkoduri/anchor-ai-sdk/tree/main/evals/benchmarks/swe-bench/results/glm53-mini)**
367
+
368
+ ## Status and contributing
369
+
370
+ **Early alpha, 0.x.** Pin exact versions and test your integration. Provider and
371
+ runtime features are version-specific; the current source may be ahead of the
372
+ installed package. Daytona, agent teams, and consumer skill bundles remain
373
+ roadmap work in the reviewed source.
374
+
375
+ Questions, bug reports, and provider contributions are welcome. Keep mechanics in
376
+ the SDK and product policy in the consumer; provider changes should include
377
+ conformance coverage and an honest capability declaration.
378
+
379
+ **[Issues](https://github.com/hrithikkoduri/anchor-ai-sdk/issues)** ·
380
+ **[Roadmap](https://github.com/hrithikkoduri/anchor-ai-sdk/blob/main/ROADMAP.md)** ·
381
+ **[Engineering decisions](https://github.com/hrithikkoduri/anchor-ai-sdk/blob/main/DECISIONS.md)** ·
382
+ **[MIT license](https://github.com/hrithikkoduri/anchor-ai-sdk/blob/main/LICENSE)**
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Background-job delivery — the engine's half.
3
+ *
4
+ * A provider with `pollJob` lets `bash` detach a long command: the tool returns
5
+ * immediately with a job id. From there the engine owns the result: it polls
6
+ * outstanding jobs at every turn boundary, injects a finished one as a
7
+ * runtime-only message (never persisted, never shown to the user), and — when
8
+ * the run is about to end — waits for what is left instead of dropping it.
9
+ *
10
+ * Nothing here is provider-specific: `pollJob` returning `undefined` means
11
+ * "still running", and a throw means the receipt is gone.
12
+ */
13
+ import type { ExecResult, Sandbox } from "../sandbox/types.js";
14
+ import type { BackgroundJob } from "./types.js";
15
+ /** How long to sleep between polls while waiting for a run-ending job. */
16
+ export declare const BACKGROUND_POLL_MS = 500;
17
+ export interface FinishedJob {
18
+ job: BackgroundJob;
19
+ result: ExecResult;
20
+ }
21
+ export interface LostJob {
22
+ job: BackgroundJob;
23
+ error: string;
24
+ }
25
+ export interface PollOutcome {
26
+ finished: FinishedJob[];
27
+ running: BackgroundJob[];
28
+ lost: LostJob[];
29
+ }
30
+ /** One non-blocking round over every outstanding job. */
31
+ export declare function pollBackgroundJobs(sandbox: Sandbox | undefined, jobs: BackgroundJob[]): Promise<PollOutcome>;
32
+ /**
33
+ * Wait for the outstanding jobs, bounded by each job's own deadline + grace.
34
+ * Used only when a run is about to finish normally: ending the run while a
35
+ * job is still out there would silently lose its result.
36
+ */
37
+ export declare function waitForBackgroundJobs(sandbox: Sandbox | undefined, jobs: BackgroundJob[], opts?: {
38
+ signal?: AbortSignal;
39
+ sleep?: (ms: number) => Promise<void>;
40
+ }): Promise<PollOutcome>;
41
+ /**
42
+ * The runtime message the model sees. The body goes through the SAME
43
+ * truncate-and-spill path as a normal tool result, so a long test log is
44
+ * bounded and the full text stays addressable in scratch.
45
+ */
46
+ export declare function renderBackgroundResult(job: FinishedJob, sandbox: Sandbox | undefined): Promise<string>;
47
+ export declare function renderLostJob(job: LostJob): string;
48
+ //# sourceMappingURL=background.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"background.d.ts","sourceRoot":"","sources":["../../src/engine/background.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAG/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,0EAA0E;AAC1E,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAItC,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,aAAa,CAAC;IACnB,MAAM,EAAE,UAAU,CAAC;CACpB;AAED,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,aAAa,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,IAAI,EAAE,OAAO,EAAE,CAAC;CACjB;AAED,yDAAyD;AACzD,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,OAAO,GAAG,SAAS,EAC5B,IAAI,EAAE,aAAa,EAAE,GACpB,OAAO,CAAC,WAAW,CAAC,CAyBtB;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,OAAO,GAAG,SAAS,EAC5B,IAAI,EAAE,aAAa,EAAE,EACrB,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,WAAW,CAAC;IAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAAO,GACzE,OAAO,CAAC,WAAW,CAAC,CAwCtB;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,WAAW,EAChB,OAAO,EAAE,OAAO,GAAG,SAAS,GAC3B,OAAO,CAAC,MAAM,CAAC,CAWjB;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAKlD"}
@@ -0,0 +1,98 @@
1
+ import { formatExec } from "../tools/types.js";
2
+ import { spillOrTruncate } from "../tools/truncate.js";
3
+ /** How long to sleep between polls while waiting for a run-ending job. */
4
+ export const BACKGROUND_POLL_MS = 500;
5
+ /** Grace past the job's own deadline before we call the receipt lost. */
6
+ const DEADLINE_GRACE_MS = 30_000;
7
+ /** One non-blocking round over every outstanding job. */
8
+ export async function pollBackgroundJobs(sandbox, jobs) {
9
+ if (!sandbox?.pollJob || jobs.length === 0) {
10
+ return { finished: [], running: [...jobs], lost: [] };
11
+ }
12
+ const finished = [];
13
+ const running = [];
14
+ const lost = [];
15
+ for (const job of jobs) {
16
+ try {
17
+ const result = await sandbox.pollJob(job.jobId, {
18
+ startedAt: job.startedAt,
19
+ ...(job.timeoutSecs !== undefined
20
+ ? { timeoutSecs: job.timeoutSecs }
21
+ : {}),
22
+ });
23
+ if (result)
24
+ finished.push({ job, result });
25
+ else
26
+ running.push(job);
27
+ }
28
+ catch (err) {
29
+ lost.push({
30
+ job,
31
+ error: err instanceof Error ? err.message : String(err),
32
+ });
33
+ }
34
+ }
35
+ return { finished, running, lost };
36
+ }
37
+ /**
38
+ * Wait for the outstanding jobs, bounded by each job's own deadline + grace.
39
+ * Used only when a run is about to finish normally: ending the run while a
40
+ * job is still out there would silently lose its result.
41
+ */
42
+ export async function waitForBackgroundJobs(sandbox, jobs, opts = {}) {
43
+ // Without a poller there is nothing to wait on; end the run and report the
44
+ // jobs instead of sleeping until a deadline that can never be observed.
45
+ if (!sandbox?.pollJob || jobs.length === 0) {
46
+ return { finished: [], running: [...jobs], lost: [] };
47
+ }
48
+ const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
49
+ const finished = [];
50
+ const lost = [];
51
+ let remaining = [...jobs];
52
+ while (remaining.length > 0) {
53
+ if (opts.signal?.aborted)
54
+ break;
55
+ const outcome = await pollBackgroundJobs(sandbox, remaining);
56
+ finished.push(...outcome.finished);
57
+ lost.push(...outcome.lost);
58
+ remaining = outcome.running;
59
+ if (remaining.length === 0)
60
+ break;
61
+ const now = Date.now();
62
+ const expired = remaining.filter((job) => now >
63
+ job.startedAt + ((job.timeoutSecs ?? 3600) + 30) * 1000 +
64
+ DEADLINE_GRACE_MS);
65
+ for (const job of expired) {
66
+ lost.push({
67
+ job,
68
+ error: "no result before the job's deadline",
69
+ });
70
+ }
71
+ if (expired.length > 0) {
72
+ remaining = remaining.filter((job) => !expired.includes(job));
73
+ if (remaining.length === 0)
74
+ break;
75
+ }
76
+ await sleep(BACKGROUND_POLL_MS);
77
+ }
78
+ return { finished, running: remaining, lost };
79
+ }
80
+ /**
81
+ * The runtime message the model sees. The body goes through the SAME
82
+ * truncate-and-spill path as a normal tool result, so a long test log is
83
+ * bounded and the full text stays addressable in scratch.
84
+ */
85
+ export async function renderBackgroundResult(job, sandbox) {
86
+ const { result } = job;
87
+ const label = job.job.command ? ` (${job.job.command})` : "";
88
+ const status = result.timedOut
89
+ ? `timed out (exit ${result.exitCode})`
90
+ : `exit ${result.exitCode}`;
91
+ const body = await spillOrTruncate(formatExec(result, job.job.timeoutSecs ?? 120), { sandbox, label: `background ${job.job.tool}` });
92
+ return `[runtime] Background job ${job.job.jobId}${label} finished: ${status}.\n${body}`;
93
+ }
94
+ export function renderLostJob(job) {
95
+ return (`[runtime] Background job ${job.job.jobId} is no longer readable: ${job.error}. ` +
96
+ `Do not assume it completed; check the workspace or rerun it if the result matters.`);
97
+ }
98
+ //# sourceMappingURL=background.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"background.js","sourceRoot":"","sources":["../../src/engine/background.ts"],"names":[],"mappings":"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"}