An agent now edits files in your working tree, runs shell commands, and spawns sub-agents that do both again with their own budgets. The interesting question stopped being can it write the code? It is can I let it? β and twenty minutes later, what exactly did it do?
Arcturn is an open-source terminal coding agent and the TypeScript harness underneath it, built around those two questions:
- Every tool call clears a permission engine before it runs β rules, scopes, modes.
- Every file edit is snapshotted first β
/rewindrestores files and forks the conversation back to any earlier turn. - Every session is a file on disk you can replay, bisect, and blame back to the exact tool call that made a change.
- Reviewers in multi-agent pipelines structurally cannot merge code β not a prompt asking nicely, an execution lane that discards their diffs by construction.
npm install -g arcturnNode 20 or newer. pnpm add -g arcturn and bun add -g arcturn work the same way;
npx arcturn runs it without installing.
export ANTHROPIC_API_KEY=sk-ant-... # or OPENAI_API_KEY, GOOGLE_API_KEY, ...
cd your-project
arcturn # interactive TUI
arcturn -p "explain this repo" # one-shot: run, print, exit
arcturn -p "..." --output-format json # NDJSON event stream, for programsRule-based allow/deny/ask decisions resolved across session, project, and user scopes,
with four modes β default, plan, acceptEdits, yolo. Deny beats yolo, always
β that ordering is what lets a workflow confine a role to its worktree, and why the rules
and the sandbox compose instead of fighting. Path matching folds case on case-insensitive
filesystems β probed at runtime, not assumed from the platform, because .ENV should not
walk past a **/.env deny rule on macOS.
<repo>/.arcturn can declare lifecycle hooks, a verify command, extensions and
stdio MCP servers β all of which run as you, and a sessionStart hook runs before
you have typed anything. Arcturn asks once, in a prompt that names every command and
file, and refuses by default. Off a terminal β --print, CI, serve, acp β there is
nobody to ask, so the answer is no: the run still completes, and says on stderr exactly
what it did not run and the three ways to approve it.
The approval covers the project's contents, not its path. Editing src/, a README
or a skill never re-asks; adding a hook, changing any file under extensions/, or
declaring an MCP server does. Your own ~/.arcturn hooks and extensions are never
gated β that is the difference between a gate and a nuisance.
$ arcturn trust --list # exactly what this project would run
$ arcturn trust --allow # approve it for these contents
$ arcturn -p "..." --trust-project # CI that already trusts the checkoutKnown edges are documented rather than implied: a hook command is a pointer into your
shell, so Arcturn cannot see what the files it invokes will contain later. See
docs/integration-notes/INTEGRATION-project-trust.md.
Every edit is snapshotted before it lands. /rewind restores the files and forks the
conversation back to that turn β the session is a tree, not a list, so the branch you
rewound away from still exists. /rewind <query> jumps by intent.
Sessions persist as JSONL trees. arcturn replay re-runs one hermetically,
arcturn bisect finds the turn where behaviour changed against a recorded cassette, and
arcturn blame traces any file change back to the session, turn, and tool call that made
it. Cost and token usage ride the same stream β --max-cost 2.00 is a hard abort, not a
suggestion.
A numbered markdown file is a multi-stage pipeline. @role dispatches a step to a named
markdown agent with its own model, tools, and turn ceiling:
---
name: ship-fix
budgetUsd: 15
---
1. @qa-functional Write a regression test that FAILS against the current code: {{input}}
2. @developer Make it pass. Repro: {{prev}}
3. - @qa-adversarial Try to break the fix from a fresh reading of the diff. {{prev}}
- @security-reviewer Audit the change for anything a hostile caller could exploit. {{prev}}
4. @tech-lead Assemble the evidence packet. Do not merge anything yourself. {{prev}}Three dispatch lanes decide what a role can touch, derived from its tools, never from
its description: read (no worktree), exec (isolated worktree, diff always
discarded), write (worktree, diff captured and applied). A reviewer cannot land code β
bash is a write primitive wearing a read costume, and the lane knows it. Worktrees are
seeded with the run's already-applied patches, so reviewers read what the pipeline
actually produced, not untouched HEAD.
Runs are journalled and resumable: /workflow status shows what an interrupted run
reached and its spend; /workflow resume re-enters it, replaying completed steps rather
than paying for them twice, and probing every recorded patch with
git apply --check --reverse so nothing lands twice. A role that hits real ambiguity
emits ORG-ASK: and the whole run pauses for a human answer, then continues exactly
where it stopped. Per-run budgetUsd: and per-step stepTimeoutMs: ceilings bound the
two things that actually run away.
A step can promise a typed reply with [contract:β¦]; on a model that takes a JSON schema
(Claude 4.5+, OpenAI, Gemini 2.5+) the contract is sent as a native response schema instead
of a fenced block to be hunted for, and the same validator still judges it. [budget:<tokens>]
gives a step a task budget on the Claude models that support one, so it paces itself rather
than being cut off at the turn ceiling.
A runnable ten-role, six-pipeline enterprise kit ships in
kits/enterprise-org/, documented alongside an honest
account of what is enforced and what is still convention.
Connect stdio and HTTP Model Context Protocol servers (OAuth 2.1 included) and their
tools, resources, and prompts just work. Or invert it: arcturn mcp-serve exposes
Arcturn as an MCP server so another agent can drive it β read-only by default, behind
a workspace boundary enforced by permission rule and a physical path check, with the
peer assumed hostile. The boundary's audit trail is public: see
the security page.
Servers can also ask back. An MCP elicitation opens a form in the TUI (and is declined,
never auto-accepted, where nobody is at a terminal); every one is audited by field name,
never by answer. Sampling, a server asking your model for a completion, is off unless
that server's mcp.json entry says "sampling": true, runs as one isolated call, and is
billed to the session and its --max-cost.
| Provider | Verified against a live endpoint |
|---|---|
| Anthropic (Messages API) | β |
| OpenAI β Chat Completions and Responses API | β |
| Google Gemini | β |
| Any OpenAI-compatible endpoint | β |
| Any Anthropic-compatible endpoint | β |
| AWS Bedrock Β· Vertex AI Β· Azure OpenAI |
Streaming, tool calls, thinking, prompt caching, and cost tracking across all of them.
"Verified" means a real request to the real endpoint: streaming, a tool call answered on
a second turn, and cost accounting checked against published rates β
the story of why that column exists
is worth two minutes. A per-event idle timeout (not a duration cap) means a slow but
progressing stream is never killed while a stalled one is. /model refresh queries each
provider's own model list and merges newly released models without touching curated
entries.
A BM25 + structural index over the workspace powers a search_code tool that finds code
by meaning. Nothing leaves your machine.
Automatic compaction, context editing, tool-output offloading, and deferred tool schemas
loaded on demand via tool_search β a long session stays inside the window without
silently dropping what mattered, and the history only ever grows at the end, so the prompt
cache and the thinking Claude's newest models bind to it survive.
Built-in tools (read, write, edit, bash with background execution, grep,
glob, ls, fetch, websearch via Brave or DuckDuckGo, or Anthropic's own web search and fetch if you set websearch.provider: "anthropic") Β· LSP diagnostics appended to
every edit (TypeScript, Python, Go, Rust) Β· markdown skills as slash commands with no
build step (.arcturn/skills, frontmatter, $ARGUMENTS) Β· lifecycle hooks that can veto
a preToolUse call Β· fuzzy @file mentions with image attachment Β· plan mode and
structured todos living in the session tree Β· sub-agents, /team, /scout, and /bg
for parallel work Β· an opt-in OS sandbox confining bash writes to the workspace Β· full
light/dark theming down to the terminal canvas itself (OSC 11) Β· live cost and context
meters in the status bar.
The CLI is a consumer of @arcturn/core β everything it does, your program can do:
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-5-5"),
systemPrompt: "You are a focused, careful coding agent.",
tools,
cwd: process.cwd(),
sessionDir: ".arcturn/sessions",
permissions: { mode: "default" },
});
agent.subscribe((event) => {
if (event.type === "toolEnd") console.log(event.result.isError ? "β" : "β", event.toolCallId);
});
await agent.prompt("Add input validation to the signup handler");
console.log(agent.finalText());One Agent per session, options in, events out β the same event stream the TUI renders
and the CLI prints as NDJSON. Start at the SDK docs.
| Package | Description |
|---|---|
arcturn |
The interactive coding agent, workflow engine, and agent-org runtime |
@arcturn/core |
Agent runtime: event loop, steering, sessions, compaction, permissions, sub-agents |
@arcturn/ai |
Unified multi-provider LLM streaming client with model catalog and retry |
@arcturn/types |
Zero-dependency shared contracts (messages, events, tools, permissions, sessions) |
@arcturn/tools |
Built-in tools: read, write, edit, bash (+background), grep, glob, ls, fetch |
@arcturn/mcp |
Model Context Protocol client bridge β internal, API may change in any release |
@arcturn/tui |
Terminal UI library with differential rendering β internal, API may change in any release |
@arcturn/index |
Token-optimized code index and BM25 semantic search β internal, API may change in any release |
@arcturn/protocol |
NDJSON wire protocol for server mode β internal, API may change in any release |
@arcturn/server |
WebSocket server exposing agent sessions β internal, API may change in any release |
@arcturn/evals |
Task-level eval harness (in-repo only, not published) |
@arcturn/types, core, index, and protocol carry no external runtime
dependencies at all β nothing in the layer that decides whether a tool call runs comes
from outside this repository.
arcturn.dev/docs β 44 pages: a CLI reference for every flag and slash command, permissions, workflows, agent organizations, the SDK, and the security limits page that states every control's known edges as content, not fine print.
From the blog, the three stories that explain the project's temperament: Why I built an agent harness you can audit Β· Three providers, three bugs, under two cents Β· Reviewers who cannot merge
Arcturn runs on macOS, Linux, and Windows β CI builds and tests all three on
Node 20 and 22 (.github/workflows/ci.yml). Windows differences worth knowing:
- The
bashtool and lifecycle hooks run commands through%ComSpec%(cmd.exeby default), not a POSIX shell, so agent-authored commands with POSIX idioms (&&-chained builtins,$(...), single-quoted strings) may not run as written. - The opt-in
sandbox: "workspace-write"filesystem sandbox is macOS (sandbox-exec) and Linux (bwrap) only; on Windows it runs unconfined and says so explicitly rather than silently claiming confinement. - Windows support is newer than macOS/Linux. If you hit a rough edge, WSL2 is the smoothest path to the same POSIX shell and sandbox this project runs on day to day.
git clone https://github.com/sitharaj88/arcturn.git
cd arcturn
pnpm install
pnpm build # build all packages
pnpm check # lint + typecheck
pnpm test # run all testsNode.js β₯ 20 and pnpm β₯ 10 required. See PLAN.md for architecture, the status ledger, and the roadmap β including the corrections, which stay in the record rather than getting laundered out of it.
Issues and pull requests are welcome. The engineering bar to know about up front: fixes land with a regression test that was verified to fail first, and claims of verification are reserved for things that have actually run.
Sitharaj Seenivasan
- π Website: sitharaj.in
- πΌ LinkedIn: sitharaj08
- π» GitHub: sitharaj88
If this project helps you, consider buying me a coffee β it keeps the work going.
Licensed under the Apache License 2.0. Β© 2026 Sitharaj Seenivasan.

