In-depth articles on technology shaping what comes next.

Inside an Agentic Coding CLI: Loops, Context, Permissions

What makes an agentic coding CLI actually work? A practical look at tool-call loops, context management, permission models, and client/server design.

A small glowing core surrounded by layers of gears, pipes and valves, illustrating harness versus model.
In an agentic coding CLI, the model is the small core; the harness around it is most of the machine.

The model is the easy part. That's the claim, anyway — and after watching a dozen agentic coding CLIs rise, fork, and stumble over the past couple of years, I think it's true: an agentic coding CLI succeeds or fails on its harness, not its model. The release of OpenCode 2.0 — an open-source, terminal-based coding agent that picked up serious traction this week — is a good excuse to say this out loud. The interesting things in that release aren't new model integrations. They're a client/server split, hot-reloading plugins, backgroundable tasks, and a re-architected agent loop. Plumbing, in other words. The plumbing is the product.

I'm a former ML engineer, so this conclusion annoyed me. I wanted the model quality to be the differentiator. But when I look at why I keep or abandon these tools, the answer is never "the model got smarter." It's "the tool stopped asking me to approve every single command," or "it finally reloads my config without a restart," or "I can attach a GUI to a session running on a remote box." Let's take apart the three subsystems that actually determine whether a coding agent is useful: the tool-call loop, context management, and the permission model. Then I'll concede where the contrarian claim breaks down.

The Tool-Call Loop Is a Distributed System

Strip away the marketing and every coding agent is the same program: call the model with a prompt and a list of tools, receive either text or a tool call, execute the tool, append the result, repeat. That loop sounds trivial. It is not trivial, and the tools that treat it as trivial feel janky within five minutes of real use.

Consider what the loop actually has to handle. Tool executions fail — the compiler errors, the test suite hangs, the file doesn't exist at that path because the model hallucinated it. The model emits malformed JSON for its tool arguments. A tool call takes forty seconds and the user hits Escape mid-run. Two tool calls arrive in one turn and one of them must not run if the other fails. The context window fills up mid-task and something has to give. Every one of these is a concurrency-and-failure problem, not an intelligence problem. I've argued before that LLM agents are just distributed systems, and the coding CLI is the purest example: you have an unreliable, nondeterministic remote component (the model) coordinating with local effectful components (shell, filesystem, LSP), and the loop is the consensus protocol between them.

This is why it matters that OpenCode 2.0 reportedly rebuilt its loop around Effect, a TypeScript framework for structured concurrency and typed errors. That choice signals the team stopped thinking of the loop as "a while loop with fetch in it" and started treating it as what it is: a system of interruptible, composable, retryable effects with explicit error channels. When your agent loop has real cancellation semantics, "user presses Escape" becomes a first-class event instead of a corrupted state. When tool results are typed, you can validate the model's output before executing it — which pairs nicely with techniques like constrained decoding, where you restrict the model's output space so malformed tool calls become impossible rather than merely caught.

Client/Server Is the Feature Nobody Markets

One of the most consequential design decisions a coding CLI makes is whether the agent is a process or a service. The naive design — one process, one terminal, session dies when the terminal dies — is how most of these tools started, and it's a ceiling. OpenCode's client/server model (where a long-lived server owns sessions and any number of clients — TUI, desktop app, web UI — attach to it) removes that ceiling. Practically, it means: your session survives your terminal closing, you can run the agent on a remote dev box and drive it from a native app on your laptop, and multiple interfaces can observe the same session.

If you've ever tried to run an agent on a VM over SSH and had the session clobbered by a reconnect, you know this isn't a nicety. It's the difference between a toy and infrastructure. Long-running agents belong in the same category as builds and test suites: things you start, walk away from, and check on later. OpenCode 2.0's backgroundable tasks push in the same direction — the agent stops being a synchronous conversation and becomes a job runner with a chat interface.

Now, the caveat, because I hold this opinion consistently: a server architecture does not mean a permanent daemon. Several commenters noted with suspicion that every agent harness suddenly wants a persistent background process on their machine. That instinct is correct. I've written before that CLI tools shouldn't install background daemons by default, and I stand by it. There's a clean middle ground: spawn the server on demand, scoped to the project directory, killed when the last client disconnects. You get session persistence and multi-client attach without an always-on process phoning home from your machine. OpenCode loading config from the session's folder rather than the server's working directory — another 2.0 change — suggests they understand the server should follow the project, not the other way around.

Context Management Is Where Quality Lives

Ask any practitioner why an agent "got dumb" after an hour of work and the answer is almost never model regression. It's context rot. The window filled with stale tool output, dead ends, and the full text of files that were relevant forty turns ago. The model is now making decisions over a haystack, and needle-recall degrades long before you hit the nominal token limit.

A good harness treats context as a budget with an allocation policy, not a pipe. Concretely, that means several things working together:

  • Tool output truncation with retrieval escape hatches. A 5,000-line test log should enter context as its failing forty lines plus a note that the full log lives at a path the model can re-read. The harness decides what's salient; the model decides when it needs more.
  • Sub-sessions and sub-agents. "Go explore how auth works in this repo and report back" should run in an isolated context that returns a digest, not the entire exploration transcript. OpenCode 2.0's improved sub-session communication is exactly this idea.
  • Compaction that preserves intent. When the window fills, something has to summarize the conversation so far. A harness that summarizes naively loses the user's constraints ("don't touch the billing module") along with the noise. Good compaction keeps decisions and constraints verbatim and compresses only the narrative.
  • Config and skills as context, not code. Project-level instructions, plugin-defined tools, and skill files all consume window. Hot-reloading them — another 2.0 fix — matters because editing your agent's context without restarting its session is how you iterate on agent behavior itself.

