CoreCoder

he-yufengMIT活跃维护

约 1000 行 Python 实现的极简 AI 编码 Agent,兼容任意 LLM,适合想读懂或改造编码 Agent 内部机制的开发者。

Minimal AI coding agent (~1,000 lines of Python) inspired by Claude Code. Works with any LLM. Think NanoGPT for coding agents. Formerly NanoCoder.

v0.9.0发布于 2026-11-09 05:51 UTC1,902star +6 / 7 天461fork同步于 2026-11-10 05:52 UTC

怎么上手

由 README 提炼,执行前请确认
git clone https://github.com/he-yufeng/CoreCoder
cd CoreCoder
pip install -e .
corecoder
硬件要求按 README 说明

README未说明显存或GPU要求;可对接本地Ollama(如qwen2.5-coder)或任意OpenAI兼容API,属于纯Python客户端,未提及CPU/GPU厂商型号限制

下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

一个约1400行纯Python的极简AI编码agent,用来读懂编码agent原理并fork自己的版本

约 1000 行 Python 实现的极简 AI 编码 Agent,兼容任意 LLM,适合想读懂或改造编码 Agent 内部机制的开发者。

核心能力

  • 用约20行while循环实现agent主循环,工具调用并行执行
  • 内置bash、读写文件、edit、glob、grep、todo、子agent共8个工具
  • edit_file用唯一片段搜索替换而非行号,避免改错位置
  • 三层上下文压缩,并统计token与美元花费
  • 支持Pre/PostToolUse shell钩子、plan模式与checkpoint撤销

容易踩的坑

