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.
Files changed (78) hide show
  1. {corecoder-0.8.0 → corecoder-0.9.0}/PKG-INFO +39 -21
  2. {corecoder-0.8.0 → corecoder-0.9.0}/README.md +36 -18
  3. {corecoder-0.8.0 → corecoder-0.9.0}/README_CN.md +36 -18
  4. {corecoder-0.8.0 → corecoder-0.9.0}/article/00-index.md +5 -5
  5. {corecoder-0.8.0 → corecoder-0.9.0}/article/00-index_EN.md +5 -5
  6. {corecoder-0.8.0 → corecoder-0.9.0}/article/03-llm-and-cost.md +5 -3
  7. {corecoder-0.8.0 → corecoder-0.9.0}/article/03-llm-and-cost_EN.md +5 -3
  8. {corecoder-0.8.0 → corecoder-0.9.0}/article/05-parallel-and-subagents.md +18 -27
  9. {corecoder-0.8.0 → corecoder-0.9.0}/article/05-parallel-and-subagents_EN.md +18 -27
  10. {corecoder-0.8.0 → corecoder-0.9.0}/article/06-session-and-cli.md +2 -1
  11. {corecoder-0.8.0 → corecoder-0.9.0}/article/06-session-and-cli_EN.md +2 -1
  12. {corecoder-0.8.0 → corecoder-0.9.0}/article/07-build-your-own.md +5 -5
  13. {corecoder-0.8.0 → corecoder-0.9.0}/article/07-build-your-own_EN.md +5 -5
  14. {corecoder-0.8.0 → corecoder-0.9.0}/article/08-extensibility.md +3 -3
  15. {corecoder-0.8.0 → corecoder-0.9.0}/article/08-extensibility_EN.md +3 -3
  16. corecoder-0.9.0/article/claude-code-source-analysis.md +332 -0
  17. {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/__init__.py +1 -1
  18. {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/cli.py +35 -0
  19. {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/llm.py +45 -13
  20. {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/mcp.py +41 -12
  21. {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/permissions.py +6 -0
  22. {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/session.py +2 -2
  23. corecoder-0.9.0/corecoder/tools/agent.py +143 -0
  24. {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/tools/todo.py +3 -3
  25. corecoder-0.9.0/examples/mcp_filesystem.json +8 -0
  26. corecoder-0.9.0/examples/mcp_filesystem.md +43 -0
  27. corecoder-0.9.0/examples/multi_agent_demo.py +56 -0
  28. {corecoder-0.8.0 → corecoder-0.9.0}/pyproject.toml +4 -3
  29. {corecoder-0.8.0 → corecoder-0.9.0}/tests/conftest.py +7 -0
  30. {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_core.py +271 -8
  31. corecoder-0.9.0/tests/test_live_smoke.py +52 -0
  32. {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_mcp.py +81 -0
  33. corecoder-0.9.0/tests/test_multi_agent.py +193 -0
  34. {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_session.py +20 -0
  35. corecoder-0.8.0/corecoder/tools/agent.py +0 -72
  36. {corecoder-0.8.0 → corecoder-0.9.0}/.github/workflows/ci.yml +0 -0
  37. {corecoder-0.8.0 → corecoder-0.9.0}/.github/workflows/publish.yml +0 -0
  38. {corecoder-0.8.0 → corecoder-0.9.0}/.gitignore +0 -0
  39. {corecoder-0.8.0 → corecoder-0.9.0}/LICENSE +0 -0
  40. {corecoder-0.8.0 → corecoder-0.9.0}/article/01-the-loop.md +0 -0
  41. {corecoder-0.8.0 → corecoder-0.9.0}/article/01-the-loop_EN.md +0 -0
  42. {corecoder-0.8.0 → corecoder-0.9.0}/article/02-tools.md +0 -0
  43. {corecoder-0.8.0 → corecoder-0.9.0}/article/02-tools_EN.md +0 -0
  44. {corecoder-0.8.0 → corecoder-0.9.0}/article/04-context.md +0 -0
  45. {corecoder-0.8.0 → corecoder-0.9.0}/article/04-context_EN.md +0 -0
  46. {corecoder-0.8.0 → corecoder-0.9.0}/assets/demo-plan-hooks.gif +0 -0
  47. {corecoder-0.8.0 → corecoder-0.9.0}/assets/demo.png +0 -0
  48. {corecoder-0.8.0 → corecoder-0.9.0}/assets/demo_en.png +0 -0
  49. {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/__main__.py +0 -0
  50. {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/agent.py +0 -0
  51. {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/checkpoints.py +0 -0
  52. {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/config.py +0 -0
  53. {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/context.py +0 -0
  54. {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/demo.py +0 -0
  55. {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/hooks.py +0 -0
  56. {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/prompt.py +0 -0
  57. {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/shell.py +0 -0
  58. {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/tools/__init__.py +0 -0
  59. {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/tools/base.py +0 -0
  60. {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/tools/bash.py +0 -0
  61. {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/tools/edit.py +0 -0
  62. {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/tools/glob_tool.py +0 -0
  63. {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/tools/grep.py +0 -0
  64. {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/tools/read.py +0 -0
  65. {corecoder-0.8.0 → corecoder-0.9.0}/corecoder/tools/write.py +0 -0
  66. {corecoder-0.8.0 → corecoder-0.9.0}/examples/plan_hooks_demo.py +0 -0
  67. {corecoder-0.8.0 → corecoder-0.9.0}/tests/__init__.py +0 -0
  68. {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_checkpoints.py +0 -0
  69. {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_demo.py +0 -0
  70. {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_hooks.py +0 -0
  71. {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_litellm.py +0 -0
  72. {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_permissions.py +0 -0
  73. {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_plan_mode.py +0 -0
  74. {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_repl.py +0 -0
  75. {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_safety_matrix.py +0 -0
  76. {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_shell.py +0 -0
  77. {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_slash_commands.py +0 -0
  78. {corecoder-0.8.0 → corecoder-0.9.0}/tests/test_tools.py +0 -0
@@ -1,7 +1,7 @@
1
- Metadata-Version: 2.5
1
+ Metadata-Version: 2.4
2
2
  Name: corecoder
3
- Version: 0.8.0
4
- Summary: Minimal AI coding agent (2,735 lines of Python) inspired by Claude Code. Works with any LLM. (formerly NanoCoder)
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.3k-line engine inside 2,735 readable lines of pure Python: understand how a coding agent actually works, then fork your own.**
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
  [![Python](https://img.shields.io/badge/python-3.10+-blue)](https://python.org)
48
48
  [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
49
49
  [![Tests](https://github.com/he-yufeng/CoreCoder/actions/workflows/ci.yml/badge.svg)](https://github.com/he-yufeng/CoreCoder/actions)
50
- [![engine](https://img.shields.io/badge/engine-1309_LoC-blue)](article/00-index_EN.md)
50
+ [![engine](https://img.shields.io/badge/engine-1405_LoC-blue)](article/00-index_EN.md)
51
51
  [![essays](https://img.shields.io/badge/source--reading-8_bilingual-orange)](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,309 engine / 2,735 total | hundreds of thousands (closed) | tens of thousands of Python | ~600 (two files) |
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,309 lines once you drop blank lines and comments. Counting the outer CLI, config and packaging too, the whole package is 25 files: 2,735 physical lines, 2,205 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.
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. 215 tests, all green. 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.
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
- 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, kept out of the conversation history so providers never see it come back. The key can be `export`ed directly or dropped into a `.env` at the project root, which is loaded on startup. Then:
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 332 lines
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 75 lines
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 208 lines
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 358 lines
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 spawning 72 lines
148
+ ├── agent.py sub-agent teams + isolation 143 lines
147
149
  └── base.py tool base class 32 lines
148
150
  examples/
149
- └── plan_hooks_demo.py offline plan mode + hooks demo (no API key)
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` (332 lines) is the entry point for all provider adaptation.
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
- - **Sub-agents only run the plainest synchronous execution.** Make it async or a streaming executor and you close the exact gap the fifth essay identifies between this and how production agents stream execution.
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 200 lines. With no `mcp.json` there is no MCP and nothing changes.
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` (215 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.
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](https://zhuanlan.zhihu.com/p/1898797658343862272) on Zhihu; this project is its hands-on counterpart: that one walks you through reading it, this one through rebuilding it.
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.3k-line engine inside 2,735 readable lines of pure Python: understand how a coding agent actually works, then fork your own.**
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
  [![Python](https://img.shields.io/badge/python-3.10+-blue)](https://python.org)
13
13
  [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
14
14
  [![Tests](https://github.com/he-yufeng/CoreCoder/actions/workflows/ci.yml/badge.svg)](https://github.com/he-yufeng/CoreCoder/actions)
15
- [![engine](https://img.shields.io/badge/engine-1309_LoC-blue)](article/00-index_EN.md)
15
+ [![engine](https://img.shields.io/badge/engine-1405_LoC-blue)](article/00-index_EN.md)
16
16
  [![essays](https://img.shields.io/badge/source--reading-8_bilingual-orange)](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,309 engine / 2,735 total | hundreds of thousands (closed) | tens of thousands of Python | ~600 (two files) |
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,309 lines once you drop blank lines and comments. Counting the outer CLI, config and packaging too, the whole package is 25 files: 2,735 physical lines, 2,205 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.
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. 215 tests, all green. 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.
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
- 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, kept out of the conversation history so providers never see it come back. The key can be `export`ed directly or dropped into a `.env` at the project root, which is loaded on startup. Then:
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 332 lines
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 75 lines
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 208 lines
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 358 lines
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 spawning 72 lines
113
+ ├── agent.py sub-agent teams + isolation 143 lines
112
114
  └── base.py tool base class 32 lines
113
115
  examples/
114
- └── plan_hooks_demo.py offline plan mode + hooks demo (no API key)
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` (332 lines) is the entry point for all provider adaptation.
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
- - **Sub-agents only run the plainest synchronous execution.** Make it async or a streaming executor and you close the exact gap the fifth essay identifies between this and how production agents stream execution.
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 200 lines. With no `mcp.json` there is no MCP and nothing changes.
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` (215 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.
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](https://zhuanlan.zhihu.com/p/1898797658343862272) on Zhihu; this project is its hands-on counterpart: that one walks you through reading it, this one through rebuilding it.
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.3k 行引擎、整包 2735 行纯 Python 全部一口气可读,读懂一个 coding agent 到底怎么运作,再 fork 出你自己的。**
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
  [![Python](https://img.shields.io/badge/python-3.10+-blue)](https://python.org)
13
13
  [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
14
14
  [![Tests](https://github.com/he-yufeng/CoreCoder/actions/workflows/ci.yml/badge.svg)](https://github.com/he-yufeng/CoreCoder/actions)
15
- [![engine](https://img.shields.io/badge/engine-1309_LoC-blue)](article/)
15
+ [![engine](https://img.shields.io/badge/engine-1405_LoC-blue)](article/)
16
16
  [![源码导读](https://img.shields.io/badge/源码导读-8篇双语-orange)](article/)
17
17
 
18
18
  </div>
@@ -25,7 +25,7 @@
25
25
 
26
26
  | | CoreCoder | Claude Code | aider | nanoGPT |
27
27
  |---|---|---|---|---|
28
- | 代码量 | 引擎约 1309 行 / 整包 2735 行 | 几十万行(闭源) | 数万行 Python | 约 600 行(两个文件) |
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
- 引擎部分(循环、模型接口、上下文、工具、会话)去掉空行和注释是 1309 行。连最外层的 CLI、配置、打包一起算,整个包 25 个文件、物理 2735 行、净 2205 行,每个文件都短到能一口气读完。自 1161 行快照之后的增长都花在了看得见的功能上:plan mode、hooks、checkpoints,下文各有交代。
39
+ 引擎部分(循环、模型接口、上下文、工具、会话)去掉空行和注释是 1405 行。连最外层的 CLI、配置、打包一起算,整个包 25 个文件、物理 2908 行、净 2364 行,每个文件都短到能一口气读完。自 1161 行快照之后的增长都花在了看得见的功能上:plan mode、hooks、checkpoints,下文各有交代。
40
40
 
41
- 它真能跑:读写文件、执行 shell、派子 agent、分三层压上下文,还能随时把这趟烧掉的 token 和美元数报给你。任何要动你磁盘、要跑命令的调用,都会先停下来等你点头,215 个测试是绿的。但能跑不是为了劝你拿去日用,而是为了让这份「注释」不撒谎:一个解释 agent 怎么运作的范例,自己得真能运作。
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
- Kimi、Qwen 这些同样是改这两个变量;连 OpenAI 兼容接口都不给的 provider,装上可选的 LiteLLM 后端(`pip install "corecoder[litellm]"`)能路由一百多家。第三篇文章把这块讲得更细。思考模型也是一等公民:deepseek-reasoner、kimi-k3 这类模型的思考过程会实时流出来,CoreCoder 把它用暗色显示出来,但不进对话历史,provider 永远不会在回包里看到它。key 可以直接 `export`,也可以在项目根目录扔个 `.env`,启动时自动加载。然后:
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 流式客户端 + 重试 + 成本统计 332 行
93
+ ├── llm.py 流式客户端 + 重试 + 成本统计 364 行
92
94
  ├── context.py 三层上下文压缩 220 行
93
95
  ├── session.py 会话存盘 / 续聊 + 路径穿越防护 97 行
94
- ├── permissions.py 改动类工具的用户授权 75 行
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 客户端,接外部工具 208 行
99
+ ├── mcp.py MCP stdio 客户端,接外部工具 237 行
98
100
  ├── prompt.py 系统提示词 41 行
99
- ├── cli.py REPL + 斜杠命令 + 一次性模式 358 行
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 派生 72 行
113
+ ├── agent.py 子 agent 团队与隔离 143 行
112
114
  └── base.py 工具基类 32 行
113
115
  examples/
114
- └── plan_hooks_demo.py 离线 plan mode + hooks 演示(免 API key)
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`(332 行)是所有 provider 适配的入口。
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 只有最朴素的同步执行。** 做成异步或流式执行器,正好补上第五篇点名的、相对生产级 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 工具不在只读名单里,模型要调,得先问过你。握手给十五秒,一次调用给六十秒;服务器挂了或者迟迟不应,那一次调用就以普通工具结果的形式报错,循环照常往下走。客户端只实现协议里工具那一小片(initialize、tools/list、tools/call),别的一概不碰,所以整块实现收在 `mcp.py` 一个文件里,两百行出头。没有 `mcp.json` 就没有 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`(215 个测试)、`ruff check` 和 `compileall`,绿了再提。MIT License,欢迎 fork 拿去造更好的东西,能在 README 里留一句出处就更好。
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 源码分析](https://zhuanlan.zhihu.com/p/1898797658343862272),这个项目是它的动手版:那篇带你读懂,这个带你重建。
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 这个核心一千行出头的开源项目,把 Claude Code 这类生产级编码 agent 的内部结构讲清楚。第二,带你把 CoreCoder fork 下来,改出一个属于你自己的 coding agent。
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 做的事,是把这套骨架压到一千行出头的纯 Python。准确说,agent 引擎(循环、模型接口、上下文、工具、会话)去掉空行和注释是 1201 行;连最外层的 CLI 终端、配置和打包都算上,整个包去掉空行和注释 1614 行,物理代码 2008 行。每个文件都短到能一口气读完,跑起来又确实是个能改代码、能跑命令、能自己开子任务、能压缩上下文、能算钱的 agent。它不是玩具。它是把生产级 agent 的每个设计决策,拣出最核心的那版,用最少的代码诚实地写一遍。
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`(332 行,引擎里最大的文件)怎么用一套 OpenAI 兼容接口接住 DeepSeek、Qwen、Kimi、本地 Ollama,怎么做指数退避重试,怎么在流式输出里顺手把 token 和美元成本统计出来。
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`(208 行)把远程工具接成与内建同权,hooks 用退出码 2 否决一次调用却永远杀不死循环,计划模式用一个布尔量压住包括 `--yes` 在内的整个权限层。最后一节把三块收拢回同一条契约:工具同形、错误是普通返回值、循环不被外围杀死。
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 源码分析](https://zhuanlan.zhihu.com/p/1898797658343862272),偏完整拆解;这个系列换了个角度,更想让你动手把它复刻出来。
71
+ 作者:[何宇峰](https://github.com/he-yufeng)。早前写过一篇 [Claude Code 源码分析](claude-code-source-analysis.md),偏完整拆解;这个系列换了个角度,更想让你动手把它复刻出来。