# Agents and Turns

Build a runtime, open an agent session with a provider and model, run turns to their final message, and stop the runtime.

## Overview

A runtime holds the harnesses, tools, and storage that its agents share. An agent is one session on one harness, and it runs one turn at a time. A turn starts from a prompt and finishes with a final message. To install the SDK, see [Getting Started](/docs/introduction/getting-started).

In Rust, `Nanocode::builder()` builds the runtime and `runtime.spawn()` builds each agent. In TypeScript, `AgentRuntime.instantiate` creates a runtime for one session scope, and `open` opens the session. The host then queues turns with `submit` and runs them with `drive`.

#### Rust

```rust
use nanocode::{Claude, ClaudeClient, ClaudeModel, Nanocode};

// Build a runtime, spawn a Claude agent, and run one turn
let runtime = Nanocode::builder()
    .claude(ClaudeClient::default())
    .build_async()
    .await?;
let (agent, _events) = runtime
    .spawn()
    .backend::<Claude>()
    .model(ClaudeModel::Haiku55)
    .build()
    .await?;
let result = agent
    .prompt("Explain durable agents in two sentences.")
    .await?
    .result()
    .await?;
println!("{}", result.final_message());
```

#### TypeScript

```ts
import { AgentRuntime, sessionUuid } from 'nanocode-wasm'

// Instantiate the module over a SQL host, open a session, then queue a
// turn
const runtime = await AgentRuntime.instantiate(module, sql, {
  published: () => {},
})
await runtime.open(
  {
    endpoint,
    provider: 'claude',
    scope: 'owner',
    session_id: sessionUuid(),
    subscription: false,
  },
  { dispatch },
)
await runtime.submit('owner', {
  metadata: {},
  prompt: 'Say hello.',
  request_id: 'turn-1',
})
```

## Walkthrough

The steps below build a program that opens one Claude session, runs a turn to its final message, and shuts down. The TypeScript steps use the `module` and `sql` values from [Getting Started](/docs/introduction/getting-started).

### Build the Runtime

The Rust runtime builder takes one client per harness. `Tools::builder().without_defaults()` turns off the shell, file, web search, and image tools, and `without_cua()` turns off computer use. The TypeScript runtime is one module instance, with `published` receiving each batch of logged [events](/docs/guides/events).

#### Rust

```rust title="src/main.rs"
use nanocode::{ClaudeClient, Nanocode, OpenAi, Thinking, Tools};

type Result<T> =
    std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;

#[tokio::main]
async fn main() -> Result<()> {
    // One runtime serves both harnesses with the same tools and
    // settings
    let runtime = Nanocode::builder()
        .openai(OpenAi::default())
        .claude(ClaudeClient::default())
        .tools(Tools::builder().without_defaults().build()?)
        .without_cua()
        .thinking(Thinking::Low)
        .build_async()
        .await?;
```

#### TypeScript

```ts title="host.ts"
import { AgentRuntime } from 'nanocode-wasm'

// One instance runs one session scope and prints each logged event
const runtime = await AgentRuntime.instantiate(module, sql, {
  published: (events) => {
    for (const entry of events) console.log(entry.seq, entry.event)
  },
})
```

### Open a Session

`spawn()` returns a builder. `backend::<B>()` selects the harness, `model(...)` takes that harness's model type, and `build()` returns the agent and its event stream. In TypeScript, `open` selects the provider, model, and endpoint, and registers `dispatch`. An instance opens once.

#### Rust

```rust title="src/main.rs"
    // Claude harness with Haiku 5.5
    let (agent, _events) = runtime
        .spawn()
        .backend::<nanocode::Claude>()
        .model(nanocode::ClaudeModel::Haiku55)
        .build()
        .await?;
```

#### TypeScript

```ts title="host.ts"
import { sessionUuid } from 'nanocode-wasm'

const scope = 'owner'

// Open a Claude session against the Messages API
await runtime.open(
  {
    endpoint: 'https://api.anthropic.com/v1/messages',
    model: 'claude-haiku-5-5',
    provider: 'claude',
    scope,
    session_id: sessionUuid(),
    subscription: false,
    thinking: 'low',
  },
  { dispatch },
)
```

If `config.model` is not set, Claude sessions use Opus 5.5 and Codex sessions use Sol. `config.provider` defaults to `claude`. `sessionUuid()` returns the UUIDv7 session ID that the SDK requires.

### Add Credentials

The Rust clients sign their own requests. `ClaudeClient::default()` uses the stored Claude login, and `ClaudeClient::with_api_key` uses an API key. The TypeScript SDK sends model requests without credentials, so `dispatch` adds them before the request leaves the host.

