Capabilities
Extensibility
Most of what you’ll want to add is a markdown file or a config entry. The rest is a TypeScript interface.
MCP, built in
@arcturn/mcp is a Model Context Protocol client that connects to configured servers and bridges their tools into ordinary Arcturn tools, exposing their resources and prompts through the same manager. Two transports: a stdio process, or streamable HTTP with an automatic fall back to SSE for servers that only speak the older transport.
A malformed entry throws an error naming the exact server and file rather than failing the load silently, and an unset ${ENV_VAR} reference is a hard error — a silently empty Authorization header is a worse failure mode than refusing to start. arcturn mcp add manages the same files, so you never have to edit the JSON by hand.
{
"servers": {
"playwright": {
"type": "stdio",
"command": "npx",
"args": ["@playwright/mcp@latest"]
},
"linear": {
"type": "http",
"url": "https://mcp.linear.app/sse",
"headers": { "Authorization": "Bearer ${LINEAR_TOKEN}" }
}
}
}Markdown skills
Drop a file in ~/.arcturn/skills or .arcturn/skills and it is a slash command. Frontmatter is optional; the body is the prompt template, with $ARGUMENTS, $1–$9, $CWD and $SKILL_DIR expanded when the command runs. No build step and no restart — the loader re-reads the roots each time skills are discovered.
The same library is exposed to the model itself as one ordinary tool, so it can reach for a skill mid-task without anyone typing a command. A skill can never shadow a built-in command: the built-ins win a name collision and the skill is dropped with a warning.
---
name: changelog
description: Draft a changelog entry for the current diff
---
Summarize the staged git diff as a changelog entry in Keep a Changelog style.
Focus on: $ARGUMENTSHooks at the boundaries
Hooks are shell commands declared per lifecycle point — preToolUse, postToolUse, sessionStart, runEnd — with an optional matcher restricting which tool they fire for and a timeoutMs overriding the ten-second default.
The event payload arrives on the hook’s stdin as a single JSON object, so a hook decides on the actual arguments rather than a tool name. Only preToolUse hooks can block anything, and only the call they ran for.
{
"hooks": {
"preToolUse": [
{ "command": "./.arcturn/hooks/guard-bash.sh", "matcher": "bash" }
],
"postToolUse": [
{ "command": "./.arcturn/hooks/log-tool-use.sh", "timeoutMs": 5000 }
],
"sessionStart": [
{ "command": "echo session started >> .arcturn/session.log" }
]
}
}Sub-agents, plan mode and todos
The subagent tool delegates a self-contained piece of work to a scoped child agent with its own context window, tools and model. The child’s entire event stream re-publishes on the parent as subagentEvent, so a UI can render nested activity without knowing anything about what a sub-agent is, and aborting the parent cascades to the child.
Named specializations are discovered from markdown, the same way skills are. The plan and todo tools carry the structured state beside them: plan mode is enforced by the permission engine, and the only way out is presenting a plan for approval.
---
name: doc-reviewer
description: Reviews documentation for accuracy against source code
tools: read, grep, glob
model: anthropic/claude-haiku-4-5
---
You are a documentation accuracy reviewer. For every claim in the document, find the
corresponding source location and confirm it. Flag anything unverifiable or stale.Agent teams and background agents
Three ways to spend a second agent, for three different problems. The subagent tool is one-shot and synchronous — the parent asks and waits. /bg is fire-and-forget and durable: a whole task runs off the foreground thread, and /bg logs, /bg cancel and /bg adopt check back on it later.
/team is orchestration: one goal decomposed into subtasks with provably disjoint file scopes, one agent per subtask in its own throwaway git worktree, each member’s work captured as a patch file on disk. /team merge replays those patches with git apply, checking each first and stopping at the first refusal rather than writing conflict markers into your tree.
Workflows
A workflow fixes the control flow in a file and lets the model fill in only the content of each step. Top-level numbered items are stages that run strictly in order; indented bullets under one are parallel branches whose outputs join in written order, never completion order, so the same file always produces the same pipe.
A [tag] prefix selects the model for that step, and every tag resolves before the first step runs — a workflow whose last step names a dead model must not spend two paid steps first. Anything the grammar does not accept is a parse error naming the line number.
---
name: ship-fix
description: Reproduce, patch and review one bug report
continueOnError: false
---
1. [anthropic/claude-haiku-4-5] Reproduce this bug and quote the failing output: {{input}}
2. Given the repro below, do both halves:
- Write the minimal patch. Repro: {{prev}}
- Write a regression test that fails before the patch. Repro: {{prev}}
3. Review the patch and the test for correctness. Work so far: {{prev}}Agent organizations
Put a named role behind each step — an architect, a developer, two kinds of QA, a security reviewer — and its declared tools: decide its lane, not its prompt and not the session’s permission mode. No bash and no write tool is the read lane: fresh context, no worktree, nothing to apply. bash without write or edit is the exec lane. Either write tool is the write lane, whose patch is replayed into your checkout with a plain git apply — no --3way, no --force.
The exec lane is the one that earns the whole design. A reviewer usually needs to run things, and bash is a write primitive wearing a read costume — so it gets a real isolated worktree and that worktree’s diff is discarded on every path, success and failure alike. A reviewer that cannot land a change has nowhere to put a finding except the report you read. A role declaring no tools: at all is refused at dispatch rather than defaulted to anything.
For the questions a model should not answer alone — single-tenant or multi-tenant, whether a breaking change is acceptable — a role writes ORG-ASK: and the run pauses. /workflow status prints the question and the exact command to answer it; your reply becomes that step’s output and the run continues. Nothing that already completed is re-executed, and no patch that already landed is applied a second time.
---
name: security-reviewer
description: Audits a diff for injection and unvalidated input reaching a sink.
model: anthropic/claude-opus-5
tools: [read, grep, glob, ls, bash]
maxTurns: 50
---
You audit changes. You do not fix them.Custom tools and extensions
A tool is one object: a JSON-Schema definition the model sees, and an execute function that does the work. The contract in one sentence — execute must resolve with a ToolResult for every expected outcome, including failure, and reject only for genuine programming errors.
Its context carries the abort signal, a requestPermission callback into the same engine the built-ins use, and an onUpdate channel for incremental progress. Modules dropped in .arcturn/extensions are loaded the same way, so an extension is TypeScript on disk rather than a fork.
interface Tool {
definition: ToolDefinition;
execute(input: Record<string, unknown>, ctx: ToolExecutionContext): Promise<ToolResult>;
}
interface ToolExecutionContext {
cwd: string;
/** Aborts when the user interrupts the run. */
signal: AbortSignal;
/** Ask the permission engine (may prompt the user) before a sensitive action. */
requestPermission: PermissionRequester;
/** Report incremental progress; safe to call many times. */
onUpdate: (update: ToolUpdate) => void;
sessionId: string;
toolCallId: string;
}Every turn counts.
Start a session, watch every tool call ask first, then go back and read exactly what happened.