koracodedocs

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 with enabled_providers in kcode.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 studio opens 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-graph and 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 with kcode 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.

KoraCode Studio's Workflows screen: a graph of browser steps on a canvas, the selected step in a panel, and the chat beside it

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.