> ## Documentation Index
> Fetch the complete documentation index at: https://nativeharness.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# How it works

> What happens between chat() and the file appearing in your workspace — the five primitives, on one page.

Five primitives, and one sentence that explains the rest: **the workspace owns the files, and
the sandbox is a disposable view of them.**

```
your app  ──►  Turn  ──►  Bash  ──►  Sandbox     (disposable)
                 │          │           ▲
                 │          └── hydrate ┘ collect
                 ▼                      │
              events  ◄────────────  Workspace   (durable, portable)
```

## Workspace

Everything durable: files as versioned records, blobs by content hash, key/value state,
sessions, turns, executions, and the event log. One workspace maps to one of *your* users or
projects, and its `permissions` name who may work in it.

```ts theme={null}
const workspace = await harness.workspace({ name: "acme / billing", actor: "user-42" });
await workspace.write("/src/main.js", "…");
await workspace.readText("/src/main.js");
await workspace.export("./backups");     // portable: import it anywhere
```

Files are records, not bytes on a disk somewhere — which is what makes the next part true.

## Sandbox

Where commands run, and nothing else. Before a command, the workspace's files are **hydrated**
into it; after, its changes are **collected** back as new versions. It never holds the only
copy of anything, so it can be replaced at any moment.

The default provider is [nativesandbox](https://nativesandbox.dev): rootless containers,
every Linux capability dropped, cgroup limits, and no need for `/dev/kvm`. A provider that
does not isolate — `sandbox-local` — is refused with a real model, in code.

A warm sandbox is reused when its shape still serves: same image, and memory and CPU
**greater than or equal to** what was asked. That is why a second `npm install` is free.

## Bash

The only built-in tool. No `read`, `write`, `edit` or `grep` — bash already does those, and
one tool means **one write path**, so versioning, size caps, the delete-ratio guard, path
scope and approval each apply once, in one place.

Every command returns its output *and* what it changed:

```ts theme={null}
const { result } = await workspace.exec("npm run build");
result.exit_code;      // 0
result.files.changed;  // ["/dist/bundle.js"]
```

The harness cannot tell a read from a write by a tool's name. It knows what a command did,
because collection is a diff.

## Connectors

How an agent reaches GitHub or your API **without the credential entering the sandbox**. On
hydrate, an `nh` command is written into the guest and given a token minted for that one
command. Code inside runs `nh call github create_issue '{…}'`; the gateway verifies the token,
attaches the real credential on your side, and records the call against the execution.

<Note>
  The machinery is built and tested — declarations, the gateway, per-actor credentials,
  outbound safety. **No connector ships yet**, so today you declare your own:
  [Connectors](/guides/connectors) walks through declaring, attaching and calling one.
</Note>

## Turn

One user message and everything the agent does in response. `chat()` yields protocol events as
it goes, and the loop ends in one of five ways:

| Status               |                                                           |
| -------------------- | --------------------------------------------------------- |
| `completed`          | the model answered with no further tool call              |
| `needs_continuation` | it stopped to ask a person — an approval, or a question   |
| `blocked`            | a ceiling: steps, tokens, or an unfinished plan           |
| `cancelled`          | `workspace.cancel()` took effect; durable across restarts |
| `failed`             | a model, sandbox, store or connector error                |

`needs_continuation` is the one worth understanding. When a command's effects are held for
approval, or the model calls `ask_user`, **the turn ends** — nothing blocks a server thread.
Your UI shows the person the question, and their answer resumes it, even a day later:

```ts theme={null}
for await (const event of workspace.resume({ answers: { [id]: "the second option" } })) …
for await (const event of workspace.resume({ approval: { approval_id, decision: "allow", decided_by } })) …
```

The loop also handles compaction against the model's real context window, plan mode,
subagents, and step and token ceilings — all of them options, and all covered in
[controlling the turn](/guides/controlling-the-turn).

## Reaching past the facade

`Harness` assembles the layers with defaults and exposes every one:

```ts theme={null}
harness.store     // WorkspaceStore   — swap for your own
harness.sandbox   // SandboxProvider
harness.events    // the bus the protocol replays from
harness.bash      // the tool, without the loop
harness.agent     // the loop
```

Use one directly whenever the defaults stop fitting. [Integrating](/guides/integrating)
covers assembling them yourself.
