A terminal coding agent that runs on your own provider key.
OpenRouter (400+ models, per-turn billed cost), OpenAI, DeepSeek, Kimi, Z.ai, local Ollama / LM Studio, or any OpenAI-compatible endpoint.
Part of Directionally Local, an open-source stack for running coding agents on your own hardware, alongside Hotcell and Serverless Objects. Product page: directionallylocal.com/autopilot.
autopilot sends every model call to the provider you choose using your own key — bring-your-own-key, nothing proxied, nothing marked up. On first run you pick a provider, connect it once (the key is checked before it's saved) and pick a model. That's the whole setup. OpenRouter is the recommended default; see Providers for the rest.
On OpenRouter you also get:
- Any model, one key. The model picker is OpenRouter's live catalog — Claude, GPT, Gemini, Grok, Qwen, GLM, Kimi, DeepSeek, free models and more — opening on the best & latest by benchmark score, with fuzzy search over everything. Switch any time with
/model. - Real cost, per turn. OpenRouter reports what each generation actually cost; the status bar shows it (a local estimate marked
≈$only until the billed number arrives)./costtotals it by session, day, month and all time. - Reliable tool calling. Requests only go to upstream providers that support every parameter autopilot sends (tools above all), and OpenRouter routes tool-calling traffic to the providers with the best tool-call success rates. The status bar shows which provider served the last turn.
- Prompt caching that stays warm. Each session pins to one upstream provider (OpenRouter sticky routing), so the long, stable prompt prefix keeps hitting the provider's cache across turns.
- Up to 1M+ context (model-dependent) — Read entire modules, large configs, and full stack traces without the model losing track.
- Image understanding — Drop image paths (PNG, JPG, WebP, GIF, BMP up to 5 MB) into any prompt. Great for UI reviews, diagrams, and screenshots.
- Plan / Edit / Auto modes —
planis a whitelist-only research mode: only read-only tools (read, glob, grep, web search, GitHub read-only, browser fetch) are allowed. Writes, edits, mutating bash, MCP tools, and LSP renames are all blocked.edit(default) prompts per mutating call.autoapproves everything for trusted tasks. - Subagents — By default, the coordinator assesses substantial tasks for independent research and may delegate without special phrasing. Worker calls still go through permission checks and need a configured backend. See the subagents guide for examples, controls, limits, and setup.
- Long-running unattended runs — The headless server can launch detached jobs, sleep between check-ins, and resume from saved checkpoints. Overall runtime, token, cost, and action budgets are optional; explicit budget exhaustion pauses for input. See the long-running runs guide.
- Windows support — OS-aware shell auto-detects
cmd.exe/ PowerShell on Windows,bashon Unix. Thebashtool works out of the box on all platforms. - Message queuing — Submit multiple messages while the agent is busy; they queue and auto-drain. Escape interrupts the current turn but preserves the queue.
- Smart permission modal — Denying a tool opens inline feedback so you can tell the agent what to do instead. Keyboard-native navigation (
↑/↓,j/k,Alt+1/2/3). - Loop guardrails — Agent hard-stops when all tools in a turn are blocked, preventing infinite token-burning cycles.
- Persistent all-time cost history — Append-only
history.jsonltracks daily usage forever, so/costshows true all-time and monthly totals that survive across sessions and version updates. - LSP + MCP — Semantic code intelligence (hover, go-to-definition, references, diagnostics) via Language Server Protocol. Extend with external tools via Model Context Protocol.
- Local structured memory — SQLite + embeddings cross-session memory. The agent recalls facts, instructions, and preferences across sessions via
remember,recall, andforgettools. - Web search, GitHub, and headless browser — Research the web, read GitHub repos, and fetch JavaScript-rendered pages without leaving your terminal.
- OpenRouter as the model provider — Every OpenRouter model, your own key, billed cost per turn. Replaces Cloudflare Workers AI / AI Gateway (see Upgrading from Cloudflare).
- OS-aware shell with Windows support — Auto-detects
cmd.exe, PowerShell, or bash based on platform. Override withKIMIFLARE_SHELLor/shell. - Smart permission modal with inline feedback — Deny a tool and immediately tell the agent what to do instead. Keyboard-native navigation with
↑/↓,j/k,Alt+1/2/3. - True message queuing — Enter queues messages while the agent is busy; Escape interrupts and auto-drains the queue.
- Hard-stop loop guardrail — Stops token-burning cycles when all tools in a turn are blocked.
- Headless SDK — Programmatic
createAgentSessionAPI and JSONL-over-stdio RPC mode for building on top of autopilot.
See the full changelog at github.com/sinameraji/autopilot/releases.
npm install -g autopilot-ai
autopilotOn first run, Sign in with OpenRouter: approve autopilot in your browser and a key is created for you (or paste one from https://openrouter.ai/keys). Then pick a model. That's it.
Or install with Homebrew on macOS or Linux (Homebrew installs Node.js for you):
brew install sinameraji/tap/autopilot
autopilotOr run without installing:
npx autopilot-aiComing from
kimiflare? The package was renamed. Runnpm uninstall -g kimiflare && npm install -g autopilot-aiand useautopilotinstead ofkimiflare. Your settings, sessions, memory and cost history are kept (they stay in~/.config/kimiflareand~/.local/share/kimiflare), andKIMIFLARE_*environment variables still work.
Requires Node.js ≥ 20.
To pick up an earlier conversation, run autopilot resume <id> (the exact command is printed when you exit), autopilot resume to choose one from this directory, or autopilot -c for the most recent one. Inside autopilot, /resume does the same.
| Provider | id | Key | Notes |
|---|---|---|---|
| OpenRouter (default) | openrouter |
OPENROUTER_API_KEY or browser sign-in |
400+ models, billed cost per turn, sticky routing, subagents, Jev checks |
| Anthropic | anthropic |
ANTHROPIC_API_KEY |
native Messages API: extended/adaptive thinking, explicit prompt caching (API keys only — Claude Pro/Max sign-in isn't permitted for third-party tools) |
| OpenAI | openai |
OPENAI_API_KEY |
Responses API: reasoning carries across tool loops (encrypted, stateless), codex models |
| Google Gemini | gemini |
GEMINI_API_KEY / GOOGLE_API_KEY |
OpenAI-compatible endpoint; Gemini 3 thought signatures are kept with each tool call |
| ChatGPT (Plus/Pro/Team plan) | chatgpt |
autopilot auth chatgpt (browser sign-in with token sharing) |
your plan's GPT models over the Responses API; tokens refresh automatically; usage counts against your plan. Uses OpenAI's official token-sharing sign-in, not the Codex backend |
| GitHub Copilot | copilot |
autopilot auth copilot (GitHub device login; --enterprise <domain>) |
your Copilot plan's models — Claude over Messages, GPT over Responses; usage counts as premium requests. Not a documented public API, so GitHub may change it |
| Google Vertex AI | vertex |
Google credentials (gcloud auth application-default login, a service-account key in GOOGLE_APPLICATION_CREDENTIALS, or GOOGLE_VERTEX_ACCESS_TOKEN) + GOOGLE_VERTEX_PROJECT (or autopilot auth vertex --project …) |
Gemini and partner models through Vertex's OpenAI-compatible endpoint, Claude through Anthropic's Messages API on Vertex; location GOOGLE_VERTEX_LOCATION (default global) |
| Amazon Bedrock | bedrock |
AWS_BEARER_TOKEN_BEDROCK (Bedrock API key), or AWS credentials (env or ~/.aws/credentials / AWS_PROFILE; for SSO, eval "$(aws configure export-credentials --format env)"); region AWS_REGION |
Converse API with SigV4 signing; Claude thinking and prompt caching; inference-profile ids like global.anthropic.claude-sonnet-5-5 |
| DeepSeek | deepseek |
DEEPSEEK_API_KEY |
|
| MiniMax | minimax |
MINIMAX_API_KEY |
OpenAI-compatible endpoint with split reasoning (<think> tags are separated out if they appear) |
| Moonshot AI (Kimi) | moonshot |
MOONSHOT_API_KEY |
|
| Kimi Code (subscription) | kimi-code |
KIMI_API_KEY |
|
| Z.ai (GLM) | zai |
ZHIPU_API_KEY |
|
| Z.ai GLM Coding Plan | zai-coding |
ZHIPU_API_KEY |
|
| xAI | xai |
XAI_API_KEY |
Grok over the Responses API |
| Mistral | mistral |
MISTRAL_API_KEY |
tool-call ids adapted to Mistral's format; mistral-embed |
| Groq · Cerebras · Together AI · Fireworks AI · DeepInfra | groq · cerebras · together · fireworks · deepinfra |
GROQ_API_KEY · CEREBRAS_API_KEY · TOGETHER_API_KEY · FIREWORKS_API_KEY · DEEPINFRA_API_KEY |
fast / open-model hosts |
| Vercel AI Gateway | vercel |
AI_GATEWAY_API_KEY |
many vendors with one key |
| Cloudflare Workers AI | cloudflare |
CLOUDFLARE_API_TOKEN + CLOUDFLARE_ACCOUNT_ID (or autopilot auth cloudflare --account-id …) |
Cloudflare-era kimiflare configs reuse their credentials, and memory stays on bge-base-en-v1.5. For AI Gateway, use its OpenAI-compatible URL as a custom endpoint |
| Azure OpenAI | azure |
AZURE_API_KEY + AZURE_RESOURCE_NAME (or autopilot auth azure --resource <name>) |
v1 API over Responses; model ids are your deployment names, discovered from the resource. Entra ID sign-in isn't supported yet — use a key |
| Ollama (local) | ollama |
none | installed models are discovered; providers.ollama.baseUrl for another host |
| LM Studio (local) | lmstudio |
none | |
| Requesty | requesty |
REQUESTY_API_KEY |
|
| Any OpenAI-compatible endpoint | custom |
KIMIFLARE_API_KEY (optional) |
KIMIFLARE_BASE_URL — LiteLLM, vLLM, llama.cpp, a company gateway |
- First run asks which provider to use; providers with a key already in your environment are offered first and used without saving the key.
- Switch any time with
/provider <id> [key](/providerlists them and where each key comes from), or from the CLI:autopilot auth <id>(saves a key and makes it active),autopilot auth use <id>,autopilot auth status. For one run, setAUTOPILOT_PROVIDER=<id>. - Everything follows the provider: chat, compaction and memory side-calls, memory/skill embeddings, the model picker (each provider's own catalog — models.dev for hosted providers, live discovery for Ollama and LM Studio), pricing, error messages and the status page link. Nothing is sent to OpenRouter while another provider is active.
- OpenRouter-only features switch off on other providers: billed-cost confirmation (elsewhere
/costshows estimates from the provider's published prices), Hotcell subagents, and the Jev completion/triage checks. - Embeddings: providers without an embeddings API (Anthropic, DeepSeek, Moonshot, Kimi Code, the Coding Plan, LM Studio) keep memory working through text and topic-key recall. To keep vector recall, set
"embeddingProvider": "openai"(orAUTOPILOT_EMBEDDING_PROVIDER) with that provider's key, ormemoryEmbeddingModel.
Config file shape (keys can live here instead of the environment):
{
"provider": "deepseek",
"providers": {
"deepseek": { "apiKey": "sk-…", "model": "deepseek-v4-pro" },
"ollama": { "baseUrl": "http://gpu-box:11434/v1" }
}
}Three ways to provide it — the first one found wins:
- Environment:
OPENROUTER_API_KEY(orKIMIFLARE_OPENROUTER_KEY). With this set, the setup screen never appears — the way to run kimiflare headless (CI, a VM, a container). - Sign in / setup screen / CLI: "Sign in with OpenRouter" on first run, or
autopilot auth openrouter— approve in the browser and a key is created and saved (OpenRouter's OAuth PKCE flow). Over SSH or in a container it shows a link to open on any device and asks for the code OpenRouter displays (force this with--code).--pasteprompts for an existing key instead, andautopilot auth openrouter <key>saves one directly. Inside the TUI,/key set <key>replaces it. - Config file:
"openrouterApiKey": "sk-or-…"in~/.config/kimiflare/config.json(created with mode 600).
/key shows which key is in use, what it has spent and how much credit is left. Model calls — including memory embeddings and small internal side-calls (summaries, memory extraction) — are all billed to this key.
A key with no credits can still use OpenRouter's free models (the "Free" section of /model, ids ending in :free), subject to OpenRouter's daily request cap.
Earlier kimiflare versions ran on Cloudflare Workers AI / AI Gateway. On the first launch after upgrading you'll be asked for an OpenRouter key once; after that:
- Your settings (theme, MCP/LSP servers, hooks, memory, sessions, cost history) carry over.
- Model ids are migrated (
@cf/moonshotai/kimi-k2.6→moonshotai/kimi-k2.6, and so on), and the retired Cloudflare fields (OAuth login, gateway, Unified Billing, provider keys) are removed fromconfig.json. - Memory keeps working unchanged: on OpenRouter, embeddings use the same
bge-base-en-v1.5model Workers AI served. /gateway,autopilot auth cloudflare,--cloudand the Cloud-only commands are gone./multi-agent(Commute) is retired; subagents now run locally through Hotcell.
For substantial tasks, the agent can hand independent investigations to subagents: separate Autopilot instances that each explore the repository in their own local Hotcell sandbox and report back cited findings. Several subagents run in parallel behind a single approval while the main agent keeps the conversation and does the edits. You don't need to ask for them; say "don't use subagents" to opt out, or "use subagents" to insist.
Setup: install and start Hotcell, then add your OpenRouter key to its host key store (hotcell keys add openrouter). The real key never enters a sandbox. Subagents are read-only (no edits, shell, PRs, or MCP) and see your current working tree, including uncommitted changes — it is streamed into the sandbox, so private repositories work and nothing needs to be pushed (secrets like .env are never copied). The first subagent after an install builds a cached runtime (about two minutes, once per version); later ones start in seconds. See docs/subagents.md for limits and troubleshooting.
While subagents run they are listed above the prompt (Ink) or in the activity panel (/agents, Camouflage). /subagents cancel <n> stops one without interrupting the turn, and /subagents stats shows how often subagents were offered, used, and succeeded over the last week (kept locally in ~/.local/share/kimiflare/subagents.jsonl; no prompts or code are recorded).
You pick the model on first run, and can switch any time with /model. The picker opens on the best & latest models — recent, tool-capable models ranked by the agentic and coding benchmark scores OpenRouter publishes (Artificial Analysis), at most two per lab — so it stays current as new models ship, with no list maintained in code. Start typing to fuzzy-search all of them (sonet finds Claude Sonnet, gpt 6 the GPT-6 family). Or set one directly:
/model moonshotai/kimi-k3 # 1M context
/model anthropic/claude-sonnet-5
/model deepseek/deepseek-v4-flash
autopilot -m moonshotai/kimi-k2.7-code -p "..."Any OpenRouter model id works, including variants like :free and :nitro. Models OpenRouter lists without tool calling are hidden from the picker — a coding agent needs tools.
Provider routing (optional). OpenRouter serves most models through several upstream providers. autopilot always requires providers that support every parameter it sends, and otherwise leaves routing to OpenRouter (price-weighted, uptime-aware, tool-calling quality first, sticky per session for caching). To steer it, add an openrouterProvider object to the config file — any of OpenRouter's provider preferences:
{ "openrouterProvider": { "ignore": ["SomeProvider"], "data_collection": "deny" } }Avoid order / sort unless you need them: they turn off sticky routing, which is what keeps the prompt cache warm.
Point every model call at your own OpenAI-compatible endpoint instead of OpenRouter — useful when a host application (CI, an agents platform, a container) fronts model access with its own broker and doesn't want to hand autopilot a raw provider key:
export KIMIFLARE_BASE_URL="https://your-broker.example.com/v1" # /chat/completions is appended
export KIMIFLARE_API_KEY="<bearer for that endpoint>" # optional; header omitted if unset
autopilot -p "..." # or --mode rpc — no OpenRouter key neededThe same pair can be persisted in ~/.config/kimiflare/config.json as baseUrl / apiKey
(env vars win over the file, field by field). When a base URL is configured:
- Chat requests go to
<baseUrl>/chat/completionsand memory embeddings to<baseUrl>/embeddings, withAuthorization: Bearer $KIMIFLARE_API_KEY(noAuthorizationheader at all when the key is unset — e.g. a local llama.cpp/Ollama server). - Model ids pass through in the request body unchanged; your endpoint owns provider dispatch.
- Cost is the local estimate only — your endpoint does its own metering.
Requesty is another OpenAI-compatible gateway you can use with your own key instead of OpenRouter. It is opt-in: it is only used when no OpenRouter key and no custom endpoint are configured, so an existing setup never changes.
export REQUESTY_API_KEY="<your key>" # from https://app.requesty.ai/api-keys
autopilot # or: autopilot auth requesty (checks and saves the key)
autopilot -p "..." -m openai/gpt-4o-mini- The key can also live in
~/.config/kimiflare/config.jsonasrequestyApiKey(the env var wins). REQUESTY_BASE_URLpicks a region, e.g.https://router.eu.requesty.ai/v1for the EU. Only https URLs on Requesty's hosts (router.requesty.ai,router.eu.requesty.ai,router.us.requesty.ai,router.ap.requesty.ai) are accepted, so the key is never sent anywhere else./modellists Requesty's managed policies first (short ids such askimi-k2.6orclaude-sonnet-4-5), then its fullvendor/modelcatalog. Both kinds of id work.- Defaults on Requesty:
kimi-k2.6for the main model,deepseek-v4-flashfor side calls, andopenai/text-embedding-3-smallfor memory embeddings. - Cost per turn is the one Requesty reports inline.
/keyshows whether the key is valid; OpenRouter only features (browser sign in,/key set, the billed cost lookup) are not available.
autopilot -p "summarize PLAN.md" # stream answer to stdout
autopilot -p "..." --dangerously-allow-all # auto-approve mutating tools (for scripts)
autopilot -p "..." --reasoning # include chain-of-thought in stderrSend a task to an Autopilot server you run. It works in its own sandbox and opens a pull request, and you can close your laptop while it does.
autopilot remote add https://acme.example.com # approve the code in your browser; no keys copied
autopilot --on acme "fix the flaky checkout test" # runs on the server; uncommitted changes go along
autopilot runs # what's running or done, with PR links
autopilot runs watch <run> # follow a run (Ctrl-C detaches, the run keeps going)
autopilot pull <run> # check out the run's branch hereThe run starts from the last commit your branch shares with origin. Unpushed commits, staged and unstaged edits, and untracked files that aren't ignored are sent along as a patch, up to 3 MiB. Add --detach to return right away and -m <model> to pick one of the server's models. Each task gets its own sandbox and branch, so several can run at once. The server keeps the model key and GitHub credentials; your laptop holds only a revocable, 90-day pairing credential in ~/.config/autopilot/remotes.json. Setting up the server side is covered in docs/aster-api.md.
Use autopilot programmatically from your own application — no TUI required.
import { createAgentSession } from "autopilot-ai/sdk";
const { session } = await createAgentSession({
cwd: "/path/to/project",
config: {
openrouterApiKey: process.env.OPENROUTER_API_KEY,
model: "moonshotai/kimi-k2.7-code",
},
});
// Stream every event: text deltas, tool calls, tasks, usage
session.subscribe((event) => {
console.log(event.type, event);
});
// Send a prompt
await session.prompt("Refactor auth to JWT + Redis");
// Mid-flight correction while the agent is still running
await session.steer("Use Redis instead of in-memory store");
// After the turn finishes
await session.followUp("Also add unit tests");
// Clean up
session.dispose();Key features:
subscribe()— receive typed events (text_delta,tool_call,tool_result,task_update,usage,warning,error,done, etc.)prompt()/steer()/followUp()— full conversation lifecyclepause()/resume()— graceful preemptiongetStatus()/getUsage()— inspect session state- Custom
permissionHandler— decide programmatically whether to allow mutating tools - Optional
memoryEnabled,lspEnabled,costAttributionflags
Call session.dispose() when you're done with a session: it is the reliable way to end it
(SessionEnd hooks, the trace's session_end, background subagents, LSP and memory).
As a safety net, a live session also ends if your process exits without dispose(). On
beforeExit, SIGTERM, SIGINT or SIGHUP the SDK runs SessionEnd hooks and flushes OpenTelemetry
events within a 3 s budget. It never calls process.exit and doesn't change whether your
process exits:
- If your app has its own listener for the signal, the SDK starts the shutdown work and your handler decides what happens next.
- If it doesn't, the SDK finishes the work, then re-raises the signal, so the process still dies from it (exit status 143 for SIGTERM, 130 for SIGINT).
An uncaught exception that would crash the process, or an explicit process.exit(), leaves no
time for asynchronous work. The SDK only starts the SessionEnd hook commands, and buffered
OpenTelemetry events may be lost. If your app handles uncaughtException itself, the SDK leaves
its sessions alone; call dispose() from your handler.
Apps that manage their own lifecycle can turn this off:
const { session } = await createAgentSession({ cwd, handleProcessExit: false });The SDK uses the active provider — provider in config (or AUTOPILOT_PROVIDER),
else OpenRouter — and needs its key: providers.<id>.apiKey in config or the provider's env var
(OPENAI_API_KEY, DEEPSEEK_API_KEY, …). A custom gateway endpoint
(baseUrl / apiKey in config, or KIMIFLARE_BASE_URL / KIMIFLARE_API_KEY) needs no other key.
For OpenRouter, the key is resolved in this priority order:
- Explicit
configobject (openrouterApiKey) — recommended for apps - Environment variables:
OPENROUTER_API_KEY/KIMIFLARE_OPENROUTER_KEY - Config file:
~/.config/kimiflare/config.json
A Requesty key (requestyApiKey in config, or REQUESTY_API_KEY) also works
when no OpenRouter key is set; pass a Requesty model id such as openai/gpt-4o-mini as model.
Pass provider in createAgentSession options to set OpenRouter provider-routing preferences for that session.
For Electron / desktop apps, we recommend storing the key in the OS keychain (e.g. Electron safeStorage or keytar) and passing it explicitly:
import { createAgentSession } from "autopilot-ai/sdk";
const openrouterApiKey = await keytar.getPassword("autopilot", "openrouter");
const { session } = await createAgentSession({
cwd: projectPath,
config: { openrouterApiKey },
});If you need process isolation or a non-Node consumer, run autopilot in JSONL-over-stdio RPC mode:
node bin/autopilot.mjs --mode rpcGive the subprocess OPENROUTER_API_KEY — or a custom gateway endpoint
(KIMIFLARE_BASE_URL + KIMIFLARE_API_KEY), ideal for host apps that broker model access themselves.
import { spawn } from "node:child_process";
const proc = spawn("npx", ["autopilot-ai", "--mode", "rpc"], {
cwd: projectPath,
stdio: ["pipe", "pipe", "pipe"],
});
// Read events
proc.stdout.on("data", (chunk) => {
for (const line of chunk.toString().split("\n")) {
if (!line.trim()) continue;
const event = JSON.parse(line);
console.log(event.type, event);
}
});
// Send commands
proc.stdin.write(JSON.stringify({ type: "new_session" }) + "\n");
proc.stdin.write(JSON.stringify({ type: "prompt", message: "Hello" }) + "\n");
// Resolve a permission request
proc.stdin.write(
JSON.stringify({ type: "resolve_permission", requestId: "req_0", decision: "allow" }) + "\n"
);
// Resume a previous session after a process restart (the `new_session`
// response echoes back the sessionId to store for later)
proc.stdin.write(JSON.stringify({ type: "new_session", sessionId: "sdk-session-…" }) + "\n");autopilot
› fix the layout bug in this screenshot docs/bug.png
› convert this mockup design.png to Tailwind HTML| Command | Effect |
|---|---|
/mode edit|plan|auto |
Switch permission mode |
/shell auto|bash|cmd|powershell |
Show or set the shell for the bash tool |
/thinking low|medium|high |
Reasoning effort (persists) |
/resume |
Pick a past conversation to restore |
/compact |
Summarize older turns to free context |
/init |
Scan repo and write KIMI.md project context |
/memory |
Show memory stats and search |
/mcp list / /mcp reload |
Manage MCP servers |
/reasoning |
Toggle chain-of-thought display |
/model |
Pick a model from the active provider's catalog (or /model <id>, /model list [filter]) |
/provider |
List providers, or switch with /provider <id> [key] (see Providers) |
/jev |
Ask Jev for a typed, scoped one-shot decision |
/subagents |
List running subagents; /subagents cancel <n>|all stops them; /subagents off|suggest|auto sets the delegation policy (default: auto) |
/now |
Run your latest queued message now instead of after the current turn |
/key |
Show the active provider's key — on OpenRouter with spend and credit (/key set <key>, /key clear) |
/cost |
Show cost by session, day, month and all time (OpenRouter-confirmed, or estimated from the provider's prices) |
/good / /bad [note] |
Rate the last turn; recorded in the session trace when tracing is on |
/update |
Check for updates |
/help |
List all commands |
/subagents auto is the default: on substantial tasks the agent is asked to look for independent parts and delegate them, and it decides whether delegation is worth it. suggest only hints at delegation for likely parallel work, and off disables harness guidance (you can still ask for subagents explicitly). Explicit requests to use subagents or to work without them always win. Set the policy with /subagents, KIMIFLARE_SUBAGENT_POLICY, or subagentPolicy in config.
The harness never starts a subagent by itself: the agent calls the subagent tool, which always needs normal tool permission (one prompt covers a parallel batch) and obeys provider, read-only, spend, concurrency, and timeout limits. In Code Mode the subagent tool stays a direct tool next to execute_code. Jev (a fast yes/no decision model on OpenRouter) sharpens this: in auto mode it checks whether a substantial request has independent parts (strong delegation guidance at ≥80%, otherwise the softer nudge), and in suggest mode it decides whether to suggest at all. It receives at most 1,200 characters of redacted request text, runs alongside other pre-turn work, and any failure falls back to the default guidance.
When a substantial turn ends without the agent asking you anything, a Jev yes/no check asks whether it actually finished — or clearly explained a blocker — rather than stopping after a plan or "next I'll…". If it clearly didn't, the agent is told to continue (once per turn), so you don't have to type "go on". Typically under a second; OpenRouter only. Turn it off with "completionCheck": false in config or KIMIFLARE_COMPLETION_CHECK=0.
Type ! followed by a command (for example ! gh auth login) to run it in your own terminal, inside the session. Logins, password prompts and interactive programs work because the command gets the real TTY. The output is added to the conversation so the agent can see it. The agent's own bash tool has no terminal, so autopilot suggests ! <command> when something needs you: a login, a sudo password, an SSH passphrase, or a secret you'd rather not paste into chat. On macOS and Linux the output is captured with script(1); elsewhere the command still runs but its output isn't captured.
| Shortcut | Action |
|---|---|
Ctrl+C / Esc |
Interrupt current turn when busy; exit when idle |
Ctrl+R |
Toggle reasoning display |
Ctrl+O |
Toggle verbose tool output |
Shift+Tab |
Cycle mode (edit → plan → auto) |
↑ / ↓ |
Walk prompt history |
A message you send mid-turn is triaged instead of always waiting in line:
- interrupt ("stop, that's the wrong file"): the turn stops at the next safe point and your message runs next. Running subagents are never cancelled by an interrupt; while they run, the message is delivered as soon as they finish.
- steer ("also handle the empty case"): folded into the current task at the agent's next step.
- queue ("after this, update the changelog", or anything unclear): runs as its own turn afterwards. Send
/nowto promote it. - aside ("how's it going?"): answered from the current state without disturbing the turn.
Clear phrasing is decided instantly; ambiguous messages get a quick model check (OpenRouter only), and anything uncertain is queued. Use /subagents cancel <n> to stop a single subagent.
autopilot writes structured JSON logs of agent-side activity (tool calls,
permission decisions, MCP/LSP lifecycle, session events, errors) to
~/.config/kimiflare/logs/<date>.jsonl, one file per day, with 7-day
retention pruned automatically at startup.
The logs deliberately exclude prompts and completions. To capture full
request/response payloads locally for debugging, run with
KIMIFLARE_DUMP_LLM=1; each generation is also visible in your
OpenRouter activity log.
autopilot logs path # today's file
autopilot logs dir # log directory
autopilot logs prune # delete files older than 7 days
# Tail this session's activity, formatted:
tail -f $(autopilot logs path) | jq
# Find the slowest tool calls in the last day:
jq -r 'select(.event == "tool:end") | "\(.data.duration_ms)\t\(.data.tool)"' \
$(autopilot logs path) | sort -rn | headDisable the file sink entirely with KIMIFLARE_LOG_SINK=off. The
separate KIMIFLARE_LOG_LEVEL env var (default off) controls stderr
output — independent of the file sink.
If you set KIMIFLARE_OTEL_ENDPOINT, autopilot also ships each log
entry to that endpoint over OTLP/HTTP
so it lands in Datadog, Honeycomb, Grafana Loki, an internal collector,
or any other backend that speaks OTel. Batched every 5 s (or every
100 entries, whichever first) and best-effort — never blocks the agent
loop.
# Full path:
export KIMIFLARE_OTEL_ENDPOINT="https://otel.example.com/v1/logs"
# Or just the base URL (we auto-append /v1/logs):
export KIMIFLARE_OTEL_ENDPOINT="https://otel.example.com"
# Optional headers (comma-separated key=value pairs) — e.g. for auth:
export KIMIFLARE_OTEL_HEADERS="Authorization=Bearer xyz,X-Tenant=acme"Each log entry maps to one OTel LogRecord. Correlation IDs
(session_id, turn_id, request_id) become record attributes,
data.* fields are flattened to attributes with type-preserving
encoding, and a service.name=kimiflare + service.version pair sits
on the resource.
Autopilot can record its own sessions so you can see how it performs on your work, and compare setups on your own tasks. Everything stays on your machine.
Tracing is off by default. Turn it on for one run with KIMIFLARE_TRACE=1, or always with "trace": { "enabled": true } in ~/.config/kimiflare/config.json. Each session then writes a trace.jsonl under ~/.local/share/kimiflare/traces/ (KIMIFLARE_TRACE_DIR or trace.root to change it), one event per line:
- every model request and response, with tokens, cost and latency
- tool calls and their results, and each permission decision
- compactions, completion checks, subagents and turn ends
- outcome signals: the git changes a session made, test, lint, typecheck and build commands and whether they passed, your
/goodand/badratings (with an optional note), and whether your next message corrected the agent or told it to carry on
Large bodies are stored once, by hash, next to the trace. Likely secrets are redacted before anything is written, nothing is uploaded, and old traces are pruned automatically: after 90 days, above 200 MB for one session, or when all traces pass 2 GB.
autopilot eval runs Autopilot headlessly on a set of tasks and compares two configurations: a model, a setting, or a harness change.
autopilot eval run --tasks evals/tasks/autopilot-bench --config evals/configs/baseline.json --model <id> --out /tmp/eval-a
autopilot eval run --tasks evals/tasks/autopilot-bench --config evals/configs/my-change.json --model <id> --out /tmp/eval-b
autopilot eval compare /tmp/eval-a/results.jsonl /tmp/eval-b/results.jsonlEach task is a JSON file naming a repo, a base commit, a prompt and a test command. Every run gets its own git worktree with tracing forced on, and the task passes if the test exits 0. eval compare prints success rates with a paired significance test, cost, turns and the tasks that flipped. This repository ships autopilot-bench, twenty tasks mined from its own merged pull requests; see evals/README.md for the task format and how to build a set from your own repo.
autopilot can fire shell commands at these points, configured
per-project (.autopilot/settings.json) or globally
(~/.config/autopilot/settings.json); the older kimiflare paths are
still read (see Settings location):
| Event | Fires when | Veto? |
|---|---|---|
SessionStart |
A session's first prompt, before UserPromptSubmit (source: startup/resume) |
No |
PreToolUse |
A tool call is about to run | Yes |
PostToolUse |
A tool call just finished (success or failure) | No |
PostToolUseFailure |
A tool call failed (also fires PostToolUse) |
No |
PermissionRequest |
You're about to be asked to approve a call | Allow / deny |
UserPromptSubmit |
A prompt is submitted, in every mode (TUI, -p, --emit-events, serve, SDK) |
Yes |
Stop |
A turn ended cleanly | No |
PreCompact |
Auto-compaction is about to run | No |
SessionEnd |
autopilot exits, or /clear (reason) |
No |
Hooks receive the event payload as JSON on stdin and as
AUTOPILOT_HOOK_* env vars (for shell-one-liner ergonomics; every one
is also set under its old KIMIFLARE_HOOK_* name, so existing scripts
keep working). The full
JSON copy in AUTOPILOT_HOOK_PAYLOAD is only set for payloads up to
32 KiB (OSes cap environment variable size); above that it is omitted,
AUTOPILOT_HOOK_PAYLOAD_OMITTED=1 is set, and stdin is the only source —
prefer reading stdin. Payloads
also carry Claude Code's field names (hook_event_name, tool_name,
tool_input, tool_response, tool_call_id, transcript_path), so
Claude Code hook scripts — and tools built for them, like
Agent Beacon — work unchanged. A successful
write/edit's tool_response carries Claude Code's structuredPatch
and originalFile (and type: "create" | "update" for Write); see
docs/beacon.md for the
size limits.
A blocked UserPromptSubmit never reaches the model: the TUI shows the
reason, -p and one-shot --emit-events exit with code 44,
serve answers 403 prompt_blocked, and the SDK's prompt() rejects
with PromptBlockedError (details in
docs/beacon.md).
Veto events (PreToolUse, UserPromptSubmit) follow Claude Code's rules:
| Hook result | Effect |
|---|---|
exit 0 |
Allowed. Stdout may carry a JSON decision (below). |
exit 2 |
Blocked. Stderr (or stdout if stderr is empty) is the reason shown to the model. |
| any other non-zero exit, timeout, command not found (127), spawn error | The hook failed: you get a warning and the action is allowed (fail-open), so a broken or missing hook never wedges every tool call — unless the hook sets "failClosed": true (see Hook failures). |
A PreToolUse hook that exits 0 can instead print a JSON decision:
{ "hookSpecificOutput": { "hookEventName": "PreToolUse",
"permissionDecision": "deny", "permissionDecisionReason": "no writes to prod config" } }denyblocks, withpermissionDecisionReasonas the reason.asksends the call through the normal permission prompt, even for tools that don't usually ask. Hosts with no one to ask answer as they already do (--printdenies unless--dangerously-allow-all).allowmeans the hook doesn't object; it does not bypass your own permission rules or mode.
With several hooks, deny wins over ask, which wins over allow.
The legacy {"decision": "block", "reason": "..."} form also blocks
(use it for UserPromptSubmit). Stdout that isn't JSON is ignored, as
before. Hooks written for older versions that blocked with exit 1
must switch to exit 2.
When a hook times out, can't be started (missing binary, exit 127) or
exits non-zero (other than 2 on a veto event), Autopilot tells you
instead of only logging it:
- The TUI (classic and Camouflage) shows a warning naming the hook, the
event, why it failed and what happened to the action.
--printwrites it to stderr (hook_failedevent in--output-format stream-json, ahookFailuresarray injson);--emitsends aRuntimeErrorwarning; the SDK emits ahook.failedsession event;autopilot servesends ahook.failedSSE event and lists them underhookFailuresinGET /session/:id. - It is shown once per hook per session. Later failures are counted
(
/hooks listshows[failed N× this session]) but not shown again. - Every failure is also an OpenTelemetry event,
autopilot.hook.failedat WARN, withhook.id,hook.event,hook.failure(timeout/spawn_failed/command_not_found/nonzero_exit),hook.reason,hook.allowed,hook.fail_closedandhook.failure_count, so a collector such as Agent Beacon records that a policy hook did not run.
For a policy hook that must never be skipped, set "failClosed": true
on it. On the veto events (PreToolUse, UserPromptSubmit,
PermissionRequest) a failure then blocks the action, with a reason
naming the hook and the failure. It has no effect on informational
events.
{ "hooks": { "PreToolUse": [
{ "id": "policy", "command": "my-policy-check", "timeoutMs": 10000, "failClosed": true }
] } }PermissionRequest fires when Autopilot is about to ask you to approve a
tool call — after plan mode, --dangerously-allow-all and config
permission rules have had their say, so it never fires for calls those
already settled. The payload has tool, args, tool_call_id,
mode (edit, plan, …; also $AUTOPILOT_HOOK_MODE), plus Claude Code's tool_name,
tool_input, tool_use_id and permission_mode; matcher matches the
tool name. The hook decides like a PreToolUse hook:
- exit
2, or{"hookSpecificOutput": {"permissionDecision": "deny", "permissionDecisionReason": "..."}}: denied without asking you; the reason goes to the model. permissionDecision: "allow": approved without asking you. UnlikePreToolUse, whereallowonly means "no objection", answering the approval prompt is what this event is for. It never overrides plan mode (anallowthere still shows the prompt) or a config deny rule (those calls never reach the hook)."ask", no JSON, or a hook that fails open: the normal prompt. Hosts with no one to ask answer as before (--printdenies).
Claude Code's form, {"hookSpecificOutput": {"decision": {"behavior": "allow"|"deny", "message": "..."}}},
works too.
PostToolUseFailure fires when a tool call fails — an error, a denied
permission, or a bash command that exits non-zero or is killed — with
tool, args, tool_call_id, error (the failure as text) and
result, plus Claude Code's tool_name, tool_input, tool_use_id,
error and is_interrupt. PostToolUse keeps firing for failures
too, as before, so existing hooks are unaffected; register a hook on
only one of the two if you don't want to see a failure twice.
Both fire from every host: the TUI, --print, --emit, the SDK and
autopilot serve.
Hooks are read from four files and merged, in this order:
~/.config/kimiflare/settings.json(legacy global)~/.config/autopilot/settings.json(global).kimiflare/settings.json(legacy project).autopilot/settings.json(project)
~/.config is $XDG_CONFIG_HOME when that is set. For each event,
hooks run in that order; an entry with the same id as an earlier one
replaces it (ids are derived from event + command when not set, so a
hook copied verbatim from a legacy file runs once). The autopilot paths
are canonical: /hooks enable, the hook wizard and the dashboard write
new hooks there, while enabling or disabling an existing hook edits the
file it lives in. Legacy files are never moved or dropped; if your hooks
are only in a legacy file, autopilot shows a one-time notice naming the
new path. /hooks path prints all four.
matcher is a regex tested against the tool name for PreToolUse,
PostToolUse, PostToolUseFailure and PermissionRequest. It is tried against both autopilot's native name and the
Claude Code name (bash/Bash, edit/Edit, write/Write,
read/Read, glob/Glob, grep/Grep, web_fetch/WebFetch,
search_web/WebSearch; an edit with edits[] also answers to
MultiEdit), so a matcher copied from a Claude Code config works.
| Matcher | Matches |
|---|---|
bash, Bash, ^bash$ |
bash calls |
Bash|Edit, ^(edit|write)$ |
either tool |
^mcp_ |
every MCP tool |
*, "", or no matcher |
every tool |
Matching is case-sensitive (BASH matches nothing) and unanchored
(dit matches edit; use ^…$ for an exact name). An invalid regex
matches nothing.
/hooks # list configured hooks
/hooks recommended # list starter hooks shipped with autopilot
/hooks enable stop-bell # enable one (writes to .autopilot/settings.json)
/hooks enable stop-bell global # ...or the global file
/hooks disable stop-bell
/hooks path # print settings.json paths (incl. legacy ones)
/hooks reload # re-read settings.json after a manual edit
The recommended catalog includes terminal bells / macOS notifications
on Stop, secret-file guards on PreToolUse (e.g. block edits to
*.env), auto-format-with-prettier on PostToolUse, and a tool-call
audit log. All ship disabled — /hooks recommended lists them.
{
"hooks": {
"PreToolUse": [
{
"id": "no-secrets",
"matcher": "^(edit|write)$",
"command": "case \"$AUTOPILOT_HOOK_PATH\" in *.env|*.pem) echo 'blocked' >&2; exit 2;; esac"
}
],
"PostToolUse": [
{
"id": "format-ts",
"matcher": "^(edit|write)$",
"command": "npx --no-install prettier --write \"$AUTOPILOT_HOOK_PATH\" >/dev/null 2>&1 || true"
}
],
"Stop": [
{ "id": "bell", "command": "printf '\\a'" }
]
}
}Per-hook fields:
command(required) — the shell command.matcher(optional) — regex matched against the native or Claude Code tool name forPreToolUse/PostToolUse(see Matchers). Ignored for other events.id(optional) — stable handle for/hooks enable|disable. Auto-derived fromevent + commandwhen omitted.enabled(defaulttrue) — setfalseto keep a hook in config but skip it.timeoutMs(default30000) — hard kill if the hook hangs (the action then proceeds, with a warning).description(optional) — shown by/hooks list.failClosed(defaultfalse) — on veto events, block when the hook itself fails instead of failing open.
Hooks are always-on infrastructure: they fire whether the TUI is open
or autopilot is running in --print mode. They also fire for tool
calls generated from inside the Code Mode sandbox (heavy-tier turns),
because hook firing lives on the ToolExecutor itself — every call
path uses the same plumbing.
When intent classification has assigned a tier, hook payloads include
it as tier: "light" | "medium" | "heavy" (on UserPromptSubmit,
PreToolUse, PostToolUse) and as $AUTOPILOT_HOOK_TIER. Useful for
"skip auto-format on light turns" or "audit every heavy-turn write."
SDK consumers opt in to hooks with enableHooks: true on
createAgentSession. Default is off because the SDK is a primitive,
not the TUI. SDK sessions fire SessionEnd on dispose(), or when the
process exits without it (see Ending a session).
git clone https://github.com/sinameraji/autopilot
cd autopilot
npm install
npm run build
npm linkScripts:
npm run build— bundle with tsupnpm run dev— run via tsxnpm run typecheck—tsc --noEmitnpm test— run tests
- Fork the repository
- Create a branch:
git checkout -b feat/your-feature - Make your changes
- Run
npm run typecheckandnpm run build - Commit with Conventional Commits
- Open a Pull Request
Built by Sina Meraji and contributors · MIT License