None of this is model intelligence. All of it determines how intelligent the model appears. A frontier model with a sloppy context policy will lose to a cheaper model behind a disciplined one on any task longer than twenty minutes.

Glass funnel compressing streams of documents into a thin beam of light, symbolizing context compression.
Context management is compression with judgment: everything goes in, only the salient comes out.

Permission Models: The Boring Feature That Decides Adoption

Here's a question from the HN discussion that I consider the single most important product question in this space: is there anything between "approve every command" and "yolo mode"? Full auto-approval is genuinely dangerous — a coding agent with shell access can rm -rf, git push --force, and exfiltrate your environment variables in one turn. Full manual approval is genuinely unusable — approving eighty commands an hour means you're not supervising the agent, you're a slow human keyboard interrupt. The tool that nails the middle wins the pragmatists.

The middle is a graded permission model, and its design space is better understood than people think:

  • Read-only vs. write vs. execute. Reads are nearly free; writes should be scoped to the project tree; execution is where policy matters most.
  • Allowlists with argument awareness. git status auto-approves; git push prompts. Naive tools match the binary name; good tools match verb and flags, because npm test and npm publish are the same binary with wildly different blast radii.
  • Session-scoped grants. "Always allow this command in this session" is the single highest-value permission feature there is. It converts seventy prompts into one.
  • Containment for the irreversible. Network access, anything outside the project directory, and anything touching credentials should be structurally restricted, not merely prompted about.

That last bullet is the part most harnesses underinvest in, because prompting is easy and containment is hard. But a prompt is a question the model hopes you'll answer correctly at 6pm on a Friday. Sandboxing the agent's process tree — restricted network, scoped filesystem mounts — makes entire failure classes impossible regardless of what the model or the user does. I've laid out the trade-off in detail in containment vs. monitoring for AI agents; my short version is that prompts are monitoring, sandboxing is containment, and you want containment for anything irreversible. The CLI ecosystem is slowly learning this. The ones that learn it fastest will earn the trust that lets users actually enable auto-approval for the boring 90%.

Extensibility and the Self-Hosted Model Problem

Two smaller lessons from the 2.0 discussion deserve attention. First: "everything's a plugin" is winning. Users are building skills, config editors, and workflow hooks on top of these harnesses, and the harness that makes plugins first-class — hot-reloaded, able to observe sessions, able to modify behavior — compounds community effort instead of fighting it. The tell that OpenCode takes this seriously: their own GitHub app triages bug reports by reproducing the issue and commenting findings, essentially using the agent to maintain the agent. That's the "self-awareness" users praised, and it's a plugin-architecture outcome, not a model outcome.

Second: model-agnosticism is harder than it looks, and it's a context-format problem. One user tried an excellent local model that's trained to issue commands in bash, while the harness stubbornly expected JavaScript-flavored tool calls, and the two simply couldn't interoperate. This is the hidden coupling in every agent harness: the tool-call format, the system prompt's assumptions, and the model's training distribution have to line up. A harness that hardcodes one model family's conventions isn't model-agnostic, it's model-tolerant at best. If local models are going to matter for coding agents — and for cost and privacy reasons they should — harnesses need pluggable tool-call dialects, not just pluggable API endpoints.

Where the Contrarian Claim Breaks Down

I said the model is the easy part. Time to concede the edges. The claim holds for harness design — loops, context, permissions — but there are places where the model is genuinely the bottleneck. Tool-use reliability itself is trained into models; a model that can't consistently emit valid function calls makes every harness look broken, and no amount of Effect-based structured concurrency fixes a model that argues with its own schema. Multi-step planning over long horizons still separates model tiers in ways you can feel within a single session. And the harness-vs-model framing has a shelf life: as models absorb agentic behavior into training (native tool use, native long-horizon planning), capabilities migrate from harness to weights, and today's clever harness feature becomes tomorrow's wasted effort. Teams building harnesses should assume they're building scaffolding that models will eventually make obsolete — which is, honestly, an argument for open-source harnesses with plugin ecosystems, because those survive on community rather than on a temporary capability gap.

There's also a UX floor no architecture excuses. Some users tried OpenCode 2.0 and still found it clunky; others complained about slow startup and an ever-growing database of dead chat history. These aren't deep problems, but they're the ones that make people bounce off a tool in the first ten minutes before the elegant agent loop ever gets to show off. Polish is a feature too, and it's the one open-source projects most often underfund.

The model writes the code, but the harness decides whether you trust it enough to let it.

So my pragmatic advice, as someone who's watched this category churn: pick your coding agent the way you'd pick a database, not the way you'd pick a model benchmark winner. Look at the loop's failure handling, the context policy, the permission granularity, and whether sessions survive your terminal. Those are the parts that will still matter after the next model release makes everyone equally smart again.