corecoder 0.8.0__tar.gz → 0.9.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {corecoder-0.8.0 → corecoder-0.9.0}/PKG-INFO +39 -21
- {corecoder-0.8.0 → corecoder-0.9.0}/README.md +36 -18
- {corecoder-0.8.0 → corecoder-0.9.0}/README_CN.md +36 -18
- {corecoder-0.8.0 → corecoder-0.9.0}/article/00-index.md +5 -5
- {corecoder-0.8.0 → corecoder-0.9.0}/article/00-index_EN.md +5 -5
- {corecoder-0.8.0 → corecoder-0.9.0}/article/03-llm-and-cost.md +5 -3
- {corecoder-0.8.0 → corecoder-0.9.0}/article/03-llm-and-cost_EN.md +5 -3
- {corecoder-0.8.0 → corecoder-0.9.0}/article/05-parallel-and-subagents.md +18 -27
- {corecoder-0.8.0 → corecoder-0.9.0}/article/05-parallel-and-subagents_EN.md +18 -27
- {corecoder-0.8.0 → corecoder-0.9.0}/article/06-session-and-cli.md +2 -1
- {corecoder-0.8.0 → corecoder-0.9.0}/article/06-session-and-cli_EN.md +2 -1
- {corecoder-0.8.0 → corecoder-0.9.0}/article/07-build-your-own.md +5 -5
- {corecoder-0.8.0 → corecoder-0.9.0}/article/07-build-your-own_EN.md +5 -5
- {corecoder-0.8.0 → corecoder-0.9.0}/article/08-extensibility.md +3 -3
- {corecoder-0.8.0 → corecoder-0.9.0}/article/08-extensibility_EN.md +3 -3
- corecoder-0.9.0/article/claude-code-source-analysis.md +332 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/__init__.py +1 -1
- {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/cli.py +35 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/llm.py +45 -13
- {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/mcp.py +41 -12
- {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/permissions.py +6 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/session.py +2 -2
- corecoder-0.9.0/corecoder/tools/agent.py +143 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/tools/todo.py +3 -3
- corecoder-0.9.0/examples/mcp_filesystem.json +8 -0
- corecoder-0.9.0/examples/mcp_filesystem.md +43 -0
- corecoder-0.9.0/examples/multi_agent_demo.py +56 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/pyproject.toml +4 -3
- {corecoder-0.8.0 → corecoder-0.9.0}/tests/conftest.py +7 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_core.py +271 -8
- corecoder-0.9.0/tests/test_live_smoke.py +52 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_mcp.py +81 -0
- corecoder-0.9.0/tests/test_multi_agent.py +193 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_session.py +20 -0
- corecoder-0.8.0/corecoder/tools/agent.py +0 -72
- {corecoder-0.8.0 → corecoder-0.9.0}/.github/workflows/ci.yml +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/.github/workflows/publish.yml +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/.gitignore +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/LICENSE +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/article/01-the-loop.md +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/article/01-the-loop_EN.md +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/article/02-tools.md +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/article/02-tools_EN.md +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/article/04-context.md +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/article/04-context_EN.md +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/assets/demo-plan-hooks.gif +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/assets/demo.png +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/assets/demo_en.png +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/__main__.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/agent.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/checkpoints.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/config.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/context.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/demo.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/hooks.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/prompt.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/shell.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/tools/__init__.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/tools/base.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/tools/bash.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/tools/edit.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/tools/glob_tool.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/tools/grep.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/tools/read.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/tools/write.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/examples/plan_hooks_demo.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/tests/__init__.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_checkpoints.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_demo.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_hooks.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_litellm.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_permissions.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_plan_mode.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_repl.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_safety_matrix.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_shell.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_slash_commands.py +0 -0
- {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_tools.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: corecoder
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Minimal AI coding agent (2,
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: Minimal AI coding agent (2,908 lines of Python) inspired by Claude Code. Works with any LLM. (formerly NanoCoder)
|
|
5
5
|
Project-URL: Homepage, https://github.com/he-yufeng/CoreCoder
|
|
6
6
|
Project-URL: Repository, https://github.com/he-yufeng/CoreCoder
|
|
7
7
|
Project-URL: Issues, https://github.com/he-yufeng/CoreCoder/issues
|
|
@@ -37,7 +37,7 @@ Description-Content-Type: text/markdown
|
|
|
37
37
|
|
|
38
38
|
# CoreCoder
|
|
39
39
|
|
|
40
|
-
**The nanoGPT of coding agents. A 1.
|
|
40
|
+
**The nanoGPT of coding agents. A 1.4k-line engine inside 2,908 readable lines of pure Python: understand how a coding agent actually works, then fork your own.**
|
|
41
41
|
|
|
42
42
|
*learn from it · fork it · ship something better*
|
|
43
43
|
|
|
@@ -47,7 +47,7 @@ Description-Content-Type: text/markdown
|
|
|
47
47
|
[](https://python.org)
|
|
48
48
|
[](LICENSE)
|
|
49
49
|
[](https://github.com/he-yufeng/CoreCoder/actions)
|
|
50
|
-
[](article/00-index_EN.md)
|
|
51
51
|
[](article/00-index_EN.md)
|
|
52
52
|
|
|
53
53
|
</div>
|
|
@@ -60,7 +60,7 @@ Description-Content-Type: text/markdown
|
|
|
60
60
|
|
|
61
61
|
| | CoreCoder | Claude Code | aider | nanoGPT |
|
|
62
62
|
|---|---|---|---|---|
|
|
63
|
-
| Lines of code | ~1,
|
|
63
|
+
| Lines of code | ~1,405 engine / 2,908 total | hundreds of thousands (closed) | tens of thousands of Python | ~600 (two files) |
|
|
64
64
|
| Time to read it all | one afternoon | can't (closed) | a few days of slogging | one afternoon |
|
|
65
65
|
| Breakpoint, change, rerun? | yes, every line | no | yes, but there's a lot | yes |
|
|
66
66
|
| What it's for | understand one, then fork your own | production coding assistant | terminal pair-programming | minimal GPT for teaching |
|
|
@@ -71,9 +71,9 @@ The nanoGPT column is there as a reference point: minimal, readable, but it teac
|
|
|
71
71
|
|
|
72
72
|
I've always felt coding agents get talked about as if they were arcane. Strip a tool like Claude Code or Cursor all the way down and the core is a `while` loop wrapped around a large model, plus seven or eight tools that let it actually do things. The hard part was never the loop; it's everything the loop has to cope with once it meets the real world. CoreCoder is the minimal version that writes that core out honestly.
|
|
73
73
|
|
|
74
|
-
The engine (loop, model interface, context, tools, sessions) is 1,
|
|
74
|
+
The engine (loop, model interface, context, tools, sessions) is 1,405 lines once you drop blank lines and comments. Counting the outer CLI, config and packaging too, the whole package is 25 files: 2,908 physical lines, 2,364 net, every one short enough to read in a single sitting. The growth since the original 1,161-line snapshot went into visible features: plan mode, hooks and checkpoints, each documented below.
|
|
75
75
|
|
|
76
|
-
And it really runs: reads and writes files, executes shell, spawns sub-agents, compacts context in three tiers, and tells you the tokens and dollars a run burned whenever you ask. Anything that would mutate your disk or run a command stops for your consent first.
|
|
76
|
+
And it really runs: reads and writes files, executes shell, spawns sub-agents, compacts context in three tiers, and tells you the tokens and dollars a run burned whenever you ask. Anything that would mutate your disk or run a command stops for your consent first. The suite has 242 offline tests and an opt-in live smoke test. But the point of it running isn't to become your daily driver. It runs so the walkthrough can't lie: a reference that shows how an agent works has to actually work.
|
|
77
77
|
|
|
78
78
|
The code came out of a public teardown: open analyses have already exposed a lot of the load-bearing architecture inside production agents like Claude Code. I took the most essential layer and rewrote it honestly, in as little code as I could. So reading CoreCoder is roughly like reading a runnable, annotated take on how that kind of agent works, except it's only a minimal reimplementation, sitting right there on your machine for you to take apart and change.
|
|
79
79
|
|
|
@@ -107,7 +107,9 @@ Give it a model and a key and it goes. It speaks the OpenAI-compatible API by de
|
|
|
107
107
|
| OmniRoute | `OPENAI_API_KEY=your-key OPENAI_BASE_URL=http://localhost:20128/v1 CORECODER_MODEL=auto` |
|
|
108
108
|
| Local Ollama | `OPENAI_API_KEY=ollama OPENAI_BASE_URL=http://localhost:11434/v1 CORECODER_MODEL=qwen2.5-coder` |
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
On a 400 that names `temperature`, CoreCoder retries without it so the provider can apply its supported default. This includes Kimi's unquoted rejection; no `CORECODER_TEMPERATURE` override is needed.
|
|
111
|
+
|
|
112
|
+
Kimi, Qwen and the like are the same two variables; for providers that don't even offer an OpenAI-compatible endpoint, the optional LiteLLM backend (`pip install "corecoder[litellm]"`) routes to a hundred-plus of them. The third essay goes into this in detail. Thinking models are first-class too: deepseek-reasoner, kimi-k3 and friends stream their chain-of-thought, and CoreCoder shows it dimmed as it works. Plain answers keep reasoning out of the conversation history, while Kimi and DeepSeek tool-call turns carry `reasoning_content` into the next request as their thinking protocols require. The key can be `export`ed directly or dropped into a `.env` at the project root, which is loaded on startup. Then:
|
|
111
113
|
|
|
112
114
|
Smoke-tested end to end (read the file, edit it, run it, report back) against DeepSeek, Qwen3 and Kimi K2 via a single OpenRouter-compatible endpoint; each completed the full loop. One note for one-shot scripts: `-p` refuses mutating tools unless you pass `--yes`, by design.
|
|
113
115
|
|
|
@@ -123,15 +125,15 @@ Laid out flat, the whole project is this big. Skim it before you clone and you'l
|
|
|
123
125
|
```
|
|
124
126
|
corecoder/
|
|
125
127
|
├── agent.py agent loop + parallel tool exec 240 lines ← start here
|
|
126
|
-
├── llm.py streaming client + retry + cost
|
|
128
|
+
├── llm.py streaming client + retry + cost 364 lines
|
|
127
129
|
├── context.py three-tier context compaction 220 lines
|
|
128
130
|
├── session.py save / resume + path-traversal guard 97 lines
|
|
129
|
-
├── permissions.py consent for mutating tools
|
|
131
|
+
├── permissions.py consent for mutating tools 81 lines
|
|
130
132
|
├── hooks.py Pre/PostToolUse shell hooks 87 lines
|
|
131
133
|
├── shell.py POSIX shell routing (Git Bash on Windows) 61 lines
|
|
132
|
-
├── mcp.py MCP stdio client for external tools
|
|
134
|
+
├── mcp.py MCP stdio client for external tools 237 lines
|
|
133
135
|
├── prompt.py system prompt 41 lines
|
|
134
|
-
├── cli.py REPL + slash commands + one-shot
|
|
136
|
+
├── cli.py REPL + slash commands + one-shot 393 lines
|
|
135
137
|
├── config.py env-var config 55 lines
|
|
136
138
|
├── checkpoints.py /undo snapshot and restore 93 lines
|
|
137
139
|
├── demo.py offline end-to-end demo 101 lines
|
|
@@ -143,10 +145,11 @@ corecoder/
|
|
|
143
145
|
├── read.py file read 56 lines
|
|
144
146
|
├── write.py file write 46 lines
|
|
145
147
|
├── todo.py agent-maintained task checklist 79 lines
|
|
146
|
-
├── agent.py sub-agent
|
|
148
|
+
├── agent.py sub-agent teams + isolation 143 lines
|
|
147
149
|
└── base.py tool base class 32 lines
|
|
148
150
|
examples/
|
|
149
|
-
|
|
151
|
+
├── plan_hooks_demo.py offline plan mode + hooks demo (no API key)
|
|
152
|
+
└── multi_agent_demo.py offline three-role parallel demo (no API key)
|
|
150
153
|
```
|
|
151
154
|
|
|
152
155
|
Eight tools: `bash`, `read_file`, `write_file`, `edit_file`, `glob`, `grep`, `todo_write` (a task checklist the agent maintains for itself), and `agent` (which spawns a sub-agent). Everything else is the CLI shell, config, and packaging wrapped around that engine core. If `~/.corecoder/mcp.json` exists, its MCP servers join the eight as extra `mcp__*` tools; the MCP section below covers it.
|
|
@@ -180,6 +183,20 @@ Three decisions are worth a closer look, because they're the kind of call you ca
|
|
|
180
183
|
|
|
181
184
|
**You constrain a sub-agent by withholding the tool, not by writing rules and hoping it obeys.** A spawned sub-agent gets an isolated context and its own separate history, with a toolset exactly one item shorter than the parent's: the `agent` tool itself, so it can't recursively spawn more sub-agents. Handing it one fewer tool is cleaner than legislating a rule after the fact. It also reuses the parent's model connection (its spend folded into the same running total), truncates its output once it runs past 5,000 characters down to just the opening, and runs on a shorter round limit than the parent. The same restraint, end to end.
|
|
182
185
|
|
|
186
|
+
### Multi-agent, without an orchestration framework
|
|
187
|
+
|
|
188
|
+
The same `agent` tool now fans out a small team. Give each worker a name and an independent task; it gets a fresh message history and an empty checklist, while permission and hooks stay on the parent's contract:
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
report = agent_tool.execute(tasks=[
|
|
192
|
+
{"name": "researcher", "task": "Map the parser and its failure modes"},
|
|
193
|
+
{"name": "implementer", "task": "Propose the smallest fix"},
|
|
194
|
+
{"name": "reviewer", "task": "Attack the proposed fix with edge cases"},
|
|
195
|
+
])
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
One to eight workers run up to four at a time, each through the normal agent loop. Reports come back in input order, not completion order, and one worker's exception does not cancel its siblings. Shared token accounting and interactive permission prompts are serialized, so concurrency doesn't lose cost or interleave consent. There is no scheduler, shared message bus, recursive spawning, or second agent loop. `examples/multi_agent_demo.py` runs the researcher / implementer / reviewer pattern offline with scripted findings; no API key and no model-quality claim.
|
|
199
|
+
|
|
183
200
|
Every one of these *whys* is traced down to the actual lines of code in the series below.
|
|
184
201
|
|
|
185
202
|
## The source-reading series · 8 bilingual essays
|
|
@@ -200,7 +217,7 @@ I also wrote a bilingual source-reading series, one intro plus eight parts, each
|
|
|
200
217
|
|
|
201
218
|
Once you understand it, the natural next step is to fork. Getting started doesn't take much:
|
|
202
219
|
|
|
203
|
-
- **Swap in a model you actually use.** It's the two env vars from above; `llm.py` (
|
|
220
|
+
- **Swap in a model you actually use.** It's the two env vars from above; `llm.py` (364 lines) is the entry point for all provider adaptation.
|
|
204
221
|
- **Add a tool of your own.** Write a new file against the tool base class in `tools/base.py` (32 lines): run tests, fetch a page, call an LSP, whatever. The end of the second essay walks you through your first one by hand.
|
|
205
222
|
- **Rewrite the system prompt.** `prompt.py` is all of 41 lines; change one line and you'll watch the agent's temperament shift. It's the cheapest "change one thing, see a result" in the whole project.
|
|
206
223
|
- **Import it as a library.** The top level exports `Agent`, `LLM`, and `Config`, ready to embed in your own program:
|
|
@@ -216,7 +233,7 @@ Going deeper, the directions are out in the open too. None of the following is i
|
|
|
216
233
|
|
|
217
234
|
- **The dangerous-command blocking in bash is just a regex blacklist.** It guards against slips, not a security sandbox. Facing untrusted input means reaching for seccomp or container isolation. This is the hardest of the four; it goes all the way down to the syscall and isolation layer.
|
|
218
235
|
- **Retry is only exponential backoff.** No fallback model, no hard dollar budget. Follow `llm.py` down and add a fallback model chain plus a stop-on-over-budget gate; the change stays mostly inside that one file.
|
|
219
|
-
- **
|
|
236
|
+
- **Agent teams join before returning.** Named workers already run concurrently with isolated contexts; asynchronous background jobs, streaming progress and role-specific tool presets remain open extension points rather than another orchestration layer to add by default.
|
|
220
237
|
- **No RAG, and the MCP client speaks tools only.** Retrieval-based code location for big repos is still open, and `mcp.py` leaves resources and prompts unimplemented on purpose. Either one is a real way to grow from a minimal core into your own stronger agent.
|
|
221
238
|
|
|
222
239
|
The README only points; the seventh essay picks up the code details for each. Pick one and start; that's the whole reason the core is kept this small.
|
|
@@ -233,10 +250,11 @@ Inside the REPL, `/help` lists everything; these are the ones you'll reach for:
|
|
|
233
250
|
/undo revert the most recent file change
|
|
234
251
|
/plan toggle plan mode (read-only, then a plan to approve)
|
|
235
252
|
/save /sessions save / list sessions
|
|
253
|
+
/mcp MCP server status; /mcp reconnect <name> revives a dead one
|
|
236
254
|
quit / exit exit (Ctrl+C cancels the current round)
|
|
237
255
|
```
|
|
238
256
|
|
|
239
|
-
Session IDs are sanitized to safe characters before they become filenames, every archive lands under `~/.corecoder/sessions`, and a malicious session name can't traverse out. Undo history persists the same way: checkpoints land in `~/.corecoder/checkpoints.json`, so `/undo` still reaches back after a restart.
|
|
257
|
+
Session IDs are sanitized to safe characters before they become filenames, every archive lands under `~/.corecoder/sessions`, and a malicious session name can't traverse out. Undo history persists the same way: checkpoints land in `~/.corecoder/checkpoints.json`, so `/undo` still reaches back after a restart. The cost estimate behind `/tokens` reads a built-in price table, and `~/.corecoder/pricing.json` overrides any entry in it, so a new model or a moved price lands without waiting for a release.
|
|
240
258
|
|
|
241
259
|
## Permissions
|
|
242
260
|
|
|
@@ -300,7 +318,7 @@ Drop a `mcp.json` under `~/.corecoder` and tools from any MCP server join the ag
|
|
|
300
318
|
}
|
|
301
319
|
```
|
|
302
320
|
|
|
303
|
-
Each configured server starts as a subprocess at launch, handshakes, and lists its tools; every one is registered as `mcp__<server>__<tool>`, so hook matchers and the consent gate treat it exactly like a built-in. MCP tools stay out of the read-only set, meaning the agent asks before running one. The handshake gets fifteen seconds, a call gets sixty, and a server that dies or never answers fails that one call as an ordinary tool result instead of killing the loop. The client speaks the tools slice of the protocol (initialize, tools/list, tools/call) and nothing else, which keeps the whole thing inside `mcp.py` at about
|
|
321
|
+
Each configured server starts as a subprocess at launch, handshakes, and lists its tools; every one is registered as `mcp__<server>__<tool>`, so hook matchers and the consent gate treat it exactly like a built-in. MCP tools stay out of the read-only set, meaning the agent asks before running one. The handshake gets fifteen seconds, a call gets sixty, and a server that dies or never answers fails that one call as an ordinary tool result instead of killing the loop. The REPL's `/mcp` lists every server with its state, and `/mcp reconnect <name>` starts a dead one fresh: new process, new handshake, and the tools the agent already holds work again with no restart. The client speaks the tools slice of the protocol (initialize, tools/list, tools/call) and nothing else, which keeps the whole thing inside `mcp.py` at about 240 lines. With no `mcp.json` there is no MCP and nothing changes. A runnable starter config lives in [`examples/mcp_filesystem.md`](examples/mcp_filesystem.md): the reference filesystem server, scoped to one directory, behind the same consent gate.
|
|
304
322
|
|
|
305
323
|
## Related Projects
|
|
306
324
|
|
|
@@ -314,10 +332,10 @@ If working through CoreCoder was useful, here are a few other tools I've built a
|
|
|
314
332
|
|
|
315
333
|
## Contributing / License
|
|
316
334
|
|
|
317
|
-
Before you send anything, run `pytest tests/ -q` (
|
|
335
|
+
Before you send anything, run `pytest tests/ -q` (240 tests), `ruff check`, and `compileall`, and make sure they're green. MIT licensed: fork it, learn from it, ship something better. A mention of this project is appreciated.
|
|
318
336
|
|
|
319
337
|
---
|
|
320
338
|
|
|
321
|
-
By [Yufeng He](https://github.com/he-yufeng), formerly at Moonshot AI (Kimi). I earlier wrote a fairly complete [Claude Code source analysis](
|
|
339
|
+
By [Yufeng He](https://github.com/he-yufeng), formerly at Moonshot AI (Kimi). I earlier wrote a fairly complete [Claude Code source analysis](article/claude-code-source-analysis.md) (written in Chinese); this project is its hands-on counterpart: that one walks you through reading it, this one through rebuilding it.
|
|
322
340
|
|
|
323
341
|
> CoreCoder was formerly named NanoCoder; it was renamed to avoid confusion with [Nano-Collective/nanocoder](https://github.com/Nano-Collective/nanocoder), and old links redirect here automatically.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# CoreCoder
|
|
4
4
|
|
|
5
|
-
**The nanoGPT of coding agents. A 1.
|
|
5
|
+
**The nanoGPT of coding agents. A 1.4k-line engine inside 2,908 readable lines of pure Python: understand how a coding agent actually works, then fork your own.**
|
|
6
6
|
|
|
7
7
|
*learn from it · fork it · ship something better*
|
|
8
8
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
[](https://python.org)
|
|
13
13
|
[](LICENSE)
|
|
14
14
|
[](https://github.com/he-yufeng/CoreCoder/actions)
|
|
15
|
-
[](article/00-index_EN.md)
|
|
16
16
|
[](article/00-index_EN.md)
|
|
17
17
|
|
|
18
18
|
</div>
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
| | CoreCoder | Claude Code | aider | nanoGPT |
|
|
27
27
|
|---|---|---|---|---|
|
|
28
|
-
| Lines of code | ~1,
|
|
28
|
+
| Lines of code | ~1,405 engine / 2,908 total | hundreds of thousands (closed) | tens of thousands of Python | ~600 (two files) |
|
|
29
29
|
| Time to read it all | one afternoon | can't (closed) | a few days of slogging | one afternoon |
|
|
30
30
|
| Breakpoint, change, rerun? | yes, every line | no | yes, but there's a lot | yes |
|
|
31
31
|
| What it's for | understand one, then fork your own | production coding assistant | terminal pair-programming | minimal GPT for teaching |
|
|
@@ -36,9 +36,9 @@ The nanoGPT column is there as a reference point: minimal, readable, but it teac
|
|
|
36
36
|
|
|
37
37
|
I've always felt coding agents get talked about as if they were arcane. Strip a tool like Claude Code or Cursor all the way down and the core is a `while` loop wrapped around a large model, plus seven or eight tools that let it actually do things. The hard part was never the loop; it's everything the loop has to cope with once it meets the real world. CoreCoder is the minimal version that writes that core out honestly.
|
|
38
38
|
|
|
39
|
-
The engine (loop, model interface, context, tools, sessions) is 1,
|
|
39
|
+
The engine (loop, model interface, context, tools, sessions) is 1,405 lines once you drop blank lines and comments. Counting the outer CLI, config and packaging too, the whole package is 25 files: 2,908 physical lines, 2,364 net, every one short enough to read in a single sitting. The growth since the original 1,161-line snapshot went into visible features: plan mode, hooks and checkpoints, each documented below.
|
|
40
40
|
|
|
41
|
-
And it really runs: reads and writes files, executes shell, spawns sub-agents, compacts context in three tiers, and tells you the tokens and dollars a run burned whenever you ask. Anything that would mutate your disk or run a command stops for your consent first.
|
|
41
|
+
And it really runs: reads and writes files, executes shell, spawns sub-agents, compacts context in three tiers, and tells you the tokens and dollars a run burned whenever you ask. Anything that would mutate your disk or run a command stops for your consent first. The suite has 242 offline tests and an opt-in live smoke test. But the point of it running isn't to become your daily driver. It runs so the walkthrough can't lie: a reference that shows how an agent works has to actually work.
|
|
42
42
|
|
|
43
43
|
The code came out of a public teardown: open analyses have already exposed a lot of the load-bearing architecture inside production agents like Claude Code. I took the most essential layer and rewrote it honestly, in as little code as I could. So reading CoreCoder is roughly like reading a runnable, annotated take on how that kind of agent works, except it's only a minimal reimplementation, sitting right there on your machine for you to take apart and change.
|
|
44
44
|
|
|
@@ -72,7 +72,9 @@ Give it a model and a key and it goes. It speaks the OpenAI-compatible API by de
|
|
|
72
72
|
| OmniRoute | `OPENAI_API_KEY=your-key OPENAI_BASE_URL=http://localhost:20128/v1 CORECODER_MODEL=auto` |
|
|
73
73
|
| Local Ollama | `OPENAI_API_KEY=ollama OPENAI_BASE_URL=http://localhost:11434/v1 CORECODER_MODEL=qwen2.5-coder` |
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
On a 400 that names `temperature`, CoreCoder retries without it so the provider can apply its supported default. This includes Kimi's unquoted rejection; no `CORECODER_TEMPERATURE` override is needed.
|
|
76
|
+
|
|
77
|
+
Kimi, Qwen and the like are the same two variables; for providers that don't even offer an OpenAI-compatible endpoint, the optional LiteLLM backend (`pip install "corecoder[litellm]"`) routes to a hundred-plus of them. The third essay goes into this in detail. Thinking models are first-class too: deepseek-reasoner, kimi-k3 and friends stream their chain-of-thought, and CoreCoder shows it dimmed as it works. Plain answers keep reasoning out of the conversation history, while Kimi and DeepSeek tool-call turns carry `reasoning_content` into the next request as their thinking protocols require. The key can be `export`ed directly or dropped into a `.env` at the project root, which is loaded on startup. Then:
|
|
76
78
|
|
|
77
79
|
Smoke-tested end to end (read the file, edit it, run it, report back) against DeepSeek, Qwen3 and Kimi K2 via a single OpenRouter-compatible endpoint; each completed the full loop. One note for one-shot scripts: `-p` refuses mutating tools unless you pass `--yes`, by design.
|
|
78
80
|
|
|
@@ -88,15 +90,15 @@ Laid out flat, the whole project is this big. Skim it before you clone and you'l
|
|
|
88
90
|
```
|
|
89
91
|
corecoder/
|
|
90
92
|
├── agent.py agent loop + parallel tool exec 240 lines ← start here
|
|
91
|
-
├── llm.py streaming client + retry + cost
|
|
93
|
+
├── llm.py streaming client + retry + cost 364 lines
|
|
92
94
|
├── context.py three-tier context compaction 220 lines
|
|
93
95
|
├── session.py save / resume + path-traversal guard 97 lines
|
|
94
|
-
├── permissions.py consent for mutating tools
|
|
96
|
+
├── permissions.py consent for mutating tools 81 lines
|
|
95
97
|
├── hooks.py Pre/PostToolUse shell hooks 87 lines
|
|
96
98
|
├── shell.py POSIX shell routing (Git Bash on Windows) 61 lines
|
|
97
|
-
├── mcp.py MCP stdio client for external tools
|
|
99
|
+
├── mcp.py MCP stdio client for external tools 237 lines
|
|
98
100
|
├── prompt.py system prompt 41 lines
|
|
99
|
-
├── cli.py REPL + slash commands + one-shot
|
|
101
|
+
├── cli.py REPL + slash commands + one-shot 393 lines
|
|
100
102
|
├── config.py env-var config 55 lines
|
|
101
103
|
├── checkpoints.py /undo snapshot and restore 93 lines
|
|
102
104
|
├── demo.py offline end-to-end demo 101 lines
|
|
@@ -108,10 +110,11 @@ corecoder/
|
|
|
108
110
|
├── read.py file read 56 lines
|
|
109
111
|
├── write.py file write 46 lines
|
|
110
112
|
├── todo.py agent-maintained task checklist 79 lines
|
|
111
|
-
├── agent.py sub-agent
|
|
113
|
+
├── agent.py sub-agent teams + isolation 143 lines
|
|
112
114
|
└── base.py tool base class 32 lines
|
|
113
115
|
examples/
|
|
114
|
-
|
|
116
|
+
├── plan_hooks_demo.py offline plan mode + hooks demo (no API key)
|
|
117
|
+
└── multi_agent_demo.py offline three-role parallel demo (no API key)
|
|
115
118
|
```
|
|
116
119
|
|
|
117
120
|
Eight tools: `bash`, `read_file`, `write_file`, `edit_file`, `glob`, `grep`, `todo_write` (a task checklist the agent maintains for itself), and `agent` (which spawns a sub-agent). Everything else is the CLI shell, config, and packaging wrapped around that engine core. If `~/.corecoder/mcp.json` exists, its MCP servers join the eight as extra `mcp__*` tools; the MCP section below covers it.
|
|
@@ -145,6 +148,20 @@ Three decisions are worth a closer look, because they're the kind of call you ca
|
|
|
145
148
|
|
|
146
149
|
**You constrain a sub-agent by withholding the tool, not by writing rules and hoping it obeys.** A spawned sub-agent gets an isolated context and its own separate history, with a toolset exactly one item shorter than the parent's: the `agent` tool itself, so it can't recursively spawn more sub-agents. Handing it one fewer tool is cleaner than legislating a rule after the fact. It also reuses the parent's model connection (its spend folded into the same running total), truncates its output once it runs past 5,000 characters down to just the opening, and runs on a shorter round limit than the parent. The same restraint, end to end.
|
|
147
150
|
|
|
151
|
+
### Multi-agent, without an orchestration framework
|
|
152
|
+
|
|
153
|
+
The same `agent` tool now fans out a small team. Give each worker a name and an independent task; it gets a fresh message history and an empty checklist, while permission and hooks stay on the parent's contract:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
report = agent_tool.execute(tasks=[
|
|
157
|
+
{"name": "researcher", "task": "Map the parser and its failure modes"},
|
|
158
|
+
{"name": "implementer", "task": "Propose the smallest fix"},
|
|
159
|
+
{"name": "reviewer", "task": "Attack the proposed fix with edge cases"},
|
|
160
|
+
])
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
One to eight workers run up to four at a time, each through the normal agent loop. Reports come back in input order, not completion order, and one worker's exception does not cancel its siblings. Shared token accounting and interactive permission prompts are serialized, so concurrency doesn't lose cost or interleave consent. There is no scheduler, shared message bus, recursive spawning, or second agent loop. `examples/multi_agent_demo.py` runs the researcher / implementer / reviewer pattern offline with scripted findings; no API key and no model-quality claim.
|
|
164
|
+
|
|
148
165
|
Every one of these *whys* is traced down to the actual lines of code in the series below.
|
|
149
166
|
|
|
150
167
|
## The source-reading series · 8 bilingual essays
|
|
@@ -165,7 +182,7 @@ I also wrote a bilingual source-reading series, one intro plus eight parts, each
|
|
|
165
182
|
|
|
166
183
|
Once you understand it, the natural next step is to fork. Getting started doesn't take much:
|
|
167
184
|
|
|
168
|
-
- **Swap in a model you actually use.** It's the two env vars from above; `llm.py` (
|
|
185
|
+
- **Swap in a model you actually use.** It's the two env vars from above; `llm.py` (364 lines) is the entry point for all provider adaptation.
|
|
169
186
|
- **Add a tool of your own.** Write a new file against the tool base class in `tools/base.py` (32 lines): run tests, fetch a page, call an LSP, whatever. The end of the second essay walks you through your first one by hand.
|
|
170
187
|
- **Rewrite the system prompt.** `prompt.py` is all of 41 lines; change one line and you'll watch the agent's temperament shift. It's the cheapest "change one thing, see a result" in the whole project.
|
|
171
188
|
- **Import it as a library.** The top level exports `Agent`, `LLM`, and `Config`, ready to embed in your own program:
|
|
@@ -181,7 +198,7 @@ Going deeper, the directions are out in the open too. None of the following is i
|
|
|
181
198
|
|
|
182
199
|
- **The dangerous-command blocking in bash is just a regex blacklist.** It guards against slips, not a security sandbox. Facing untrusted input means reaching for seccomp or container isolation. This is the hardest of the four; it goes all the way down to the syscall and isolation layer.
|
|
183
200
|
- **Retry is only exponential backoff.** No fallback model, no hard dollar budget. Follow `llm.py` down and add a fallback model chain plus a stop-on-over-budget gate; the change stays mostly inside that one file.
|
|
184
|
-
- **
|
|
201
|
+
- **Agent teams join before returning.** Named workers already run concurrently with isolated contexts; asynchronous background jobs, streaming progress and role-specific tool presets remain open extension points rather than another orchestration layer to add by default.
|
|
185
202
|
- **No RAG, and the MCP client speaks tools only.** Retrieval-based code location for big repos is still open, and `mcp.py` leaves resources and prompts unimplemented on purpose. Either one is a real way to grow from a minimal core into your own stronger agent.
|
|
186
203
|
|
|
187
204
|
The README only points; the seventh essay picks up the code details for each. Pick one and start; that's the whole reason the core is kept this small.
|
|
@@ -198,10 +215,11 @@ Inside the REPL, `/help` lists everything; these are the ones you'll reach for:
|
|
|
198
215
|
/undo revert the most recent file change
|
|
199
216
|
/plan toggle plan mode (read-only, then a plan to approve)
|
|
200
217
|
/save /sessions save / list sessions
|
|
218
|
+
/mcp MCP server status; /mcp reconnect <name> revives a dead one
|
|
201
219
|
quit / exit exit (Ctrl+C cancels the current round)
|
|
202
220
|
```
|
|
203
221
|
|
|
204
|
-
Session IDs are sanitized to safe characters before they become filenames, every archive lands under `~/.corecoder/sessions`, and a malicious session name can't traverse out. Undo history persists the same way: checkpoints land in `~/.corecoder/checkpoints.json`, so `/undo` still reaches back after a restart.
|
|
222
|
+
Session IDs are sanitized to safe characters before they become filenames, every archive lands under `~/.corecoder/sessions`, and a malicious session name can't traverse out. Undo history persists the same way: checkpoints land in `~/.corecoder/checkpoints.json`, so `/undo` still reaches back after a restart. The cost estimate behind `/tokens` reads a built-in price table, and `~/.corecoder/pricing.json` overrides any entry in it, so a new model or a moved price lands without waiting for a release.
|
|
205
223
|
|
|
206
224
|
## Permissions
|
|
207
225
|
|
|
@@ -265,7 +283,7 @@ Drop a `mcp.json` under `~/.corecoder` and tools from any MCP server join the ag
|
|
|
265
283
|
}
|
|
266
284
|
```
|
|
267
285
|
|
|
268
|
-
Each configured server starts as a subprocess at launch, handshakes, and lists its tools; every one is registered as `mcp__<server>__<tool>`, so hook matchers and the consent gate treat it exactly like a built-in. MCP tools stay out of the read-only set, meaning the agent asks before running one. The handshake gets fifteen seconds, a call gets sixty, and a server that dies or never answers fails that one call as an ordinary tool result instead of killing the loop. The client speaks the tools slice of the protocol (initialize, tools/list, tools/call) and nothing else, which keeps the whole thing inside `mcp.py` at about
|
|
286
|
+
Each configured server starts as a subprocess at launch, handshakes, and lists its tools; every one is registered as `mcp__<server>__<tool>`, so hook matchers and the consent gate treat it exactly like a built-in. MCP tools stay out of the read-only set, meaning the agent asks before running one. The handshake gets fifteen seconds, a call gets sixty, and a server that dies or never answers fails that one call as an ordinary tool result instead of killing the loop. The REPL's `/mcp` lists every server with its state, and `/mcp reconnect <name>` starts a dead one fresh: new process, new handshake, and the tools the agent already holds work again with no restart. The client speaks the tools slice of the protocol (initialize, tools/list, tools/call) and nothing else, which keeps the whole thing inside `mcp.py` at about 240 lines. With no `mcp.json` there is no MCP and nothing changes. A runnable starter config lives in [`examples/mcp_filesystem.md`](examples/mcp_filesystem.md): the reference filesystem server, scoped to one directory, behind the same consent gate.
|
|
269
287
|
|
|
270
288
|
## Related Projects
|
|
271
289
|
|
|
@@ -279,10 +297,10 @@ If working through CoreCoder was useful, here are a few other tools I've built a
|
|
|
279
297
|
|
|
280
298
|
## Contributing / License
|
|
281
299
|
|
|
282
|
-
Before you send anything, run `pytest tests/ -q` (
|
|
300
|
+
Before you send anything, run `pytest tests/ -q` (240 tests), `ruff check`, and `compileall`, and make sure they're green. MIT licensed: fork it, learn from it, ship something better. A mention of this project is appreciated.
|
|
283
301
|
|
|
284
302
|
---
|
|
285
303
|
|
|
286
|
-
By [Yufeng He](https://github.com/he-yufeng), formerly at Moonshot AI (Kimi). I earlier wrote a fairly complete [Claude Code source analysis](
|
|
304
|
+
By [Yufeng He](https://github.com/he-yufeng), formerly at Moonshot AI (Kimi). I earlier wrote a fairly complete [Claude Code source analysis](article/claude-code-source-analysis.md) (written in Chinese); this project is its hands-on counterpart: that one walks you through reading it, this one through rebuilding it.
|
|
287
305
|
|
|
288
306
|
> CoreCoder was formerly named NanoCoder; it was renamed to avoid confusion with [Nano-Collective/nanocoder](https://github.com/Nano-Collective/nanocoder), and old links redirect here automatically.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# CoreCoder
|
|
4
4
|
|
|
5
|
-
**编程 agent 里的 nanoGPT。1.
|
|
5
|
+
**编程 agent 里的 nanoGPT。1.4k 行引擎、整包 2908 行纯 Python 全部一口气可读,读懂一个 coding agent 到底怎么运作,再 fork 出你自己的。**
|
|
6
6
|
|
|
7
7
|
*learn from it · fork it · ship something better*
|
|
8
8
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
[](https://python.org)
|
|
13
13
|
[](LICENSE)
|
|
14
14
|
[](https://github.com/he-yufeng/CoreCoder/actions)
|
|
15
|
-
[](article/)
|
|
16
16
|
[](article/)
|
|
17
17
|
|
|
18
18
|
</div>
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
| | CoreCoder | Claude Code | aider | nanoGPT |
|
|
27
27
|
|---|---|---|---|---|
|
|
28
|
-
| 代码量 | 引擎约
|
|
28
|
+
| 代码量 | 引擎约 1405 行 / 整包 2908 行 | 几十万行(闭源) | 数万行 Python | 约 600 行(两个文件) |
|
|
29
29
|
| 读完要多久 | 一个下午 | 读不了(闭源) | 得啃几天 | 一个下午 |
|
|
30
30
|
| 能不能下断点改了再跑 | 能,每一行 | 不能 | 能,但量大 | 能 |
|
|
31
31
|
| 定位 | 读懂并 fork 出你自己的 agent | 生产级编程助手 | 终端结对编程 | 教学用最小 GPT |
|
|
@@ -36,9 +36,9 @@ nanoGPT 那一列是拿来对照的:它最小、可读,但教的是训一个
|
|
|
36
36
|
|
|
37
37
|
我一直觉得 coding agent 被讲得太玄了。把 Claude Code、Cursor 这类工具扒到底,核心是一个 while 循环套着一个大模型,外加七八个让它能真正动手的工具。难的从来不是这个循环,而是循环跑进真实世界以后要兜的那些底。CoreCoder 就是把这个核心老老实实写出来的最小版本。
|
|
38
38
|
|
|
39
|
-
引擎部分(循环、模型接口、上下文、工具、会话)去掉空行和注释是
|
|
39
|
+
引擎部分(循环、模型接口、上下文、工具、会话)去掉空行和注释是 1405 行。连最外层的 CLI、配置、打包一起算,整个包 25 个文件、物理 2908 行、净 2364 行,每个文件都短到能一口气读完。自 1161 行快照之后的增长都花在了看得见的功能上:plan mode、hooks、checkpoints,下文各有交代。
|
|
40
40
|
|
|
41
|
-
它真能跑:读写文件、执行 shell、派子 agent、分三层压上下文,还能随时把这趟烧掉的 token
|
|
41
|
+
它真能跑:读写文件、执行 shell、派子 agent、分三层压上下文,还能随时把这趟烧掉的 token 和美元数报给你。任何要动你磁盘、要跑命令的调用,都会先停下来等你点头。现在有 242 个离线测试,另有按需开启的真实 API 冒烟。但能跑不是为了劝你拿去日用,而是为了让这份「注释」不撒谎:一个解释 agent 怎么运作的范例,自己得真能运作。
|
|
42
42
|
|
|
43
43
|
代码来自一次公开拆解。公开的源码分析里,Claude Code 这类生产级 agent 暴露出不少关键架构,我挑出最核心的一层,用尽量少的代码诚实地复写了一遍。所以读 CoreCoder,约等于读一份基于公开源码分析的「可运行注释版」:讲的是这类 agent 的核心思路,而它本身只是最小复写,就摆在你机器上,随你拆、随你改。
|
|
44
44
|
|
|
@@ -72,7 +72,9 @@ pip install -e .
|
|
|
72
72
|
| OmniRoute | `OPENAI_API_KEY=your-key OPENAI_BASE_URL=http://localhost:20128/v1 CORECODER_MODEL=auto` |
|
|
73
73
|
| 本地 Ollama | `OPENAI_API_KEY=ollama OPENAI_BASE_URL=http://localhost:11434/v1 CORECODER_MODEL=qwen2.5-coder` |
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
如果服务用 400 错误指出 `temperature` 不受支持,CoreCoder 会移除这个参数重试,采用服务端支持的默认值。Kimi 不带引号的报错也能识别,无需额外设置 `CORECODER_TEMPERATURE`。
|
|
76
|
+
|
|
77
|
+
Kimi、Qwen 这些同样是改这两个变量;连 OpenAI 兼容接口都不给的 provider,装上可选的 LiteLLM 后端(`pip install "corecoder[litellm]"`)能路由一百多家。第三篇文章把这块讲得更细。思考模型也是一等公民:deepseek-reasoner、kimi-k3 这类模型的思考过程会实时流出来,CoreCoder 把它用暗色显示出来。普通回答不把思考过程放进对话历史;Kimi 和 DeepSeek 的工具调用轮则按 thinking 协议要求,把 `reasoning_content` 带进下一次请求。key 可以直接 `export`,也可以在项目根目录扔个 `.env`,启动时自动加载。然后:
|
|
76
78
|
|
|
77
79
|
端到端真机冒烟过三家(读文件、改代码、跑一次确认、自己报告):DeepSeek、Qwen3、Kimi K2,走同一个 OpenRouter 兼容端点,各自完整跑完全循环。写脚本用 one-shot 的留意:`-p` 默认拒绝一切改动类工具,要加 `--yes`,这是设计如此。
|
|
78
80
|
|
|
@@ -88,15 +90,15 @@ corecoder -p "给 parse_config() 加错误处理" # 一次性模式,干完
|
|
|
88
90
|
```
|
|
89
91
|
corecoder/
|
|
90
92
|
├── agent.py agent 主循环 + 并行工具执行 240 行 ← 从这里开始读
|
|
91
|
-
├── llm.py 流式客户端 + 重试 + 成本统计
|
|
93
|
+
├── llm.py 流式客户端 + 重试 + 成本统计 364 行
|
|
92
94
|
├── context.py 三层上下文压缩 220 行
|
|
93
95
|
├── session.py 会话存盘 / 续聊 + 路径穿越防护 97 行
|
|
94
|
-
├── permissions.py 改动类工具的用户授权
|
|
96
|
+
├── permissions.py 改动类工具的用户授权 81 行
|
|
95
97
|
├── hooks.py 工具调用前后的用户 shell 钩子 87 行
|
|
96
98
|
├── shell.py POSIX shell 路由(Windows 走 Git Bash) 61 行
|
|
97
|
-
├── mcp.py MCP stdio 客户端,接外部工具
|
|
99
|
+
├── mcp.py MCP stdio 客户端,接外部工具 237 行
|
|
98
100
|
├── prompt.py 系统提示词 41 行
|
|
99
|
-
├── cli.py REPL + 斜杠命令 + 一次性模式
|
|
101
|
+
├── cli.py REPL + 斜杠命令 + 一次性模式 393 行
|
|
100
102
|
├── config.py 环境变量配置 55 行
|
|
101
103
|
├── checkpoints.py /undo 快照与回滚 93 行
|
|
102
104
|
├── demo.py 离线端到端演示 101 行
|
|
@@ -108,10 +110,11 @@ corecoder/
|
|
|
108
110
|
├── read.py 文件读取 56 行
|
|
109
111
|
├── write.py 文件写入 46 行
|
|
110
112
|
├── todo.py agent 自维护的任务清单 79 行
|
|
111
|
-
├── agent.py 子 agent
|
|
113
|
+
├── agent.py 子 agent 团队与隔离 143 行
|
|
112
114
|
└── base.py 工具基类 32 行
|
|
113
115
|
examples/
|
|
114
|
-
|
|
116
|
+
├── plan_hooks_demo.py 离线 plan mode + hooks 演示(免 API key)
|
|
117
|
+
└── multi_agent_demo.py 离线三角色并行演示(免 API key)
|
|
115
118
|
```
|
|
116
119
|
|
|
117
120
|
八个工具:`bash`、`read_file`、`write_file`、`edit_file`、`glob`、`grep`、`todo_write`(agent 自己维护的任务清单)、`agent`(派子 agent)。其余都是包在引擎核心外面的 CLI 外壳、配置和打包。存在 `~/.corecoder/mcp.json` 时,里面的 MCP 服务器会以 `mcp__*` 工具的身份并进这八件里,下面有专门一节讲。
|
|
@@ -145,6 +148,20 @@ def chat(self, user_input):
|
|
|
145
148
|
|
|
146
149
|
**约束子 agent 能干什么,靠的是不给它那把工具,而不是写一堆规则求它听话。** 派出去的子 agent 拿到的是隔离的上下文、自己独立的一份历史,工具集只比父 agent 少一样:`agent` 工具本身,于是它没法再往下递归派子 agent。少给一件工具,比事后立一条规矩干净得多。它还复用父 agent 同一个模型连接(花销一并算进总账),输出一过 5000 字就截短、只留开头一段,轮次上限也压得比父 agent 更短。同一套克制,从头贯到尾。
|
|
147
150
|
|
|
151
|
+
### 多 agent,不另起一套编排框架
|
|
152
|
+
|
|
153
|
+
同一个 `agent` 工具现在能 fan-out 一个小团队。给每个 worker 一个名字和独立任务,它会拿到全新的消息历史和空清单;权限与 hooks 仍沿用父 agent 的契约:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
report = agent_tool.execute(tasks=[
|
|
157
|
+
{"name": "researcher", "task": "梳理 parser 及其失败模式"},
|
|
158
|
+
{"name": "implementer", "task": "提出最小修复方案"},
|
|
159
|
+
{"name": "reviewer", "task": "用边界情况攻击这个修复方案"},
|
|
160
|
+
])
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
1 到 8 个 worker、最多 4 个并发,每个仍走原来的 agent 循环。报告按输入顺序返回,不按完成顺序;单个 worker 出错不会取消其他 worker。共享的 token 记账和交互式授权提示都串行化,并发不会漏账,也不会把确认问题交错打印。没有调度器、共享消息总线、递归派生或第二个 agent 循环。`examples/multi_agent_demo.py` 用脚本化结论离线演示 researcher / implementer / reviewer,不需要 API key,也不拿它冒充真实模型质量验证。
|
|
164
|
+
|
|
148
165
|
每一个「为什么」,下面的文章系列都拆到了具体代码行。
|
|
149
166
|
|
|
150
167
|
## 配套源码导读 · 八篇双语
|
|
@@ -165,7 +182,7 @@ def chat(self, user_input):
|
|
|
165
182
|
|
|
166
183
|
读懂之后,最自然的下一步就是 fork。起手不用伤筋动骨:
|
|
167
184
|
|
|
168
|
-
- **换个你常用的模型。** 就是上面那两个环境变量,`llm.py`(
|
|
185
|
+
- **换个你常用的模型。** 就是上面那两个环境变量,`llm.py`(364 行)是所有 provider 适配的入口。
|
|
169
186
|
- **加一件你自己的工具。** 照 `tools/base.py`(32 行)的工具基类写个新文件,跑测试、抓网页、调 LSP 都行,第二篇文章末尾手把手带你写第一个。
|
|
170
187
|
- **改系统提示词。** `prompt.py` 才 41 行,改一句就能看到 agent 的脾气变了,是门槛最低的「改一处就有反馈」。
|
|
171
188
|
- **直接当库 import。** 顶层导出了 `Agent`、`LLM`、`Config`,能嵌进你自己的程序:
|
|
@@ -181,7 +198,7 @@ print(Agent(llm=llm).chat("找出项目里所有 TODO 注释并列出来"))
|
|
|
181
198
|
|
|
182
199
|
- **bash 的危险命令拦截只是正则黑名单。** 防手滑,不是安全沙箱。要面对不可信输入,就得上 seccomp 或容器隔离。这条最硬,要一路走到系统调用和隔离那一层。
|
|
183
200
|
- **重试只做了指数退避。** 没有 fallback 模型,也没有美元硬预算。顺着 `llm.py` 往下,加一条 fallback 模型链和超预算自动停的闸,改动基本就集中在这一个文件。
|
|
184
|
-
- **子 agent
|
|
201
|
+
- **子 agent 团队会在返回前汇合。** 命名 worker 已经能带着隔离上下文并发执行;异步后台任务、流式进度和角色专属工具预设仍是扩展点,不为了多 agent 就默认再加一层编排框架。
|
|
185
202
|
- **不做 RAG,MCP 客户端也只讲工具这一小片。** 给大仓加检索式代码定位还空着,`mcp.py` 也特意没实现 resources 和 prompts。随便挑一个,都是从最小核心往你自己的更强 agent 扩的真实方向。
|
|
186
203
|
|
|
187
204
|
README 只给方向,每条的代码细节第七篇接着讲。挑一个动手,就是把它做得更好的开始。
|
|
@@ -198,10 +215,11 @@ README 只给方向,每条的代码细节第七篇接着讲。挑一个动手
|
|
|
198
215
|
/undo 撤销最近一次文件改动
|
|
199
216
|
/plan 开关计划模式(只读摸底,再交出待批准的计划)
|
|
200
217
|
/save /sessions 保存 / 列出会话
|
|
218
|
+
/mcp MCP 服务器状态;/mcp reconnect <名字> 拉起挂掉的一台
|
|
201
219
|
quit / exit 退出(Ctrl+C 取消当前回合)
|
|
202
220
|
```
|
|
203
221
|
|
|
204
|
-
会话 ID 会先清洗成安全字符再拿去当文件名,存档统统落在 `~/.corecoder/sessions` 里,恶意会话名穿越不出去。undo 历史也一样落盘:快照写在 `~/.corecoder/checkpoints.json`,重启之后 `/undo`
|
|
222
|
+
会话 ID 会先清洗成安全字符再拿去当文件名,存档统统落在 `~/.corecoder/sessions` 里,恶意会话名穿越不出去。undo 历史也一样落盘:快照写在 `~/.corecoder/checkpoints.json`,重启之后 `/undo` 照样能往回撤。`/tokens` 的费用估算读的是内置价目表,在 `~/.corecoder/pricing.json` 里写自己的价格就会盖掉对应条目,新模型、调价都不用等发版。
|
|
205
223
|
|
|
206
224
|
## 权限
|
|
207
225
|
|
|
@@ -264,7 +282,7 @@ REPL 里 `/plan` 开关计划模式。开着的时候,提示符变成 `(plan)`
|
|
|
264
282
|
}
|
|
265
283
|
```
|
|
266
284
|
|
|
267
|
-
每个配好的服务器在启动时拉起一个子进程,握手、列出工具;每件工具都注册成 `mcp__<服务器>__<工具>`,钩子匹配和授权闸对它和内建工具一视同仁。MCP
|
|
285
|
+
每个配好的服务器在启动时拉起一个子进程,握手、列出工具;每件工具都注册成 `mcp__<服务器>__<工具>`,钩子匹配和授权闸对它和内建工具一视同仁。MCP 工具不在只读名单里,模型要调,得先问过你。握手给十五秒,一次调用给六十秒;服务器挂了或者迟迟不应,那一次调用就以普通工具结果的形式报错,循环照常往下走。REPL 里的 `/mcp` 列出每台服务器的状态,`/mcp reconnect <名字>` 把挂掉的重新拉起:新进程、重新握手,agent 手里已有的工具不用重启就恢复可用。客户端只实现协议里工具那一小片(initialize、tools/list、tools/call),别的一概不碰,所以整块实现收在 `mcp.py` 一个文件里,两百四十行上下。没有 `mcp.json` 就没有 MCP,一切照旧。想直接跑起来的话,[`examples/mcp_filesystem.md`](examples/mcp_filesystem.md) 给了一个最小配置:MCP 官方的参考文件系统服务器,只暴露一个目录,照样过授权闸。
|
|
268
286
|
|
|
269
287
|
## 相关项目
|
|
270
288
|
|
|
@@ -278,10 +296,10 @@ REPL 里 `/plan` 开关计划模式。开着的时候,提示符变成 `(plan)`
|
|
|
278
296
|
|
|
279
297
|
## 贡献 / License
|
|
280
298
|
|
|
281
|
-
动手之前先跑一遍 `pytest tests/ -q`(
|
|
299
|
+
动手之前先跑一遍 `pytest tests/ -q`(240 个测试)、`ruff check` 和 `compileall`,绿了再提。MIT License,欢迎 fork 拿去造更好的东西,能在 README 里留一句出处就更好。
|
|
282
300
|
|
|
283
301
|
---
|
|
284
302
|
|
|
285
|
-
作者 [何宇峰](https://github.com/he-yufeng),曾任职 Moonshot AI (Kimi)。早前写过一篇相当完整的 [Claude Code 源码分析](
|
|
303
|
+
作者 [何宇峰](https://github.com/he-yufeng),曾任职 Moonshot AI (Kimi)。早前写过一篇相当完整的 [Claude Code 源码分析](article/claude-code-source-analysis.md),这个项目是它的动手版:那篇带你读懂,这个带你重建。
|
|
286
304
|
|
|
287
305
|
> CoreCoder 原名 NanoCoder,为避免和 [Nano-Collective/nanocoder](https://github.com/Nano-Collective/nanocoder) 混淆而改名,旧链接会自动跳到这里。
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
这是一份写给工程师的系列导读。它想干两件事。
|
|
4
4
|
|
|
5
|
-
第一,借 CoreCoder
|
|
5
|
+
第一,借 CoreCoder 这个核心一千四百行出头的开源项目,把 Claude Code 这类生产级编码 agent 的内部结构讲清楚。第二,带你把 CoreCoder fork 下来,改出一个属于你自己的 coding agent。
|
|
6
6
|
|
|
7
7
|
## 为什么要绕一圈
|
|
8
8
|
|
|
9
9
|
因为 Claude Code 本体太大了。它的代码量在几十万行的量级,光一个负责跑 shell 命令的 BashTool 就上千行,真要逐行读完,多数人会在第三个文件就关掉编辑器。可 agent 的核心其实没那么复杂,复杂的是工程化之后那些为真实世界兜底的边角:模型半路被打断怎么办、上下文塞满了怎么办、十个工具调用同时回来怎么办、provider 偶尔抽风返回 500 又怎么办。这些东西摊在几十万行里,你很难一眼看到骨架。
|
|
10
10
|
|
|
11
|
-
CoreCoder
|
|
11
|
+
CoreCoder 做的事,是把这套骨架压到一千四百行出头的纯 Python。准确说,agent 引擎(循环、模型接口、上下文、工具、会话)去掉空行和注释是 1405 行;连最外层的 CLI 终端、配置和打包都算上,整个包去掉空行和注释 2364 行,物理代码 2908 行。每个文件都短到能一口气读完,跑起来又确实是个能改代码、能跑命令、能自己开子任务、能压缩上下文、能算钱的 agent。它不是玩具。它是把生产级 agent 的每个设计决策,拣出最核心的那版,用最少的代码诚实地写一遍。
|
|
12
12
|
|
|
13
13
|
读它,约等于读一份 Claude Code 的「可运行注释版」。区别在于,CoreCoder 的每一行你都能在自己机器上断点、改、跑、看它出什么效果。本系列里我引用的每一个行数、每一段代码,都是从仓库里现读现核的,不是凭印象。这点我后面会反复较真,因为编码 agent 这个领域,太多文章在凭感觉编数字。
|
|
14
14
|
|
|
@@ -24,12 +24,12 @@ CoreCoder 做的事,是把这套骨架压到一千行出头的纯 Python。准
|
|
|
24
24
|
|
|
25
25
|
1. [一个 agent 的本体,是一个 while 循环](01-the-loop.md)。整个 agent 最核心的东西,是一个「问模型、跑工具、把结果喂回去、再问」的循环。我们看 CoreCoder 的 `agent.py`(240 行)怎么把它写明白,以及打断、轮次上限、半截工具调用回填这些真实世界的麻烦各自怎么收场。
|
|
26
26
|
2. [工具系统:让模型安全地动手](02-tools.md)。模型本身只会吐字,是工具让它能读文件、写文件、跑命令。这篇讲 CoreCoder 的七个工具,重点是那个看似平平无奇、实则是 Claude Code 关键创新的「唯一性搜索替换」编辑,以及 bash 的安全闸。v0.6.0 之后还补了一节「门之外」:plan 模式、hooks、MCP 这三个进阶件怎么挂在一次调用的前后。末尾教你写第一个自己的工具。
|
|
27
|
-
3. [接入任意大模型,顺便把钱算清楚](03-llm-and-cost.md)。`llm.py`(
|
|
27
|
+
3. [接入任意大模型,顺便把钱算清楚](03-llm-and-cost.md)。`llm.py`(364 行,引擎里最大的文件)怎么用一套 OpenAI 兼容接口接住 DeepSeek、Qwen、Kimi、本地 Ollama,怎么做指数退避重试,怎么在流式输出里顺手把 token 和美元成本统计出来。
|
|
28
28
|
4. [用有限的窗口扛住一个长任务](04-context.md)。上下文窗口是 agent 的硬约束。`context.py`(220 行)实现了三层压缩,从轻到重。这篇还会讲一个特别容易踩、API 一定报错的坑:孤儿 tool 消息。这是我做这个项目时真改过的 bug。
|
|
29
29
|
5. [并行执行与子 agent](05-parallel-and-subagents.md)。模型一次返回多个工具调用时,CoreCoder 用线程池并发跑。这篇老实讲这个简化版相对 Claude Code 的流式执行器差在哪,并发又会引入什么新麻烦,以及子 agent 为什么不准递归。
|
|
30
30
|
6. [把它跑成一个真正的命令行工具](06-session-and-cli.md)。会话存盘、断点续聊、斜杠命令、一次性模式。`session.py` 里有个不起眼但很要命的安全细节:怎么防住用恶意会话名做路径穿越。
|
|
31
31
|
7. [Fork CoreCoder,搭一个你自己的 coding agent](07-build-your-own.md)。收尾的实操篇。从 clone 到换成你常用的模型,到加一个真正有用的自定义工具,到改系统提示词调教它的风格,到打包发布。读完前六篇你已经懂了原理,这篇让你真有一个东西。
|
|
32
|
-
8. [不动主循环的三种加法:MCP、钩子与计划模式](08-extensibility.md)。给 agent 加东西的三条正路:`mcp.py`(
|
|
32
|
+
8. [不动主循环的三种加法:MCP、钩子与计划模式](08-extensibility.md)。给 agent 加东西的三条正路:`mcp.py`(237 行)把远程工具接成与内建同权,hooks 用退出码 2 否决一次调用却永远杀不死循环,计划模式用一个布尔量压住包括 `--yes` 在内的整个权限层。最后一节把三块收拢回同一条契约:工具同形、错误是普通返回值、循环不被外围杀死。
|
|
33
33
|
|
|
34
34
|
不想按顺序也行。想搞懂「它凭什么敢自动跑命令」直接跳第二篇;想接自己的模型直接跳第三篇;只想赶紧 fork 出个能用的,第七篇是自洽的。
|
|
35
35
|
|
|
@@ -68,4 +68,4 @@ corecoder
|
|
|
68
68
|
|
|
69
69
|
---
|
|
70
70
|
|
|
71
|
-
作者:[何宇峰](https://github.com/he-yufeng)。早前写过一篇 [Claude Code 源码分析](
|
|
71
|
+
作者:[何宇峰](https://github.com/he-yufeng)。早前写过一篇 [Claude Code 源码分析](claude-code-source-analysis.md),偏完整拆解;这个系列换了个角度,更想让你动手把它复刻出来。
|