DeepSeeker-Code: A Coding Agent That Admits Models Lie, So It Doesn't Trust Them
Foreword
There's a common misconception about coding agents: that the threshold is "getting it to move."
Hook up a model API, stuff a few tools into a prompt, and in half a day you can have a demo that reads and writes files and executes commands. The "getting it to move" step has long been worthless.
What's truly valuable, and truly difficult, is what happens after it moves: the context balloons and explodes; the model stares at an error and insists it succeeded; it reads a config file and casually sends your secret keys to the cloud; it runs itself into an infinite loop and burns through all your tokens. These aren't things you can fix with a little tweaking — they are the watershed that separates an agent from a chatbot. Whether you can cross that line depends entirely on engineering governance.
DeepSeeker-Code is my complete attempt to fill in these potholes: a coding agent that runs locally, driven by DeepSeek, with a VS Code extension, a terminal CLI, and an HTTP service all sharing one brain. It's open source: github.com/xknk/deepSeekCode.
This piece won't list features. I'll only talk about a few key trade-offs I made at that watershed — why I chose them, what I gained, and what I sacrificed. In the end, whether an agent is good or not is determined by these trade-offs.
1. One brain for three ends: a single engine feeding three entry points
VS Code, terminal, HTTP — how to arrange these three entry points was my earliest dilemma.
The conventional approach is to split the core into an npm package that all three entry points depend on. I didn't do that. Instead, I welded the engine (I call it core) directly into the build artifacts of the three entry points, bundled with esbuild and referenced via tsconfig path aliases. It isn't depended on — it's compiled in.
Why do this? Core is still changing every day. Once you split it into a standalone package, the three entry points each lock onto a version, and soon you get the fragmentation where a bug is fixed in the plugin but the CLI still has it, and users have to figure out "which shell goes with which version." Inlining avoids this problem — the three ends always run the exact same code; change one place and everything takes effect. Add to that a shared local data directory, and a project's session can seamlessly continue across all three ends.
Of course there's a cost: core can't be referenced as a library by anyone else, and the build chain becomes considerably more complex. This is trading reusability for consistency.
What makes it unique? Most multi-entry-point products love to tout the elegant decoupling of "a core library plus multiple shells." I went the opposite way, admitting that this thing is still growing wildly, and deliberately chose tight coupling in exchange for the peace of mind that the three ends are guaranteed identical.
2. Tailor-made for DeepSeek: not a "model-agnostic" general agent
Many agent frameworks love to emphasize one phrase: model-agnostic. Meaning you can swap in another model and it'll still run. I didn't chase that. This set of governance logic is tuned to DeepSeek's temperament.
For context compression, a general framework just follows textbook ratios. But DeepSeek has its own temperament: there's a fixed deviation between locally estimated tokens and real tokens, and it has implicit prefix caching — a cache hit saves a big chunk. What happens if you go by general assumptions? Either compression is always half a beat too slow, thinking it's safe when it's about to overflow, and you end up relying on API errors as a safety net; or right when you should be reaping the cache dividend, a compression blows the cache wide open.
So I did two things: use each round's real token usage to calibrate the local estimate; and let the timing of compression take its cues from the cache hit rate — if the hit rate is high, delay compression and keep reaping the dividend; if it's low, compress earlier.
What did I gain? Long tasks run stably, and tokens are spent where they count.
The cost is also clear: swap in another model, and you have to re-learn its temperament and re-tune everything. This set of logic is pretty tightly bound to DeepSeek.
What makes it unique? Others all aim for the flexibility of "it'll run with another model." I insist on being responsible only for DeepSeek. Trading generality for specificity — for a local tool, I think it's worth it.
3. Don't trust its self-reporting: make "preventing the model from being stupid" a first-class citizen
After dealing with models long enough, you recognize a fact: it will lie to you, and it will be stupid.
It runs a command that clearly reports an error, but it may selectively ignore it and come back telling you "it succeeded." It can get stuck in a loop, bouncing back and forth on the same call, burning through tokens without stopping.
My philosophy is: don't expect the model to be honest on its own; the engineering layer must provide a safety net.
That's why the tool protocol has a field called verifyResult — the tool's underlying layer analyzes its own output and makes a hard judgment of success or failure. Once it judges failure, I forcibly insert a line into the result fed to the model: "System judgment: execution failed. Don't be blindly optimistic; honestly look at the error." Add to that infinite-loop circuit breakers, round self-convergence, and a hard upper limit — all following the same approach.
What did I gain? Automation that doesn't need a human watching over it, so you can actually dare to let go. The most common types of model stupidity are hard-blocked at the protocol layer.
The cost is: every tool needs its own judgment logic written, and the maintenance cost isn't low; hard-coded judgments occasionally struggle at the boundary — whether a warning counts as failure requires careful consideration.
What makes it unique? In most frameworks, a tool is just an execution function — run it and you're done; whether you trust the model is up to you. I elevated "preventing model mistakes" to the protocol layer, making it a first-class citizen that must be thought through when defining every tool.
4. Humans and models are two different audiences: don't use one output to please neither
This is the one I want to talk about most. After a tool runs, who is the output for?
The answer is: for both the human and the model. But what the two need is completely different. The most clichéd example: run npm install, and the terminal can spit out two thousand lines of download progress. The user wants to see that animation; the model only wants to know one sentence: "Installed 45 packages." Feed two thousand lines raw to the model, and its attention is drowned in noise; feed only a one-sentence conclusion, and the user doesn't see the process — it feels dry.
So the tool can split its output into two paths: one for the human — vivid, streaming, with animation; one for the model — clean, short, only the conclusion.
What did I gain? The model's mind stays clear, and the user still sees everything they should. The awkwardness of one output pleasing neither is gone.
The cost is: tools with noisy output need an extra layer of diversion written; for simple tools whose output is already clean, this layer is just extra baggage.
What makes it unique? Most agents dump everything into one pot — whatever they get is shown to the human and fed to the model, making do for both. I explicitly separated terminal presentation from context feeding.
5. Two boundaries: data safety for the cloud brain, and the restraint of single-user local
Finally, two boundaries — one about data, one about product.
The first, data. DeepSeek is a cloud model, which means every file the agent reads and every command result it runs must be sent out. So what happens when it reads your .env, your database password? My approach is sanitization: content containing secret keys is replaced with asterisks according to rules before being sent to the cloud. Locally, you use it as normal; the cloud only sees the redacted version.
To be honest, this treats the symptom, not the root cause. The data ultimately leaves your machine — it's not as thorough as a purely local model; the sanitization rules also need maintenance — missing a new type of secret key, or accidentally redacting normal content, are both possible pitfalls. But it at least pulls out the sharpest thorn in the form of "using a cloud brain for local work."
The second, product. From the very start, I defined its nature: a single-user local tool. No public network deployment, no multi-tenancy, no SaaS, and it doesn't carry the burden of OS-level sandboxing that server-side setups require. The HTTP service only opens on 127.0.0.1, serving as a local programmatic entry point.
Why this restraint? Product form determines the threshold. For a single-user local tool, "letting the user dare to let go and use it" is far more important than "multi-tenant isolation." All the complexity I saved, I poured into the agent's own experience — approval flows, sanitization, undo/rollback — all centered on one goal: "do you dare to let it work on its own?"
The cost is also laid out plainly: it naturally doesn't support team collaboration, and its security ceiling is at the single-machine level. This is an active trade-off, and also a real limitation. If you want to use it to build a team platform, better look elsewhere early.
Conclusion
After finishing this thing, I increasingly feel: for agents, the threshold isn't "can it run" — it's "after it runs, is it good to use, and do you dare to use it?"
Hook up an API, stuff a few tools into a prompt, and you can get it moving in half a day. But the context balloons and explodes, the model stares at an error and lies through its teeth, it reads a config file and sends your password to the cloud, it runs itself into an infinite loop burning tokens — these are the things that really take effort.
And filling these potholes, in the end, is all about making trade-offs: trading generality for specificity, reusability for consistency, development effort for reliability. Every design has its cost; I simply chose to honestly accept these costs in exchange for the thing I wanted most.
The project is open source: github.com/xknk/deepSeekCode. If you're interested in a local coding agent driven by a domestic model, or want to see how agentic architecture lands in engineering practice, welcome to take a look, and welcome to raise issues. If you find it a bit interesting, a star is the greatest encouragement to me.
Summary
- One brain for three ends: One engine inlined into three entry points, sacrificing reusability for guaranteed consistency across all three and seamless session continuation;
- Tailored for DeepSeek: Governance logic tuned to DeepSeek's real characteristics, sacrificing generality for stable long tasks and tokens spent where they count;
- Don't trust model self-reporting: Making hallucination prevention and infinite-loop prevention first-class citizens of the tool protocol, sacrificing development effort for the reliability to dare to let automation run unattended;
- Humans and models are two audiences: Tool output explicitly split into two paths, sacrificing the simplicity of simple tools for a clear-minded model and a satisfied user;
- Two boundaries: Data sanitization for the cloud brain (treats the symptom but pulls out the sharpest thorn), and product restraint as single-user local (doesn't carry server-side baggage, but also doesn't support team collaboration).