Skip to main content
The turn loop asks a ModelAdapter for one decision at a time and receives a stream of ModelEvents: text deltas, tool calls, usage. Everything above the adapter is vendor-blind, and swapping one changes no line of the harness.

The adapters

Each adapter reports its model’s real context window, which is what compaction budgets against — not a guessed number.

Scripted, for tests

ScriptedAdapter from @nativeharness/agent plays a fixed sequence of decisions: text, tool calls, or a thrown error to exercise the failure path. It is how the harness’s own suite runs a whole turn — a real command, in a real sandbox — with no key, and how the CLI’s --model scripted:<file> works. It also declares itself deterministic, which is the one case the agent will run on a non-isolating sandbox provider: no model is deciding anything, so there is nothing to contain.

Choosing from the CLI

nhar chat takes --model, then $NH_MODEL, then the first key it finds in the environment, in this order: Anthropic, OpenAI, Google, Cohere, xAI, DeepSeek, Groq, Mistral. Adapters are loaded only when chosen, so an Anthropic user never loads the OpenAI SDK.

Writing one

A ModelAdapter is one method, decide, taking the system prompt, the messages, the tools and a token budget, and yielding ModelEvents — plus an info block naming the model, its context window and its capabilities. The interface is model-adapter.ts; the Ollama adapter is the smallest real one to read.