kkārya
pre-1.0open-source agent harness · single Go binaryroadmap →

An agent harness is the runtime around a language model: it calls the model and its tools, keeps the conversation state, applies policy, and carries a task through to the end. Karya is a small one that runs in your terminal, built so the engineer stays in the control seat.

$go install github.com/prateek041/karya/cmd/karya@latest
Read the docs →
live demo · when it asks, press a or done loop · four tools · two approvals
Karya~/src/tasks
›
arise, resolved to fight
task
plan
read
edit
bash
tests
answer
4 toolsread runs on its ownwrite asksedit asksbash asksone sequential loopone provider interfaceHARNESS_MODELsandbox-exec on macOSbubblewrap + seccomp on Linuxnetwork off by defaultappend-only JSONL sessionsresume on any machineHARNESS_MAX_COSTHARNESS_MAX_STEPSsingle Go binaryopen sourcepre-1.04 toolsread runs on its ownwrite asksedit asksbash asksone sequential loopone provider interfaceHARNESS_MODELsandbox-exec on macOSbubblewrap + seccomp on Linuxnetwork off by defaultappend-only JSONL sessionsresume on any machineHARNESS_MAX_COSTHARNESS_MAX_STEPSsingle Go binaryopen sourcepre-1.0
asks firstruns containedcosts visiblefollows youno lock-inasks firstruns containedcosts visiblefollows youno lock-in
4
tools · read, write, edit, bash
1
sequential loop · one request in flight
1
provider interface · swap with HARNESS_MODEL
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. Reads proceed on their own. Anything that writes a file or runs a command stops at the approval gate. A denial goes back to the model as a structured result, so it can try another approach or stop.

iteration 1 · step 1 of 11est. $0.0000
task
you
→↓
model
auto
→↓
tool call
auto
→↓
approval gate
waits on you
→↓
tool result
auto
→↓
model
auto · next step
→↓
final answer
+ usage and cost
›you: add a --json flag to the list command and make the tests pass
↺ model → tool call → gate → result repeats until the model answers or a limit is hit
exits onfinal answermax stepsmax costcontext rejectedcancelledautomaticwaits on the human
the approval gate

Nothing changes until you press a.

write, edit and bash show a unified diff or the exact command, then stop and wait. 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
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 truncated with the original byte count recorded. Every turn goes to a JSONL session log.
applies policy
An approval table per tool, the OS sandbox around shell commands, and file paths confined to the project root.
keeps the task moving
A sequential loop that runs until the task completes or a limit is reached.
01 · you are in control

Four tools. Three of them ask.

read runs automatically. write and edit show a unified diff, bash shows the exact command and its timeout, and each waits for you to press a key.

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()
writeasks
Writes to a temp file in the destination directory, then atomically replaces the target. File permissions are preserved.
cmd/.list_test.go.tmp → cmd/list_test.go
mode 0644 kept · atomic replace
editasks
old_text must match exactly once. Otherwise the tool returns not_found or ambiguous_match and the file is left untouched.
edit cmd/list.go
error ambiguous_match  old_text found 2 times
bashasks
Captures stdout, stderr, exit code, duration and timeout status. Long output is truncated, keeping the tail where compiler and test errors live.
exit 1 · 6.2s · timed_out false
output truncated, 48,213 bytes original
--- FAIL: TestListJSON (0.00s)

A denial is information.

Press d and the call does not run. The model receives a structured result saying so, and can propose something else or stop. Nothing runs that you did not see first.

bashproject root
✕ denied
$ rm -rf ./dist && go generate ./...
timeout 120s · sandboxed · network off
tool_result → model
{ "tool": "bash", "status": "denied", "by": "user" }
02 · no lock-in

Change the model with one variable.

The agent loop depends on a single internal provider interface. Set HARNESS_MODEL and the loop, tools and approvals stay the same.

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 HARNESS_MODEL=claude-sonnet-5
$ export HARNESS_MAX_COST=1.00
$ karya
environment
HARNESS_MODELrequired
The model to run. The loop talks to it through one internal provider interface, so changing this value changes the model and nothing else.
HARNESS_BASE_URLoptional
Overrides the provider API endpoint.
HARNESS_MAX_STEPSsafety limit · conservative default
Maximum loop steps per session. When reached, the loop stops, reports the limit, and the session stays resumable.
HARNESS_MAX_COSTsession cost ceiling
Estimated cost ceiling for the session. When reached, the loop stops, reports the limit, and the session stays resumable.
flags
karya
Start a session. The current directory becomes the project root and cannot change for the session.
karya --root <path>
Use a different directory as the project root.
karya --resume <session-id>
Resume a recorded session, on this machine or another clone of the same repository.
03 · contained by default

A smaller blast radius.

Every bash command, and every process it starts, runs under the operating system's sandbox: sandbox-exec on macOS, bubblewrap with seccomp on Linux. Writes are limited to the project root and a per-session temp directory. Network egress is denied unless you enable it.

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 egressdenied
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 URL rather than the local path, and the log contains no absolute paths or hostnames. 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 · observability and cost

Every turn has a receipt.

Each turn records the model name, input and output tokens, estimated cost, duration and tool outcomes, in the session and in structured local logs. HARNESS_MAX_COST and HARNESS_MAX_STEPS stop the loop safely, say which limit was hit, and leave the session resumable.

$0.0000
estimated cost of the task above · 4 turns · 41s
turnmodelinoutcostdurationtooloutcome
1claude-sonnet-5812188$0.00344.2sreadok · auto
2claude-sonnet-51,046421$0.00619.8seditapproved
3claude-sonnet-51,29796$0.003418.3sbashapproved · exit 0
4claude-sonnet-51,657388$0.00718.7snonefinal answer
total00$0.00000.0s
stopped  HARNESS_MAX_COST reached (1.00)
session 7f3a2c is resumable: karya --resume 7f3a2c
open source

Small enough to read.

Four tools, one loop, one provider interface, one binary. The source is on GitHub under the license in the repository. Karya is pre-1.0, and 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 weighed against the roadmap.

Stay in the control seat.

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