<div align="center">
CoreCoder
코딩 에이전트의 nanoGPT입니다. 순수 Python으로 작성된 2,908줄의 읽기 쉬운 코드 안에 담긴 1.4k 라인 엔진입니다. 코딩 에이전트가 실제로 어떻게 작동하는지 이해하고, 자신만의 것을 포크(fork)하세요.
이것으로부터 배우고 · 포크하여 · 더 나은 무언가를 배포하세요
中文 | English | Source-reading series · 8 bilingual essays
PyPI
Python
License: MIT
Tests
engine
essays
</div>
- 전체적으로 읽기 쉬움(Readable end to end). 마법처럼 숨겨진 부분이 없어 오후 안에 전체 엔진을 읽을 수 있습니다.
- 해킹 가능(Hackable). 아무 줄에나 중단점(breakpoint)을 설정하고, 변경하고, 다시 실행할 수 있습니다. 모두 사용자의 자체 머신에서 작동합니다. 이는 단순한 다이어그램이 아니라 실제로 작동하는 살아있는 참고 자료입니다.
- 빈틈(Gap) 자체가 핵심입니다. 최소한의 코어만 의도적으로 유지했습니다. 빠진 부분은 미완성된 것이 아니라, 사용자가 분기하여 자신만의 것으로 만들 수 있는 지점입니다.
비교 분석
| CoreCoder | Claude Code | aider | nanoGPT |
|---|
| 코드 라인 수 | ~1,405 엔진 / 2,908 총합 | 수십만 개 (비공개) | 수만 개의 Python | ~600 (두 파일) |
| ... | | | | |
nanoGPT 열은 참고 지점입니다: 최소화되고 읽기 쉽지만, GPT를 훈련하는 방법을 가르칩니다. CoreCoder는 같은 것을 목표로 하지만, 주제가 실제로 코드를 수정하는 에이전트라는 점만 다릅니다. Claude Code와 aider 옆에 놓여 있는 것은 그들의 사용자를 두고 경쟁하려는 것이 아닙니다. CoreCoder는 사용자가 그것들로부터 배우고 나아갈 때 기반으로 삼는 토대이며, 같은 경주를 하는 것이 아닙니다.
이것은 무엇인가요?
코딩 에이전트들은 항상 마치 난해한 것인 양 이야기되는 것 같았습니다. Claude Code나 Cursor 같은 도구의 핵심 구조를 완전히 분해해보면, 그 근본은 대규모 모델(large model)을 감싼 while 루프와 실제로 무언가를 수행할 수 있게 해주는 7~8개의 도구들로 이루어져 있습니다. 어려운 부분은 결코 루프 자체가 아니었습니다. 그것은 루프가 실제 세계를 만나게 되었을 때 처리해야 하는 모든 것입니다. CoreCoder는 이 핵심 구조를 정직하게 구현한 최소 버전입니다.
엔진(루프, 모델 인터페이스, 컨텍스트, 도구, 세션)은 빈 줄과 주석을 제외하면 1,405줄입니다. 외부 CLI, 설정 및 패키징까지 계산하면 전체 패키지는 25개 파일로 구성되어 있으며, 총 물리적 라인 수는 2,908줄, 순수 코드는 2,364줄에 달합니다. 이 모든 것은 한 번에 읽을 수 있을 만큼 짧습니다. 원래의 1,161줄 스냅샷 이후 증가한 부분은 계획 모드(plan mode), 후크(hooks) 및 체크포인트(checkpoints)와 같은 눈에 보이는 기능들로 채워졌으며, 각각 아래에 문서화되어 있습니다.
그리고 실제로 작동합니다: 파일을 읽고 쓰고, 셸(shell)을 실행하고, 서브 에이전트(sub-agents)를 생성하며, 컨텍스트를 세 단계로 압축하고, 요청할 때마다 해당 실행에 소모된 토큰과 비용($)을 알려줍니다. 디스크를 변경하거나 명령을 실행하는 모든 것은 먼저 사용자의 동의를 거칩니다. 이 스위트에는 242개의 오프라인 테스트와 선택적 라이브 스모크 테스트가 포함되어 있습니다. 하지만 이것이 작동하는 목적은 일상적인 도구가 되기 위함이 아닙니다. 워크스루(walkthrough)가 거짓말할 수 없도록 작동합니다. 에이전트가 어떻게 작동하는지 보여주는 참고 자료는 실제로 작동해야 하기 때문입니다.
이 코드는 공개적인 분해 분석(teardown)을 통해 나왔습니다. 이미 오픈된 분석들에서 Claude Code와 같은 프로덕션 에이전트 내부의 많은 핵심 구조가 노출되었습니다. 저는 가장 필수적인 계층을 가져와 가능한 한 적은 코드로 정직하게 다시 작성했습니다. 따라서 CoreCoder를 읽는 것은, 그러한 종류의 에이전트가 어떻게 작동하는지에 대한 실행 가능하고 주석 처리된 분석을 읽는 것과 비슷하지만, 단지 최소한의 재구현에 불과하며 사용자의 기기에 놓여 있어 분해하고 변경할 수 있습니다.
<p align="center">
<img src="https://raw.githubusercontent.com/he-yufeng/CoreCoder/main/assets/demo-plan-hooks.gif" width="760"
alt="Plan mode in action: the agent reads fib.py, gets its edit refused while plan mode is on, presents a plan, and only after approval edits, tests, and reports — with Pre/PostToolUse hooks firing around every call.">
</p>
<p align="center"><sub class="" id="text">이 천 줄의 코드가 실제로 끝까지 전체 루프를 실행하는 것을 보세요: buggy.py 파일을 고치라고 요청하면, 파일을 읽고, 코드를 수정하고, 한 번 실행하여 확인한 다음, 스스로 보고서를 작성합니다. 이것을 지켜본 후, 돌아와서 코드를 읽어보세요.</sub></p>
이 README 역시 같은 흐름을 따릅니다: 전반부는 여러분이 읽는 데 도움을 주고 (코드 맵, 메인 루프, 여덟 개의 에세이), 후반부는 여러분이 포크(fork) 할 수 있도록 돕고 더 깊이 파고들 만한 몇 가지 방향을 제시합니다.
먼저 실행해보기 (읽기 전에 5분)
소스 코드를 읽기 전에, 자신의 컴퓨터에서 한 번 실행하여 직관적인 감각을 익히는 것이 좋습니다. 이 기반은 포크(forking)를 위해 만들어진 것이므로, 권장되는 경로는 클론(clone)한 후 편집 가능한 상태로 설치하고 진행하면서 읽고 변경하는 것입니다:
git clone https://github.com/he-yufeng/CoreCoder
cd CoreCoder
pip install -e .
단순히 먼저 실행만 하고 싶다면, pip install corecoder를 사용해도 됩니다.
모델과 키를 제공하면 바로 작동합니다. 기본적으로 OpenAI와 호환되는 API(OpenAI-compatible API)를 사용하며, 프로바이더를 전환하는 것은 보통 환경 변수 두 개만 변경하면 됩니다:
| Provider | Example env vars |
|---|
OpenAI (기본값 gpt-5.5) | OPENAI_API_KEY=sk-... |
| ... | |
400에서 temperature라는 이름의 환경 변수가 감지되지 않을 경우, CoreCoder는 프로바이더가 지원하는 기본값을 적용할 수 있도록 재시도합니다. 여기에는 Kimi의 따옴표 없는 거부(unquoted rejection)도 포함되므로, CORECODER_TEMPERATURE를 오버라이드 할 필요가 없습니다.
Kimi, Qwen 등은 같은 두 개의 변수입니다. 심지어 OpenAI와 호환되는 엔드포인트조차 제공하지 않는 프로바이더의 경우, 선택적 LiteLLM 백엔드(pip install "corecoder[litellm]")는 100개 이상의 프로바이더로 라우팅됩니다. 세 번째 에세이에서 이 내용을 자세히 다룹니다. 추론(Thinking) 모델 역시 일급 시민입니다: deepseek-reasoner, kimi-k3 등이 자신의 chain-of-thought를 스트리밍하며, CoreCoder는 작동하는 동안 이를 점멸하는 것처럼 보여줍니다. 일반적인 답변은 추론 과정을 대화 기록에서 제외시키는 반면, Kimi와 DeepSeek의 tool-call 턴은 그들의 사고 프로토콜이 요구하듯이 reasoning_content를 다음 요청으로 전달합니다. 키(key)는 직접 export하거나 프로젝트 루트에 있는 .env 파일에 넣을 수 있으며, 이 파일은 시작 시 로드됩니다. 그다음:
DeepSeek, Qwen3, Kimi K2를 대상으로 단일 OpenRouter 호환 엔드포인트를 통해 종단 간(end to end) 테스트되었습니다 (파일 읽기, 편집, 실행, 보고). 각 모델 모두 전체 루프를 완료했습니다. one-shot 스크립트에 대한 참고 사항: -p는 --yes 플래그를 전달하지 않으면 변경하는 도구(mutating tools) 사용을 거부하도록 설계되어 있습니다.
corecoder
corecoder -p "add error handling to parse_config()"
코드 구조 살펴보기: 코드 맵
전체 프로젝트는 평평하게 펼쳐져 있으며 규모가 상당합니다. 클론하기 전에 훑어보면 모든 것이 어디에 있는지 알 수 있을 것입니다. 이는 Claude Code의 수십만 줄과 비교했을 때 가장 구체적인 차이점입니다. 마치 책의 목차처럼 읽을 수 있습니다. agent.py의 메인 루프부터 시작하세요. 그곳이 전체 에이전트의 핵심입니다.
corecoder/
├── agent.py 에이전트 루프 + 병렬 도구 실행 240 lines ← 여기서 시작
├── llm.py 스트리밍 클라이언트 + 재시도 + 비용 364 lines
...
여덟 가지 도구: bash, read_file, write_file, edit_file, glob, grep, todo_write (에이전트가 스스로 유지하는 작업 체크리스트), 그리고 서브 에이전트를 생성하는 agent입니다. 나머지 모든 것은 해당 엔진 코어 주변을 감싸는 CLI 쉘, 설정(config), 및 패키징입니다. 만약 ~/.corecoder/mcp.json 파일이 존재한다면, 이 파일의 MCP 서버들이 여덟 가지 도구에 추가적인 mcp__* 도구로 합류합니다. MCP 섹션에서 이에 대해 다룹니다.
에이전트 전체는 while 루프입니다
에이전트 전체를 한 문장으로 요약할 수 있습니다: 사용자에게 단어를 모델에 전달하고, 모델이 요청하는 모든 도구를 실행하며, 그 결과를 컨텍스트(context)에 다시 채워 넣고, 답변을 줄 때까지 계속해서 질문을 반복합니다. 코드로 보면 이는 약 12줄 정도입니다:
def chat(self, user_input):
self.messages.append(user_input)
...
이것이 전부입니다. 핵심 골격은 약 20줄이며; 병렬 실행과 Ctrl+C 인터럽트 후의 장부 정리(bookkeeping)를 계산하면 아마도 40줄 정도가 됩니다. CoreCoder의 천 줄에 달하는 거의 모든 코드는 루프가 실제 세계와 마주했을 때 발생하는 혼란을 정리하기 위해 존재합니다. llm.py는 엔진에서 가장 큰 파일이 되는데, 이는 모델 호출 자체가 어렵기 때문이 아니라 스트리밍 응답(streamed response)이 각 도구 호출의 인자들을 조각들로 분산시키고, 사용자가 이 조각들을 순서대로 재조립해야 하며, 제공업체는 JSON 객체의 절반이나 usage 필드가 null인 경우를 처리해야 하고, 429 오류(Rate Limit), 시간 초과(timeout), 연결 끊김(dropped connections) 및 5xx 오류 모두가 백오프-재시도(backoff-and-retry) 처리가 필요하고, 다른 4xx 오류들은 단순히 발생시키기만 하면 되기 때문입니다. 스트림은 이미 시작된 후에도 중단될 수 있으므로, 재시도는 연결 자체뿐만 아니라 전체 요청을 감싸야 합니다. 이 화려하지 않은 잡무(grunt work), 즉 루프가 아니라 그 부분이 에이전트를 데모에서 실제 배포 수준으로 끌어올리는 진정한 엔지니어링이 존재하며, 세 번째 논문은 이 지점까지 깊이 다룹니다.
세 가지 결정은 자세히 살펴볼 가치가 있습니다. 왜냐하면 이들은 다른 사람들이 어떻게 했는지 이해한 후에만 내릴 수 있는 종류의 호출이며, 여러분 자신의 포크(fork)로 바로 가져갈 수 있는 판단들이기 때문입니다.
edit_file은 라인 번호가 아닌 고유한 일치 항목에 대해 검색 및 대체(search-and-replace)를 수행합니다. 라인 번호는 함정입니다. 모델이 단 하나만 잘못 계산해도 조용히 잘못된 곳을 편집할 수 있습니다. 대신 원본의 고유한 스니펫(snippet)에 기반해야 합니다. 일치 항목이 없으면, 모델이 재앵커링(re-anchor) 할 수 있도록 파일 시작 부분을 반환합니다. 여러 개의 일치 항목이 있으면, 하나에 베팅하기보다 더 많은 주변 컨텍스트를 가져오도록 모델에게 요청합니다. 성공적인 편집 시에는 diff를 반환합니다. 실패 시 복구 가능하고, 성공 시 검증 가능하며: 전체 루프가 도구(tool) 내부에 유지됩니다.
컨텍스트는 가득 찼을 때 한 번에 잘리지 않습니다. 가장 저렴한 비용부터 세 단계(three tiers)로 점진적으로 공간을 확보합니다. 절반 정도 채워졌을 때(50%)는 과도하게 긴 도구 출력물(tool outputs)을 제자리에서 자릅니다. 이 단계는 순전히 기계적이며 모델 호출 비용이 들지 않습니다. 70%가 여전히 충분하지 않다면, 모델에게 더 오래된 대화 내용을 하나의 단락으로 요약하도록 요청하는 동시에 가장 최근의 내용은 원문 그대로(verbatim) 유지합니다. 오직 90%에 도달했을 때 비상 단계(emergency tier)에 진입하여, 요약 내용과 최근 대화 내용을 모두 가장 간결한 형태로 압축합니다. 무작정 잘라내는 방식(Blunt truncation)은 긴 작업이 가장 많이 의존하는 초기 결정을 정확히 버리는 경향이 있습니다. 따라서 계층적 접근법(tiering)을 사용하면, 처음부터 가장 오래된 결정들을 통째로 잘라내기보다 덜 중요한 것들부터 먼저 포기할 수 있게 합니다.
하위 에이전트(sub-agent)는 규칙을 작성하고 그것이 따르기를 바라는 방식이 아니라, 도구를 제한함으로써 제어합니다. 생성된 하위 에이전트는 격리된 컨텍스트와 자체적인 별도의 히스토리를 가지며, 도구 세트는 부모의 것보다 정확히 한 항목 적습니다. 바로 agent 도구 그 자체입니다. 따라서 재귀적으로 더 많은 하위 에이전트를 생성할 수 없습니다. 하나의 도구를 덜 제공하는 것이 나중에 규칙을 제정하는 것보다 훨씬 깔끔합니다. 또한 이 방식은 부모의 모델 연결(지출액이 동일한 누적 총액에 통합됨)을 재사용하고, 출력이 5,000자를 초과하면 시작 부분만 남기고 자르며, 부모보다 짧은 라운드 제한으로 실행됩니다. 처음부터 끝까지 같은 제약입니다.
오케스트레이션 프레임워크 없이 다중 에이전트(Multi-agent)
동일한 agent 도구는 이제 소규모 팀을 분산시킵니다. 각 작업자에게 이름과 독립적인 작업을 부여하면, 그들은 새로운 메시지 히스토리와 빈 체크리스트를 받게 되며, 권한 및 훅은 부모의 계약에 남아 있습니다:
report = agent_tool.execute(tasks=[
{"name": "researcher", "task": "파서와 그 실패 모드를 매핑하세요"},
{"name": "implementer", "task": "가장 작은 수정 사항을 제안하세요"},
...
1개에서 8개의 작업자가 한 번에 최대 4개까지 실행되며, 각 작업자는 일반적인 에이전트 루프를 거칩니다. 보고서는 완료 순서가 아닌 입력 순서로 돌아오며, 하나의 작업자 예외가 다른 작업자를 취소시키지 않습니다. 공유 토큰 회계 처리와 대화형 권한 프롬프트는 직렬화되므로, 동시성이 비용을 잃거나 동의를 간섭하지 않습니다. 스케줄러, 공유 메시지 버스, 재귀적 생성(recursive spawning), 또는 두 번째 에이전트 루프가 없습니다. examples/multi_agent_demo.py는 스크립트된 발견 사항과 함께 연구원 / 구현자 / 검토자 패턴을 오프라인으로 실행하며, API 키나 모델 품질 주장은 포함되어 있지 않습니다.
이러한 모든 이유들은 아래 시리즈의 실제 코드 라인까지 추적됩니다.
소스 읽기 시리즈 · 8개의 이중 언어 에세이