#### Rust

```rust
use nanocode::ClaudeClient;

// Sign Claude requests with an Anthropic API key instead of a login
let claude = ClaudeClient::with_api_key(
    "https://api.anthropic.com/v1/messages",
    std::env::var("ANTHROPIC_API_KEY")?,
);
```

#### TypeScript

```ts title="host.ts"
import type { RuntimeOptions } from 'nanocode-wasm'

// Forward the body unchanged and add the API key
const dispatch: RuntimeOptions['dispatch'] = (request, signal) =>
  fetch(request.endpoint, {
    body: request.body as Uint8Array<ArrayBuffer>,
    headers: [
      ...request.headers,
      ['x-api-key', process.env.ANTHROPIC_API_KEY ?? ''],
    ],
    method: 'POST',
    redirect: 'error',
    signal,
  })
```

The runtime rejects redirect responses, and `redirect: 'error'` stops `fetch` from following them first. A browser host sends requests to its own server, which adds the credentials. See [In the Browser](/docs/guides/browser).

### Run a Turn

`agent.prompt` resolves when the agent accepts the turn, and `Turn::result` resolves with the final message. A dropped `Turn` keeps running. In TypeScript, `submit` queues the turn, `drive` runs one root turn at a time, and `turn` reads its report.

#### Rust

```rust title="src/main.rs"
    // Submit the prompt and wait for the turn to finish
    let turn = agent.prompt("Name three prime numbers.").await?;
    println!("{}", turn.result().await?.final_message());
```

#### TypeScript

```ts title="host.ts"
// 1. Queue the prompt
const submitted = await runtime.submit(scope, {
  metadata: {},
  prompt: 'Name three prime numbers.',
  request_id: 'turn-1',
})
if (submitted.submitted !== 'accepted')
  throw new Error(`prompt refused: ${submitted.submitted}`)

// 2. Drive queued turns until none remain
for (;;) {
  const next = (await runtime.pending(scope))[0]
  if (!next) break
  const driven = await runtime.drive(next.report.request_id)
  if (driven.retry)
    throw new Error(
      'the runtime closed, so reopen it from the database',
    )
  if (driven.waiting)
    await new Promise((resolve) => setTimeout(resolve, 100))
}

// 3. Read the finished report
console.log((await runtime.turn(scope, 'turn-1'))?.report)
```

A `waiting` result means the turn waits on its host, for example before a model retry. Drive it again after a delay. A `retry` result means the runtime failed and disposed itself, so instantiate a new one over the same database.

### Shut Down

`flush` waits for every pending session write, and `runtime.shutdown()` stops the runtime. `dispose` shuts the TypeScript session down and cancels open model requests.

#### Rust

```rust title="src/main.rs"
    // Wait for durable writes, then stop the runtime
    agent.flush().await?;
    runtime.shutdown().await?;
    Ok(())
}
```

#### TypeScript

```ts title="host.ts"
// Shut the SDK session down
await runtime.dispose()
```

`shutdown` stops running turns and leaves turns with a request ID pending, so the next owner of the session resumes them. A completed TypeScript report has the `request_id`, a `status` of `completed`, and an `ok` object with the final `message`, the `model`, and the `session_id`.

## Recipes

### Pick a Model

The Rust runtime builder sets the default thinking level, and the spawn builder overrides it for one agent. In TypeScript, `config.model` names a model of `config.provider`, and a model of the other provider is a configuration error.

#### Rust

```rust
use nanocode::{Codex, Model, Thinking};

// Sol with high reasoning effort for this agent only
let (agent, _events) = runtime
    .spawn()
    .backend::<Codex>()
    .model(Model::Sol)
    .thinking(Thinking::High)
    .build()
    .await?;
```

#### TypeScript

```ts
// Luna with high reasoning effort. The SDK adds /responses to the
// endpoint.
await runtime.open(
  {
    ...config,
    endpoint: 'https://api.openai.com/v1',
    model: 'gpt-6-luna',
    provider: 'codex',
    thinking: 'high',
  },
  { dispatch },
)
```

| Harness | Rust models                                                         | Thinking                                                            |
| ------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- |
| Claude  | `ClaudeModel::Opus55`, `Sonnet55`, `Haiku55`, `Fable51`, `Mythos51` | `Thinking::None` to `Thinking::Max` (`none` to `max` in TypeScript) |
| Codex   | `Model::Astra`, `Sol`, `Luna`                                       | `Thinking::None` to `Thinking::Max` (`none` to `max` in TypeScript) |