需要自备模型API key或本地Ollama服务,并设置OPENAI_API_KEY等环境变量;一次性模式-p默认拒绝会修改文件的工具,需加--yes;非OpenAI兼容的厂商需额外安装corecoder[litellm]后端

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本v0.9.0
  1. v0.9.0v0.9.0: bounded worker teams最新正式版2026-11-09 · 2026-11-09 05:51 UTC

    Named workers can now run as a bounded team through the existing agent tool. Give up to eight workers independent tasks; at most four run concurrently, with isolated histories and checklists, reports in input order, and the parent's consent, hooks, and usage accounting. There is no scheduler, background job system, or second agent loop. See the offline three-role example.

    This version also preserves reasoning_content on Kimi and DeepSeek tool-call turns, and recognizes Kimi's unquoted temperature rejection so the provider can apply its supported default. These changes were already on main in #48, #50, and #51; this release makes them available through the published package.

    pip install --upgrade corecoder
    

    Validation includes 242 offline tests, the Linux/macOS/Windows CI matrix on Python 3.10 through 3.13, and a fresh installation exercised through the real CLI against a scripted loopback HTTP endpoint. The zero-key team example runs the actual worker path with scripted findings. The live API smoke test remains opt-in; no live-provider or model-quality result is claimed.

    Full changelog

  2. v0.8.02026-10-06 · 2026-10-06 08:19 UTC
    查看更新说明

    v0.8.0

    Undo and consent now survive restarts. /undo checkpoints serialize to ~/.corecoder/checkpoints.json (base64 payloads, tmp-file swap so a crash can't truncate the file) and reload lazily on first use, and "always allow" grants persist in ~/.corecoder/permissions.json. One-shot -p mode stays pure and still refuses mutating tools.

    Also in this release:

    • Test coverage for the REPL slash-command dispatch.
    • Docs pinned to the code: hook examples, test counts, and line counts are swept by an alarm so they can't drift.

    中文说明

    /undo 快照和"始终允许"授权现在跨重启保留:快照落盘到 ~/.corecoder/checkpoints.json(原子写入,崩溃不会截断),授权存到 ~/.corecoder/permissions.json。一次性 -p 模式保持纯净,仍然拒绝变更类工具。另补了 REPL 斜杠命令的测试覆盖,文档中的代码引用现在有巡检兜底不会漂移。

    pip install -U corecoder

  3. v0.7.02026-09-24 · 2026-09-24 18:00 UTC
    查看更新说明

    Twenty-three commits since v0.6.0, still in the distilled spirit of the project:

    • Thinking models show their work (745ff65): deepseek-reasoner, kimi-k2-thinking and friends stream reasoning_content next to the answer. CoreCoder now surfaces it as it arrives instead of dropping it, and keeps it out of the reply and the history so context stays clean.
    • Streams retry wholesale, not just at connect (745ff65): a stream that died mid-drain used to kill the whole round; only the connect was retried. The drain is now inside the same backoff, so a dropped connection or a 5xx partway through re-issues the request. A 4xx still raises immediately.
    • Windows is a first-class shell citizen (8a44515, a18a4ac): command spawns route through Git Bash and drive-letter paths are posixified before they reach it. The Windows CI lanes are green again, and lint agrees.
    • cwd tracking got honest (b96a3b7, 2c49e74, 9c7fc18, ea46fd8): the shell watcher follows cd chains across semicolons, never inside subshells, quote-aware, with backslashes verbatim and Python 3.10 intact. Parallel bash calls share one cwd, and same-file edits serialize instead of racing.
    • Token estimates tier by content type (d74fdbf): code, prose and markup no longer price like the same text.
    • Direct OpenAI-path param adaptation (8990203, 66804de): rejected sampling params are adapted rather than fatal, and the tests track openai 3.14's httpx2 switch.
    • /undo recreates a deleted parent tree (27977ee), with a hooks and checkpoints audit behind it.
    • Reading track: essay 08 on the v0.6.0 extensibility trio is live in both languages, the hooks section grew two copy-pasteable cookbook examples, and the first-screen demo shows plan mode and hooks as they are now.

    Full essay series and the code map are in the README. As always: readable end to end, hackable, and the gaps are still the point.

  4. v0.6.0CoreCoder v0.6.0: MCP, hooks, plan mode2026-09-06 · 2026-09-06 02:05 UTC
    查看更新说明

    Four increments since v0.5.0, all in the distilled spirit of the project:

    • Engine trim (7f323e7): the core loop got leaner without losing behavior.
    • Pre/PostToolUse hooks (d503117): user shell commands around tool calls, configured in ~/.corecoder/hooks.json.
    • MCP stdio client (2dfd156): external tools join the agent from ~/.corecoder/mcp.json. Newline-delimited JSON-RPC over subprocess pipes, handshake and crash isolation included. MCP tools flow through the v0.5.0 consent gate and the new hooks like the built-ins.
    • Plan mode (caadf45): /plan makes the agent investigate read-only and present a numbered plan before anything mutates. "approve" (or /plan again) hands control back. It outranks even --yes.

    146 tests, all green.

    中文

    自 v0.5.0 以来的四个增量,全部延续项目的「蒸馏」气质:

    • 引擎精修(7f323e7):主循环更瘦,行为不变。
    • Pre/PostToolUse 钩子(d503117):工具调用前后跑用户自己的 shell 命令,配置在 ~/.corecoder/hooks.json。
    • MCP stdio 客户端(2dfd156):从 ~/.corecoder/mcp.json 接入外部工具生态。子进程管道上的逐行 JSON-RPC,带握手与崩溃隔离;MCP 工具和内建工具一样过 v0.5.0 的同意门和新的钩子。
    • 计划模式(caadf45):/plan 让 agent 先只读调查、给出编号计划再动手,approve(或再按 /plan)交还控制权。优先级高于 --yes。

    146 个测试全绿。

  5. v0.5.0v0.5.0 — Permission layer2026-09-04 · 2026-09-04 23:59 UTC
    查看更新说明

    The CLI now asks before mutating tools run. Read-only tools run immediately; edit_file, write_file, bash, and spawning a sub-agent stop for consent, with session-scoped always-allow, a --yes flag for scripts and CI, and auto-deny in one-shot mode so nothing hangs. Consent follows delegated work: a gated parent cannot spawn an ungated child. Library users are unaffected (opt-in through the CLI). 119 tests green, ruff clean.

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    CoreCoder

    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.

    learn from it · fork it · ship something better

    中文 | English | Source-reading series · 8 bilingual essays

    PyPI Python License: MIT Tests engine essays

    • Readable end to end. Read the whole engine in an afternoon, with no magic hidden anywhere you can't follow it.
    • Hackable. Set a breakpoint on any line, change it, rerun, all on your own machine. It genuinely works, which makes this a living reference rather than a diagram.
    • The gaps are the point. It deliberately keeps only the minimal core; what's missing isn't half-finished, it's where you branch off and make it your own.

    How it compares

    CoreCoderClaude CodeaidernanoGPT
    Lines of code~1,405 engine / 2,908 totalhundreds of thousands (closed)tens of thousands of Python~600 (two files)
    Time to read it allone afternooncan't (closed)a few days of sloggingone afternoon
    Breakpoint, change, rerun?yes, every linenoyes, but there's a lotyes
    What it's forunderstand one, then fork your ownproduction coding assistantterminal pair-programmingminimal GPT for teaching

    The nanoGPT column is there as a reference point: minimal, readable, but it teaches you to train a GPT. CoreCoder is after the same thing, only the subject is an agent that actually edits code. Sitting it next to Claude Code and aider isn't about competing for their users. CoreCoder is the foundation you stand on while you learn from them and get going; it isn't in the same race.

    What this is

    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.

    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.

    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. 240 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.

    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.

    This README follows the same arc: the first half helps you read it (the code map, the main loop, eight essays), the second half helps you fork it and points at a few directions worth pushing further.

    Run it once first (five minutes before you read)

    Before you read the source, get it running on your machine once to build some intuition. It's a foundation meant for forking, so the recommended path is to clone it and install editable, reading and changing as you go:

    git clone https://github.com/he-yufeng/CoreCoder
    cd CoreCoder
    pip install -e .
    

    If you just want to get it running first, pip install corecoder works too.

    Give it a model and a key and it goes. It speaks the OpenAI-compatible API by default, and switching providers is usually just two environment variables:

    ProviderExample env vars
    OpenAI (default gpt-5.5)OPENAI_API_KEY=sk-...
    DeepSeekOPENAI_API_KEY=sk-... OPENAI_BASE_URL=https://api.deepseek.com CORECODER_MODEL=deepseek-chat
    OmniRouteOPENAI_API_KEY=your-key OPENAI_BASE_URL=http://localhost:20128/v1 CORECODER_MODEL=auto
    Local OllamaOPENAI_API_KEY=ollama OPENAI_BASE_URL=http://localhost:11434/v1 CORECODER_MODEL=qwen2.5-coder

    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 exported directly or dropped into a .env at the project root, which is loaded on startup. Then:

    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.

    corecoder                                             # interactive REPL
    corecoder -p "add error handling to parse_config()"   # one-shot mode, exits when done
    

    Read it: the code map

    Laid out flat, the whole project is this big. Skim it before you clone and you'll know where everything is. This is the most concrete difference from Claude Code's hundreds of thousands of lines: you can read it like the table of contents of a book. Start from the main loop in agent.py; that's the heart of the whole agent.

    corecoder/
    ├── agent.py        agent loop + parallel tool exec       240 lines   ← start here
    ├── llm.py          streaming client + retry + cost        364 lines
    ├── context.py      three-tier context compaction          220 lines
    ├── session.py      save / resume + path-traversal guard    97 lines
    ├── permissions.py  consent for mutating tools              81 lines
    ├── hooks.py        Pre/PostToolUse shell hooks             87 lines
    ├── shell.py        POSIX shell routing (Git Bash on Windows) 61 lines
    ├── mcp.py          MCP stdio client for external tools    237 lines
    ├── prompt.py       system prompt                           41 lines
    ├── cli.py          REPL + slash commands + one-shot       393 lines
    ├── config.py       env-var config                          55 lines
    ├── checkpoints.py  /undo snapshot and restore               93 lines
    ├── demo.py         offline end-to-end demo                 101 lines
    └── tools/
        ├── bash.py       shell + dangerous-command gate + cd  203 lines
        ├── edit.py       unique-match search/replace + diff    99 lines
        ├── grep.py       content search                        93 lines
        ├── glob_tool.py  filename matching                     52 lines
        ├── read.py       file read                             56 lines
        ├── write.py      file write                            46 lines
        ├── todo.py       agent-maintained task checklist       79 lines
        ├── agent.py      sub-agent teams + isolation         143 lines
        └── base.py       tool base class                       32 lines
    examples/
    ├── plan_hooks_demo.py  offline plan mode + hooks demo (no API key)
    └── multi_agent_demo.py  offline three-role parallel demo (no API key)
    

    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.

    A while loop is the whole agent

    The whole of an agent fits in one sentence: hand the user's words to the model, run whatever tools it asks for, stuff the results back into the context, ask again, and keep going until it stops asking for tools and gives an answer. In code, that's about a dozen lines:

    # corecoder/agent.py · the main loop (trimmed skeleton)
    def chat(self, user_input):
        self.messages.append(user_input)
    
        for _ in range(self.max_rounds):                   # bounded, so it can't run away
            reply = self.llm.chat(self.messages, self.tools)   # ask the model what to do next
            if not reply.tool_calls:                       # model wants no more tools
                return reply.text                          #   -> done, hand the answer back
            results = run_parallel(reply.tool_calls)       # tools requested -> run in parallel
            self.messages += results                       # feed results back, loop again
    
        return "(hit the round limit)"
    

    That's the whole thing. The core skeleton is about twenty lines; counting parallel execution and the bookkeeping after a Ctrl+C interrupt, maybe forty. Almost everything else in CoreCoder's thousand-odd lines is there to clean up the mess the loop runs into once it meets the real world. llm.py ends up the biggest file in the engine, not because calling a model is hard, but because a streamed response splinters each tool call's arguments into fragments you have to restitch in order, a provider will hand you half a JSON object or a null usage field, and 429s, timeouts, dropped connections and 5xx all need backoff-and-retry while the other 4xx should just raise. A stream can also die after it has already started, so the retry wraps the whole request, not just the connect. That unglamorous grunt work, not the loop, is where the real engineering of taking an agent from demo to delivery actually lives; the third essay follows it down to the line.

    Three decisions are worth a closer look, because they're the kind of call you can only make after you've understood how others did it, and they're judgments you can lift straight into your own fork.

    edit_file does search-and-replace on a unique match, not line numbers. Line numbers are a trap: the model only has to miscount by one and it quietly edits the wrong place. Anchor on a unique snippet of the original instead. If there's no match, it hands the start of the file back so the model can re-anchor; if there are several matches, it makes the model bring more surrounding context rather than gamble on one. On a successful edit it returns a diff. Recoverable on failure, verifiable on success: the whole loop stays inside the tool.

    Context isn't cut all at once when it's full; it gives ground in three tiers, cheapest first. At half full (50%) it trims over-long tool outputs in place, a tier that's purely mechanical and costs no model call. If 70% still isn't enough, it has the model summarize the older turns into a single paragraph while keeping the most recent ones verbatim. Only at 90% does it hit the emergency tier and pull everything, summary and recent turns alike, down to its tightest form. Blunt truncation tends to throw away exactly the early decision a long task leans on most; tiering lets it surrender the least important things first instead of lopping off the oldest decisions wholesale from the start.

    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.

    Multi-agent, without an orchestration framework

    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:

    report = agent_tool.execute(tasks=[
        {"name": "researcher", "task": "Map the parser and its failure modes"},
        {"name": "implementer", "task": "Propose the smallest fix"},
        {"name": "reviewer", "task": "Attack the proposed fix with edge cases"},
    ])
    

    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.

    Every one of these whys is traced down to the actual lines of code in the series below.

    The source-reading series · 8 bilingual essays

    I also wrote a bilingual source-reading series, one intro plus eight parts, each in Chinese with an English mirror. Against CoreCoder's actual code, it walks through how agents like Claude Code work under the hood. One hard rule I set myself: every line count and every snippet is re-read and re-checked from the repo, never written from memory. The first six get you reading, the seventh gets you forking, and the eighth is about extending it without touching the loop; read them in any order.

    Fork it, build something better

    Once you understand it, the natural next step is to fork. Getting started doesn't take much:

    • 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.
    • 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.
    • 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.
    • Import it as a library. The top level exports Agent, LLM, and Config, ready to embed in your own program:
    from corecoder import Agent, LLM
    
    llm = LLM(model="deepseek-chat", api_key="sk-...", base_url="https://api.deepseek.com")
    print(Agent(llm=llm).chat("find every TODO comment in this project and list them"))
    

    Going deeper, the directions are out in the open too. None of the following is in CoreCoder, by design, not because it's unfinished. Flip it around and each one is an entry point you can carry into a real tool of your own:

    • 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.
    • 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.
    • 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.
    • 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.

    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.

    Commands

    Inside the REPL, /help lists everything; these are the ones you'll reach for:

    /model <name>    switch model
    /compact         compact the context by hand
    /tokens          token usage and cost estimate
    /diff            files changed this session
    /undo            revert the most recent file change
    /plan            toggle plan mode (read-only, then a plan to approve)
    /save  /sessions save / list sessions
    /mcp             MCP server status; /mcp reconnect <name> revives a dead one
    quit / exit      exit (Ctrl+C cancels the current round)
    

    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.

    Permissions

    Read-only tools (read_file, glob, grep, todo_write) run the moment the model asks. The mutating ones (edit_file, write_file, bash, and spawning a sub-agent) stop for consent first, and the REPL banner shows which mode you're in:

    • In the REPL you get one prompt per call: allow once, always allow this tool, or deny. "Always" is remembered per tool and persists in ~/.corecoder/permissions.json across restarts; a sub-agent inherits the same layer, so consent follows the work wherever it happens.
    • In one-shot mode (-p) there is nobody to ask, so a mutating call is refused on the spot and the refusal goes back to the model as an ordinary tool result: the loop never hangs on input that can't arrive. Pass --yes to approve everything up front (scripts, CI).
    • The decision itself is pure logic in permissions.py, with the terminal only supplying the prompt callback. You can unit-test consent without a TTY, or reuse the layer in your own embedding.

    Plan mode

    /plan toggles plan mode in the REPL. While it's on, the prompt shows (plan) and every mutating call (writes, edits, bash, MCP tools, sub-agents) is refused on the spot: the refusal goes back to the model as an ordinary tool result, telling it to keep investigating read-only and present a numbered plan instead. When the plan looks right, approve (or /plan again) hands control back and the agent executes. Mechanically it is one flag on the Agent plus one refusal branch ahead of the consent gate, which itself stays untouched; there is no plan file and nothing is remembered between sessions.

    Hooks

    Drop a hooks.json under ~/.corecoder and your own shell commands run around every tool call, the same idea as Claude Code's hooks:

    {
      "PreToolUse":  [{"matcher": "bash", "command": "cat >> ~/.corecoder/audit.jsonl"}],
      "PostToolUse": [{"matcher": "*",    "command": "cat >> ~/.corecoder/trace.jsonl"}]
    }
    

    Each hook gets the call as JSON on stdin (tool_name, tool_input; post hooks also get tool_response). The matcher is an exact tool name; empty or * fires on every tool. A pre hook can veto the call with exit code 2, and its stderr travels back to the model as the reason so it can route around the block. Post hooks only observe and can never block. A hook that errors or runs past ten seconds is skipped with a warning: hooks assist the loop, they never get to kill it. The whole mechanism is hooks.py, and the REPL banner shows how many hooks loaded.

    Two worth stealing (the commands lean on jq, the usual suspect):

    # 1. lint gate: after every edit/write, run the project's fast linter on the
    #    touched file. The model sees the output and fixes its own mistakes in
    #    the same turn instead of waiting for CI.
    {
      "PostToolUse": [{
        "matcher": "edit_file",
        "command": "f=$(jq -r .tool_input.file_path); ruff check \"$f\" 2>&1 | head -20"
      }]
    }
    
    # 2. write protect: refuse edits under paths you never want an agent to
    #    touch. Exit code 2 vetoes the call and the message reaches the model.
    {
      "PreToolUse": [{
        "matcher": "edit_file",
        "command": "case \"$(jq -r .tool_input.file_path)\" in .env*|*/secrets/*|*.pem) echo 'that path is off-limits' >&2; exit 2;; esac"
      }]
    }
    

    Both are plain shell; nothing here is CoreCoder-specific syntax beyond the JSON shape and the exit-code-2 veto.

    MCP servers

    Drop a mcp.json under ~/.corecoder and tools from any MCP server join the agent over stdio, the same config shape as Claude Code's:

    {
      "mcpServers": {
        "filesystem": {"command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]}
      }
    }
    

    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: the reference filesystem server, scoped to one directory, behind the same consent gate.

    Related Projects

    If working through CoreCoder was useful, here are a few other tools I've built around agents and LLM systems:

    • RepoWiki — dropped into an unfamiliar codebase? It gives you a guided wiki and a where-to-start reading path, a self-hostable DeepWiki alternative.
    • FindJobs-Agent — stop sifting job boards by hand: it ranks postings against your resume and runs mock interviews.
    • ContractGuard — catch the risky clauses before you sign: it reads contracts and flags the dangerous bits.
    • GitSense — want to contribute to open source? It finds issues worth your time and gauges whether your PR will get merged.
    • CodeABC — understand any codebase even if you don't code, built for non-programmers.

    Contributing / License

    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.


    By Yufeng He, formerly at Moonshot AI (Kimi). I earlier wrote a fairly complete Claude Code source analysis (written in Chinese); this project is its hands-on counterpart: that one walks you through reading it, this one through rebuilding it.

    CoreCoder was formerly named NanoCoder; it was renamed to avoid confusion with Nano-Collective/nanocoder, and old links redirect here automatically.