Skip to content
Open source · Apache-2.0 · TypeScript

The coding agent you can hold accountable.

Arcturn is an open-source terminal coding agent and the TypeScript harness underneath it.

npm install -g arcturn

Node 20 or newer. See Getting started.

Allowed. The edit landed, snapshotted first so /rewind can undo it.

An arcturn session. The header names the model and the working directory; a prompt asks for input validation on the /signup handler; arcturn reads the route, greps for existing validation, then starts an edit to src/routes/signup.ts and stops — a permission dialog asks before the write, offering allow once, allow always for that file in this project, or deny. The edit was allowed: it landed as 24 lines added and 3 removed, and the run finished in 12 seconds having spent four cents.

The problem

Capability raced ahead of accountability.

The models write the code — that question is settled. The one I actually have is whether I can let an agent run, and, twenty minutes later, what exactly it did.

git diff is the whole forensic story.

Forty tool calls leave the residue of maybe twelve, in a flat pile, with no indication of which turn produced which hunk.

The shell commands leave no trace at all.

Fetches, background tasks, and the sub-agent that ran for ninety seconds and cost more than the rest of the session.

“Undo” means git checkout plus remembering.

The conversation that produced the change isn’t in version control, so going back means losing it.

Control

Decide once, at the choke point.

The runtime’s tool dispatcher checks the permission engine and returns a denial before a tool’s execute is ever reached — there is no second path. Rules are allow, deny or ask; scopes resolve session over project over user. Read-only tools pass. Anything that reaches the ask step with no permission requester configured resolves to deny, not “assume it’s fine.”

  • Four modes — default, acceptEdits, plan, yolo
  • --dry-run sends file mutations to a shadow tree for /diff before /apply
  • Lifecycle hooks can veto a preToolUse call
  • An opt-in OS sandbox confines bash writes to the workspace

A permission prompt in an arcturn session: editing src/routes/signup.ts requires approval, with allow, deny, and always-allow for src/**.ts offered.

  1. Session header: arcturn, claude-sonnet-4-5, ~/projects/api.
  2. Prompt: add input validation to the /signup handler
  3. Tool call: read src/routes/signup.ts.
  4. Tool call: grep "validate" src/**.
  5. Permission required for edit: src/routes/signup.ts. edit: add a SignupSchema check before the handler body. Options: Allow once, selected; Allow always: edit src/**.ts (project); Deny and tell the model why.
  6. Status bar: arcturn, claude-sonnet-4-5, default, $0.0087, ctx 9%.

Accountability

The session is the artifact.

Every session is a .jsonl file — a header line, then one JSON line per entry, appended in order. The structure is a tree: each entry carries a parentId, so resuming from three turns ago and trying a different approach starts a branch instead of overwriting what came after. Both branches stay walkable.

Underneath, every write and edit snapshots the file’s prior content first, so /rewind can restore it.

Replay, bisect and blame
arcturn replay <session>
Re-runs the original prompts against the same model or another one, emitting NDJSON per turn, so you can diff two runs mechanically.
arcturn bisect <session>
Binary-searches those prompts for the turn where behaviour diverged, replaying a recorded cassette hermetically: no provider, no network.
arcturn blame <file>
Per line, which turn wrote it and what evidence that turn had: files read, pages fetched, commands run, with anything from a fetch or an MCP server marked untrusted.

Models

Bring your own provider.

One interface across Anthropic, OpenAI and every OpenAI-compatible endpoint, Google Gemini, Bedrock, Vertex and Azure — streaming, tool calls, thinking, prompt caching and cost tracking included. Point --model at <provider>/<model>, route different roles to different models, or set a failover chain.

bash
arcturn --model anthropic/claude-sonnet-4-5
arcturn --model openai/gpt-5.1
arcturn --model google/gemini-3-pro-preview

Receipts

What has actually run, and what hasn’t.

6 of the 9 provider paths have completed real multi-turn tool-calling sessions against a live endpoint. Another 3 have never reached their endpoints at all. Which is which is in the table, not in a footnote.

  • openai-compatible

    Any OpenAI-shaped endpoint, credentials per endpoint. Has completed real multi-turn tool-calling sessions.

    Proven
  • anthropic

    Claude, direct — ANTHROPIC_API_KEY. Verified live on Claude Haiku 4.5.

    Proven
  • openai

    GPT via Chat Completions — OPENAI_API_KEY. Verified live on GPT-5 nano.

    Proven
  • openai-responses

    GPT via the Responses API — OPENAI_API_KEY. Verified live on GPT-5 nano.

    Proven
  • google

    Gemini, direct — GOOGLE_API_KEY (GEMINI_API_KEY also works). Verified live on Gemini 3.5 Flash Lite.

    Proven
  • anthropic-compatible

    Any Anthropic-Messages endpoint, credentials per endpoint. Verified live against a canonical Messages API; no third-party implementation exercised yet.

    Proven
  • bedrock

    Claude, Nova, Llama, Mistral and Titan on AWS — the standard AWS provider chain.

    Not reached
  • vertex

    Gemini and Claude on Google Cloud — application-default credentials.

    Not reached
  • azure

    GPT on Azure OpenAI, addressed by deployment — AZURE_OPENAI_API_KEY or Entra ID.

    Not reached

Four waves of adversarial review went at the seams. They found /apply writing outside the workspace through an in-workspace symlink, served sessions and sub-agents escaping the audit trail entirely, a WebSocket upgrade with no Origin check, and two features that were present but unreachable. All four are written up on the security page rather than quietly patched out.

Every fix landed with a regression test verified to fail against the previous behaviour first.

Embed it

The same runtime, without a terminal in front of it.

@arcturn/core is what the CLI is built on. One Agent per session, one AgentEvent stream out — the same events the CLI emits with --output-format json.

agent.ts
import { createAgent } from "@arcturn/core";
import { createClient, requireModel } from "@arcturn/ai";
import { createDefaultTools } from "@arcturn/tools";

const llm = createClient(); // resolves API keys from the environment
const { tools } = createDefaultTools({ cwd: process.cwd() });

const agent = createAgent({
  llm,
  model: requireModel("anthropic/claude-sonnet-4-5"),
  systemPrompt: "You are a focused, careful coding agent.",
  tools,
  cwd: process.cwd(),
  sessionDir: ".arcturn/sessions", // omit for an unpersisted, in-memory agent
  permissions: { mode: "default" },
});

agent.subscribe((event) => {
  if (event.type === "toolStart") console.log("→", event.toolName);
});

await agent.prompt("Add input validation to the signup handler");
console.log(agent.finalText());
Embedding with the SDK

Open source

Apache-2.0, and checkable.

No commercial-use restriction, no source-available licence with a catch in clause four. One repository holds the CLI, the runtime, the harness and the regression tests behind the findings above — and the commands that check them are written down rather than described.

Every turn counts.

Start a session, watch every tool call ask first, then go back and read exactly what happened.

npm install -g arcturn