GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing 3code.capocasa.dev — 3code - the economical coding agent
☰
3code - the economical coding agentContents
Introduction3code is the coding agent I wanted for my own work: capable, quiet, and not forever looking for new ways to spend tokens. It works with OpenAI-compatible providers, including free tiers, flat-rate coding plans, and local servers. The design rule is simple: get a correct result with the least model work, token use, and computer time. Open models, especially GLM, are good enough for most of my programming work. 3code gives them a focused prompt, a small tool set, persistent sessions, and a terminal interface that stays out of the way. The standard way to use 3code is also the simplest one: type what you want, like fix the flickering caret in the resize path or add tests for the parser edge cases, and press Enter. Current models read the code, do the work, and report back, without asking you anything in between. If a task is overly broad (improve this project), you will get questions or a plan first; narrow it down and it is back to hands-off. The interface exists to stay out of that loop: scrollback is plain terminal history, the footer is two rows, and everything else is your terminal's own scrolling, search, and selection. This manual is organized how-to style: getting a provider, daily use, the screen, sessions, the sandbox, embedding. The dry inventory of every config key, command-line switch, and : command lives in the reference; the internal API, for embedding and contributing, in the developer docs. QuickstartFirst, give 3code somewhere to run inference: an API provider or a local OpenAI-compatible server. Local models can work (Ollama and friends speak the same API), though small ones still have a rough time with difficult coding tasks. Get a providerThere are plenty of compatible providers. A free tier is a good place to start. Pay for one after you know which model actually works for your projects. Free providersNVIDIA Build and Novita often have useful free models. The lists and rate limits move around, as free things tend to do, so check before signing up. Flat-rate coding plansThe z.ai coding plan is my main setup. GLM is capable, inexpensive, and happy to keep working through a long task. 3code can also log in with ChatGPT Plus/Pro and SuperGrok or X Premium+ subscriptions. These are separate from OpenAI and xAI API billing, despite the similar names. EU providersWhen data must stay under EU jurisdiction, I use GreenPT. Nebius and Eurouter are also in the provider catalog. Low-cost providersDeepInfra, OpenCode, nano-gpt, Novita, and similar aggregators serve quantized open models cheaply. Quality and availability vary, so a little comparison shopping pays off. Other modelsDeepSeek, MiniMax, Kimi, Tencent Hy, Qwen, Grok, and LongCat are all supported somewhere. The registry changes too often for a copied list to age gracefully, so ask 3code for the current one: 3code --good OpenRouter and Eurouter are handy when you want to try many models through one account. Once you settle on one, a direct provider account removes a service from the request path and is often cheaper. The provider catalog below lists who hosts what, where, and which ones serve quantized weights. Install# macOS / Linux curl -fsSL https://3code.capocasa.dev/install | sh # Windows (PowerShell) irm https://3code.capocasa.dev/install.ps1 | iex The install script is the recommended way to install 3code. npm is there for those who prefer it: npm install -g 3code Both install the same release binaries. Manual install on WindowsThe PowerShell one-liner also downloads a private PortableGit tree (bash
of its own. If you would rather not, install Git for Windows and run 3code from a plain release folder:
The bash tool has its own timeout and diff computation built in, so it needs no GNU coreutils on the host; a bare PortableGit or MSYS2 bash is enough. If no bash can be found at all, 3code warns and drops the bash tool (file tools keep working) instead of failing to start. The one-time 3code setup (sandbox user + network fence) is still required on Windows. It needs admin rights: run it from a console/RDP session and it asks for them via the Windows UAC prompt (from ssh it prints what to do instead; there the run must come from an admin account or elevated shell). Termux on Android arm64Inside Termux, the same one-liner installs the Android arm64 build: curl -fsSL https://3code.capocasa.dev/install | sh It detects Termux via $PREFIX and installs to $PREFIX/bin, which is already on PATH. If you prefer to build where it will run, Termux only needs: pkg install nim git openssl nimble install https://github.com/capocasa/3code The binary uses Termux OpenSSL for TLS. Android has no OS sandbox or desktop notifications, but the in-process path checks still apply. Notifications do nothing. To update, install again or unpack a new archive. Add a providerRun 3code in your project directory. With no provider configured, it goes straight to the setup wizard. You can return to the wizard later with: :provider add The first field accepts:
The wizard lists the provider's known-good models (a curated registry of combinations 3code has tested) and saves your selection as-is. With --experimental the wizard queries the provider's models endpoint and verifies each selection with a 1-token call; press Esc to stop verification. The same steps work without the wizard, from the command line: 3code provider add zai --key $ZAI_KEY 3code provider add zai --key $ZAI_KEY --models "glm-5.3 glm-5.3-flash" 3code provider models zai glm-5.3 3code provider list 3code provider rm zai add takes the same first field as the wizard (catalog name, URL under --experimental, API key, or a subscription login name) plus --key and --models; without --models it stores the provider's full known-good list. models replaces the list. No verification ping runs; a bad key surfaces on the first turn. Run your first prompt❯ Build a Hello World program in Nim That is enough to get started. The same prompt given as a command-line argument runs once and exits, for scripts and one-offs; -i/--interactive runs it and then drops you into the REPL. Chaining one-shot runs into a scripted session is covered in Scripting. All switches are in the reference. Providers and authentication:provider lists configured providers and marks the current one, along with the model a switch would land on. :model does the same for its models. Both commands have tab completion, so there is no need to memorize catalog names. Model choices are sticky per provider: switching back with :provider returns to the model you last used there, not the first one in its list. The current provider and model are also sticky per directory: each project starts where it last left off, while directories never touched by :provider / :model keep the global config default. All of this lives in the config file; see config file in the reference for every key (-c FILE points a single run at a different one). :provider nvidia :model gpt-oss-20b When a provider ships new models, :provider update <name> re-runs just the models step of the wizard: the offered list refreshes (curated in regular mode, the provider's /models endpoint under --experimental) and Enter keeps the current selection. Name, URL, and key pass through untouched; :provider edit <name> is the full wizard when those need changing too. To see what you are missing without entering the wizard, :provider new lists every configured provider's not-yet-configured models (:provider new <name> for one provider), and :provider pull brings them in, optionally scoped the same way or down to named models: :provider pull nvidia glm-5.4. New models append to the stored list; nothing is removed, and under --experimental each addition is verified with a one-token call first. API keysMost providers use an API key, and the wizard recognizes the common prefixes. For an unknown provider or a custom URL, start 3code with --experimental. The flag is deliberate: compatibility is likely, not promised. xAI and SuperGrokAn xai- key creates a normal xai provider. Enter supergrok instead to sign in with a SuperGrok or X Premium+ subscription. They can live side by side, which is useful if you have both kinds of account. The browser OAuth flow stores tokens in: $XDG_DATA_HOME/3code/auth/xai.json On POSIX systems the file is created with mode 0600, and tokens refresh automatically; a refresh token that dies ends the turn with a re-login hint, not a crash. Switch accounts with :provider xai and :provider supergrok. OpenAI and ChatGPTAn OpenAI API key creates an openai provider. Enter chatgpt instead to use a ChatGPT Plus/Pro subscription. As with xAI and SuperGrok, both providers can exist together. ChatGPT login opens the OpenAI browser OAuth flow and stores tokens in: $XDG_DATA_HOME/3code/auth/openai.json On POSIX systems the file is created with mode 0600, and tokens refresh automatically; a dead refresh token ends the turn with a re-login hint rather than a crash. ChatGPT model listings and requests go through the Codex backend at chatgpt.com/backend-api/codex. OpenAI and ChatGPT use the Responses API. Other OpenAI-compatible providers use Chat Completions. The Codex backend is internal, unversioned, and intended for first-party clients. It works, but using it through 3code is your responsibility. Switch accounts with: :provider openai :provider chatgpt Anthropic and ClaudeAn sk-ant- key (or anthropic in the wizard) creates a normal API-key provider against api.anthropic.com. Claude runs on the native Anthropic Messages wire, not a compatibility shim: the system prompt is a top-level field, tool calls are typed content blocks. Pick a thinking level with :reasoning. Claude 5.x always thinks; models that reject off simply do not offer it. Thinking summaries stream to the reasoning ticker while you wait. Thinking survives the long haul. Blocks replay across tool loops with their signatures, and a compacted or interrupted history degrades instead of failing. A Claude Pro or Max subscription works like the ChatGPT login: enter claudecode in the provider wizard and finish the browser OAuth on claude.ai. Tokens live in: $XDG_DATA_HOME/3code/auth/anthropic.json On POSIX systems the file is created with mode 0600, and tokens refresh automatically. The subscription token only works against api.anthropic.com (the url is pinned), authenticates as a Bearer with Anthropic's OAuth beta opt-in, and the endpoint requires the request's system prompt to open with the Claude Code line — 3code sends that line first and its own Claude preamble second. An API-key anthropic provider can sit beside it. The same caveat as the ChatGPT/Codex twin applies: the OAuth surface is scoped to Anthropic's own client and is neither versioned nor offered to third parties. It works today; using it through 3code is your responsibility, and it can stop at any time. Command CodeA Command Code plan (GOAT, Pro, Max, or the Provider plan) gives API access through their provider gateway. Generate a key in Command Code Studio (the same key authenticates their cmd CLI) and add it with :provider add commandcode, or paste the key and pick commandcode when asked: :provider add commandcode The open-model lineup (DeepSeek, GLM, Kimi, MiniMax, Grok, MiMo) rides the gateway's OpenAI-compatible /provider/v1/chat/completions endpoint. Usage is metered against the plan's credits. Provider catalogEverything the wizard knows about, grouped by where inference actually runs. A ★ marks the providers on the home page rotation, which is the shortlist I recommend. Rows marked quantized serve fp8 or fp4 weights: cheaper per token, but a long agentic run hits the quality floor sooner than the same model at full precision, so treat them as bulk workhorse power, not the model you trust with a hard refactor. Hosting location is where your prompts run, not where the company is registered; for retention policies see Picking a private provider. 3code --good prints the validated provider/model pairs for whichever of these you configure. Model labsFirst-party APIs from the labs that trained the models. Three of the home page names are subscription logins rather than catalog entries: chatgpt is a ChatGPT Plus/Pro login that rides on openai, supergrok is a SuperGrok or X Premium+ login that rides on xai, and claudecode is a Claude Pro/Max login that rides on anthropic.
US inference clouds
EU providersWhere to point 3code when data must stay under EU jurisdiction.
Routers and aggregatorsOne account, many models. The model you get is the model some upstream provider served; quantization, price, and policy vary by route, so check the route before blaming the model.
The known-good registry also covers a few providers that are not in the wizard catalog: TensorX (EU, Dublin and Helsinki), poolside, kilo, LongCat, and Xiaomi MiMo. Add those by hand as custom providers when wanted. Interactive useAt the interactive prompt, describe the result you want or enter a command beginning with :. Tab completes commands, provider names, model names, and paths where supported. 3code loads 3CODE.md and AGENTS.md from the project directory and its parents. These are good homes for project rules, build commands, and style requirements. To include a file directly in a prompt, prefix its path with @: Review @src/parser.nim and add tests for malformed input. To run a shell command yourself without sending anything to the model, prefix it with :!: :! cargo test Output streams into a live viewport while the command runs, then folds into your scrollback; the model never sees it. The command runs through the same sandbox policy and the same executor as model-run commands. The daily keys:
If you forget one, :help prints the keys in effect. The full table, kill ring and all, lives in the reference. Pasting behaves the way you want: a multi-line paste stays one draft instead of submitting on the first embedded newline, so you can paste a code block, edit it, and submit with Enter. :! and @file prefixes still work inside a pasted draft. An empty prompt is a real gesture: press Enter with nothing typed and the ongoing session continues — a turn runs on the conversation exactly as it stands, no user message appended. It is the cheap way to say "keep going" after a turn you want more of. On a fresh session with no turns yet it does nothing. allow-empty-prompt = off in [settings] turns the gesture off (it is on by default). The defaults are the same readline-style Ctrl keys everywhere, including macOS. If you want the Mac editing keys instead, bind the Cmd names in the config; they arrive through the kitty keyboard protocol (kitty, Ghostty, iTerm2, WezTerm, foot, Warp), which 3code enables automatically where available: [shortcuts] edit-in-editor = AltE CmdE CtrlX>CtrlE home = Home CtrlA CmdLeft end = End CtrlE CmdRight word-left = AltB CtrlLeft AltLeft word-right = AltF CtrlRight AltRight delete-to-bol = CmdBackspace delete-word-left = CtrlW AltBackspace Terminals without the protocol ignore the handshake, and unbound Cmd keys simply do nothing. This is what makes the Command key usable on macOS: Option composes characters there (a dead accent on German layouts, so Alt+E and friends cannot arrive as keystrokes), while Cmd chords are reported as-is through the protocol. Selecting textThe input buffer is a real text widget: hold Shift with the arrows (or Home/End, Ctrl+Arrow for words, Ctrl+Shift+Home/End for the whole buffer) and the range highlights in reverse video. Cmd+A selects the whole buffer. Then:
Any edit (typing, backspace, delete) replaces the selection, and any plain cursor motion drops it, the way every desktop text field behaves. Copy and cut also publish to the system clipboard through OSC 52, which most modern terminals honor (xterm needs allowWindowOps); paste reads the clipboard through the platform helper (pbpaste, wl-paste, xclip, xsel, or PowerShell) and falls back to the kill ring when the platform has none. Rebinding keysKeys are commands, and commands are reassignable in the config file (~/.config/3code/config; see config file in the reference for every platform). Add a [shortcuts] section that maps a command name to a space-separated list of keys: [shortcuts] cancel = DoubleESC Now a single Esc does nothing and two quick Esc presses cancel the current turn, handy if you keep brushing Esc by accident. Values are merged onto the defaults, so a command keeps its built-in keys until you override it. An empty value unbinds a command entirely: [shortcuts] suspend = The commands: cancel, clear, quit-if-empty, home, end, left, right, word-left, word-right, up, down, history-previous, history-next, backspace, delete, insert, delete-word-left, delete-to-boundary-left, delete-word-right, delete-to-boundary-right, delete-to-eol, delete-to-bol, clear-screen, suspend, complete, reverse-complete, edit-in-editor, insert-newline, buffer-start, buffer-end, select-left, select-right, select-up, select-down, select-home, select-end, select-word-left, select-word-right, select-all, select-buffer-start, select-buffer-end, cut, copy, paste, yank, yank-pop, yank-last-arg, transpose-chars, transpose-words, upcase-word, downcase-word, capitalize-word, undo, revert-line, isearch-backward, isearch-forward, nonincremental-search-backward, nonincremental-search-forward, history-begin, history-end, set-mark, exchange-point-and-mark, quoted-insert, char-search, char-search-backward, delete-horizontal-space, tab-insert. Keys joined by > form a chord: pressed in sequence, they run the command once. The default edit-in-editor binding is exactly that: [shortcuts] edit-in-editor = AltE CtrlX>CtrlE Alt+E opens the editor directly, and so does Ctrl+X followed by Ctrl+E (the emacs edit-and-execute-command chord). While a chord is half pressed, the first key is reserved for the chord: its own single-key commands wait (Ctrl+X cuts only when a selection exists), a key that completes no chord is swallowed, and Esc abandons the chord without touching the line. A space still means alternatives, so Home End binds two keys, while CtrlX>CtrlE binds one chord. Key names look like CtrlC, AltF, CmdE, ShiftTab, Home, End, Up, Down, Left, Right, Backspace, Delete, Insert, Tab, or ESC. CmdX (alias SuperX) is the macOS Command key, delivered by terminals with the kitty keyboard protocol. Modified navigation keys take a prefix: ShiftLeft, ShiftHome, CtrlLeft, CtrlRight, CtrlDelete, CtrlShiftLeft, CtrlShiftEnd, and so on across the arrows, Home, End, and Delete. Names are case-insensitive (ctrlc is CtrlC). Double requires two quick presses, as in DoubleESC or DoubleCtrlC. :help always shows the bindings currently in effect. The screen3code is not a TUI. The screen is ordinary terminal history scrolling up (your terminal's own scrollback, search, and mouse selection all work), plus a small live footer at the bottom: a thinking ticker row and a token bar above the prompt. Everything committed below the footer stays in the scrollback forever and can be re-read or copied like any shell output. Scrollback contentsEach conversation event gets a one-character marker or banner icon:
Tool output below the banner is dimmed to keep the model's prose the loudest thing on screen. Long output is elided in the middle (... N lines omitted · T7.log ↗); :show N prints the whole capture, :log lists every tool call of the session. The whole elision row is a terminal hyperlink (OSC 8) over a dump of the full output in $TMPDIR/3code_out_<pid>/T<N>.log(.diff), so terminals that support links open it in the file: handler (e.g. $VISUAL) on a click; the visible file name keeps the row honest where links are unsupported, and the dumps live as long as the session does. Diffs are painted green/red, and a long diff is clipped the same linked way rather than flooding the screen. Failed tools mark their banner so a red Ø/✕ scan tells you where a turn went wrong without reading anything. Grey · rows are 3code's own commentary: the context gauge warning before auto-summarization, the elapsed time when a turn ends without usage, the ● resumed banner after --resume. The » rows deserve a second look when something odd happened: they are the interventions described in when the model misbehaves, shown verbatim instead of hidden, so you can see exactly what was sent on your behalf. The token barWhile a turn runs, the footer's bottom row is the token bar: ⠋ ◔25% ↑4.2k ↻18k ↓1.1k 00:12
The bar is an instrument panel, not a decoration: watch ↻ dominate ↑ to confirm the cache is hot (resuming a session re-sends byte-identical history precisely so it stays that way), watch the filling circle to decide between :summarize now and a clean :clear later, and treat a nonzero Ø together with a stuck spinner as your cue to look at the last » row. In private mode the whole bar repaints magenta so the mode is visible from across the room. Its color is configurable (token-bar in the reference). The thinking tickerThe row above the token bar is a one-line ticker. While a reasoning model thinks, its planning text scrolls through it like a stock ticker, then the row goes quiet when the answer starts. It is an activity indicator and a quick-glance channel: enough to see the model is reasoning about the right files, not a panel to read. Nothing on it enters the transcript or the session; reasoning text is replayed to the provider only when the model's family needs it (the think-back parameter, see params in the reference). The same row doubles as the notice row for retry countdowns and live warnings. Receipts and conversation costEach response ends with a compact token receipt spliced under the last scrollback item of the turn: ○12% ↑4.2k ↻18k ↓1.1k 8s
The receipts are a per-turn cost ledger. ↑ and ↻ are what the provider bills as input (cached usually at a steep discount), ↓ is the output you waited for, and the percentage tells you how much of the window the turn consumed. Adding the ↑, ↻, and ↓ columns down the scrollback gives the token cost of the whole conversation, which is usually more interesting than any single turn: it shows the cache working (a hot session is mostly ↻), the moment a task went sideways (a jump in ↑), and the true price of those just one more thing follow-ups. :tokens totals the same columns for the session so far. If a field is missing, the provider did not report it. 3code declines to invent accounting. Patient retryPatient retry is enabled by default. On 429 limits, 5xx responses, and network failures, 3code backs off exponentially rather than failing immediately. Delays are capped at 2048 seconds and retries can continue for about 36 hours, long enough to outwait a rolling limit or a bad connection. After the short initial retry period, attempts become part of the transcript: Usage limit reached for the past 5 hours. (code 429). retry 42/64 in 2048s Use these commands to inspect or change the setting: :retry list :retry off :retry on :retry with no argument is retry now: it cuts a backoff wait short, keeping the backoff schedule as it was so later failures back off exactly as they would have, and it cancels and resends a request that has not shipped any output yet (still connecting, or headers only). Once model output is streaming, or at the idle prompt, it does nothing. Commands typed while a turn runs take effect at Enter, so this works mid-wait. Even with patient retry off, 3code gives transient failures about a minute before returning to the interactive prompt. The setting is stored as patient_retry in [settings] (settings in the reference). Press Esc to cancel the wait. A stream that stalls network-quiet or arrives truncated mid-response is detected and retried on its own, regardless of the setting. For a provider whose streaming is the problem rather than its availability, :streaming off switches requests to plain request/response mode: the reliable fallback when a provider's SSE implementation drops or truncates responses. The setting is per session, or permanent as streaming = off in [settings]. When the model misbehavesTwo failure modes are common enough across providers that 3code guards against them automatically. Both guards are deliberately small: a few hundred tokens of mechanism for a failure mode that would otherwise burn millions. Provider API errors along the way surface as formatted messages, never raw JSON bodies. The flailing detectorSometimes a model gets stuck repeating the same tool call: the same failing patch, the same unhelpful grep, a loop it cannot think its way out of. Every repeat burns another round trip and another batch of output tokens while making no progress. The flailing detector fingerprints each tool call and watches the recent window for repeats: identical calls back to back, A-B-A-B cycles, or a long uninterrupted streak of near-identical calls to the same tool whose results stopped saying anything new; a call about something else restarts the streak, and a streak that keeps delivering fresh output is never a loop no matter how similar the calls look, so a systematic scan (one fixed query walked over successive windows of a file) runs uninterrupted: every window is a new question, and "no match here" is its answer, not a repeat. A verification run retrying one command while reading other files in between is not a loop either. When a streak does flag, the recovery ladder climbs one rung per model response: a batch of parallel tool calls gets one nudge it can actually see, never three warnings and a sentence in a single breath. When it sees one, it intervenes in the model's voice of authority: a tool result is replaced by a short SYSTEM note naming the loop and demanding a strategy change. Two more escalations follow if needed, the last one telling the model to stop and report what is blocking it. A fourth flagged repeat aborts the turn and hands control back to you. Every intervention appears in the scrollback as a » row with the exact text sent, so nothing happens invisibly. A genuinely new tool call resets the ladder; normal work never sees it. The thinking loop guardReasoning models have their own version of getting stuck: the thinking stream wedges on one phrase and re-emits it until the model escapes, the output budget dies, or you interrupt. No tool call is ever emitted, so the flailing detector watches in vain. The stream itself carries the guard: while a reply is still pure thinking, the recent tail is scanned for one phrase repeating, and a wedged stream is cut mid-flight. What lands instead is an empty turn plus a » re-prompt naming what happened - "your reasoning kept repeating itself" - which is usually enough; two re-prompts that both re-wedge end the turn with a notice instead of burning retries. The tripwire is calibrated against 63,479 recorded reasoning passes: thinking that merely repeats a topic phrase, or quotes dense tables of output, sails under it; every recorded loop trips it. The empty response handlerSome providers occasionally return a reply with no content and no tool calls: the model spent its budget thinking, or the gateway clipped the response. 3code refuses to accept that as an answer. Depending on what the response signaled, it either raises the output budget (the reply was cut off by length) or sends a short steering note demanding a real answer, then retries. Bare empties are retried up to twelve times with increasing backoff, and each attempt is visible as a grey or » scrollback row saying why and when the next attempt fires. If the provider stays empty, the turn ends with a notice instead of a silent nothing, and the scrollback shows exactly what was tried. Private modePrivate mode is incognito mode for the agent: off by default, on for one session only, never persisted. While it is on, turns only run on providers you have marked as trusted with your data, and the live token bar repaints in the private color (magenta by default) so the mode is always visible. Scrollback receipts keep the normal color, so old receipts also show which turns ran private. Turn it on either way: 3code -p # from the start :private on # mid-session (turns the bar magenta) A provider or model is allow-private when:
Everything else is refused at the gate with a pointer to the fix: zai.glm-5.2 is not allow-private (trust it with :private allow zai, or switch to a zero-training provider; :private lists) The goal-mode verifier is fenced the same way: the satisfaction check ships the goal text and the working-tree diff, so while private mode is on a verifier that is not allow-private never sees them — the check quietly takes the session model instead (a dim note in scrollback says so). Trust a verifier with :private allow like any other model. Picking a private providerPick a provider with a zero-training policy you are satisfied with (a strict zero data retention policy, or at least a no-training plus bounded retention policy). A shortlist by region:
Or pick any provider you trust, and allow it explicitly. That is a judgment call, not a recommendation. Walkthrough❯ :provider myprovider ❯ :model glm-5.3 ❯ :private allow myprovider glm-5.3 # persisted as [params] allow-private ❯ :private on # bar turns magenta, turns are gated :private with no argument shows the mode, whether the current model is allowed, and which configured providers are trusted. :private deny removes trust. The bar color is configurable like the other colors (colors in the reference): [colors] private-bar = "\x1b[95m" token-bar = "\x1b[36m" Reasoning:reasoning lists the levels the current model actually supports and marks the active one: :reasoning :reasoning high :reasoning off There is no common scale. Most models use low, medium, and high. Some start at off, some end at max, some cannot turn thinking off at all. 3code knows the scale of the model you picked and sends the right field for it. A level missing from the list is a level the model does not have. When a provider streams reasoning text, 3code keeps it moving through a one-line ticker above the interactive prompt. It is not added to the transcript. Web researchThe model has two web tools: web_search (titles, URLs, snippets) and web_fetch (a URL rendered to readable text, boilerplate stripped). Ask for research in plain language and the tools load; the system prompt's web skill is only injected when the task needs it, so ordinary coding turns do not pay for it. The search backend is configurable: Exa (default, runs keyless), Parallel (keyless), and Brave (needs a free API key). Pick one in the config: [search] engine = "brave" brave-key = "..." There is no automatic failover between engines; the chosen one is used as is. See search in the reference for all keys. Known-good modelsProvider APIs differ in small, consequential ways. 3code therefore keeps a registry of combinations tested with the right prompt, reasoning settings, token fields, and context size. Print the current registry with: 3code --good For combinations outside the registry, --experimental opens the gate. System prompt overrideEach model family's system prompt is compiled in, but a file override can replace it wholesale. Place a <family>.txt in one of these (first match wins):
The family name is the branch the prompt selection takes (glm, gpt, claude, and so on). 3code --good shows each combo's family, and the experimental family provider setting picks one for combos off the registry. When an override matches, startup announces it on stderr, and :prompt shows the exact prompt in effect. Two placeholders are substituted into the file: {{credit}} (the provider credit line) and {{skills}} (the discovered skill catalog). A file without them runs without those sections. An override takes effect in new sessions; a running session keeps its prompt until restart or profile change. ImagesVision-capable models can see images. Two ways to get one in front of the model:
Which models can see: the ones --good marks as vision-capable. The mark is earned. Every curated combo was probed with a real image (grok looked at a button and said "Button."). Two exceptions stay off: nebius, whose proxy rejects image parts outright, and the claude line, whose wire path has no image translation yet. You can overrule the mark with [params] vision = "off" or "on" (set via :provider edit). Nothing fails quietly either way: on a model without vision, @x.png and read x.png both fail with a hint naming a model that has it. Delivery is budgeted, not original: images are re-encoded once to JPEG q85 inside a 1280 px box and a 190 KB base64 budget, with a quality-then-scale ladder shrinking further until it fits. The delivered bytes are written under the session's image dir and re-used verbatim, so a resumed session re-sends identical bytes and the provider's prompt cache stays hot. At most 3 images attach per message and 4 per turn; repeated reads of the same image count as flailing. Session files store image <path> <sha1> records, never base64. On resume the image re-embeds from disk when file and checksum still match; otherwise it becomes an [image stale: name] note. Compaction collapses old image blocks to [image WxH] text notes — summaries never carry image blobs. read on an image needs a resizer for the budget ladder: ImageMagick (magick or convert) or, on macOS, sips. Without one, small originals pass through unchanged and large ones error with an install hint. (Windows resizer support is pending the Windows sandbox work.) Computer useVision models can also drive your desktop: :computer on (or --computer) arms the screenshot tool for the session. Off by default, never persisted — a resumed session starts with it off until you arm it again. On activation the backend is probed (X11: one of import/maim/scrot plus xdotool; missing binaries are named in the error), a one-time note enters the conversation teaching the model the observe→act→verify loop, and the screenshot schema joins the offered tools (a one-time prompt-cache bust, like any mid-session tools change). Idle cost is one skills-catalog line (computer-use.md).
macOS (screencapture + cliclick), Wayland (grim + ydotool) and Windows are not implemented; arming there fails with a clear error instead of half-working. SessionsList the 20 most recent sessions for the current directory: 3code --list # or 3code -l Resume the latest session: 3code --resume Resume a session by ID (any value-taking option accepts the value attached, =/:-separated, or space-separated): 3code --resume=abc123 # or 3code -r abc123 3code -rabc123 3code -r:abc123 Or, without restarting, from inside a running 3code: :resume lists this directory's sessions, :resume abc123 swaps the running REPL onto that session (state, locks, and profile move with it; the scrollback is not replayed, scroll up or :show for old turns). Sessions are listed and searched per working directory by default, so unrelated projects stay out of each other's history; -a opts out of the scoping for both. Provider prompt caches may expire before a saved session does. The session will still resume, but its old context may no longer get the cache discount. A resumed session replays the full prior conversation into your scrollback (the same ❯/●/tool banners, receipts included) so you land back in context, and the history is re-sent byte-identically to the provider, which keeps the prompt cache hot: a resume usually costs cached-input prices on everything before your new prompt. Session files are ordinary JSON under 3code's data directory and are created readable only by your user (mode 0600 on POSIX). Also note that 3code refuses to run as root: it executes the shell commands the model proposes, and root blast radius is unacceptable. Run it as your normal user (THREECODE_ALLOW_ROOT=1 overrides, for containers). Notifications and auto-updateWhen a turn ends, 3code can fire a desktop notification, useful when you switched to a browser while a long turn grinds. It is on by default where supported; toggle it with :notify on|off or the notify setting. On Termux there is no notification service, so the setting does nothing. The terminal tab title also reports state: a 3 brand plus a spinner glyph while a turn runs, then a checkmark (or an ✗ when you cancelled) when it ends. The project directory rides along for wide tabs. Toggle it with :tab-title on|off or the tab-title setting. Prebuilt binaries self-update in the background: a detached worker checks GitHub releases at most every four hours, downloads the matching tarball, and atomically swaps the binary for the next launch. One dim line on the first launch after a swap is the whole visible footprint. Source builds do not self-update by default (what you built should not quietly replace itself); the auto-update setting overrides the default either way. Both settings are covered in settings in the reference. Context management:clear starts a new conversation with the same provider and model: :clear :summarize replaces older turns with a generated recap. It is useful when the task must continue but the context has become expensive: :summarize If the old work is no longer relevant, clear it. A fresh context is safer than a summary of things the model no longer needs. Chunked modeFor a large mechanical task, ask the model to split the work into files. Each chunk ends by calling the clear tool with instructions for the next one. The model carries the plan forward and leaves the spent context behind. Example: Divide this task into 4-6 chunks in impl-N.md files. End each file by calling clear with instructions to read the next file. Then execute chunk 1. Task: add test coverage to the parser module. Chunked mode works best when little human input is needed, such as adding basic tests across many modules. If the model forgets to load the next chunk, the task was probably too difficult or the plan too vague. SkillsA skill is a markdown file with instructions for a class of task: how to run a systematic debug, how to write release notes, how to behave as a thinking partner. Skills are cheap by construction: the system prompt carries only the file list (one bullet per skill), and the model reads a skill file with the normal read tool only when the task calls for it. Nothing is preloaded, so a skill you never use costs nothing. Skill files are found in these directories, first match wins on a name collision:
The built-ins shipped today are the role skills (conversational, sysadmin, thinking partner, writing) and task-debug-systematic. To override one, put a file with the same name in a directory higher up the list. To add your own, drop a .md file in any of those directories with an instructive name: the filename is the trigger, so release-notes.md loads when the task smells like release notes. For heavier machinery than a skill there is a native plugin tier (C-ABI hooks and model-facing tools, running host-side); see plugins. A skill file plus command-line tools still covers most of the same ground at a fraction of the context cost. Goal modeGoal mode is the sturdier option for a long coding task. Drop a goal.md into the project root stating the goal: what must be true when the work is done, how to verify it. Anyone can write the file — you directly, or the model when you ask for multi-part work. While goal.md exists, every turn end runs a satisfaction check: a fresh side conversation with the session's own model, given the goal text, a summary of what the turn actually did (files, patches, commands — never the agent's own claims), and the working-tree diff, judges from those inputs alone and answers with a verdict. Not satisfied, the agent is sent back in with the verifier's feedback. Satisfied, the harness renames the file to goal-confirmed.md (the record of the run) and idles. The check has no tools and no rounds: orientation is the harness's job, so the check is one model call. And it shows its work: the verifier's reply lands in scrollback as an item of its own, led by a ⌕ glyph and rendered exactly like any other response — same markdown, same wrapping — with the verdict line stripped and replaced by an icon row under the reply: a green ✓ when the goal is satisfied, a red ✗ when it is not. Nothing the harness runs is ever silent. Verifier modelsThe check can also run on a dedicated verification-class model: :verifier show the verifier setting :verifier list list the verification-class models you can use :verifier jev-latest run every goal check on that model :verifier off back to the session model (the default) Verification-class models are decision models — TypeSafe's Jev (served through OpenRouter, so an existing OpenRouter key covers it) is the curated one. A decision model does not write prose: it answers typed questions (yes/no with a calibrated probability, pick-one-of-N) in a single forward pass, which makes a check per turn end cheap enough to run always. The goal check asks it two questions — is the goal satisfied, and what is the most important gap — and turns the answers into the same verdict/feedback the text verifier produces: p(satisfied) at or above 0.75 confirms the goal, anything else sends the agent back in with the gap as its direction. The decision lands in scrollback as a ⌕ item with the model's own numbers and the same verdict icon row as a text check. These models never appear in ordinary model lists (:model, completion, the provider wizard): they cannot run a turn, they only judge one, so :model jev-latest refuses and points here. Any ordinary model works as a verifier too (:verifier glm-5.3-flash on the current provider) — that check is the regular text side call, just on the named model instead of the session's. verifier off is exactly the behavior from before verifier models existed: the session model checks itself. The selection persists in the config ([settings] verifier_model = "openrouter.typesafe/jev-latest", plus verifier = "off" only while disabled), and the welcome block grows a verifier line naming the active verifier so the state is visible at a glance. A verifier that stops resolving (provider removed, key gone) is skipped with a visible note — the check falls back to the session model rather than going silent. The file is the whole interface. Edit goal.md mid-run to steer (:goal edit opens it in $VISUAL/$EDITOR; the next check reads it fresh), delete it to end goal mode silently, interrupt to pause. Arming starts work immediately: :goal <text> writes the file and sends the agent off in the same breath, no extra prompt needed — and a goal.md that is already on disk when 3code starts arms identically: the session loads it and the kickoff turn runs at once, before the first prompt. The arm itself is quiet (the echoed command is the whole announcement); a session resumed from file replays the kickoff prompt under its ◎ marker. :goal cancel (or :goal!) cancels the active goal, retiring the file to goal-cancelled.md (:goal new <text> is the balking synonym that refuses a live goal, :goal! <text> replaces it, bare :goal shows it). A failing verifier call is retried at the next turn end; the harness does not give up on its own, but if it ever does the record lands in goal-given-up.md. Retired files accumulate harmlessly; delete or archive them whenever. No sub-agents3code does not provide sub-agents. They use plenty of tokens, and I have not seen enough benefit to justify the little management consultancy they form. For parallel or long-running work, use worktrees with goal mode. SandboxThe model is useful, but it does not need the run of your entire computer. 3code applies a filesystem policy to every tool call. Bash commands also enter an OS sandbox, and host rules can fence their network access. The sandbox is questionless: it never interrupts the turn to ask whether a command may run. That is a deliberate trade. An agent that asks is an agent that costs a round trip and a context refresh every time it touches a new directory, which in practice means either an agent that nags or an agent whose guardrails get switched off. Instead the boundary is enforced by the kernel (Landlock, Seatbelt, Windows restricted tokens), decided by a policy you wrote before the turn started, and a denial is simply a failed command the model can read and route around. The tradeoff is that the policy needs setting up first: a couple of minutes once per project, and after that it does not stop on trivialities. The one exception is the ask verb. An ask rule denies exactly like deny, but when a sandboxed bash command is blocked on an ask-protected path, 3code puts one question in the tool output view: sandbox ask: ~/vault/notes/x.md is ask-protected. [f] ~/vault [d] ~ [n]o (60s) One key answers it: f allows the ask-protected path (the ask rule's own target), d the directory around it (the grant is written into the active policy file, so it sticks for the session and beyond), n keeps the denial. No answer within 60 seconds counts as n, and the failed command is never retried without an explicit grant. Use ask for the gray zone: paths you would probably allow if you were watching, like a browser profile or a notes vault, where deny would cost a manual policy edit and allow feels too broad unattended. In practice:
Exactly one policy source is active:
Policies do not cascade or merge. One file wins, so what is allowed remains inspectable. A project file replaces the user file. 3code never creates the user file; create it yourself to set defaults for projects without .sandbox. The first :sandbox allow, :sandbox readonly, :sandbox deny, or :sandbox edit command in a project copies the effective policy to .sandbox before changing it. You get a complete project policy, not a mysterious patch on top of another file. Policy formatA policy has one rule per line:
The target type is determined by its first character:
Paths are intentionally explicit. Use ./foo for a project-relative path. An access word on its own means the project directory itself: deny / allow readonly /var deny ./secrets Rules are read from top to bottom and the last matching path rule wins. In this example the project is writable, /var is read-only, and ./secrets is denied. Everything else is denied because unmatched paths are denied by default. Blank lines and lines beginning with # are ignored. An access word only has special meaning when followed by whitespace or the end of the line, so a host such as deny.example.com remains a hostname. Built-in defaultThe default is a plain text table in the source, one literal block per OS, so it is as inspectable as any policy file: src/threecode/defaults.nim. It changes only through ordinary commits. On macOS, Linux, and other POSIX systems, the built-in policy is: deny / allow /tmp allow /var/tmp allow ./ allow ~/go allow ~/.cache allow ~/.npm allow ~/.cargo/registry allow ~/.cargo/git readonly ~/.cargo/bin readonly ~/.cargo/config.toml readonly ~/.rustup allow ~/.nimble readonly ~/.choosenim readonly ~/.local/share/uv readonly ~/.gitconfig readonly ~/.config/git readonly ~/.hgrc readonly ~/.config/hg allow * On macOS the same policy plus allow ~/Library/Caches applies. This lets the model write in the project and temporary directories and the development caches, not wander through other user files. System programs, libraries, configuration, and device files get a read-only baseline so commands can still run, and the version-control config lines exist because git aborts every command when ~/.gitconfig exists but is unreadable. allow * leaves the network open. On Windows, the built-in policy is: deny / allow ~/AppData/Local/Temp allow ~/AppData/Local/npm-cache allow ~/AppData/Local/pip/cache allow ~/go allow ~/.npm allow ~/.cargo/registry allow ~/.cargo/git readonly ~/.cargo/bin readonly ~/.cargo/config.toml readonly ~/.rustup allow ~/.nimble readonly ~/.choosenim allow ~/AppData/Local/zig allow ./ Windows grants network capability when no host rules are present. When no policy file exists, the built-in default is materialized into a per-run temporary file for the Bash sandbox; nothing is written into the project or the config directory. The default grants the project directory itself (allow ./), so a fresh project is writable out of the box. Sandbox settings collectionRecipes for stacks past the basics. The default policy already covers git, mercurial, C, Zig, Go, Nim, Python (pip, uv, venv), Rust, and the npm/pip caches; on macOS javac itself runs too (the JDK homes are read-only by default, the JVM's system-level lookups ride the sandbox profile) - nothing below is needed for those. Copy the lines you need into .sandbox (via :sandbox allow / :sandbox readonly), or into ~/.config/3code/sandbox to make them your baseline. Each entry is the tool's own documented home, narrowed wherever the tool keeps credentials next to its data; the Windows line is given when the paths move. maven (Java):: allow ~/.m2/repository `~/.m2/settings.xml` holds server passwords, so never allow `~/.m2` whole. The rule is inert until the directory exists: run the first build unfenced once, or `mkdir -p ~/.m2/repository`. Inside a fenced network, JVM tools ignore `http_proxy`; pass `-Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=$WALL_PROXY_PORT`. gradle:: allow ~/.gradle/caches allow ~/.gradle/wrapper allow ~/.gradle/jdks Not `~/.gradle` whole: `gradle.properties` often holds credentials. Same JVM proxy note as maven. dotnet (.NET / MSBuild):: allow ~/.nuget/packages readonly ~/.nuget/NuGet NuGet dies on an unreadable config (`~/.nuget/NuGet/NuGet.Config` can hold feed tokens, so it is readable, not writable - the same treatment `~/.gitconfig` gets), and the package cache is the only writable dot it needs: `~/.dotnet` writes are optional. MSBuild's worker nodes and the dotnet diagnostic server bind unix sockets in the temp dir, which is writable by default; the socket rights ride the writable grant (sandwall 0.5.12). Inside a fenced network, dotnet honors `http_proxy`/`https_proxy`. ruby (RubyGems + Bundler):: allow ~/.gem/ruby allow ~/.local/share/gem allow ~/.bundle/cache `gem install --user-install` writes the first two (macOS uses ~/.gem/ruby, Linux's RubyGems prefers the XDG dir). Bundler needs ~/.bundle/cache for `bundle install`; point installs at the project (`bundle config set --local path vendor/bundle`) since the system gem dir is read-only. Fenced on purpose: ~/.gem/credentials (the rubygems push token) and ~/.bundle/config (can hold API tokens). pnpm:: allow ~/.local/share/pnpm (Windows: allow ~/AppData/Local/pnpm) yarn (berry):: allow ~/.yarn readonly ~/.yarnrc (can hold npm tokens) bun:: allow ~/.bun deno:: cache already rides ~/.cache / ~/Library/Caches; add readonly ~/.deno only for the old standalone layout. nvm / volta (node version managers):: readonly ~/.nvm readonly ~/.volta pyenv / rbenv:: readonly ~/.pyenv (add allow ~/.pyenv/versions to install) readonly ~/.rbenv (add allow ~/.rbenv/versions to install) conda:: readonly ~/miniconda3 Writable envs belong in projects (`conda create -p ./env`). asdf / mise:: readonly ~/.asdf
allow ~/.local/share/mise (mise's installs; Windows:
allow ~/AppData/Local/mise)
zig toolchain:: the build caches are already covered (~/.cache/zig, project .zig-cache, Windows ~/AppData/Local/zig). The compiler itself is wherever you unpacked it - typically readonly ~/zig or /opt/zig. flutter / dart:: allow ~/.pub-cache readonly ~/flutter (wherever the SDK is cloned) android:: readonly ~/Android/Sdk allow ~/.android/avd (emulator state, only if you run sims) julia:: allow ~/.julia (depots, artifacts, registries) R packages:: allow ~/R (Windows: allow ~/Documents/R) composer (PHP):: cache rides ~/.cache/composer; ~/.config/composer/auth.json is a token file - keep it out and pass credentials via env. terraform:: allow ~/.terraform.d/plugin-cache xcode:: allow ~/Library/Developer/Xcode/DerivedData allow ~/Library/Developer/CoreSimulator readonly ~/Library/MobileDevice Prefer `-derivedDataPath <project>/build` so DerivedData stays in the project. Executing Xcode needs no rule: `/Applications` is a read-only baseline dir. CoreSimulator is only needed to run `xcodebuild test`. Not yet verified against a full Xcode install. Deliberately not defaultable, because handing them over is handing over the account: ~/.kube, ~/.aws, ~/.config/gcloud, ~/.gnupg, ~/.pgpass, ~/.my.cnf, ~/.netrc, and the docker socket (/var/run/docker.sock is root-equivalent). If a task needs one, allow it for that project, for that session, and revoke it. Network rulesWith no host rules, every host is allowed. allow * says the same thing explicitly. To close the network completely, use: deny * Host targets may be domains or IP addresses. A single allow rule turns the policy into an allowlist: that host works and all others do not. allow api.example.com A single deny rule does the inverse: that host is blocked and the rest of the network stays open. deny telemetry.example.com IP addresses work the same way, for example allow 192.0.2.10 or deny 192.0.2.10. The spelling matters: bare foo is a hostname, while ./foo is a file or directory named foo in the working directory. A host without a port matches every port. Add one when it matters, as in allow github.com:443. Wildcards work too: allow *.example.org allows that domain pattern. As with paths, the last matching rule wins. Any allow host rule makes unmatched hosts denied. With only deny host rules, unmatched hosts remain allowed. This is the small detail that decides whether you have made an allowlist or a blocklist. On Linux and macOS, fenced commands reach the network through a local CONNECT and SOCKS5 allowlist proxy. Linux puts the command in a network namespace; macOS confines it to loopback with Seatbelt. 3code sets the standard proxy variables and a Git SSH proxy command for the child process. Denied HTTP connections return 403. Note:
Inside the fence, only clients that speak the proxy variables get out: curl, wget, git over HTTPS, package managers. ping (ICMP) and host/dig (their own DNS queries) ignore the proxy and fail: the fenced namespace has no route off the machine, and name resolution happens at the proxy. REST over HTTP(S) only, for now.
On Windows, host rules require one admin setup command: 3code setup It asks for admin rights through the UAC prompt when run unelevated from a console or RDP session; over ssh (no screen for the prompt) it says so and exits - run it from an account with admin instead. Without that setup, 3code warns and runs the command without a network fence. That is safer than pretending the fence exists. Set sandbox_wall_warn = off in [settings] if you no longer need the warning. 3code unsetup removes the firewall filters; it is safe to run repeatedly. Editing the policyUse an editor or the sandbox commands: :sandbox :sandbox show :sandbox edit :sandbox allow /opt :sandbox readonly /var :sandbox deny ./secrets :sandbox on :sandbox off :sandbox edit tries $VISUAL, then $EDITOR, then the platform default. Changes take effect when the editor exits. Rule commands store project paths in portable ./foo form and reload immediately. :sandbox off disables filesystem and network enforcement for the session. The setting is stored as sandbox = off in [settings]. Use :sandbox on to restore enforcement. The one-shot equivalent is the --no-sandbox flag, and 3code sandbox --policy FILE restrict -- CMD runs a single command under a policy without starting the REPL. To allow the full filesystem, replace the policy with allow /. Without host rules, the network is unrestricted too. This is yolo mode. 3code makes you ask for it explicitly and will not have the bright idea on its own. Policy file protectionA policy is not much use if the model can edit it. The file tools therefore cannot change .sandbox or ~/.config/3code/sandbox. After loading the policy, 3code adds hidden read-only guards for both files. They do not appear in :sandbox show, and later rules cannot override them. macOS and Windows enforce these guards for Bash and in-process tools. Landlock cannot subtract a read-only file from an already writable root. On Linux, the in-process tools still enforce the guard, but a Bash command under allow / can write the policy file. Avoid allow / if policy-file protection matters. A denied Bash command usually reports Permission denied. 3code includes the active policy path in denial messages, saving you from debugging the wrong layer. What is sandboxedThe read, write, and patch tools check paths inside 3code itself. Bash needs a stronger boundary, so commands are re-executed through: 3code sandbox --policy FILE restrict -- COMMAND 3code sb is the short alias. The command and all its children inherit the OS restriction. The sandbox process reads the policy afresh, so an edit applies to the next Bash launch. Platform behavior and fallbackThe Bash backend uses Landlock on Linux, Seatbelt on macOS, and restricted tokens plus ACLs on Windows. 3code probes it at startup rather than assuming it works. Old Linux kernels and containers whose seccomp policy blocks Landlock can fail the probe. If the OS backend is unavailable, Bash runs without filesystem confinement. The in-process read, write, and patch checks remain active. This keeps 3code usable, but the distinction matters: an arbitrary Bash command is then outside the filesystem fence. Colors3code detects whether your terminal has a dark or light background by querying it directly (OSC 11) and picks the matching palette, so a light theme just works without configuration. Terminals that do not answer are assumed dark. To pin a palette regardless of the terminal: [settings] tone = "light" The white family (prompt text, help, banners) adapts between tones; the functional colors (cyan receipts, green/red diffs, magenta private bar) are the same in both. Every white-family color plus the two bar colors can be overridden with quoted ANSI escapes under [colors]; see colors in the reference. Design decisionsWhy 3code looks and behaves the way it does. The through-line is economy: the least tokens, the least computer time, the least of your attention. No TUI3code renders plain scrolling text, not a full-screen terminal interface. Committing to the shell/REPL paradigm buys a lot for free: your terminal's own scrollback, search, copy-paste, and font rendering keep working; the transcript is history in the literal sense, so yesterday's session is 3code -r and a scroll up; logging and terminal multiplexers capture it unchanged. A TUI owns the screen, reimplements all of that worse, and adds a rendering surface that can itself be buggy (every terminal-rendering bug 3code ever had lived in the footer, never in the history). The live surface is deliberately two rows. Kernel enforcement, no questionsThe sandbox asks nothing and enforces at the kernel (Landlock, Seatbelt, Windows restricted tokens). The alternative architectures were worse. A judge model re-reading every command is unacceptable: it burns tokens on guesswork, adds latency to every tool call, and is security by vibes, a second model approving what the first one wrote. Approval prompts are security by interruption, and in practice train users to mash yes. A static kernel policy uses zero tokens, adds microseconds, and fails closed; the cost is the one-time setup described in the sandbox section. And the honest calibration: most coding agent sessions run effectively unfenced anyway (yolo mode, --dangerously- skip-permissions and friends), so a fence that is cheap to set up and never interrupts is a real improvement over both the nagging gate and the absent one, even before it stops anything. One model, small tool setNo sub-agents, no trees of delegated prompts. A single conversation with bash, file tools, plan, and web access, plus skills loaded on demand, keeps the token budget on the work instead of on orchestration. When a task outgrows one context, chunked mode and goal files split it along worktrees instead of spawning agents. ScriptingThe REPL is optional. A prompt given as a command-line argument runs one turn and exits (oneshot mode), and -r continues the directory's latest session in another oneshot, so a shell script or cron job can hold an ongoing conversation: 3code "list the failing tests in this repo" # starts a session 3code -r "fix the first one and rerun" # continues it 3code -r "commit what changed" # and so on Each -r call resumes the latest session saved for the working directory with the full prior context, byte-identically, so the prompt cache stays warm and the model sees the whole conversation. Run from the project directory; sessions are per-directory, same as --list. Oneshot output is the regular transcript rendering on stdout (the reply, tool banners, receipts, exactly what the interactive screen commits); diagnostics go to stderr. The limitation: for now that regular output is all a script gets. The exit code separates a completed turn (0) from usage, config, and crash failures (nonzero), but anything in between, including provider errors mid-turn, has to be parsed out of the text. A parseable-output flag is planned; until then, prompt for the shape you need (answer with just the file names, one per line). For embedding without parsing anything, the library API returns events as data. Library APIA Nim program can embed the same agent loop without bringing along the terminal interface: import threecode let session = initAgentSession( AgentOptions(model: "deepinfra.deepseek-v3.2")) # blocking: run a full turn (model calls + tool calls) and get the reply let reply = session.prompt("what does this project do?") # streaming: same call, events as they happen session.onEvent = proc(event: AgentEvent) = case event.kind of aevDelta: stdout.write event.text # assistant text chunks of aevRetry, aevTool: stderr.writeLine event.text # tool results, notices of aevDone: echo event.usage # per-call token usage else: discard discard session.prompt("Run the tests and fix the failure.") # colon commands work too echo session.command(":tokens") session.close() AgentOptions mirrors the CLI flags: model, cwd, resume/resumeId, sessionPath, experimental, debug. prompt blocks until the model finishes calling tools and returns its reply. Meanwhile, onEvent receives text, reasoning, tool output, retry notices, and usage. promptAsync puts the turn on a library-managed thread and reports failures with aevError; interrupt cancels it. Sessions use the same provider config, filesystem policy, locks, and saved format as the CLI. Only one AgentSession may be active in a process, and calls on it are not thread-safe. In an asynchronous server, keep the session on one worker thread and pass prompts and events through queues. This avoids sharing session state across threads. example/webserve.nim is the complete example: a web frontend that serves a chat page, keeps the session on a worker thread, and streams replies to the browser over server-sent events. nim c -r example/webserve.nim and open http://localhost:8501. Agent Client Protocol3code --acp turns the binary into an ACP v1 agent: newline-delimited JSON-RPC on stdin/stdout, the protocol Zed, JetBrains, Neovim plugins, and a growing pile of custom clients speak. Your editor launches 3code like any other ACP agent: # Zed: agent settings, "Add custom agent" Command: 3code Arguments: --acp One session per process. The session id is the .3log id, so session/load resumes any past 3code conversation and session/resume reconnects without replaying it. Prompt turns stream as session/update notifications (message and reasoning chunks, tool activity, token usage when the context window is known); session/cancel is Ctrl-C over the wire and answers the prompt with cancelled. Model and provider come from the normal config (-m picks, :model philosophy otherwise); the sandbox and loop guard run exactly as in the terminal. Not advertised (because 3code does not do them over ACP yet): MCP servers, client-side filesystem and terminal execution, permission requests, elicitation, modes, and authentication. Diagnostics go to stderr; stdout is protocol records only, so the stream stays parseable. Build from sourcenimble install https://github.com/capocasa/3code Requires Nim >= 2.0 and curl on PATH. ContributingPatches welcome at github.com/capocasa/3code.
Bug reports welcome, but make sure you give enough specific information to reproduce the issue. Terminal rendering bugs need a visual PTY test that shows the bad frame before the fix. A test that cannot show the bug has not reproduced it yet. Developer API documentation lives in the developer docs: module-by-module, generated from the source doc comments. Prior art3code is influenced by Claude Code, Goose, Pi, and Codex. It chooses different tradeoffs, but the family resemblance is intentional. As a European, using Claude Code can feel like driving a turbocharged Ford F-150 to buy a pack of gum. It is still a fine truck. 3code omits features that do not help it write software reliably with fewer resources. Economy is the feature, not a billing surprise. Made with Nim. Generated: 2026-10-27 14:28:45 UTC | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |