What was missing from the AI CLIs I already used, and the decisions that came out of it
Published: October 19, 2026
Reading time: 8 min
Series: lemon-code in 30 days, part 1 of 13
In March 2026 I had four coding-agent CLIs on my Mac: Claude Code, Codex, Gemini CLI and OpenCode. All good. None of them knew what the others had done in the same folder ten minutes earlier.
I was pasting context between terminals, feeding one session's summary in as the next one's prompt, and picking a tool for each task from memory. When something broke, there was no record of why.
On March 7 I made the first commit of lemon-code, a terminal coding agent written in Bun, TypeScript and Ink. Seven months and more than 440 commits later, it's the tool I use every day. This series walks through the technical decisions behind it, including the ones I got wrong.
Imagine an orchestra where every musician plays well but nobody has the others' sheet music and there's no conductor. Each sounds good; the ensemble doesn't. lemon-code was born to be the conductor: it doesn't play instead of the CLIs, it decides who comes in, when, and writes down what each one played.
An agent is a loop. The model proposes an action, the harness decides whether it may run, runs it and feeds the result back.
// minimal agent loop (illustrative, no dependencies)
type Step = { tool: string; args: unknown } | { done: string };
interface Model { next(history: string[]): Promise<Step> }
interface Tools {
approve(step: Step): Promise<boolean>;
exec(step: Step): Promise<string>;
}
export async function run(task: string, model: Model, tools: Tools) {
const history = [task];
for (let turn = 0; turn < 20; turn++) {
const step = await model.next(history);
if ("done" in step) return step.done;
const ok = await tools.approve(step); // human in the loop
history.push(ok ? await tools.exec(step) : "denied by user");
}
throw new Error("turn limit reached");
}
Every CLI I used implements some version of this loop. What sets them apart is everything around it: which model answers, what context goes in, who approves, what gets recorded. That's the part I wanted to own.
Fragmented context. Each tool kept sessions and memory its own way. Switching CLIs mid-task meant losing the thread.
Invisible routing. Some tasks need the strongest model available. Others are a file search any small model handles. I made that call by hand, inconsistently. When a tool made it for me, I couldn't see why.
Approvals without a trail. Every CLI asks before writing a file or running a command, and the approval disappears when the session closes. I wanted to answer later: who approved this write, under which permission mode, with which model.
Free and paid products to turn ideas into a foundation you can actually ship.
13 products availableContinue exploring similar topics
I reached for an HTTP provider on tasks an installed CLI could handle. lemon-code's CLI-first rule fits in four lines of precedence.
For HTTP-provider routes, lemon-code records score factors. This teaching example shows how to read the sum and distinguish it from the JEV layer.
lemon-code has six ADRs numbered 0049, all created on the same day. That mistake teaches more about project memory with multiple agents than any well-numbered ADR.
A practical checklist to validate security, cost, evals, and observability before you ship your AI app to production.
Production-tested templates trusted by developers. Save weeks of setup on your next project.
Writing down non-goals helped more than listing features. The project's PRD defines two boundaries:
In current code, that describes the locally calculated baseline score for HTTP providers. There is also a separate JEV step: without a gateway key it uses the available local client (Laya or BuiltinOne); with a key, it can evaluate candidates through the Vercel Gateway. Shadow mode does not change the route; in advisory or enforce mode, an accepted decision can change the destination. This configurable layer does not fit the claim that every dispatch path is local and deterministic.
| Principle | In practice |
|---|---|
| Local first | Automatic CLI-first for eligible runtimes; HTTP API when explicit, CLI-first is off or no runtime is eligible; state in local SQLite |
| Explainable routing | HTTP-provider baseline scores keep their factors; JEV and CLI runtimes use separate paths |
| Human in the loop | Writes and sensitive commands go through approval, with visible permission modes |
Each gets its own post. The next one, on preferring eligible CLIs before HTTP providers, comes out Wednesday.
Separate from dispatch, lemon-model is a work-in-progress lab for local classification, an in-house nano transformer and evaluation of open-weight bases with MLX. Training smokes validate the pipeline, not the coding quality of a model.
The macOS app doesn't reimplement anything. It talks to the CLI through a versioned JSON-RPC contract, which gets its own post later.
I've written React for years, so building the terminal UI with React components through Ink removed day-one friction. Bun runs TypeScript directly with no dev build step and ships a test runner and SQLite.
The cost showed up early: Ink doesn't run cleanly under Bun on macOS ARM64 without a couple of patches. That's post 3.
After a few months of daily use I asked three questions:
If you use more than one AI CLI and answered "no" to any of these, you probably have my problem. Building a whole agent is only one answer. Sometimes a script that standardizes context across tools gets you most of the way.
Three or more checked: at least standardize sessions and decision logs.
I built lemon-code because the CLIs I use are too good to keep using in isolation. The missing piece was the layer that ties context together, picks a model with visible criteria and keeps a record of what was approved.
Over the next 30 days I'll open up the decisions in that layer, one per post. The code stays closed. The ideas and trade-offs are here.
If your team is trying to fit coding agents into a workflow with approvals and auditability, that's the kind of architecture work I do at LEMON.DEV.