kārya
in development · see the roadmap →open-source terminal coding agent · single Go binarybring your own key · Claude (Anthropic API) is the next provider after Gemini

Karya is a coding agent for your terminal. Commands run freely inside an OS sandbox, every turn can be undone, budgets are enforced before you overspend, and sessions follow you to any machine. You review the diffs. You stop answering "may I run ls?"

$go install github.com/prateek041/karya/cmd/karya@latest
Read the docs →

Today this installs the TUI preview with a demo core. The agent loop, sandbox and undo land in milestones M1–M3. The demo below shows the target behavior.

demo · when it asks, press a or done loop · sandboxed commands · one approval
Karya~/src/tasks
›
arise, resolved to fight
task
plan
read
edit
bash
verify
report
reads run on their ownedits show a diff and askbash runs in the sandboxplan · default · accept-edits/undo · /rewind · /redosandbox-exec on macOSbubblewrap + seccomp on Linuxnetwork off by defaultGemini · Anthropic · OpenAI-compatibleappend-only JSONL sessionsend-to-end encrypted sync--max-cost · --max-stepsloop detectionverify before donesingle Go binaryopen sourcepre-1.0reads run on their ownedits show a diff and askbash runs in the sandboxplan · default · accept-edits/undo · /rewind · /redosandbox-exec on macOSbubblewrap + seccomp on Linuxnetwork off by defaultGemini · Anthropic · OpenAI-compatibleappend-only JSONL sessionsend-to-end encrypted sync--max-cost · --max-stepsloop detectionverify before donesingle Go binaryopen sourcepre-1.0
runs containedundo anythingbudgets enforcedfollows youno lock-inruns containedundo anythingbudgets enforcedfollows youno lock-in
the problem

Babysit it, or hope for the best.

Most coding agents make you pick one. Approve every command and you spend the afternoon pressing y. Skip the prompts and a stray command runs on your real machine. Karya takes a third path: let it run, inside walls, with a way back.

the cautious agent
asks for everything
prompts on a typical taskdozens of prompts
what a bad command can reachyour machine, once you press y
undo a bad turngit, by hand
a runaway loop's costnoticed on the bill
"done" meanswhen the model says so
the yolo agent
runs on your host
prompts on a typical tasknone
what a bad command can reachyour whole machine
undo a bad turngit, by hand
a runaway loop's costnoticed on the bill
"done" meanswhen the model says so
contained and undoable
kārya
prompts on a typical task✓ edits only
what a bad command can reach✓ the project root and a temp dir
undo a bad turn✓ /undo, bash included
a runaway loop's cost✓ stopped at your budget
"done" means✓ when your checks pass
7
tools · read, list, search, write, edit, bash, fetch
3
modes · plan, default, accept-edits
1
provider interface · Gemini, Anthropic, OpenAI-compatible
1
Go binary · go install and run
the loop

One sequential loop, one step at a time.

Karya keeps one model request in flight per session, and tool calls run one at a time in the order the model asked. Each call passes through the mode and the sandbox: reads and sandboxed commands run, edits in default mode wait for you. When the model stops asking for tools, your repo's checks run, and only then is the task reported done.

iteration 1 · step 1 of 11est. $0.0000
task
you
→↓
model
auto
→↓
tool call
auto
→↓
policy
mode + sandbox
→↓
tool result
auto
→↓
model
auto · next step
→↓
verify + report
files · cost · checks
›you: add a --json flag to the list command and make the tests pass
↺ model → tool call → policy → result repeats until the model answers or a limit is hit · failed checks go back to the model
exits onverified answermax stepsmax costloop detectedinterruptedautomaticwaits on you (edits in default mode)
the one prompt that matters

Your files change when you press a.

In default mode, write and edit show a unified diff and wait. Commands don't ask: they run in the sandbox, and anything that tries to leave it always asks. Try it: press a or d on your keyboard, or click a key.

edit cmd/list.go · +5 −0› waiting on you · [a]pprove [d]eny
try it today

The terminal UI is real. Take it for a spin.

The agent is still being built, but the TUI already runs: side-by-side diffs, threads, @ file mentions, ! shell commands, $EDITOR prompts and queued messages. The demo needs no API key.

$ go install github.com/prateek041/karya/cmd/karya@latest
$ karya -demo
real frame · not a mockup
 kārya  ~/src/tasks  main a1b2c3 · clean                claude-sonnet-5 · in 0 · out 0 · $0.0000 / $1.00 · 0s

                                                  
                                                 
                                                  
                                                        
                                                       

                                                  k ā r y a
                                         every task, carried through

                                 claude-sonnet-5   normal mode   ~/src/tasks 
                                   session 7447c5  ·  github.com/acme/tasks

    try one of these
    1  add a --json flag to tasks list so scripts can parse the output
    2  regenerate the shell completions in ./dist for the new flag
    3  run golangci-lint and fix what it reports


── waiting for input ─────────────────────────────────────────────────────────────────────────────────────────
  ›  describe a task  ·  / commands  ·  @ files  ·  ! shell
  enter send · shift+enter new line · shift+tab mode · ctrl+g editor · ctrl+r threads · ? help
