ker is an open-source coding agent that runs on your own machines and is meant to be driven from any screen (desktop, browser, or phone).
It's very early. Right now ker runs a complete coding-agent loop either as one local daemon or as a control-plane server with nodes on the machines where the code lives. You send it a prompt, the model can read and change files or run shell commands, and the loop continues until the model returns its final answer.
ker has a server that keeps projects, session history, and documents, plus nodes that execute where the code lives. Bundled mode runs both halves on one machine. Remote deployments use device pairing for browsers and a TLS-terminating reverse proxy for encrypted browser and node connections.
ker is currently a personal project under active development, and I'm not accepting contributions yet. I'd like to establish its direction and bring it to a more stable state first. I plan to open it to contributions in some form when it's ready.
Thank you for your interest and understanding.
- Three runtime modes: a bundled daemon, a control-plane server, and outbound-connecting nodes. Projects and sessions are managed through the web UI.
- One-time node enrollment, per-node secrets and revocation, multi-node workspace routing, and a Nodes screen in the web UI.
- Single-user remote access: one-time pairing links and QR codes, a private cookie per browser,
and a Devices screen for pairing and revocation.
ker pairbootstraps the first browser. - A streaming tool-call loop using OpenAI Responses or Anthropic Messages, with its own retry/backoff on transient failures before provider output starts.
- Claude Code and Codex session runtimes using the vendor CLIs installed on each node, with their own login, tools, instructions, and compaction. Streaming output, Stop, and the context meter share the native transcript.
- Four built-in tools:
read,write,edit, andbash. - Repository
AGENTS.mdinstructions reload before every model step, with nested rules added after a successful read, edit, or write in that folder. The session log records the rules used. - Durable session history in the control plane, outside the repository. A remote node writes through
a transient acknowledged spool, so a server restart during an admitted turn loses no records.
Restarting restores completed history and interrupted turns; queued prompts expire by default, or
resume when
recoveryWindowMinutessays they are recent enough. - A runtime, model, and reasoning effort chosen per session at creation, with the choice saved in its log and shown in the session list and transcript header. Nodes report their available models.
- Multiple sessions per project, each with its own FIFO queue. One turn runs at a time in a session, while different sessions can run concurrently.
- Session-owned cancellation that every monitoring client can observe. Waiting turns cancel
immediately; running turns stay visibly
cancellinguntil abort cleanup finishes. - Session snapshots and cursor-based event catch-up for monitoring during a response or reconnecting after a client disconnects.
- OpenAI and Anthropic API keys and ChatGPT OAuth managed in the Providers screen and stored on the control plane. Nodes pull credentials before each native request and keep an in-memory copy for outages. Credential identity checks apply within the provider that last answered the session.
- Bounded tool output:
readpages large files, andbashkeeps a bounded tail while spilling the full stream to a private temporary file. - Transparent context compaction that summarizes older history near the model ceiling while keeping the complete transcript on disk. Compaction can also be requested manually.
- A web UI served by the control plane: projects, sessions, a live transcript with prompt, cancel, and compaction controls, and per-project documents with a Markdown editor. Create projects with an optional first folder, rename or delete them in Settings, and remove folders with their sessions. Delete individual sessions from the list or transcript header.
Not there yet: multi-user accounts and permissions, or managed hosting.
I'm not paying attention to the UI right now. What I care about at this stage is what ker can do, so the web app is completely vibe coded: I describe what I need, the model writes it, and I keep what works. It isn't designed, it isn't optimized, and none of it is final. It has interaction tests so the behavior doesn't regress, but that's the only care it gets at the moment. Expect rough edges. The daemon, the protocol, and the session store are where the attention goes.
- Node 24
- An OpenAI or Anthropic API key, a ChatGPT login for the native runtime, or an installed and authenticated Claude Code / Codex CLI for a headless runtime
npm install
npm run buildSelect a provider and model in ~/.config/ker/config.json (or $XDG_CONFIG_HOME/ker/config.json)
on the execution node. Set both fields together; omitting both defaults to openai / gpt-5.5.
These fields and reasoningEffort set the default for new sessions on that node. Restart the node
after changing the file. Choose a different model in New session without restarting; existing
sessions keep their recorded runtime.
API keys. Open Providers in the GUI, paste an OpenAI or Anthropic key, and Save. The plane stores it in its private catalog and makes it available to every enrolled node. Keys are accepted without a vendor validation call; Replace or Remove needs no node restart. For an Anthropic default, the node config is:
{
"provider": "anthropic",
"model": "claude-opus-5",
"reasoningEffort": "high"
}For OpenAI, use "provider": "openai" and an OpenAI model such as gpt-5.5.
Native sessions ignore OPENAI_API_KEY and ANTHROPIC_API_KEY. Credentials resolve independently
by provider; a ChatGPT login does not override either API key.
The Anthropic registry includes claude-fable-5-1, claude-opus-5, and claude-sonnet-5.
Fable requires 30-day data retention and returns a 400 for zero-retention
organizations. Native Anthropic sessions use API keys only. Claude subscriptions are available
through the Claude Code runtime below. ker uses each vendor's API without impersonating another client.
reasoningEffort accepts none, minimal, low, medium, high, xhigh, and max.
Leaving it unset keeps the provider's default effort and display. Anthropic explicitly enables
adaptive thinking and binding controls even with no effort configured; none explicitly disables
thinking. Anthropic maps minimal to low; other enabled levels request adaptive thinking with
readable summaries. Models may reject unsupported settings; OpenAI does not accept max.
Haiku 4.5 is excluded until its budgeted-thinking mode is supported.
Anthropic requests mark system instructions and the final message block for prompt caching.
Completed thinking blocks replay with a control that lets Anthropic drop blocks whose conversation
prefix changed during pruning, compaction, or instruction updates.
ChatGPT subscription. Select Codex explicitly:
{
"provider": "openai-codex",
"model": "gpt-5.5"
}Open Providers → ChatGPT subscription → Log in, open the authorization link and approve it. With a bundled daemon on the same laptop, the loopback callback completes the login automatically. From a phone or with a remote plane, copy the failed localhost redirect URL (or code), paste it back into Providers, and Confirm. A pending login expires after ten minutes; starting another replaces it. A busy loopback port still allows pasted-code completion.
The plane keeps OAuth tokens in its catalog and refreshes them before expiry. Log out forgets
the login. A definitive refresh rejection also forgets it and asks for another login in Providers;
transient failures keep it. openai-codex never falls back to an API key. A different ChatGPT account
is rejected for a session bound to the original account. This uses OpenAI's Codex login flow;
it is not an officially supported third-party integration.
Upgrading. Enter API keys once in Providers and log in to ChatGPT there. Remove apiKeys from
node configs: its presence now refuses startup with apiKeys moved to the plane; add keys in Providers.
The old ~/.config/ker/auth.json (or KER_AUTH_FILE) is no longer read; delete it yourself to remove
the refresh token it still holds. ker login and ker logout are removed. Provider/model defaults
remain on each node. Headless CLIs keep their own login.
Upgrade the plane and nodes together: HTTP protocol v33, node protocol 4, store v9, and catalog v7.
Catalog v6 migrates in place by adding an empty credential table; session logs are unchanged.
Store v8 and earlier logs become unreadable and remain unchanged; export before upgrading to keep
a copy. The catalog migrates in place, and older imported archives remain unreadable. Both session
creation routes now require a JSON body: {} uses the node default, or pass { "runtime": { "kind": "native", "provider": "anthropic", "model": "claude-sonnet-5", "reasoningEffort": "high" } }.
Install Claude Code or Codex on the execution node and sign in using that CLI as the same OS user that runs ker. Its own subscription or API-key setup applies. These runtimes inherit the node's environment and use the vendor's settings, skills, tools, and instruction files. Providers configures native sessions; ker does not pass plane credentials to headless processes or vendor files.
Restart the node after installing a CLI. At startup ker probes claude --version and
codex --version; installed runtimes appear in New session. Model and Effort both offer
Vendor default. Model choices come from ker's registry, while the transcript shows the model
reported by the CLI. Each runtime remembers its own picker choices.
| Runtime | Modes |
|---|---|
| Claude Code | Full access, Accept edits, Plan |
| Codex | Full access, Approve for me, Read only |
Full access is preselected. These modes never wait for a permission dialog in ker. Claude's restricted modes deny operations that need a prompt; Codex Approve for me uses the vendor's automatic reviewer. Denials appear in the transcript.
One vendor process serves each session across turns. It closes after 15 idle minutes; the next prompt starts another process and resumes the saved vendor conversation. Optional node settings:
{
"headless": {
"idleMinutes": 15,
"claudePath": "/absolute/path/to/claude",
"codexPath": "/absolute/path/to/codex"
}
}idleMinutes must be a positive integer. Omit the paths to search the node's PATH. Absolute paths
are useful for services whose PATH differs from an interactive terminal.
Compact asks the vendor to compact its own context. The meter uses vendor-reported token counts and context size. Native pruning, context caps, and automatic compaction do not apply. Stop first interrupts the vendor; if it does not finish within ten seconds, ker closes the process, escalating to SIGKILL after a further five seconds. Node shutdown also closes session processes.
Ker stores a display transcript and the vendor's session ID. The vendor's resumable history stays
on that node, under its own CLI data directory, and is not included in ker exports. A missing CLI
reports runtime_unavailable. If the vendor transcript is missing and ker has no assistant
message in its history, ker retries a fresh start automatically, so a failed or stopped first
turn can recover. Once an assistant message exists, a missing transcript reports
child_session_missing and the ker transcript remains readable. Start a new session if that
vendor history is lost; importing a ker archive on another node cannot recreate it.
recoveryWindowMinutes defaults to 0: after a node restart, queued prompts older than that many
minutes expire instead of running automatically; 0 expires all waiting prompts.
For native sessions, the optional compaction object accepts enabled (default true), reserveTokens (default 16384),
keepRecentTokens (default 20000), prune (default true), and a reasoningEffort override.
Without that override, compaction inherits the session effort.
The registry uses each model's published limits. Most tracked OpenAI models now use a 922,000-token
input window; GPT-5.4 mini and nano remain at 272,000. To compact earlier, set an absolute token
cap under compaction.contextLimit, keyed by provider and model:
{
"compaction": {
"contextLimit": {
"openai": { "gpt-5.5": 272000 },
"openai-codex": { "gpt-5.5": 272000 },
"anthropic": { "claude-opus-5": 200000 }
}
}
}A 272,000-token cap helps keep GPT-5.5 in the cheaper API tier: above 272,000 input tokens, standard input pricing doubles from $5 to $10 per million tokens.
The map defaults to {}. A cap must be a positive integer and cannot enlarge a known model's
window. It can supply a limit for an unknown model. The effective window drives automatic
compaction, full-context refusal, persisted stats, and the GUI context meter. Existing sessions
keep their saved window until the next usage event refreshes it after a restart.
Run these from the repo root. Start the daemon in one terminal (it listens on 127.0.0.1:5537):
npx ker daemonThe same address serves the web UI: open http://127.0.0.1:5537 in a browser once the daemon is
up.
To run the two halves separately, start the server and use the web UI's Nodes screen to create an enrollment command:
npx ker server
npx ker node --server http://127.0.0.1:5537 --token <token>The secret returned during enrollment is stored in ~/.ker/node.json; later starts need only
npx ker node. For remote access, put a TLS-terminating proxy in front of the server and configure
its public HTTPS origin:
npx ker server --public-url https://ker.example.comIn another terminal on the server host, under the same OS user, mint the first pairing link:
npx ker pairOpen that link in the browser, then use Devices → Pair device to pair the phone by QR. Nodes → Enroll node supplies the laptop command with the public URL. ker never terminates TLS itself; follow the deployment guide for Caddy, systemd, backups, and a Tailscale laptop variant. Open the public HTTPS URL in remote mode; opening the loopback URL returns 403 by design.
Both server and daemon accept --host, --port, and --public-url, with KER_HOST,
KER_PORT, and KER_PUBLIC_URL as fallbacks. Flags win over environment variables. The default
bind is 127.0.0.1:5537; a non-loopback bind requires a public URL. For example, a container behind
a proxy can use ker server --host 0.0.0.0 --port 5537 --public-url https://ker.example.com.
The public URL must be an HTTPS origin, with no path, query, fragment, or credentials.
In the browser, choose New project, enter a name, and optionally add a folder on a node. The Sessions tab lets you add folders and start sessions. Open a session to send prompts, stop turns, or compact context. Queued prompts run in order; closing the browser leaves work running.
New session lets you choose a usable folder, a model grouped by provider, and its effort. The folder selector appears when several folders are available; the runtime selector appears when a node offers several runtimes: Native, Claude Code, or Codex. Headless runtimes also offer a Mode selector. The browser remembers the last successful choice per runtime kind when the selected node offers it, otherwise it uses that node's default. Native models without a credential are disabled and link to Providers. If a remembered choice has no credential, the picker selects the first enabled model. If none is enabled, the default remains selected and session creation is allowed. The choice is fixed for the session. Credentials are checked before each request; removing one makes the next prompt show an error linking to Providers.
For native sessions, the node reads AGENTS.md from the session's working directory up to the filesystem root and
appends the files to the system prompt, outermost first. Successful read, edit, and write
calls below the working directory add any nested AGENTS.md along that path for the next model
step. Files reload before each step, so edits take effect during a session; retries reuse the
same instructions. Only AGENTS.md is recognized, with no imports or separate global file.
bash does not discover nested rules. The log records each changed set of paths and contents.
Use Settings to rename or permanently delete a project. Remove on a folder deletes that folder's sessions and history. Delete on a session row or transcript header removes just that session and its history, including unreadable sessions. Export the project first to keep a copy. Running sessions on a connected node must be stopped before deleting them, their project, or folder.
The control plane stores sessions under KER_SESSION_DIR when set, otherwise at
~/.ker/sessions, grouped by canonical Git root and session ID. Remote nodes keep only unacknowledged
records under ~/.ker/spool. Older session-log store versions are reported as unreadable and left
byte-for-byte unchanged until deleted. Store v9 records the runtime in its header and vendor
session IDs in child records; store-v8 and older logs become unreadable after upgrading. Upgrade the server and nodes together.
Project exports use the documented, versioned ker archive format. The server/node connection is specified in the node protocol.
Concurrent sessions intentionally use their recorded working directories without worktree isolation. Running two sessions against the same files can therefore conflict. Native cancellation remains cooperative: code that ignores its abort signal cannot be force-stopped safely until native turns run in isolated processes.