# Build Agents with the SDK

> Navigate the SDK contracts used to author Agent behavior, durable execution, Tools, Resources, and analytics.

Use `@constal/sdk` inside the immutable Agent package. The smallest useful Agent reads accepted history, makes one model turn, and returns a result that the runtime records durably:

## Minimal Agent {#minimal-agent}

```ts
import { agent, type HistoryView } from "@constal/sdk";

export default agent({
  id: "support", version: "1.0.0", model: "model",
  async onMessage(message, ctx) {
    const history = await ctx.ledger.view<HistoryView>("history");
    const turn = await ctx.turn({
      system: "Resolve the request accurately.",
      objective: message,
      context: { priorFacts: history.facts },
    });
    return { answer: turn.message.content };
  },
});
```

`model` is a logical Resource binding, not caller-selected provider input. `ctx.resources` contains only accepted CRNs, and Credential bytes never enter Agent code.

## Interpret a model turn {#model-turn}

`ctx.turn()` returns the provider's result even when it has no visible text or Tool calls. Inspect `turn.message.content`, `turn.toolCalls`, and `turn.completion` to decide what the Agent should do next. `turn.refusal` and `turn.reasoningContent` carry provider-exposed text when available. `turn.cost.tokens.output` records output-token usage; it can be positive even when the visible message is empty. An incomplete model response does not by itself finish the Agent's task.

## Judge structured state {#model-evaluation}

Use `ctx.evaluate()` when a bound evaluation Model should answer typed Choice, Score, or Noul questions about one state. It returns constrained answers and settled cost without creating a conversational turn. Bind the evaluation Model under a logical name such as `judge`, then pass `model: "judge"` in the call. See [Evaluate state with a Model](/docs/agents/evaluate.md) for a complete manifest, Agent, answer shapes, and a direct Gateway option.

## Add a durable boundary {#durable-boundary}

```ts
async onMessage(message, ctx) {
  if (message.kind === "checkpoint") {
    return ctx.commit({ kind: "checkpoint", payload: message.payload });
  }
  return { accepted: true };
}
```

## Restore a sandbox from a recorded image {#sandbox-images}

`pool.createImage(sandbox)` returns a pool-owned image whose `id` an Agent can record, for example in a Fact. A later Run addresses that image with `pool.image(id)` without invoking the pool. To recreate the Session's sandbox from it, the Run deletes the current sandbox, then passes the image to `pool.createSandbox(agent, session, { image })`; the pool refuses an image while the Session's sandbox is live. `image.delete()` removes the image once it is no longer needed. An addressed image does not know its cache key (`cacheKey` is `null`), and deleting it removes a setup-cache image too, so address only images the Agent recorded.

## Schedule a follow-up {#schedules}

Use `ctx.schedule(name, { after: "10m", input })` to record a future invocation that survives the current Run. `every` maintains a cadence; `ctx.listSchedules()` and `ctx.cancelSchedule(name)` manage the Session's schedules. See [Schedule Agent follow-ups](/docs/agents/schedules.md) for timing, destinations, replay, and cancellation semantics.

Use [Durable Agent patterns](/docs/agents/durable.md) for waits and subtasks, [Resources SDK guide](/docs/resources/sdk.md) for effects, and [Analytics SDK guide](/docs/analytics/sdk.md) for typed events. Deployment remains a Console, CLI, or Platform API operation; it is intentionally absent from `Ctx`.
