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

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.
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: 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:
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.
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 walks through declaring, attaching and calling one.

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: 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:
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.

Reaching past the facade

Harness assembles the layers with defaults and exposes every one:
Use one directly whenever the defaults stop fitting. Integrating covers assembling them yourself.