Why we build Crystal Code
Crystal Code began as a .NET library for building agents, because we could not find a clean open source one. This post covers why we then built a coding agent on it, and how much control it gives you.
Crystal Code is a coding agent that runs in your terminal, works in a local repository, and is built on .NET. Other tools already do this job well. This post explains why we made another one.
Other tools are good
Claude Code, Codex and OpenCode are excellent products. Nothing here is a claim that they got it wrong.
What we needed was narrower. We wanted a coding agent that is open source and that lets you change almost everything about how it behaves. That includes the prompts it runs with.
OpenCode is a very good open source agent. Its auto mode approves every request that is not explicitly denied, so nothing is actually reviewed. That is the same as Crystal Code’s full mode, and we do not count it as automatic approval.
What we wanted is a second model that reviews each command before it runs, so you are not asked every time and you are not giving up review either. This is what Codex does, and Crystal Code has it too. The reviewing model can use its own provider, model and thinking setting, and an optional second model can check the calls the first one allowed.
There are five approval modes: default, edit, review, audit and full. review and audit use the reviewing model. full skips approval. You choose how much to hand over, and you can choose it per process.
It began with a library
Crystal Code did not start as a product. It started as a gap.
We wanted an open source, clean library for building AI agents in .NET. We did not find one, or at least we did not find one that fit. So we wrote Crystal.
Crystal is deliberately plain. It has no built-in system prompt, no built-in provider, and nothing tied to a particular vendor. It is a set of general interfaces. You bring the prompts and the providers.
Crystal Code came afterwards. It is the coding product built on that library, and it has been iterated on ever since. The library supplies protocol types and tool dispatch. The product supplies everything a person actually touches: prompts, tools, approval, compaction, sessions, providers and the terminal.
Why .NET
Part of the reason is the platform itself. Agents are often built on Python or Node.js, and in our experience those stacks bring a lot of weight with them. .NET does not. A release of Crystal Code is a self-contained build for Linux, macOS and Windows, on x64 and ARM64.
The other part is that we write .NET. We wanted the agent we use every day to be built in the language and runtime we know best.
The terminal was the one place where the ecosystem did not give us enough. We use Spectre.Console to render markup, tables and cards, and it does that well. It is not a toolkit for a full terminal interface with a streaming transcript, a multiline composer, and a queue of follow-up requests. So we wrote CrystalCode.Display, a rendering engine that handles those terminal operations. The engine behind the session has no terminal code at all, which is why the same session can also be served over HTTP or to an editor.
Control is the point
Everything above comes back to one idea: you should decide what the agent can do.
Modes. Plan is read-only. Work can edit files, apply multi-file patches and run shell commands, including background shell tasks you can inspect and stop. Beyond those two, you can write your own modes. A mode is a directory with a mode.json and a prompt.md: it names the built-in tools it exposes and carries its own system prompt. Tab cycles through them.
Approval. Every tool call passes through a risk-aware policy, and a custom mode never loosens it. Listing bash in a mode does not make shell calls pass. A workspace boundary also keeps the agent inside the repository you opened.
Prompts. Prompts are yours to change, and that includes the system prompts, not only the instructions you add on top. You can keep reusable prompt sets, and attach extra prompt pieces to a single run.
Sessions. A session can be resumed or forked, and older context is compacted when it grows, either automatically or when you ask. A failed model call is retried with backoff, and the retry is shown on screen so you can see what is happening.
One engine, three ways in
The terminal is the main way to use Crystal Code, but it is not the only one. The engine that runs a session has no terminal code in it, and three front ends sit on top of it:
crystalopens the terminal interface.crystal serverserves the same session over HTTP, so a web page or a desktop program can drive it.crystal bridgespeaks the Agent Client Protocol over stdio, so an editor that supports it can use a session.
There is also crystal run, which runs a single task with no operator. Anything that would normally ask you is denied.
Keeping the engine separate costs some effort, and we think it is worth it. A new front end should reuse the session, not copy it.
Make it yours
There are several places to change what Crystal Code does, from light to heavy:
- Skills are Markdown instructions the model loads when it needs them.
- Tool sets add model-callable tools. A tool set is a directory with a
tools.json. It runs an executable, or a .NET assembly loaded in its own context. - MCP servers can contribute tools too.
- Plugins run inside the process. A plugin is a directory with a
plugin.jsonand an assembly. It can contribute tools, slash commands, hooks, approval classifiers and prompt placeholders.
In all of these, the host still owns approval, workspace limits, output truncation and timeouts. Extension code does not get around the policy.
We want plugins to be able to do more than they do today. We have not settled the exact shape, so this post does not name an API. Names and contracts in Crystal Code can still change, because the product has no compatibility baseline yet.
What is not finished
Crystal Code is young. A few things to know before you try it:
- Child Agents exist but are off by default.
- Linux and macOS are the recommended platforms. On Windows, the built-in
bashtool needs Git Bash. - There is no NuGet package yet.
We will write about the parts that are done, and the parts we are still working out, in the posts that follow.