karya -demo · the welcome screen
anatomy of a harness

The model is the brain. The harness is everything around it.

drives model and tool calls
Streams text and assembles tool-call deltas as they arrive. One in-flight request per session.
manages state and context
Canonical messages. Tool output is capped with the full copy kept readable. Compaction near the context limit is visible, and every turn goes to a JSONL log.
applies policy
Three modes, the OS sandbox around shell commands, a network allowlist, and file paths confined to the project root.
keeps the task moving
A sequential loop that runs until your checks pass or a limit is reached. Steer it mid-turn without interrupting.
modes and tools

Seven tools. Only edits ask.

Reads, searches and sandboxed commands run on their own. write and edit show a unified diff and wait. Anything that tries to leave the sandbox, like an unsandboxed run or a domain off the allowlist, always asks. shift+tab cycles the mode.

readauto
Returns bounded, line-numbered text. offset and limit page through large files.
read cmd/list.go offset=14 limit=3
  14 │ cmd := &cobra.Command{Use: "list"}
  15 │ cmd.RunE = func(c *cobra.Command, …
  16 │   tasks := store.List()
list · searchauto
A directory tree and a regex search, both bounded and both respecting .gitignore.
search "printTable" --context 1
cmd/list.go:53   return printTable(c.OutOrStdout(), tasks)
internal/ui/table.go:12  func printTable(w io.Writer, …
write · editasks
A diff before anything changes. old_text must match exactly once, and writes are atomic with permissions kept.
edit cmd/list.go
error ambiguous_match  old_text found 2 times
file left untouched
bash · fetchsandboxed
bash runs in the OS sandbox and captures exit code, duration and the tail where errors live. fetch reaches allowlisted domains only.
exit 1 · 6.2s · timed_out false
output capped, 48,213 bytes kept at @output/c7
--- FAIL: TestListJSON (0.00s)
moderead · list · searchwrite · editbashfetch
planrunrefusedrun · read-only sandboxallowlist
defaultrunask, with diffrun · sandboxedallowlist
accept-editsrunrunrun · sandboxedallowlist
--no-sandboxrunaskask every timeask

A denial is information.

Approve once, for this session, or always (saved as a rule). Or deny with a reason: the call does not run, and your reason goes back to the model so it can propose something else or stop.

editcmd/list.go
✕ denied
@@ -14,3 +14,3 @@
- return printTable(c.OutOrStdout(), tasks)
+ return printJSON(c.OutOrStdout(), tasks)
tool_result → model
{ "tool": "edit", "status": "denied", "reason": "keep the table as the default" }
01 · contained by default

Contained, not interrogated.

Most agents either ask before every command or run them on your host. Karya does neither: every bash command, and every process it starts, runs under the operating system's sandbox, so it can run without asking. sandbox-exec on macOS, bubblewrap with seccomp on Linux and WSL2. Writes are limited to the project root and a per-session temp directory, and .git stays read-only. Network is off except for allowlisted domains, with profiles for go, npm, pypi and cargo. A missing sandbox fails closed.

The file tools are confined to the root. They reject absolute paths, paths that escape with .., and symlinks that resolve outside it.

note
The sandbox limits how much damage a mistaken command can do. It is a blast-radius limiter, not a security boundary, and it should not be relied on to contain hostile code.
sandbox bubblewrap+seccomp · profile golive
/denied
├─ etc/ usr/ var/denied
├─ home/you/denied
│ ├─ .ssh/denied
│ ├─ .cache/go-build/profile go
│ ├─ go/pkg/mod/profile go
│ └─ src/tasks/ project rootwritable
└─ tmp/karya-7f3a2c/ session tempwritable
⇢ network egress via local proxyallowlist only
02 · every turn can be undone

Let it run. Take it back.

Before a turn's first side effect, Karya snapshots the working tree to a shadow git directory outside your project. /undo restores it, /rewind goes back several turns, /redo puts it all back. It covers every tool, bash included.

Every tool call is written to the log before it runs. If Karya crashes mid-call, resume marks the call as outcome unknown and never repeats it on its own.

turn 3 · snapshot 9c1e04 · before first side effect
· edit  cmd/list.go  ok
· bash  go generate ./...  sandboxed  exit 0
· write internal/gen/list_gen.go  ok

› /undo
✓ restored the tree to 9c1e04 · 3 files
  not covered: .gitignored build/ (reported, untouched)
03 · cost you can predict

Budgets, enforced.

Limits on steps, tokens, cost and time are enforced while the task runs, not reported after. Hitting one stops the turn, says which limit fired, and leaves the session resumable. Loop detection pauses a turn after three identical calls or the same failure three times. Every task ends with a report: files changed, commands run, checks, tokens, cost and duration.

$0.0000
estimated cost of the task above · 4 turns · 41s
turnmodelinoutcostdurationtooloutcome
1gemini-3.5-flash-lite812188$0.00344.2sreadok · auto
2gemini-3.5-flash-lite1,046421$0.00619.8seditapproved
3gemini-3.5-flash-lite1,29796$0.003418.3sbashsandboxed · exit 0
4gemini-3.5-flash-lite1,657388$0.00718.7sverifypassed · done
total00$0.00000.0s
stopped  --max-cost reached ($1.00)
session 7f3a2c is resumable: karya resume 7f3a2c
04 · sessions that follow you

Start on one machine. Resume on another.

Sessions are append-only JSONL logs stored outside the project. They are keyed to the git remote rather than the local path, and contain no absolute paths, hostnames or secrets. karya sync moves them through a folder, bucket or git ref you own, end-to-end encrypted, so the target never sees plaintext. On resume, Karya compares the recorded HEAD and dirty-tree flag with the live repository and tells the model what changed.

machine a · darwin
$ cd ~/src/tasks $ karya session 7f3a2c HEAD a1b2c3 · clean
session key
github.com/acme/tasks
7f3a2c.jsonl · append-only
stored outside the project
machine b · linux
$ cd ~/work/tasks $ karya resume 7f3a2c HEAD d4e5f6 · dirty
drift note, sent to the model on resume
HEAD moved from a1b2c3 to d4e5f6, working tree dirty, platform changed from darwin to linux.
05 · no lock-in

Switch models mid-session.

The agent loop depends on a single internal provider interface: Gemini first, then Anthropic and OpenAI-compatible APIs (OpenRouter, DeepSeek, Qwen, Ollama, vLLM, LM Studio). Switch at any turn boundary with /model. Your own API keys, never logged.

● GeminiAnthropic (Claude)OpenAIOpenRouterDeepSeekQwenKimiGLMOllamavLLMLM Studio● works today · others in M4

Sessions are stored in a canonical message format, never in a provider's wire types, so a log does not depend on the provider that wrote it.

$ export GEMINI_API_KEY=…
$ karya --model gemini-3.5-flash-lite --max-cost 1.00
environment
GEMINI_API_KEYworks today · required
Your Gemini API key. Karya also reads it from a .env file in the working directory; a real exported variable wins over the file.
GOOGLE_API_KEYworks today · optional
Alternative name for the Gemini key. Wins if both are set.
ANTHROPIC_API_KEY · OpenAI-compatibleplanned · M4
Anthropic and OpenAI-compatible providers (OpenRouter, DeepSeek, Qwen, Ollama, vLLM, LM Studio). Keys never reach logs, sessions or sync bundles.
commands and flags
karyaworks today
Start the TUI in the current directory, talking to the demo core.
karya -liveworks today
Stream answers straight from Gemini, without the core. Threads are saved under $XDG_DATA_HOME/karya/threads.
karya -demoworks today
Play the scripted demo. No API key needed.
karya -theme lightworks today
Colour theme: dark or light.
--model · --mode · --max-cost · --max-stepsplanned · M1–M3
Pick the model and mode, and set hard budgets for the session.
--root <dir> · --no-sandboxplanned · M2
Choose the project root. Run bash without the sandbox, in which case every command asks.
karya run "<task>" [--json]planned · M1
Run one task headless. Exit codes: 0 done and verified, 1 failed, 2 limit reached, 3 verification failed, 4 interrupted.
karya resume [<id>] · sessionsplanned · M1
List and resume sessions, on this machine or another clone of the same repository.
karya sync · doctorplanned · M4–M5
End-to-end encrypted session sync, and a health check for the sandbox, keys and config.
questions

Before you install.

Is Karya free?+

Yes. Karya is open source and you bring your own API keys, so you pay your model provider directly. There is no markup on tokens and no Karya account.

Which models can I use?+

Gemini works today. Anthropic and OpenAI-compatible APIs (OpenAI, OpenRouter, DeepSeek, Qwen, Kimi, GLM, Ollama, vLLM, LM Studio) arrive in milestone M4, and you will be able to switch between them mid-session.

Can I use my Claude or ChatGPT subscription?+

No. Karya uses API keys only. Consumer subscriptions are not resold or proxied.

Is the sandbox a security boundary?+

No, and Karya never describes it as one. It limits the damage a mistaken command can do: writes stay in the project, the network is off by default, and .git is read-only. Do not rely on it to contain hostile code.

Where does my code go?+

Only to the model provider you choose, as part of the conversation. Sessions are stored on your machine, outside the project, with secrets redacted. Sync is end-to-end encrypted to a target you own.

Which platforms are supported?+

macOS (arm64 and x64) and Linux (x64 and arm64), including WSL2. Native Windows comes after launch.

Can I use it today?+

You can install the TUI and run the demo or stream from Gemini today. The agent loop, sandbox and undo land over the next milestones; the roadmap shows where each piece is.

open source

Small enough to read.

One loop, one provider interface, one binary, and every dependency has to earn its place. Karya is pre-1.0 and in active development; the roadmap shows what works today and what is next. Interfaces may change between releases.

Issues and pull requests are welcome. For anything larger than a bug fix, open an issue first so the change can be discussed before the work is done.

kārya · कार्य
every task, carried through

Let it run. Stay in control.

$go install github.com/prateek041/karya/cmd/karya@latest