KoraCode
The browser agent harness.
KoraCode is an agent harness for the web: an AI agent that drives real browsers from your terminal or over an API. Instead of a fixed menu of click, type, and scroll actions, the agent writes JavaScript and runs it directly against Chrome through the DevTools Protocol, so it adapts to any site at runtime and saves the scripts it writes to reuse later.
-
One browser primitive.
browser_execute(code)evaluates JavaScript in-process against a persistent CDP session. Logs, return values, and screenshots come back to the agent. - Your browser, an isolated browser, or a cloud browser. Attach to the Chrome you are already logged into, launch a clean profile for unattended work, or let KoraCode start a cloud browser (Anchor, Browserbase, Hyperbrowser, Steel, Kernel) when the machine has none.
- Watch it work, live. The page the agent is driving appears in a browser window next to the chat, and every step reads in plain words. It asks before anything irreversible.
-
Scripts that compound. Reusable browser code lives in
.kcode/agent-workspace/and is imported back into later runs. - Long-running work. Workflow graphs split a long task into steps that survive restarts, wait for you when they must, and replay without AI once they work (experimental).
-
A full agent harness. Sessions, sub-agents, permissions, MCP servers, skills, and an HTTP
API (
kcode serve) for running browser tasks from your own code. -
Top models. Bring an OpenAI, Anthropic, Google Gemini, xAI, OpenRouter, or DeepSeek key
and switch with
/connect. Any other models.dev provider can be turned on withenabled_providersinkcode.json.
Install
curl -fsSL https://kcode.outhad.com/install | bash
This installs the kcode binary to ~/.kcode/bin on macOS, Linux, and Windows (Git
Bash). Open a new terminal afterwards so kcode is on your PATH. Pin a release
with --version:
curl -fsSL https://kcode.outhad.com/install | bash -s -- --version v0.4.0
Upgrade later with kcode upgrade; remove with kcode uninstall. Release downloads
are published to
tanzil7890/kcode-releases.
Best terminal
For the best experience, use iTerm2. KoraCode shows the live browser right next to the chat, and iTerm2 draws it as a sharp picture. On a Mac, download the stable iTerm2, open it, then install and run KoraCode there.
How the live browser looks depends on whether your terminal can draw images:
| Terminal | Live browser view |
|---|---|
| iTerm2 (recommended), WezTerm, Ghostty, kitty | Sharp picture, out of the box |
| VS Code terminal | Sharp picture once images are on (see below) |
| macOS Terminal | Lower-resolution block preview; Terminal cannot draw images |
| Inside tmux or screen | Block preview; multiplexers drop images |
In VS Code, add these to your user settings, then reload the window:
"terminal.integrated.enableImages": true,
"terminal.integrated.gpuAcceleration": "auto"
Usage
Open KoraCode Studio in your browser:
kcode
The first time, it walks you through connecting a model and picking a browser. Then you give it tasks
on the Tasks screen and watch the browser live. The studio runs on a background server,
so it stays open after you close the terminal; kcode daemon stop ends it.
Prefer the terminal? kcode tui opens the terminal UI. Over SSH, with any argument, or with
KCODE_UI=tui set, a bare kcode opens the terminal UI as before.
Run a task headlessly and print the result:
kcode run "On Google Flights, return every flight from New York to SF tomorrow"
Other entry points:
kcode tui # the terminal UI in the current project
kcode <directory> # open the TUI in another project
kcode studio --foreground # the studio on a server in this terminal only
kcode serve # headless API server
kcode attach <url> # attach a TUI to a running server
kcode models # list available models
kcode mcp # manage MCP servers
kcode session # inspect and manage sessions
A serve-only build, kcode-serve, is available for headless containers.
Connect a model
The studio's setup connects one: sign in with ChatGPT or SuperGrok, or paste an OpenAI,
Anthropic, Google Gemini, xAI, OpenRouter, or DeepSeek key. In the terminal UI,
/connect does the same. You can also set the provider's API key in your environment.
OpenRouter reaches most other models with one key.
To use any other provider from models.dev, list the providers you want in kcode.json. The list
replaces the defaults, so include every provider you use:
{
"enabled_providers": ["openai", "anthropic", "groq"]
}
Providers you already use, through a saved key or a configured model, keep working.
Connect a browser
The studio's setup (and the connection button in its top bar) picks the browser. The simplest is
KoraCode's Chrome: a separate Chrome window with its own profile that KoraCode starts
when a task needs it. Sign in to sites there once and the logins stay for later tasks. From the shell:
kcode browser use kcode.
You can also just ask; the agent knows how to connect. For example:
Connect to my current tab on amazon.com and find deals on 64GB DDR5 RAM; return the URLs
The agent attaches to your running Chrome and works in your real profile.
Make a new browser profile, QA-test http://localhost:3000, and report every bug with a screenshot
The agent launches an isolated Chrome with its own profile.
Using the remote browser, extract every item sold on mcdonalds.com in SF
With a cloud browser selected (see Cloud browsers), KoraCode starts one for the session and the agent works there; nothing runs on your machine.
Under the hood these are the connection modes of browser_execute:
| Mode | When | How |
|---|---|---|
| Your running Chrome | Tasks that need your logged-in sites, cookies, and current tabs |
Turn on Allow remote debugging at chrome://inspect/#remote-debugging, then
approve the connection popup
|
| KoraCode's Chrome | Everyday tasks, with logins that stay, apart from your own browsing |
The studio's setup, /browser in the TUI, or kcode browser use kcode;
KoraCode starts it and gives each task a tab of its own
|
| Isolated Chrome | Unattended automation, clean state |
Chrome launched with --remote-debugging-port and a dedicated
--user-data-dir (for example ./.kcode/chrome-data-dir)
|
| Cloud browser | No local browser, or background work |
/browser in the TUI or kcode browser use <provider>; KoraCode starts,
connects, and stops the browser
|
| Custom endpoint | A browser you host yourself |
kcode browser use custom --cdp-url wss://…, or set KCODE_CDP_WS (alias
KCODE_CDP_URL), which overrides every other setting
|
Cloud browsers
Pick a provider and save its API key once, either with /browser in the TUI or from the shell:
kcode browser login anchor # prompts for the key; stored in auth.json
kcode browser use anchor # sets browser.provider in the global config
kcode browser test # starts a browser, checks CDP, stops it (may be billed)
kcode browser status # what browser_execute uses here, and what is running
From then on, the first browser task in a session starts a cloud browser and the browser panel shows it
live. KoraCode stops the browser after 5 minutes idle, after 1 hour, when the session is deleted, or when
KoraCode exits; provider-side timeouts are always set as a backstop. API keys and the CDP URLs of cloud
browsers never reach the model or the logs, and /browser endpoints report where a key comes
from, never the key itself.
| Provider | browser.provider |
Env fallback for the key |
|---|---|---|
| Anchor | anchor |
ANCHOR_API_KEY |
| Browserbase | browserbase |
BROWSERBASE_API_KEY |
| Hyperbrowser | hyperbrowser |
HYPERBROWSER_API_KEY |
| Steel | steel |
STEEL_API_KEY |
| Kernel | kernel |
KERNEL_API_KEY |
Prefer saved keys over environment variables. KoraCode never passes a saved key to the model or to
snippets, while an environment variable is visible to any snippet through process.env.
Tune it in kcode.json (a project file overrides the global one):
{
"browser": {
"provider": "anchor",
"idle_timeout": 300, // seconds idle before stopping; 0 = never on idle
"max_duration": 3600, // hard cap per browser, seconds
"options": { "proxy": true, "captcha": true, "stealth": false, "region": "us-east" },
},
}
provider_options passes raw fields to the provider's create-session request.
Watch it live
When a session uses a browser and your terminal is at least 140 columns wide, a browser panel opens next to the chat. It shows the page the agent is driving inside a browser window, with the page title, the URL, and the latest steps, and it follows the agent from tab to tab. How sharp it looks depends on your terminal; see Best terminal.
| Key or command | What it does |
|---|---|
ctrl+x then v, or /browser-panel |
Switch between the browser panel and the sidebar |
/browser-view |
Cycle the picture between auto, sharp, and blocks |
ctrl+x then o, or /live |
Open the cloud browser's own live view in your web browser |
ctrl+x then i, or /screenshot |
Open the latest screenshot the agent took |
/browser-stop |
Stop this session's cloud browser right away |
Workflow graphs
Experimental. Turn graphs on with KCODE_EXPERIMENTAL_GRAPHS=1. Their commands
work but are not listed in kcode --help yet.
Some browser work is too long for one conversation: hundreds of invoices, a nightly report, a portal that asks you to log in halfway through. A workflow graph splits that work into steps connected like a flowchart. Each step runs as its own short agent session in one shared browser, so the context stays small however long the run is, and each step's outcome is saved as it finishes. After a crash, a restart, or a closed laptop, the run picks up at the step it stopped on without repeating the steps before it.
export KCODE_EXPERIMENTAL_GRAPHS=1 # add it to your shell profile to keep graphs on
Build a graph
There are four ways, and all of them write the same file:
-
On a canvas.
kcode studioopens a node editor in your browser: add steps from the toolbar, drag connections between them, and see problems as you edit. Save (or ⌘S) writes the file, and the Runs and Schedules screens follow it from there. - In the studio's chat. Describe the job in the chat beside the canvas. The agent explores the site, proposes the steps, and builds the graph once you agree (see In the studio).
-
By asking. Do a task with the agent, then ask it to make that a graph; the steps it took
become a graph whose steps already have scripts. Or type
/new-graphand describe what the graph should do. -
By hand. A graph is a JSON file at
.kcode/agent-workspace/graphs/<name>.json, with optional rules every step follows in<name>.rules.md. Check it withkcode graph validate <name>.
| Node | What it does |
|---|---|
Agent step (agent) |
An agent does one job in the browser and reports what it found |
Loop (loop) |
Runs a step once for each item of a list, such as the previous step's answer |
Check (condition) |
Reads the page (the URL, some text, or an element) and goes on by true or false, without AI |
Subgraph (subworkflow) |
Runs another graph |
Output (output) |
Ends the run with a result |
Success, Failure (success, error) |
End the run as done, or as failed |
A step leads on by success or error, a check by true or
false, and an agent step can instead choose between labeled paths (ai). Every node
has a position on the canvas. This graph reads every invoice on a portal and decides whether any
is overdue:
{
"version": 3,
"name": "invoices",
"entry": "list",
"variables": [{ "name": "PORTAL_URL", "required": true }],
"nodes": [
{
"id": "list", "type": "agent", "name": "List the invoices",
"instruction": "List the invoice numbers on {{.PORTAL_URL}}/invoices.",
"position": { "x": 0, "y": 0 }
},
{
"id": "each", "type": "loop", "name": "Read each invoice",
"items_variable": "PREV_OUTPUT", "max_iterations": 50,
"item_instruction": "Open invoice {{.ITEM}} and read its status and total.",
"position": { "x": 260, "y": 0 }
},
{
"id": "decide", "type": "agent", "name": "Decide",
"instruction": "Is any of these invoices overdue? {{.NODE_EACH_OUTPUT}}",
"position": { "x": 520, "y": 0 }
},
{
"id": "overdue", "type": "output", "name": "overdue",
"output_binding": { "from": "previous" }, "position": { "x": 780, "y": -80 }
},
{
"id": "all_paid", "type": "output", "name": "all_paid",
"output_binding": { "from": "previous" }, "position": { "x": 780, "y": 80 }
},
{ "id": "failed", "type": "error", "position": { "x": 520, "y": 200 } }
],
"edges": [
{ "id": "list_ok", "from": "list", "to": "each", "when": "success" },
{ "id": "list_failed", "from": "list", "to": "failed", "when": "error" },
{ "id": "each_ok", "from": "each", "to": "decide", "when": "success" },
{ "id": "each_failed", "from": "each", "to": "failed", "when": "error" },
{ "id": "late", "from": "decide", "to": "overdue", "when": "ai", "label": "Overdue" },
{ "id": "paid", "from": "decide", "to": "all_paid", "when": "ai", "label": "All paid" },
{
"id": "unread", "from": "decide", "to": "failed", "when": "ai",
"label": "The invoices cannot be read", "outcome_role": "failure"
}
]
}
Run it
kcode graph list
kcode graph run invoices --input PORTAL_URL=https://portal.example.com
kcode graph runs # this project's runs, newest first
kcode graph status <run-id> # its steps, what it waits for, the result
kcode graph pause|resume|cancel <run-id>
kcode graph run follows the run until it ends, and exits with code 2 when the run is waiting for
you. --detach starts it and returns, and whichever KoraCode is open for the project carries it
on. In the TUI, /graphs lists graphs and runs, and each step opens the session that did it.
Run it by asking. Ask the agent to run a graph and it follows the run live in the
conversation, step by step, then shows what the run produced (the data itself, not just a run ID) and
which steps ran without AI. If the run fails, the agent reads why, fixes the graph, and runs it again, up
to five runs, until it completes. It stops and asks instead when only you can fix the cause (a login, a
missing input, a model your account can't use) and before running again a graph that buys, sends, or
deletes something. To ask about a run, type @run in the prompt and pick it from the list.
In the studio
kcode opens the studio in your browser: Workflows is the canvas,
Runs shows each run live, and Schedules lists the schedules. The chat
beside every screen is the terminal's agent with the same tools, and it knows what you are looking at:
describe a job and it builds the graph, select a step and ask for a change, or ask it to run the graph and
it follows the run, fixes what fails, and shows the result.
The Studio guide covers every screen: the canvas and the step panel, working through the chat, scripting steps, runs, schedules, your projects and chats, and the shortcuts.
When a run needs you
A step that reaches a login, a captcha, or a decision pauses the run and says what it needs. The run waits, and goes on when you answer:
kcode graph reply <run-id> "Logged in" # answer a step's question
kcode graph confirm <run-id> --yes # approve what it is waiting to do (--no declines)
You can also answer from /graphs or the studio. A secret input names a saved site secret
(NAME=SITE.KEY) instead of carrying its value.
Cheaper every run
When the agent finishes a step that only acts on the page (no later step reads its answer), KoraCode saves
what it did as a script next to the graph. The next run replays that script without calling the model. If
the site changed and the replay fails, the agent does the step instead and the script is rewritten from what
it did. Turn this off with "capture_scripts": false.
A script belongs to the instruction it was captured for. Change a step's instruction and the step runs with AI again until a good run captures a new script; the studio marks such a step Script out of date.
Script a step on purpose. Select an agent step in the studio and press
Script this step with kcode (see the Studio guide), or
type /script-step <graph> <step>.
The agent takes a successful attempt at the step (a run's, or its own in the browser), turns it into a
script, attaches it, and runs the graph to prove it: the step must show script, no AI. If
it doesn't, it scripts again and reruns, up to four tries. This also works for steps that read data, such
as a list of products: their script returns what it reads, so later steps and the run's result get the
same data the agent would have given. A graph whose steps are all scripted runs in seconds without using
the model.
Schedules, batches, and the background runner
kcode graph publish invoices # pin this version for schedules
kcode graph schedule add invoices --cron "0 9 * * 1-5" --tz America/New_York \
--input PORTAL_URL=https://portal.example.com
kcode graph schedule list
kcode graph batch invoices portals.csv # one run per row; the header names the inputs
kcode daemon start # keeps runs and schedules going in the background
A schedule runs the published version in whichever KoraCode process is open for the project.
kcode daemon start keeps one open in the background, and opening the studio
(kcode) starts it too. A schedule skips a fire while its last run is still going unless you pass
--overlap queue, and --catch-up one makes up one fire missed while nothing was open.
Settings
In kcode.json:
{
"graphs": {
"models": { "fast": "openai/gpt-5-mini" }, // aliases a step can name
"max_concurrent_runs": 1, // runs at once; the rest wait their turn
"max_node_steps": 50, // turns one step may take
"max_run_cost": 5, // cancel a run once it has cost more, in US dollars
"desktop_notifications": true, // when a run waits for you or ends
"notifications": [
{ "url": "https://example.com/hooks/kcode", "secret_env": "KCODE_WEBHOOK_SECRET" },
],
"retention_days": 30, // how long finished runs keep their step details
},
}
Webhooks are signed with HMAC-SHA256: X-Kcode-Signature: t=<unix>,v1=<hex> over
<t>.<body>. One local Chrome runs one graph at a time; a cloud browser provider gives
each run its own browser, so max_concurrent_runs can go higher.
Graphs are also on the HTTP API of kcode serve (/graph, /graph-run,
/graph-schedule, /graph-batch) and in the SDK as client.graph.
How it works
KoraCode treats browser automation as code: instead of fixed click and type actions, the agent writes JavaScript against a live browser session.
browser_execute(code)
-> runs the JavaScript in-process
-> talks to Chrome through the DevTools Protocol
-> keeps the browser session alive across calls
-> returns logs, values, and screenshots to the agent
There are no helper abstractions between the agent and the browser, just a live session,
console, and standard JavaScript. The agent writes plain .ts files under
<project>/.kcode/agent-workspace/ and imports them back into later snippets, so a
five-line wrapper and a five-hundred-line scraper are handled the same way.
Configuration
KoraCode reads kcode.json or kcode.jsonc from your project's .kcode/
directory, from ~/.kcode/, and from the global config directory. Project configuration is
discovered by walking up from the current directory. Add a schema for autocomplete in your editor:
{
"$schema": "https://kcode.outhad.com/config.json"
}
For automated and benchmark runs, set KCODE_ISOLATED=1. KoraCode then reads nothing from the
machine's own setup: no global or ~/.kcode/ config, no global AGENTS.md, no Claude
Code instructions or skills, and no other skill folders, and titles and summaries use the run's own model.
Configuration the run passes itself, such as the project's .kcode/, KCODE_CONFIG,
or KCODE_CONFIG_DIR, still applies.
Telemetry
Telemetry is off unless you turn it on. Set KCODE_TELEMETRY=1 to send anonymous traces (timing
and errors) that help improve KoraCode. They never include prompts, model output, page content, snippet
code, screenshots, or your username. DO_NOT_TRACK=1 always turns telemetry off.
Your own trace backend (LMNR_PROJECT_API_KEY or OTEL_EXPORTER_OTLP_ENDPOINT)
records full content; set KCODE_TELEMETRY_CONTENT=0 to keep content out of it too.
License
MIT; see LICENSE.