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@latestToday 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.
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.
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.
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.
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
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
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.
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()search "printTable" --context 1 cmd/list.go:53 return printTable(c.OutOrStdout(), tasks) internal/ui/table.go:12 func printTable(w io.Writer, …
edit cmd/list.go error ambiguous_match old_text found 2 times file left untouched
exit 1 · 6.2s · timed_out false output capped, 48,213 bytes kept at @output/c7 --- FAIL: TestListJSON (0.00s)
| mode | read · list · search | write · edit | bash | fetch |
|---|---|---|---|---|
| plan | run | refused | run · read-only sandbox | allowlist |
| default | run | ask, with diff | run · sandboxed | allowlist |
| accept-edits | run | run | run · sandboxed | allowlist |
| --no-sandbox | run | ask | ask every time | ask |
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.
tool_result → model
{ "tool": "edit", "status": "denied", "reason": "keep the table as the default" }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.
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)
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.
| turn | model | in | out | cost | duration | tool | outcome |
|---|---|---|---|---|---|---|---|
| 1 | gemini-3.5-flash-lite | 812 | 188 | $0.0034 | 4.2s | read | ok · auto |
| 2 | gemini-3.5-flash-lite | 1,046 | 421 | $0.0061 | 9.8s | edit | approved |
| 3 | gemini-3.5-flash-lite | 1,297 | 96 | $0.0034 | 18.3s | bash | sandboxed · exit 0 |
| 4 | gemini-3.5-flash-lite | 1,657 | 388 | $0.0071 | 8.7s | verify | passed · done |
| total | 0 | 0 | $0.0000 | 0.0s |
stopped --max-cost reached ($1.00) session 7f3a2c is resumable: karya resume 7f3a2c
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.
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.
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
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.
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.
No. Karya uses API keys only. Consumer subscriptions are not resold or proxied.
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.
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.
macOS (arm64 and x64) and Linux (x64 and arm64), including WSL2. Native Windows comes after launch.
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.
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.
go install github.com/prateek041/karya/cmd/karya@latest