### Add Instructions

Host instructions go into the agent's developer context, separate from the provider system prompt. In Rust, a value on the runtime builder applies to every agent, and a value on the spawn builder replaces it for one agent.

#### Rust

```rust
// Instructions for this agent only
let (agent, _events) = runtime
    .spawn()
    .instructions(
        "Answer in plain English. Keep replies under 100 words.",
    )
    .build()
    .await?;
```

#### TypeScript

```ts
// Shapes every turn of this session
await runtime.open(
  { ...config, instructions: 'Answer in one sentence.' },
  { dispatch },
)
```

### Set the Workspace

Only the Rust runtime takes a workspace. A TypeScript session reaches files through host tools or [remote hands](/docs/guides/remote-hands). The workspace defaults to the current directory, and a relative path resolves against it.

```rust
use nanocode::{Nanocode, OpenAi};

// Shell and file tools start in this checkout
let runtime = Nanocode::builder()
    .openai(OpenAi::default())
    .workspace("/srv/checkouts/app")
    .build_async()
    .await?;
```

### Use Request IDs

A request ID names a turn so a retry does not run it twice. With a durable store, a repeated Rust request ID resumes the turn or returns its recorded result. In TypeScript, `submit` returns `existing` for the same request ID and prompt, and `conflict` for a different prompt.

#### Rust

```rust
use nanocode::PromptRequest;

// The request ID must not be empty
let turn = agent
    .prompt(
        PromptRequest::new("Summarize the release.")
            .request_id("release-42"),
    )
    .await?;
```

#### TypeScript

```ts
// Submitting the same request again returns the recorded turn
const again = await runtime.submit(scope, {
  metadata: {},
  prompt: 'Summarize the release.',
  request_id: 'release-42',
})
if (again.submitted === 'existing')
  console.log(again.turn.report.status)
```

See [Durability](/docs/guides/durability) for reopening sessions and recovering turns.

### Steer a Turn

Only the Rust SDK steers a running turn. `steer` adds input to the turn instead of starting a new one, and the steer must not be empty.

```rust
// Redirect the running turn without waiting for it to finish
let turn = agent.prompt("Draft release notes for this week.").await?;
turn.steer("Group the notes by crate.").await?;
println!("{}", turn.result().await?.final_message());
```

### Cancel a Turn

`turn.control()` returns a `TurnControl` that another task can keep after `result()` consumes the turn. In TypeScript, `cancel` finishes a queued turn as cancelled at once and sets `dequeued`. A running turn records `cancel_requested` instead.

#### Rust

```rust
// Cancel the turn after 30 seconds from another task
let turn = agent
    .prompt("Audit every dependency in this workspace.")
    .await?;
let control = turn.control();
tokio::spawn(async move {
    tokio::time::sleep(std::time::Duration::from_secs(30)).await;
    let _ = control.cancel().await;
});
let outcome = turn.result().await;
```

#### TypeScript

```ts
// Cancels turn-1 and records the reason in its result
const { dequeued, turn } = await runtime.cancel(scope, 'turn-1', {
  reason: 'user stopped it',
})
```

`agent.cancel_active_turn()` cancels the running Rust turn without a `Turn` handle.

### Handle Refusals

`open` throws `HostFailure` with `kind` set to `configuration` and `permanent` set to `true` when the SDK refuses the configuration. A permanent failure does not succeed on retry. This recipe applies only to TypeScript, since the Rust builders return a `Result`.

```ts
import { HostFailure } from 'nanocode-wasm'

try {
  await runtime.open(
    { ...config, model: 'gpt-6-luna', provider: 'claude' },
    { dispatch },
  )
} catch (error) {
  // Fail the request instead of retrying it
  if (error instanceof HostFailure && error.permanent)
    console.error(error.kind, error.message)
  else throw error
}
```

## More

- [Events](/docs/guides/events): Stream answer text and tool calls while a turn runs.

- [Durability](/docs/guides/durability): Reopen sessions and recover turns by request ID.

- [Nanocode](/docs/api/rust/Nanocode): Reference for the runtime and its builder.

- [SpawnBuilder](/docs/api/rust/SpawnBuilder): Reference for selecting a backend, model, and thinking level.

- [Turn](/docs/api/rust/Turn): Reference for results, steering, and cancellation.

- [open](/docs/api/wasm/open): Every configuration field and runtime option.

- [drive](/docs/api/wasm/drive): The results a drive returns.

- [cancel](/docs/api/wasm/cancel): Cancel a queued or running turn.
