A harness adapter is the second axis of a sandboxed run: it decides which coding agent runs and translates that agent's work back into chat() stream chunks. The provider decides where the agent runs; the harness decides what runs. Both sit behind the same chat() + withSandbox() wiring, so you can swap a harness without touching your provider or workspace.
Every harness adapter declares requires: [SandboxCapability], so chat() fails fast at the call site unless a sandbox is provided via withSandbox(...).
Each agent has its own package with curated per-model metadata. Pass the adapter to chat({ adapter }) and run it under any provider.
| Harness | Package | Adapter | Auth |
|---|---|---|---|
| Grok Build | @tanstack/ai-grok-build | grokBuildText | Default 'api-key' (XAI_API_KEY). 'host' uses grok login. |
| Claude Code | @tanstack/ai-claude-code | claudeCodeText | Default 'api-key' (ANTHROPIC_API_KEY). 'host' uses claude login. |
| Codex | @tanstack/ai-codex | codexText | Default 'api-key' (CODEX_API_KEY). 'host' uses codex login. |
| OpenCode | @tanstack/ai-opencode | opencodeText | OPENAI_API_KEY from the process env. No authMode flag. |
| ACP-Compatible | @tanstack/ai-acp | acpCompatible / acpCompatibleText | Default 'api-key' uses authMethodId. 'host' skips ACP authenticate. |
The provider is where the agent runs, not how it signs in. The default authMode is 'api-key'. Set 'host' when the machine already has a CLI login. See Harness Auth.
import { chat } from '@tanstack/ai'
import { grokBuildText } from '@tanstack/ai-grok-build'
import { withSandbox } from '@tanstack/ai-sandbox'
import { sandbox } from './sandbox'
import { messages } from './chat-context'
const stream = chat({
adapter: grokBuildText('grok-build'),
messages,
middleware: [withSandbox(sandbox)],
})If you want a typed object after the agent inspects the repo, pass outputSchema on the same chat() call. Dedicated adapters and acpCompatible honor it. See Harness Agents.
grokBuildText, claudeCodeText, and codexText can stop holding the agent's stdout pipe: instead they redirect it to an append-only NDJSON file inside the sandbox (/tmp/tanstack-runs/<runId>.ndjson) and tail that, so losing the host cannot signal the agent and any later reader can replay the run from byte 0.
They do that only for a durable run. Journaling is opt-in, and the opt-in is passing withSandbox both runs and durability (the snippet above), a plain withSandbox(sandbox), writes no journal and streams over a pipe exactly as it always has. Pass both and forward the runId, whose value the journal path is derived from (a durable run with no caller-supplied runId throws DurableRunIdRequiredError rather than minting one nothing can recompute):
import {
chat,
chatParamsFromRequest,
memoryStream,
toServerSentEventsResponse,
} from '@tanstack/ai'
import { codexText } from '@tanstack/ai-codex'
import { memoryPersistence } from '@tanstack/ai-persistence'
import { withSandbox } from '@tanstack/ai-sandbox'
import { sandbox } from './sandbox'
// Single-process stand-ins; ./takeover has the multi-replica wiring.
const persistence = memoryPersistence()
const { runs } = persistence.stores
export async function POST(request: Request) {
const { messages, threadId, runId } = await chatParamsFromRequest(request)
const adapter = memoryStream(request)
const stream = chat({
adapter: codexText('gpt-5.3-codex'),
messages,
threadId,
runId, // makes the run's journal findable again
// BOTH stores, or there is no journal: this is the whole opt-in.
middleware: [withSandbox(sandbox, { runs, durability: { adapter } })],
})
return toServerSentEventsResponse(stream, { durability: { adapter } })
}The id must be unique per run: the journal appends, so reusing one makes a reader stop at the previous run's exit sentinel. Full details, including what you get without the opt-in and replay against an already-delivered log, are in The Run Journal.
opencodeText and acpCompatible harnesses do not read NDJSON off stdout at all, so they have no journal even when a run is durable.
Many coding agents speak the Agent Client Protocol (ACP), pi, gemini --acp, and dozens of others. For any of them that doesn't have a dedicated package, acpCompatible (from @tanstack/ai-acp) builds a harness adapter on the spot, the harness equivalent of openaiCompatible. Configure how to launch it once, then run it under any provider like the built-in adapters:
import { acpCompatible } from '@tanstack/ai-acp'
const pi = acpCompatible({
name: 'pi',
models: ['pi-fast', 'pi-pro'],
command: ({ model, harnessCwd }) => `pi --acp -m ${model} --cwd ${harnessCwd}`,
authMethodId: 'pi-api-key',
})See the ACP-Compatible Harness guide for the full config (typed models, per-call modelOptions, WebSocket transports, permissions, and protocol coverage). For which agents you can plug in, browse the official ACP agents list and the ACP registry.