Source: https://anchor-sdk.dev/docs.html
Package: anchor-ai-sdk@0.1.7

# Quickstart

Run your first agent, then learn the pieces. Build from phases, strategies, and tools to a complete harness you can own and extend.

Node 20+ESMSDK 0.1.7Provider API key required

01

## Install

[](https://anchor-sdk.dev/docs.html#install)

`npm install anchor-ai-sdk@0.1.7`

02

## Your first agent

[](https://anchor-sdk.dev/docs.html#quickstart)

No sandbox needed. Configure an OpenAI-compatible model, hand it a task, and read the result.

chat.mjsJAVASCRIPT

```
import { chatAgent, llmFromCatalog, runAgent } from "anchor-ai-sdk";

const { llm, compaction } = await llmFromCatalog({
  baseUrl: "https://openrouter.ai/api/v1",
  apiKey: process.env.OPENROUTER_KEY,
  model: "z-ai/glm-5.3",
});

const result = await runAgent(
  { task: "Explain exponential backoff in plain English" },
  { llm, compaction, agent: chatAgent },
);

console.log(result.summary);
```

Save the file as `chat.mjs`, then set your provider key and run it. `.mjs` tells Node to use JavaScript ES modules; `.js` also works in a project with`"type": "module"`. Model calls may incur cost.

terminalSHELL

```
export OPENROUTER_KEY="your-key"
node chat.mjs
```

WHAT COMES BACK

`summary`The agent’s answer`evidence`Tool name, status, and metadata`usage`Tokens and known cost`reason`Why the run ended

`llmFromCatalog` uses the provider catalog’s pricing and context-window metadata when available. Pass its `compaction` configuration into the engine, as above. Metadata failures fall back rather than breaking the run.

[Next: unpack the bundle and build from primitives](https://anchor-sdk.dev/docs.html#primitives)

Want the reasoning behind these APIs? Read the [visual guide to Anchor's philosophy](https://anchor-sdk.dev/philosophy.html)— then return here to assemble the pieces.

03

## How the pieces fit

[](https://anchor-sdk.dev/docs.html#primitives)

The quickstart used a ready-made bundle. Now build one yourself. An agent is **behavior + capabilities**; a harness is the runtime wiring that lets it work with a model, resources, state, and limits. You do not need every optional feature to start.

1.  01
    
    [Phases → strategy](https://anchor-sdk.dev/docs.html#phases)
    
    Name the kinds of work, then put their goals into the prompt.
    
2.  02
    
    [Tools → agent](https://anchor-sdk.dev/docs.html#custom-agents)
    
    Implement capabilities and bundle them with the strategy.
    
3.  03
    
    [Model + machine → run](https://anchor-sdk.dev/docs.html#machine)
    
    Supply live dependencies and let the engine execute the loop.
    
4.  04
    
    [State + controls → harness](https://anchor-sdk.dev/docs.html#assembled)
    
    Add continuity, delivery, observability, and resource ownership.
    

`phases + systemPrompt → strategy``strategy + tools → agent``task + llm + agent + options → runAgent → result`

A `StrategyPhase` is data, a `Tool` performs an operation, and the engine runs the loop. A `Sandbox` is an optional execution dependency used by machine tools—not a different kind of agent. Stores, traces, and budgets plug into run options rather than the reusable bundle.

Reusable

Phase data, strategy instructions, and tool definitions belong in your agent module.

Per call

A task, model client (LLMClient), optional sandbox, and EngineOptions configure this execution.

Across calls

A stable thread ID and your storage/worker policies connect executions without making the agent a stateful singleton.

04

## Phases become a strategy

[](https://anchor-sdk.dev/docs.html#phases)

### 1\. Name the kinds of work

A phase needs an `id` and `goal`, with optional `guidance`. Start with the smallest useful vocabulary. This policy assistant only needs lookup and answer; a repository agent might use understand, inspect, validate, and respond.

policy-agent.mjs · phasesJAVASCRIPT

```
export const policyPhases = [
  { id: "lookup", goal: "Find the relevant policy with lookup_policy." },
  {
    id: "answer",
    goal: "Give a short answer grounded in the retrieved policy.",
  },
];
```

### 2\. Make those phases part of a strategy

The strategy binds phase data to instructions. Assign the array to `phases`, and render its goals in `systemPrompt` so the model sees your intended approach. Requirements are checked at startup; they do not force a tool call or verify the answer.

policy-agent.mjs · strategyJAVASCRIPT

```
// Strategy consumes the phase data; it does not execute a list of steps.
export const policyStrategy = {
  name: "policy",
  phases: policyPhases,
  requiredTools: ["lookup_policy"],
  requiresSandbox: false,
  systemPrompt({ context, summary }) {
    return [
      "You are a store policy assistant working with demo data.",
      "Use lookup_policy before making a claim about store policy.",
      "If the policy is unknown, say so. Never invent terms or exceptions.",
      "Keep the answer short and cite the topic you looked up.",
      ...policyPhases.map((phase) => `${phase.id}: ${phase.goal}`),
      context ? `Authorized application context: ${context}` : "",
      summary ? `Earlier thread context: ${summary}` : "",
    ]
      .filter(Boolean)
      .join("\n");
  },
};
```

`phases → progress vocabulary`

The engine uses phase IDs and exposes set\_phase for a multi-phase strategy by default. Calling it updates the current label and emits phase\_started; it does not invoke an executable phase handler.

`systemPrompt → model instructions`

The engine calls this with task, application context, and an earlier thread summary. Read the values your strategy needs. Declaring a phase goal is not a substitute for including it in the prompt.

`requiredTools / requiresSandbox → startup checks`

Catch missing dependencies before model execution. These are availability checks, not per-phase permissions or proof the model used the capability.

Phases guide the approach; they are not a fixed pipeline. The model can skip, revisit, or finish from any phase. A one-phase strategy works too. For an enforced approval or release gate, implement host-side policy rather than relying on a phase label.

[Next: give this strategy a tool and assemble the agent](https://anchor-sdk.dev/docs.html#custom-agents)

05

## Build your own agent

[](https://anchor-sdk.dev/docs.html#custom-agents)

You now have `policyPhases` inside `policyStrategy`. Give that strategy a real capability, then export the pair as a plain agent object. This continues the same policy assistant in the same file; it uses demo data and needs no sandbox.

Strategy

Your system prompt, phases, and startup requirements.

Phase

A named kind of work, not a permission or an executable step.

Tool

A schema the model sees and a handler you implement.

Agent

A reusable name + strategy + tools bundle.

### 3\. Implement the capability

The strategy refers to `lookup_policy`; a tool gives that name an input schema and executable handler. Its `content` becomes the model's observation. Here it reads demo policies; in your app it could call an authorized service.

policy-agent.mjs · toolJAVASCRIPT

```
// 01 / TOOL
// Demo data only. Replace this with your authorized data service.
const policies = new Map([
  [
    "refunds",
    "Refunds are available within 30 days with the original receipt.",
  ],
  ["shipping", "Standard shipping takes 3–5 business days."],
]);

export const lookupPolicy = defineTool(
  {
    name: "lookup_policy",
    description: "Look up a demo store policy: refunds or shipping.",
    parameters: {
      type: "object",
      properties: { topic: { type: "string" } },
      required: ["topic"],
      additionalProperties: false,
    },
  },
  async (input) => {
    const topic = requireString(input, "topic").trim().toLowerCase();
    const policy = policies.get(topic);
    return {
      ok: policy !== undefined,
      content: policy ?? "No policy found. Do not invent one.",
      meta: { topic, source: "demo-policies" },
    };
  },
  { readOnly: true },
);
```

The `defineTool` and `requireString` helpers are root exports from `anchor-ai-sdk`. For stricter schema validation, service tools, and sandbox-backed handlers, see the [tool reference](https://anchor-sdk.dev/docs.html#tools).

### 4\. Bundle it, then supply the runtime

`agent.strategy` holds the behavior; `agent.tools`holds the capabilities. Neither boots a sandbox nor calls a model. Your host supplies `llm`, a task, and per-call options to`runAgent`. Read Agent → Run below to see the connection.

Export the bundle. It contains no model instance or per-run state.

policy-agent.mjsJAVASCRIPT

```
// 03 / AGENT
// No model or runtime state in the bundle. Reuse it across runs.
export const policyAgent = {
  name: "policy-agent",
  strategy: policyStrategy,
  tools: [lookupPolicy],
};
```

All tabs belong to one file.[Download example](https://anchor-sdk.dev/examples/policy-agent.mjs)

### 

### 

### 

Your host still owns model credentials, authorization, durable storage, and decisions such as funding revival. Anchor supplies the run loop, tool dispatch, intrinsic control tools, and runtime guardrails.

[Next: give tools a shared execution environment](https://anchor-sdk.dev/docs.html#machine)

06

## Give it a machine

[](https://anchor-sdk.dev/docs.html#machine)

Choose a provider and configure its template or image. The result is the same `Sandbox` interface, passed to the same `runAgent` call. Your toolchain, credentials, and provider account are still yours to supply.

This is another dependency, not another loop: the host starts a machine, passes it as `options.sandbox`, and machine tools use`ctx.sandbox`. The examples below reuse `codingAgent`to isolate provider setup. The [final recipe](https://anchor-sdk.dev/docs.html#assembled)replaces that bundle with an agent assembled from your own phases, strategy, and tools.

**Prebuilt Heyo image artifact.** Set HEYO\_IMAGE and HEYO\_IMAGE\_STORE to an artifact and store you can access. Heyo configures its image on the provider; it rejects per-sandbox image and workspacePath. Host-built dockerfile/git image kinds are also available, with different boot requirements.

install-heyoSHELL

```
npm install anchor-ai-sdk@0.1.7 @heyocomputer/hws
```

heyo-provider.mjsJAVASCRIPT

```
import { heyo } from "anchor-ai-sdk/sandbox/heyo";

// Configuration fragment; requiredEnv is included in the complete file.
const provider = heyo({
  token: requiredEnv("HEYO_TOKEN"),
  server: requiredEnv("HEYO_SERVER"),
  namespace: requiredEnv("HEYO_NAMESPACE"),
  image: requiredEnv("HEYO_IMAGE"),
  imageKind: "artifact",
  imageStore: requiredEnv("HEYO_IMAGE_STORE"),
  sizeClass: "small",
  diskSizeGb: 10,
});
```

Set `OPENROUTER_KEY, REPO_URL, HEYO_TOKEN, HEYO_SERVER, HEYO_NAMESPACE, HEYO_IMAGE, HEYO_IMAGE_STORE`. Use a repository and ref you can access; the downloaded example defaults to `main`. Private repositories also need `spec.git.token`.

Complete Heyo agent, not just a fragment.[Download .mjs](https://anchor-sdk.dev/examples/heyo-agent.mjs)

Save and run the downloaded file with `node heyo-agent.mjs` after setting its environment variables. `.mjs` is standard JavaScript using ES modules, not an Anchor requirement. With `"type": "module"` in your package.json, `.js` works too. Cloud sandbox and model calls may incur cost.

Provider peers can require a newer host runtime than Anchor's Node 20+ baseline. For example, E2B 2.58.0 (allowed by the install range above) requires Node 22.19+. Check the resolved peer's engines and pin its version in your lockfile; the sandbox's image runtime is separate.

### 

### 

### 

### 

[Next: connect the loop and bound its work](https://anchor-sdk.dev/docs.html#engine)

07

## Loop & budgets

[](https://anchor-sdk.dev/docs.html#engine)

The loop is driven by model responses and tool observations, with phases describing the current approach. The strategy defines the vocabulary; the engine runs model → tools → observations → model. It does not execute the phase array from top to bottom.

how-the-loop-connects.mjsJAVASCRIPT

```
import { runAgent } from "anchor-ai-sdk";
import { reviewAgent } from "./repository-harness.mjs";

// reviewAgent is { name, strategy, tools }, assembled below.
// llm and sandbox are supplied by your host, not stored in the agent.
const result = await runAgent({ task: "Investigate the retry behavior" }, {
  llm, agent: reviewAgent, sandbox,
});

// Runtime sequence (explanation, not a second loop for you to implement):
// 1. strategy.systemPrompt + thread/context + task -> model request
// 2. Model chooses tool calls -> matching tool.run(input, { sandbox, ... })
// 3. Tool content -> observations in the next model request
// 4. set_phase changes the approach label; it does not run a phase function
// 5. A final response/finish, stop condition, or budget ends this run
console.log(result.reason, result.summary, result.usage);
```

See [phases → strategy](https://anchor-sdk.dev/docs.html#phases) for the definition and [the assembled harness](https://anchor-sdk.dev/docs.html#assembled) for the full call. `startPhase` chooses a starting label; `pauseAfterPhase` can pause when the agent leaves a named phase. Neither makes earlier phases mandatory.

### Bound the work, not the reusable agent

**40**STEPS / CALL

**3M**TOKENS / CALL

**$5**KNOWN COST / CALL

These are defaults, not hard-coded policy. Override `budgets` per call (one run, or a shared inbox chain). Dollar checks require usable pricing; step and token limits still apply without it. Usage is checked between model turns, so a call can overshoot a run budget. The output cap is a separate per-call request limit.

run-options.tsTYPESCRIPT

```
// Inside runAgent(task, options):
budgets: { maxSteps: 60, maxTokens: 3_000_000, maxUsd: 3 },
sampling: { temperature: 0, seed: 42 },
```

Sampling is optional; an unset field uses the provider’s default. `set_phase` keeps the phase label current. Use `pauseAfterPhase` and `startPhase` for controlled pauses and resume.

[Next: assemble the complete harness](https://anchor-sdk.dev/docs.html#assembled)

08

## Assemble the end-to-end harness

[](https://anchor-sdk.dev/docs.html#assembled)

Now apply the same recipe to a repository reviewer: phase data → strategy → a curated tool set → an agent. The host adds a configured provider, model client, stores, controls, and traces. This example builds its own agent rather than hiding the composition behind `codingAgent`.

`reviewPhases → reviewStrategy → reviewAgent``reviewAgent + model + started sandbox + state → reviewRepository`

repository-harness.mjs · runtime wiringJAVASCRIPT

```
// 04 / HARNESS — the host owns resources; runAgent owns the model/tool loop.
export async function reviewRepository({
  llm, compaction, provider, repoUrl, ref = "main", task, context,
  threadId, threadStore, inboxStore, traceSinks = [], signal, onEvent,
}) {
  const sandbox = provider.create({ repoUrl, ref });
  try {
    await sandbox.start();
    const result = await runAgent({ task, context }, {
      llm, compaction, agent: reviewAgent, sandbox, signal,
      budgets: { maxSteps: 40, maxTokens: 3_000_000, maxUsd: 2 },
      guardrails: { repeatedToolCallLimit: 3, toolOnlyNudgeLimit: 8 },
      thread: { store: threadStore, id: threadId },
      inbox: { store: inboxStore, channel: threadId },
      traceSinks, onEvent,
      // Deliberately synchronous: background execution needs retention/resume
      // bookkeeping, rather than adding a threshold to a disposable workspace.
    });
    return {
      result,
      // A completed loop is not proof of correctness. Host policy decides what
      // evidence is sufficient; no publish/deploy operation is in this tool set.
      observedChecks: result.evidence.filter(item => item.tool === "run_tests"),
    };
  } finally {
    await sandbox.destroy();
  }
}
```

`Behavior stays reusable`

reviewAgent holds phases, a prompt, and read/search/bash/test tools. A different model or SandboxProvider does not require a new agent loop.

`Runtime options add capabilities`

ThreadStore carries conversation; inbox chains new tasks; compaction bounds replay; budgets/guardrails bound execution; trace sinks journal what happened. They are independent inputs, not a giant Agent subclass.

`Resource ownership stays explicit`

This recipe creates and destroys one workspace per call, reusing it inside an inbox chain. It deliberately leaves background execution off. Cross-call workspace continuity and background jobs require the retained-machine/resume policy in the dedicated guides.

### 

One file: phases, strategy, agent, harness, and Heyo boot.[Download harness](https://anchor-sdk.dev/examples/repository-harness.mjs)

terminalSHELL

```
npm install anchor-ai-sdk@0.1.7 @heyocomputer/hws
# Set OPENROUTER_KEY and REPO_URL, plus the HEYO_* values from provider setup.
node repository-harness.mjs
```

Use [Heyo setup](https://anchor-sdk.dev/docs.html#machine) for credentials and an accessible toolchain image. Provision dependencies needed by the repository before asking the model to run checks. Running this file boots a cloud machine and calls a model, which may incur cost.

The prompt's “do not edit” instruction is not a read-only security boundary: shell and test commands can write files. Host authorization, worker ownership, timeouts, trusted commands, durable store adapters, trace access/retention, and release policy remain application responsibilities. Memory stores here are demo-only. Inbox runs share a budget; top-level evidence covers the final run, so use your journal for checks across the whole chain.

### Grow it one capability at a time

*   [Incoming messages](https://anchor-sdk.dev/docs.html#messages): authenticate producers, push queue/interject messages, and wake workers after a call ends.
*   [Long-running commands](https://anchor-sdk.dev/docs.html#background): add retained-machine bookkeeping before enabling background execution.
*   [Work beyond one budget](https://anchor-sdk.dev/docs.html#revival): preserve a handoff and let host policy decide whether to fund another call.
*   [Application decisions](https://anchor-sdk.dev/docs.html#evidence): evaluate trusted evidence; a finished loop is not permission to publish.

09

## Messages, context & inbox

[](https://anchor-sdk.dev/docs.html#messages)

SDK **0.1.7** includes native `InboxStore`, `MemoryInboxStore`, and `EngineOptions.inbox`. Your producer pushes messages; the SDK checks interjections at safe turn boundaries and drains pending batches between runs. The host does not need its own abort-and-restart loop for inbox delivery.

native-messages.mjsJAVASCRIPT

```
import { MemoryInboxStore, MemoryThreadStore, runAgent } from "anchor-ai-sdk";

// Create once per host/session, not once per message. Memory stores are demo-only.
const inboxStore = new MemoryInboxStore();
const threadStore = new MemoryThreadStore();
const channel = "thread-42";

// Worker: llm, agent and any started sandbox come from your host setup.
const processing = runAgent({ task: "Investigate the retry behavior" }, {
  llm, agent, sandbox,
  thread: { store: threadStore, id: channel },
  inbox: { store: inboxStore, channel },
  budgets: { maxSteps: 60, maxUsd: 3 }, // shared across this call's chain
});

// Call from your authenticated HTTP/websocket/webhook handler while it runs.
async function onIncomingMessage(message) {
  return inboxStore.push({
    id: message.id, // stable, globally unique ID; retries reuse it
    channel,
    text: message.text,
    delivery: message.delivery, // your policy chooses queue or interject
    ...(message.context !== undefined ? { context: message.context } : {}),
  });
}

const result = await processing;
console.table(result.runs ?? []); // reason, steps, phase, summary, per-run usage
console.log(result.usage); // cumulative usage for the whole chain
// An external signal/cancel stops the chain. Interjections use inbox policy,
// not an AbortController that the consumer needs to manage.
```

`queue`Does not enter the active run. After the run ends, pending messages can be claimed and combined as the next run's task.

`interject`Requests a switch at the next engine boundary. The current turn is not spliced mid-tool-call; a new run follows with the claimed batch.

One configured budget covers the chain; messages do not refill it. The chain stops on cancellation, pause, errors, or exhausted allowance. It can also end when no batch remains. A stalled run may continue if inbox work is available. A zero-progress safeguard limits repeated pre-model interruptions; there is no public `maxRuns` option.

### Implement a production store

`push(message)`

Return queued or duplicate. Store a stable ID, channel, text, optional context, delivery, and your ordering/state data. Use globally unique IDs; completion/release identify messages by ID, not a channel argument.

`hasInterject(channel)`

Cheap existence check at run boundaries. It does not claim/delete messages. Current engine treats a lookup error as no interjection.

`claimAll(channel)`

Atomically claim the pending batch: interjections newest-first, then queued oldest-first. Claimed items cannot be returned to competing claims until released or recovered through your lease policy.

`complete(ids) / release(ids)`

The engine settles after the consuming run: release on error/cancel, otherwise complete. This is not an assertion that every instruction caused a model action. Retrying released work can repeat already-performed side effects; use application idempotency.

`hasPending(channel) · optional`

Allows a normal finish with outstanding jobs to skip its wait when inbox work is pending. Without it, normal wait-before-finish behavior remains. Pending work is not a reservation of budget or a guarantee the next run will start.

Production owns authentication/classification, durable inbox and thread stores, atomic claims/lease recovery, per-thread worker ownership, and waking a worker after a call has returned. Settlement/claim errors are not currently guaranteed to surface as run failures; instrument your adapter. MemoryInboxStore is single-process and not crash-durable. The interface alone is not exactly-once delivery.

### Context, runtime facts, and continuation

Inbox delivery alone does not supply conversation persistence; pass `thread` with a stable ID. The SDK reuses the started sandbox within a chain. The current chain reuses run options but does not explicitly set the next run's startPhase from the preceding result; do not assume every phase label automatically carries. `result.usage` is cumulative; `result.runs` contains per-run usage deltas. Top-level evidence/messages/steps belong to the final run rather than an aggregate of every run.

`origin: "runtime"` marks learned background results, which are retained by ThreadStore. `origin: "engine"` marks transient nudges, filtered from normal cross-run history. Neither is a secrecy flag; the origin marker is not sent to the model provider. Your strategy must consume application context, and your UI must deliberately filter what it displays.

10

## Course correction

[](https://anchor-sdk.dev/docs.html#correction)

A bad turn needn’t waste a good run. The engine can redirect repeated calls, empty turns, nudge-triggered status replies, and a capped prose turn before giving up.

01 / DETECT**Something is stuck.**

02 / REDIRECT**One clear advisory.**

03 / CONTINUE**A bounded next chance.**

guardrail-options.mjsJAVASCRIPT

```
// Runtime options for a configured llm / agent / optional sandbox:
const result = await runAgent({ task }, {
  llm, agent, sandbox,
  guardrails: {
    repeatedToolCallLimit: 3,
    toolOnlyNudgeLimit: 8,
    verboseTurnLimit: 8_000,
    proseCheckpoint: true,
  },
  maxOutputTokens: 16_000,   // provider request limit, not replay size
  maxAssistantChars: 20_000, // replay limit for one assistant reply
  truncatedReplyChars: 12_000, // head-only replay after an output cap
});
```

`repeatedToolCallLimit`

Default 3. Byte-identical name/argument calls are counted. The repeat is skipped, one redirect is allowed, and another loop can stall the run. This is not semantic detection of every unproductive action.

`toolOnlyNudgeLimit`

Default 8 consecutive tool-call turns without visible text. Injects a one-shot status/action advisory; it is not a requirement to respond every eight turns.

`verboseTurnLimit`

Default 8,000 visible output tokens. Current source subtracts provider-reported reasoningTokens from completionTokens and clamps at zero. If reasoning usage is absent, it cannot be subtracted. Fires once per run.

`proseCheckpoint`

Default true. With the finish intrinsic available, a prose-only reply following an engine nudge gets one confirmation/correction turn before it is accepted as an answer. Not a gate on every normal prose reply.

`maxOutputTokens`

Default 16,000, requested from the provider for each main generation. A capped prose turn can continue once when proseCheckpoint is enabled; repeated capped prose can still stall. A cap can interrupt or omit a tool call.

`maxAssistantChars / truncatedReplyChars`

Defaults 20,000 / 12,000 characters, not tokens. Bound replay of the model’s own text; they are not verbosity nudges or conversation compaction. The stricter truncated-reply limit never raises the main replay limit.

Use positive numbers or `false` for numeric switches; `0` is invalid. Empty-turn recovery is a fixed one-shot path in the reviewed source, not a public `emptyTurnLimit` option. Advisory text and retry counts are not individually configurable through this interface. `onEvent` observes events; it does not replace engine nudges. Put domain instructions in your strategy prompt.

Budget revival warnings use the separate opt-in `revival.warnAtPercent`. Phase-discipline guidance is controlled with `intrinsics.phase`, not a guardrail threshold. These mechanisms guide or bound execution; they do not guarantee progress, correctness, or authorization.

11

## Filesystem memory

[](https://anchor-sdk.dev/docs.html#memory)

Three layers keep a long-running agent oriented without replaying everything it has ever seen.

### Notes on disk

Agents can keep notes at `.anchor/notes.md`. The adapters initially exclude the reserved directory and force-add the notes file to Git checkpoints. Compaction doesn’t rewrite this file; it is not automatically populated.

### Conversation across runs

`ThreadStore` carries the summary, recent messages, and phase when a store is supplied. `MemoryThreadStore` ships for single-process development; durable storage is yours to provide.

### Spill, don’t discard

Oversized tool output is clipped (300 lines / 20k characters by default). The built-in tools attempt a full-output spill to sandbox scratch storage, with a pointer for later reads. Spills are best-effort and limited to 4M characters.

Assistant-text elision bounds the model’s replay, **not the stored transcript**: 20k characters per reply by default, or 12k head-only for a capped reply. Compaction separately summarizes older messages. Full-text spills require sandbox scratch storage and can fail. Durable notes and stores are useful persistence primitives, not a guarantee against data loss.

12

## Context compaction

[](https://anchor-sdk.dev/docs.html#compaction)

Before a model turn, the engine estimates the conversation size. Above the configured threshold, it asks the same model to summarize older messages and retains a recent tail. This is lossy summarization, not a durable archive of the removed detail.

compaction-options.mjsJAVASCRIPT

```
import { chatAgent, llmFromCatalog, runAgent } from "anchor-ai-sdk";

const { llm, compaction } = await llmFromCatalog({
  baseUrl: "https://openrouter.ai/api/v1",
  apiKey: process.env.OPENROUTER_KEY,
  model: "z-ai/glm-5.3",
  fallbackCompaction: {
    maxEstimatedTokens: 60_000,
    keepRecentTokens: 15_000,
  },
});

// Pass the returned config; the factory does not apply it to runAgent for you.
const result = await runAgent(
  { task: "Explain this topic" },
  { llm, agent: chatAgent, compaction },
);
```

`maxEstimatedTokens`When to attempt compaction. Engine fallback: 80,000 estimated tokens.

`keepRecentTokens`Approximate recent conversation to keep. Engine fallback: 30,000 estimated tokens. Boundaries are adjusted, not exact token cuts.

Catalog-derived settings use the advertised context window: approximately 55%, with floor/ceiling rules and a 300k ceiling; the recent tail is 25% of that threshold. For a 128k window the helper returns 70,400 / 17,600. `fallbackCompaction` is used only when catalog window data is absent. The object passed to`runAgent` overrides the engine fallback fields; use object spread to tune the returned catalog configuration before passing it.

context-window.mjsJAVASCRIPT

```
import { compactionForContextWindow } from "anchor-ai-sdk";

// For a known window, without fetching catalog metadata:
const compaction = compactionForContextWindow(128_000);
// { maxEstimatedTokens: 70400, keepRecentTokens: 17600 }

// Or tune the helper's ceiling/floor, then pass the result to runAgent:
const smaller = compactionForContextWindow(128_000, {
  ceiling: 60_000,
  floor: 24_000,
});
```

Estimates use characters divided by four, including tool-call text; they are not a model-specific tokenizer. Reserve space for instructions, tools, and output. A threshold is not a guarantee against context overflow, and a usable fold is not always possible.

thread-compaction.mjsJAVASCRIPT

```
import { MemoryThreadStore, runAgent } from "anchor-ai-sdk";

const store = new MemoryThreadStore(); // create once; in-memory, not durable
const result = await runAgent(
  { task: "Continue the same task" },
  {
    llm,
    agent,
    compaction: { maxEstimatedTokens: 60_000, keepRecentTokens: 15_000 },
    thread: { store, id: "investigation-42" },
    onEvent(event) {
      if (event.type === "compacted") {
        console.log(event.removedMessages, event.keptMessages, event.usage);
      }
    },
  },
);
```

The summary is rebuilt into the strategy prompt; the current request is preserved. A supplied `ThreadStore` saves the summary, remaining messages, and phase. Use durable storage for cross-process continuity and configured trace sinks if you need an earlier journal. Summarization consumes model usage and is charged to the run budget. Disk notes, tool-output spill, and assistant-text elision are separate mechanisms.

13

## Revival & resume

[](https://anchor-sdk.dev/docs.html#revival)

When budget runs low, preserve the handoff — not just the failure. Revival is opt-in, and funding stays in your app.

85% budgetengine warns

Handoffagent states what remains

Your decisionrevive or escalate

Enable the handoff mechanism in `runAgent`, then make the continuation decision in your host. The engine warns near budget limits and offers `request_revival`; it does not fund or schedule another run.

enable-revival.tsTYPESCRIPT

```
// Inside runAgent(task, options):
revival: { warnAtPercent: 85 },
thread: { store: threadStore, id: "task-42" },
budgets: { maxSteps: 40, maxTokens: 3_000_000, maxUsd: 2 },
```

run-with-revival.mjsJAVASCRIPT

```
import {
  codingAgent, MemoryThreadStore, ThreadBudget, planRevival, runAgent,
} from "anchor-ai-sdk";

// Your host supplies a configured provider, model and repository.
export async function runWithRevival({ llm, compaction, provider, repoUrl, task }) {
  const store = new MemoryThreadStore(); // demo only; not durable
  const threadBudget = new ThreadBudget({ maxTurns: 3, maxUsd: 8 });
  const runBudget = { maxSteps: 40, maxTokens: 3_000_000, maxUsd: 2 };
  const sandbox = provider.create({ repoUrl, ref: "main" });
  let startPhase;

  try {
    await sandbox.start();
    for (;;) {
      // Leave insufficient funding to a human, rather than assume a free run.
      if (threadBudget.exhausted || threadBudget.usdLeft < runBudget.maxUsd) {
        return { action: "needs_human", note: "Thread funding is insufficient." };
      }
      const result = await runAgent(
        { task }, // preserve the original task on every run
        {
          llm, compaction, agent: codingAgent, sandbox,
          thread: { store, id: "task-42" },
          budgets: runBudget,
          revival: { warnAtPercent: 85 },
          ...(startPhase ? { startPhase } : {}),
        },
      );

      // A budget ending normally returns an AgentResult; it is not a throw.
      threadBudget.record({ usd: result.usage.costUsd });
      if (result.usage.costUsd === undefined) {
        return { action: "needs_human", note: "Cost is unknown.", result };
      }
      const plan = planRevival({ result, task, threadBudget });
      if (plan.action === "needs_human") {
        return { ...plan, result }; // your UI/worker must deliver this note
      }
      if (plan.action !== "revive") {
        return { action: "stopped", result }; // inspect reason and evidence
      }

      console.log("Continuing:", plan.remaining);
      startPhase = plan.startPhase;
      // Same sandbox, thread, task and per-run budget; next iteration resumes.
    }
  } finally {
    await sandbox.destroy();
  }
}

// With llm / compaction from the model setup and provider from sandbox setup:
// const outcome = await runWithRevival({ llm, compaction, provider, repoUrl, task });
// Do not automatically retry thrown setup/transport exceptions.
```

`revive`A budget-limited result has handoff/checkpoint evidence and thread caps remain. The planner returns the task, phase and remaining-work text.

`needs_human`The planner sees exhausted thread caps for an otherwise revivable result. Your app delivers the note and decides whether funding or scope changes.

`none`No applicable budget ending or handoff evidence. This is not success; inspect result.reason, result.ok and evidence.

`maxTurns / maxUsd`Thread-wide recorded spend, supplied by your app. Missing cost defaults to zero in the tracker; the examples deliberately escalate instead.

The planner reacts to `max_steps`/`budget_exceeded` and a revival request or checkpoint. Requests and retained machines can be absent; do not assume either exists. Run budgets are checked between model calls and may overshoot. ThreadBudget is bookkeeping, not a billing reservation or exactly-once scheduler. A process crash can lose in-memory stores and spend.

With a native inbox, one returned result can cover a chain. Record its cumulative usage once, or sum the per-run deltas in `result.runs`, not both. Outstanding background jobs returned at a final chain boundary must be persisted and supplied as `resumeJobs` after restoring the same retained machine. Inbox continuation is separate from funding a budget-exhausted revival.

For cross-process resume, persist conversation/summary/phase in your ThreadStore, thread spend and the completed run ID in your database, and the workspace via retention or a remote checkpoint. Pass the same thread ID and a started/adopted sandbox to the next run. Explicit `startPhase` selects the label; it does not restore the conversation or repository by itself.

Git-backed snapshots commit changed work at finish or pause boundaries. Optional push carries a checkpoint off-machine. Supply a custom `SnapshotFn` to use another persistence layer.

14

## Test evidence

[](https://anchor-sdk.dev/docs.html#evidence)

`AgentResult.ok` is a runtime outcome, not permission to ship. `result.evidence` records tool names, status, and metadata. `run_tests` records its command, exit code, timeout, duration, and a **post-run** Git tree hash when available.

**Recorded command outcome. Optional freshness check.**`latestEvidenceForTree` → passed / failed / not\_verified

The helper stages the current tree and compares its hash with the latest `run_tests` event marked as executed. An inspectable matching hash and successful tool status produce`passed`. If it can’t inspect the tree, it reports`inspected: false`.

This is not proof of correctness or of an immutable tested snapshot. The hash is captured after the command; ignored files and the wider environment are outside it. The helper does not require a full-suite command or inspect test assertions. Your app must decide which commands and evidence it trusts, and whether anything may be published.

15

## Streaming & traces

[](https://anchor-sdk.dev/docs.html#traces)

Use `onEvent` for immediate text/status updates and `traceSinks` for sequenced records. Both can be attached to the same run. The host supplies the configured model, agent, task, and any sandbox used by these snippets.

stream-trace.mjsJAVASCRIPT

```
import { runAgent } from "anchor-ai-sdk";

// llm, agent, optional sandbox, and task come from your host setup.
const result = await runAgent({ task }, {
  llm, agent, sandbox,
  onEvent(event) {
    switch (event.type) {
      case "llm_text_delta":
        process.stdout.write(event.text);
        break;
      case "tool_call":
        console.error("tool:", event.name, event.callId, event.verb);
        break;
      case "tool_result":
        console.error("result:", event.name, event.ok, event.contentChars);
        // event.content and event.meta contain the detailed result.
        // Raw events are not redacted. Do not dump secrets into logs.
        break;
      case "advisory":
        console.error(event.kind, event.detail);
        break;
      case "run_finished":
        console.error("finished:", event.reason, event.usage);
        break;
    }
  },
});
// onEvent is synchronous/observational. Async handlers are not awaited.
// Avoid slow blocking I/O here; use traceSinks for serialized persistence.
```

`llm_call_finished`

Full assistant text for the turn, provider-returned reasoning if present, tool-call arguments, usage, latency, and model/provider identity when supplied. This is not a packet capture of the HTTP exchange or the full input prompt.

`tool_call / tool_result`

Inputs and call ID on the call; model-facing bounded result content and structured meta on the result. Built-in spilled outputs are pointers to sandbox files, not copies of every byte in the journal.

`compacted / checkpoint / revival_requested`

Fold summary and bounded folding preview, checkpoint references/push status, and the model’s optional remaining-work request. Relevant events appear only when that feature/path runs.

`onEvent vs traceSinks`

onEvent receives raw events and is not an async persistence hook. Sinks receive { seq, ts, event } after pattern redaction and are serialized per sink. Redaction is not guaranteed to detect every secret; audit sensitive data before sharing.

`background and inbox events`

background\_started/completed and inbox\_interject/delivered are engine events. The current live projector does not emit dedicated frames for those cases. Record them in a custom/full sink or handle selected metadata in onEvent. background\_completed does not contain the full background result body; runtime facts live in conversation state.

replay-trace.mjsJAVASCRIPT

```
import { projectConversation } from "anchor-ai-sdk";

// Split a chain journal into run segments before replaying step-scoped content.
const segments = [];
for (const entry of entries) {
  if (entry.event.type === "run_started" || segments.length === 0) segments.push([]);
  segments.at(-1).push(entry);
}
const replays = segments.map(segment => {
  const finished = segment.findLast(entry => entry.event.type === "run_finished");
  return projectConversation(segment.map(({ seq, event }) => ({
    seq, type: event.type, payload: event,
  })), finished?.event.reason ?? "running");
});
// Feed records in stored order. seq/step alone are not chain-wide identifiers.
// Reconnection transport, event storage queries, and client UI are app-owned.
```

The built-in client streams; provider-reported reasoning may be absent. Sink exceptions are isolated, but a never-settling sink can delay flush. Configure sinks before the run if you need a journal. Filesystem storage in a short-lived/serverless host may be ephemeral; use your own durable sink where required.

16

## Background tools & results

[](https://anchor-sdk.dev/docs.html#background)

Heyo currently provides the `pollJob`/`killJob` implementation used by background delivery. Any custom **sandbox-exec** tool can wire the opt-in contract; arbitrary host-side promises and all existing tools do not become backgroundable automatically. Built-in `bash` is enabled; built-in `run_tests` remains synchronous in 0.1.7.

enable-background.mjsJAVASCRIPT

```
import { codingAgent, runAgent } from "anchor-ai-sdk";

// sandbox is a STARTED Heyo sandbox from the provider setup section.
const result = await runAgent({ task: "Run checks and inspect related files" }, {
  llm, agent: codingAgent, sandbox,
  thread: { store: threadStore, id: "repo-42" },
  backgroundAfterSecs: 15,
  onEvent(event) {
    if (event.type === "background_started") {
      console.log("launched", event.jobId, event.tool, event.timeoutSecs);
    }
    if (event.type === "background_completed") {
      console.log("finished", event.jobId, event.exitCode, event.timedOut);
    }
  },
});
// bash is already background-enabled. A shorter/equal explicit command deadline
// stays synchronous. Detach is an accepted launch, not successful completion.
```

Custom tool using only public 0.1.7 exports.[Download tool](https://anchor-sdk.dev/examples/background-tool.mjs)

attach-background-tool.mjsJAVASCRIPT

```
import { codingAgent, runAgent } from "anchor-ai-sdk";
import { projectChecksTool } from "./background-tool.mjs";

const result = await runAgent({ task: "Run project_checks and report its result" }, {
  llm, agent: codingAgent, sandbox,
  tools: [...codingAgent.tools, projectChecksTool],
  backgroundAfterSecs: 15,
});
// Merely adding background: true to a copied tool object is not sufficient.
// defineTool captures its options; the handler must support detach and metadata.
```

`Tool permission`

Declare { background: true } in defineTool options. The wrapper exposes that permission as ctx.backgroundAllowed. Its handler must pass backgroundAfterSecs to sandbox.exec and return meta.background, command and timeoutSecs on detach.

`Consumer threshold + provider support`

Set a positive backgroundAfterSecs in run options; an unset value is synchronous. The provider needs pollJob. Tools/adapters without support stay synchronous or follow their documented behavior; do not rely on Docker/E2B/local detachment in this release.

`Detach threshold ≠ deadline`

The command must be permitted to outlive the threshold. Bash uses the explicit deadline or an eligible background lifetime up to 3,600s. A deadline at/below the threshold does not detach. The custom example chooses a 600s hard lifetime; the threshold does not extend it.

`Engine-owned delivery`

The dispatcher records handles, the engine polls before model turns and before normal finish, and completed output is injected as a runtime fact. Before a normal finish it waits within job deadline/grace logic unless the optional inbox hasPending indicates waiting work.

`Across an inbox chain`

Outstanding jobs carry automatically into the next run. Interjections take precedence at the boundary, and queued work can avoid a forced pre-finish wait when hasPending is implemented. A shared budget or cancellation may still end the chain with pendingJobs.

Interjecting does not kill a guest command or undo its writes. Detached jobs can mutate the same workspace while later tools run; choose safe workloads and cancel conflicting work explicitly. Heyo marks retention on detach and may defer destroy/parking while jobs remain, so the host must persist handles, wake later work, and enforce a cleanup policy.

Delivered bodies use the normal best-effort truncate/spill path and `origin: "runtime"`, so facts survive in a supplied thread store until later compaction. `background_completed` reports job/status metadata, not the full output body. Use conversation/runtime state and retained spill files where available; the event journal alone is not a byte-for-byte archive of every background result.

17

## Sandbox providers

[](https://anchor-sdk.dev/docs.html#providers)

The sandbox layer can be used with agents or directly through `SandboxProvider` and `Sandbox`. Provider factories live on subpaths; the engine and built-in machine tools share the same interface. Configure the backend once rather than giving every tool its own vendor integration.

**![](/assets/heyo-CJ5BkoQS.png)Heyo**Firecracker microVMs`anchor-ai-sdk/sandbox/heyo`

Image-based · opt-in live conformance suite

**![](data:image/svg+xml,%3csvg%20width='32'%20height='30'%20viewBox='0%200%2032%2030'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3e%3cmetadata%3eE2B%20size-specific%20symbol%20artwork%3c/metadata%3e%3cpath%20d='M24.1279%207.75171V11.7029H12.3447C12.0571%2011.703%2011.8232%2011.9367%2011.8232%2012.2244V12.5027C11.8232%2012.7903%2012.0571%2013.024%2012.3447%2013.0242H24.1279V16.9753H12.3447C12.0571%2016.9755%2011.8232%2017.2092%2011.8232%2017.4968V17.7751C11.8233%2018.0627%2012.0572%2018.2955%2012.3447%2018.2957H24.1279V22.2478H10.4717C9.03605%2022.2475%207.87209%2021.0829%207.87207%2019.6472V10.3513C7.87229%208.91577%209.03617%207.75206%2010.4717%207.75171H24.1279Z'%20fill='black'/%3e%3c/svg%3e)E2B**E2B cloud sandboxes`anchor-ai-sdk/sandbox/e2b`

Template-based · offline lifecycle tests + opt-in live suite

**![](data:image/svg+xml,%3c?xml%20version='1.0'%20encoding='UTF-8'?%3e%3csvg%20id='Layer_1'%20xmlns='http://www.w3.org/2000/svg'%20xmlns:xlink='http://www.w3.org/1999/xlink'%20version='1.1'%20viewBox='0%200%20340%20268'%3e%3c!--%20Official%20Docker%20black%20mark,%20from%20Docker-Logos-1.zip.%20--%3e%3cdefs%3e%3cstyle%3e.st0{fill:none}.st1{fill:%230f121b}.st2{clip-path:url(%23clippath)}%3c/style%3e%3cclipPath%20id='clippath'%3e%3crect%20class='st0'%20width='339.5'%20height='268'/%3e%3c/clipPath%3e%3c/defs%3e%3cg%20class='st2'%3e%3cpath%20class='st1'%20d='M334,110.1c-8.3-5.6-30.2-8-46.1-3.7-.9-15.8-9-29.2-24-40.8l-5.5-3.7-3.7,5.6c-7.2,11-10.3,25.7-9.2,39,.8,8.2,3.7,17.4,9.2,24.1-20.7,12-39.8,9.3-124.3,9.3H0c-.4,19.1,2.7,55.8,26,85.6,2.6,3.3,5.4,6.5,8.5,9.6,19,19,47.6,32.9,90.5,33,65.4,0,121.4-35.3,155.5-120.8,11.2.2,40.8,2,55.3-26,.4-.5,3.7-7.4,3.7-7.4l-5.5-3.7h0ZM85.2,92.7h-36.7v36.7h36.7v-36.7ZM132.6,92.7h-36.7v36.7h36.7v-36.7ZM179.9,92.7h-36.7v36.7h36.7v-36.7ZM227.3,92.7h-36.7v36.7h36.7v-36.7ZM37.8,92.7H1.1v36.7h36.7v-36.7ZM85.2,46.3h-36.7v36.7h36.7v-36.7ZM132.6,46.3h-36.7v36.7h36.7v-36.7ZM179.9,46.3h-36.7v36.7h36.7v-36.7ZM179.9,0h-36.7v36.7h36.7V0Z'/%3e%3c/g%3e%3c/svg%3e)Docker**Containers on your daemon`anchor-ai-sdk/sandbox/docker`

Image-based · conformance job configured in CI

**Local**Processes on this host`anchor-ai-sdk/sandbox/local`

No isolation · development / CI only

E2B, Heyo, Docker, and local are implemented adapters. Daytona remains planned and is not an exported package subpath. Optional capabilities differ: E2B and local declare fork support; Heyo and Docker do not. The latest Docker source declares egress control:`spec.egress: "none"` disconnects networks after startup. The other adapters reject that setting rather than silently running with network access.

18

## One machine contract

[](https://anchor-sdk.dev/docs.html#contract)

`SandboxProvider` owns create, adopt, release, and probe. `create` constructs a sandbox; `start`boots it and prepares the workspace. The common interface covers exec, Git checkpoints, tree comparison, and teardown. Adapters implement command deadlines; declared capabilities and provider behavior determine retention, fork, and remote-checkpoint support.

machine-without-agent.mjsJAVASCRIPT

```
import { openSandbox } from "anchor-ai-sdk";

// provider is any configured SandboxProvider, from the examples above.
const sandbox = await openSandbox({
  provider,
  spec: { repoUrl, ref: "main" },
});
try {
  const result = await sandbox.exec("git status --short", { timeoutSecs: 30 });
  console.log(result.exitCode, result.stdout, result.stderr);
} finally {
  await sandbox.destroy();
}
```

`retain: true` changes teardown into provider-specific retention (for example, E2B pause or Docker stop). Store the handle for a later `adopt`; your app must set a cleanup policy and eventually call `provider.release`. Retention and cleanup are not free or automatic across runs. Use `openSandboxSession` with a store for adoption/fallback bookkeeping.

Calls marked read-only from one turn can run concurrently. The engine dispatches other tool calls serially; this is not a ban on external or detached processes changing the workspace. Background detachment requires a participating tool, a consumer threshold, and `pollJob` support (currently Heyo). In 0.1.7 the engine polls and delivers results during the call and carries outstanding jobs through an inbox chain. After the call returns, your host owns saving `pendingJobs`, waking another run with `resumeJobs`, and cleanup.

Credentials enter through environment/adapter secret plumbing, not repository files. Keep provider capability checks in your app; don’t assume every backend supports every feature.

19

## Provider conformance

[](https://anchor-sdk.dev/docs.html#conformance)

The SDK includes one executable conformance suite for the shared contract: exec results, command deadlines, env/cwd handling, Git checkpoints, retain/adopt, and declared optional capabilities. Scenarios run through the public interface rather than vendor internals.

For another backend, implement `SandboxProvider` and `Sandbox`, declare only the capabilities it supports, and run this suite against it. Agent code can keep using the common interface; provider-specific boot and lifecycle behavior stays in the adapter.

provider.test.tsTYPESCRIPT

```
import { e2b } from "anchor-ai-sdk/sandbox/e2b";
import { runSandboxConformance } from "anchor-ai-sdk/testing";

// In a Vitest test file. This starts real cloud sandboxes and may incur cost.
runSandboxConformance(
  e2b({ apiKey: process.env.E2B_API_KEY, template: process.env.E2B_TEMPLATE }),
  {
    timeoutMs: 240_000,
    repo: { repoUrl: "https://github.com/octocat/Hello-World.git" },
  },
);
```

The repository configures local and Docker conformance in CI. Heyo and E2B live suites are credential-gated; the presence of a suite does not mean a given account, image, template, or provider version has passed it. Capability flags and optional repository fixtures affect which scenarios run. Conformance checks the public behavior contract, not security isolation or performance.

20

## Tools & strategies

[](https://anchor-sdk.dev/docs.html#tools)

A tool has a model-visible JSON Schema and a handler you write. Define the allowed inputs, validate them, do the authorized operation, then return text for the model and optional metadata for the journal.

catalog-tool.mjsJAVASCRIPT

```
import { defineTool, requireString, optionalNumber, truncate } from "anchor-ai-sdk";

// Your app provides searchCatalog and enforces authorization for this tenant.
export function makeSearchCatalogTool(searchCatalog) {
  return defineTool(
    {
      name: "search_catalog",
      description: "Find up to 10 catalog entries by a text query.",
      parameters: {
        type: "object",
        properties: {
          query: { type: "string", minLength: 1, description: "What to find" },
          limit: { type: "integer", minimum: 1, maximum: 10 },
        },
        required: ["query"],
        additionalProperties: false,
      },
    },
    async (input) => {
      // The schema is not a runtime validator: check inputs in the handler.
      if (Object.keys(input).some(key => !["query", "limit"].includes(key))) {
        throw new Error("Unexpected parameter");
      }
      const query = requireString(input, "query").trim();
      if (input.limit !== undefined && typeof input.limit !== "number") {
        throw new Error("limit must be a number");
      }
      const limit = optionalNumber(input, "limit", 5);
      if (!Number.isInteger(limit) || limit < 1 || limit > 10) {
        throw new Error("limit must be an integer from 1 to 10");
      }
      const entries = await searchCatalog({ query, limit });
      return {
        ok: true,
        content: truncate(JSON.stringify(entries), { maxLines: 80, maxChars: 8_000 }),
        meta: { count: entries.length, query },
        verb: "Searching the catalog",
      };
    },
    { readOnly: true }, // only if this handler cannot mutate the workspace
  );
}
```

`defineTool` catches thrown handler errors and returns `ok: false` with error text. It does not enforce the whole JSON Schema. The example validates required strings, number type/range, and unexpected keys itself; use your own schema validator if needed. `optionalNumber` can coerce values, so the strict type check above is deliberate.

attach-tools.mjsJAVASCRIPT

```
import { chatAgent, codingAgent, runAgent } from "anchor-ai-sdk";
import { makeSearchCatalogTool } from "./catalog-tool.mjs";

// searchCatalog is your authorized application service, not an SDK export.
const searchCatalogTool = makeSearchCatalogTool(searchCatalog);

// A custom bundle, reusing the chat strategy:
const catalogAgent = {
  name: "catalog-assistant",
  strategy: chatAgent.strategy,
  tools: [searchCatalogTool],
};
await runAgent({ task: "Find the relevant catalog entries" }, {
  llm, agent: catalogAgent,
});

// Or extend a coding bundle. Explicit tools replace the bundle's list:
// tools: [...codingAgent.tools, searchCatalogTool],
// agent: codingAgent, sandbox,
```

`definition.name / description / parameters`

The name the model calls, when it should use the capability, and the JSON Schema. Schema hints do not replace server-side authorization or validation.

`run(input, ctx)`

Your async handler. It receives an input object and ToolContext, including an optional sandbox. Custom API tools need not use a machine. ToolContext does not currently supply an AbortSignal; add timeout/cancellation in your own service.

`ok / content / meta`

ok is the handler’s operation status. content is the text sent to the model, already bounded by your tool. meta is structured trace/evidence data, not a model reply. The engine does not universally truncate arbitrary custom-tool output for you.

`verb / readOnly`

A display label, and your assertion that the tool does not mutate the workspace. The engine may dispatch a fully read-only tool-call batch concurrently; flags do not prove handlers are safe.

Keep credentials in the host/service or sandbox environment, not model-visible schemas or tool results. The built-in `finish` and multi-phase `set_phase` tools are added by the engine unless disabled; do not add duplicates to the capability array. Tool schemas do not themselves enforce phase policy.

`chatAgent` and `codingAgent` are bundles, not modes. Compose your own strategy, prompts, and tools from the same primitives.

`bash`Builds, scripts, installs, shell access

`read_file`Line-numbered, paged file reads

`edit_file`Unique exact-match edits, write-size checked

`search`rg → git grep → grep fallback

`run_tests`Structured test results and tree hashes

`finish`Declared completion + optional handoff

`defineTool` pairs a JSON Schema definition with an implementation. Tool `content` goes to the model; `meta` stays structured. A consumer-provided `handoff.schema` shapes `finish` output — the engine carries the object without interpreting its business meaning. The schema is declared to the model; validate the object against your own schema and business rules in the consuming app.

21

## Testing

[](https://anchor-sdk.dev/docs.html#testing)

The SDK includes engine, tool, and trace tests against fakes and fixtures, provider conformance suites, and genericity guards. Local conformance runs in CI; remote live tests are credential-gated.

[Explore the test suite](https://github.com/hrithikkoduri/anchor-ai-sdk/tree/main/tests)

22

## Status & versioning

[](https://anchor-sdk.dev/docs.html#status)

Anchor is 0.x and MIT-licensed. Pin versions, read the source, and treat feature availability as version-specific. Agent teams, consumer skill bundles, and Daytona are roadmap work. These examples target the published `0.1.7` package and source`193963a`. Native inbox and background examples are checked against that installed package, not just an export map. Source inspection and offline tests are not a claim that every provider deployment has been certified or that all integrations will work unchanged.

[Read the engineering decisions](https://github.com/hrithikkoduri/anchor-ai-sdk/blob/main/DECISIONS.md)
