A local-first coding agent for the terminal, powered by Ollama.
You talk to it; it reads, searches, writes and edits files and runs commands in your workspace to answer you. Everything runs on your machine — no API key, no data leaving the host.
npm install -g @cloi-ai/cloi
cloicloi setup runs automatically the first time and picks models that fit your
hardware. Requires Ollama, Node 22.5+, and a tool-capable
model.
█▀▀ █ █▀█ █ fallback qwen3:30b-a3b
█▄▄ █▄▄ █▄█ █ context 16k
root ~/my-project
────────────────────────────────────────────────── nemotron-3-nano:4b
› why is the empty-list test failing?
✓ run npm test exit 1
✓ grep /completionRate/ 3 matches
✓ read src/lib/stats.js 5 lines
← Edit src/lib/stats.js
1 export function completionRate(tasks) {
2 + if (tasks.length === 0) return 0;
3 const done = tasks.filter((t) => t.status === 'done').length;
✓ run npm test exit 0
ctx ███░░░░░░░░░░░░░ 3.5k/16k
Everything below exists because a small local model failed in a specific way during live testing. docs/internals.md has the reasoning.
Setup reads VRAM, RAM and core count and proposes two models against different constraints: a primary that fits in VRAM and runs every step, and a fallback that only has to fit in RAM because it runs rarely. Then it loads the primary and asks Ollama where it actually went, stepping down if the prediction was wrong.
Tier means measured capability, not size — a 4B model scoring 63% is ranked above an 8B scoring 46%. docs/models.md
Six signals say a turn is stuck: repeated identical calls, consecutive tool failures, unusable calls, a narrated step never taken. Any of them hands the conversation to the bigger model, which inherits everything already tried.
Routing is on observed failure, not on guessing the task type up front.
A claim that a file contains something is settled by the file. Line citations are checked against line counts, quotes against what was actually read, and a conclusion of absence against how much of the file the agent bothered to open. No model call — the workspace already knows.
A second, model-based review runs only after an escalation, because that is the one moment the answer is worth doubting.
Every substantial result is stored in full under a name taken from its
arguments — stats_js, grep_completionrate, npm_test:
✓ read README.md 294 of 638 lines
[saved as readme_md — recall it instead of running this again]
recall reads it back whole, sliced, or searched. The stored copy is complete
even when what the model was shown was truncated, and it outlives the
conversation that produced it.
A session-long interpreter where every stored result is a variable and every tool is a callable function:
files = glob(pattern='src/**/*.js').splitlines()[1:]
sizes = {f: len(read_file(path=f)) for f in files}
sorted(sizes.items(), key=lambda kv: -kv[1])[:3]One cell drives a loop that would otherwise be one tool call per turn. Variables, imports and helper functions survive across sessions. Tools that ask permission still ask, so approving a cell does not approve everything it can reach.
Measured on nemotron-3-nano:4b, 8 runs per arm, on a task needing per-file
arithmetic:
arm correct used python calls time
without examples 0/8 0/8 7.1 9s
with examples 3/8 8/8 3.8 6s
Ollama does not refuse an over-long prompt — it drops the oldest tokens, so the agent forgets what it was asked while behaving as though it remembers. Cloi summarises the older half instead, cutting only where a tool result cannot be separated from the call that produced it.
Nothing is deleted: the summary records how far it reaches, and older messages are simply not sent.
Paths are contained to the workspace. Writes, edits and shell commands ask first, with three answers — once, always-for-this-tool, or no. Subprocesses run with a sanitised environment, so a command cannot read your API keys, and any secret that reaches output is redacted before it is stored or sent.
cloi # interactive session
cloi "add tests for auth" # one request, then exit
cloi --continue # resume the last session here
cloi setup # re-pick modelsIn a session: /model, /tools, /plan, /usage, /sessions, /help.
Ctrl+C interrupts a turn; Ctrl+D exits.
- docs/models.md — how models are chosen, and the benchmark behind the catalog
- docs/internals.md — the loop, the rails, verification, and what live testing changed
- docs/configuration.md — every setting and its default
node bench/compare.js qwen3:8b nemotron-3-nano:4b # models, inside this loop
node bench/python-tool.js --repeats 8 # does documenting a tool change its use?Both drive the real agent against the real tools, because what matters is how a model behaves here, not on a leaderboard with different tools and prompts.
- Only Ollama. The provider sits behind one module, but nothing else is wired up.
- No sub-agents, no MCP, no web access.
- Hard cross-file debugging is above the ceiling of every model that fits 8 GB — 0/12 in the benchmark, and that is the honest limit.
MIT. See LICENSE.