Single-turn conversation works. Multi-step tasks never finish. No learning from experience. No safe self-extension. This is not an agent capability problem — it is an architecture problem.
Pattern Layer
1.1 Common Pain Points of Current Agent Systems
From 2023 to 2025, AI agents evolved from a proof-of-concept to a widely used tool. You can ask an agent to write code, search documentation, or operate a database. But if you tell an agent to "deploy this module, verify it's running correctly in production, and roll back if it isn't" — most agents will get stuck by step two.
This is not a limitation of LLM capability — the models are smart enough. It is an architecture problem: agents lack a runtime capable of supporting long-duration tasks.
Specifically, current agent systems universally face four bottlenecks:
First, single-turn conversation works, but multi-step tasks never finish. A task involves five steps, each depending on the previous step's output. By step three, the agent's context already contains the operational logs and results of the first two steps. But by step four — if the step-three tool call returned a large volume of data — the first two steps may have been washed out of the context window. The agent "forgets" the user's initial constraints. This is not a memory deficiency of the model; it is the sheer growth of operation history drowning out the original intent.
Second, no learning from experience. Every session is independent. You told the agent yesterday that "the project uses nightly Rust," and today the agent writes code using stable Rust features — because it does not remember yesterday's conversation. Session persistence can save conversation history, but structured information like "user preferences" that needs to be retrieved across sessions cannot be obtained by sifting through chat logs.
Third, no safe self-extension. You ask the agent to "write this workflow as a reusable script" — it writes one, but how do you execute it? Most agent systems lack a security architecture that allows the agent to execute code it has written. Either they forbid it (limiting capability) or they let it run freely (unsafe). What is missing between these two extremes is a tiered, auditable execution channel.
Fourth, and most importantly — these bottlenecks are not independent. Discontinuous identity prevents learning; the inability to learn means every task evaluation starts from scratch; the lack of self-extension capability means the only way to solve new problems is to update the binary. These four factors form a vicious cycle.
This leads to three core design questions, which form the backbone around which the entire book is structured:
- Identity: How does an agent know who it is, where it belongs, and what constraints it has?
- Capability extension: How can an agent safely acquire new capabilities — not by updating the binary, but through self-growth?
- Safety boundary: How does an agent stay within bounds when modifying itself — who approves, who audits, who stops it?
1.2 Three Paradigms of Agent Kernels
To answer these three questions, we must first examine the fundamentals: what structure should an agent's runtime kernel have?
Since 2023, agent systems in the industry have roughly gone through three kernel paradigms:
REPL Loop — the simplest starting point.
loop {
input = read_user()
think(input)
output = act()
print(output)
}
Read input, think, act, output — four steps, repeated in a loop. This is the starting point for almost all agent systems. The advantage is clear: the execution path is fully deterministic, and debugging only requires examining inputs and outputs. You always know what step the agent is on.
But the REPL loop has three inherent limitations. First, during a blocking operation, the system is completely frozen — you cannot cancel a 30-second build command midway. Second, cross-turn context stitching requires explicit handling — output from one turn does not "naturally" flow into the next. Third, no form of parallelism is supported — it is impossible for one agent to work on two tasks simultaneously.
REPL is the right choice for single-turn Q&A or short tasks. If your agent only handles "user asks → agent answers" scenarios, there is no need to upgrade to a more complex paradigm. But once tasks exceed this scope, REPL is no longer sufficient.
Event-Driven — interruptible, nestable.
The event-driven paradigm transforms the kernel from a "synchronous loop" into an "event loop." The kernel no longer blocks waiting for user input — it listens to multiple message sources (user, system, tool execution results) and responds to different types of messages.
CodeCoder's dual-channel topology embodies this paradigm. The command channel transmits only user intent (new messages, shutdown, cancel), while the event channel transmits streaming deltas and structured state (token deltas, tool call start, tool call end). Separating these two channels ensures that high-priority instructions (cancel) are not blocked by low-priority event streams.
The most significant advancement of event-driven architecture is cooperative cancellation. When the user presses Ctrl+C, the system does not kill a thread — it flips a shared CancelToken. The agent's tool execution loop checks this token at safe points; if it has been flipped, the agent terminates gracefully and returns a "cancelled" status. Sub-agents can also exist under the same event channel architecture — the parent agent spawns a read-only AgentLoop instance via the agent tool, and both agents share the same event distribution mechanism.
The event-driven paradigm supports multi-step tasks and sub-agent nesting — this is the foundational paradigm most agent systems should adopt. Its limitation is that tool execution within a turn is still serial and does not support true parallelism. This is not a defect — it is an intentional choice to maintain "determinism within a single turn's logic."
Micro Operating System — multi-tasking, persistent services.
When an agent needs to run as a daemon (listening on sockets, accepting multiple client connections, running background persistent services), the event-driven paradigm is no longer sufficient. This is where the micro-OS paradigm comes in: OS threads + channels, replacing async runtime.
CodeCoder does not use tokio or async-std. There are three reasons: tool calls are often blocking system operations (reading files, running commands) — blocking in an async runtime would stall the entire event loop; sub-agents need independent blocking — OS threads naturally support one thread blocking on a channel receive while other threads continue running; cancellation paths are clearer in OS threads — a shared CancelToken avoids the implicit uncertainty of abort() or drop().
Multi-threading in CodeCoder is primarily concentrated in the kernel infrastructure layer: the main agent thread handles turn logic, worker threads advance headless milestones, the daemon thread listens on Unix sockets, and the compaction thread periodically compresses context. Tool execution within the main turn remains serial — this preserves the predictability of "the agent only does one thing at a time."
These three paradigms are not a history of technological progress — they coexist, each useful in its own domain. The selection criterion is not "which is newer" but "what level of isolation does the complexity of your problem require."
1.3 Three Core Questions → The Book's Backbone
The three core questions posed earlier — identity, capability extension, and safety boundary — run through all five parts of the technical volume.
Identity is the core of Part 1, Philosophical Foundations. Chapter 2, "Filesystem as Self," develops this in full: why identity should come from files, what each of the five identity files is responsible for, and the trust implications of runtime loading versus compile-time injection.
Capability extension is the subject of Part 3, Self-Evolution. The four chapters of Part 3 (Chapters 6-9) cover the Tool/Skill/Capability three-part architecture, the Skill prompt-to-production promotion mechanism, Environment and Lifecycle for Capabilities, and the self-authoring safety loop.
Safety boundary does not belong to any single part — it is a thread that runs throughout. Chapter 4 (Tool System and Permission Model) addresses safety at the tool-calling level, Chapter 9 (The Self-Authoring Safety Loop) addresses safety at the self-modification level, and Chapter 11 (Objective Acceptance Gates) addresses safety at the output quality level.
This backbone is the starting point for the entire book's design. Every chapter in every part responds to at least one of these three questions.
Case Layer
1.4 The Origins of CodeCoder
CodeCoder was not originally a planned project — in this book's pedagogically reorganized account of the design motivation, it was a response to the accumulated frustration of using other agent systems.
Consider a typical scenario (a pedagogically reorganized example, not a verbatim record of a specific project): around 2025, a multi-module refactoring project involves interface adjustments across five crates. The agent in use performs well within each session — but it does not remember the constraints stated the day before. "Don't touch the public interface of this module" — this has to be repeated every new session. Worse, when a task requires more than six steps, the agent forgets the first step's context by step five.
The stopgap in this scenario: paste the full project constraints into the system prompt every turn. The prompt grows longer and increasingly unmaintainable. This naturally raises a question: what if the agent could read a file on its own to know "who I am" and "what my constraints are," instead of the user spelling it out in the prompt every time?
That was the germ of "filesystem as self."
The first version of the prototype was a REPL loop supporting four tools: read_file, write_file, glob, and run_command. It had no permission system, no session persistence, and no concurrency. But it validated one key hypothesis: an agent that can read files, write files, and run commands can accomplish most engineering tasks.
Bottlenecks quickly emerged. The first was cancellation in the REPL loop — when the agent executed a 10-second build, the terminal was completely frozen. The second was context management — a task lasting over an hour generated a massive amount of tool call logs, and the context ballooned rapidly. The third was the capability boundary — to let the agent write its own scripts and execute them, a gate was needed between "writing" and "execution."
CodeCoder's evolution from prototype to production system was a process of solving these bottlenecks one by one — from REPL to event-driven, from no permissions to a four-tier permission model, from a flat tool set to the Tool/Skill/Capability three-part architecture.
1.5 Project Overview
At the time of writing, CodeCoder's scale was as follows:
| Metric | Value | Notes |
|---|---|---|
| Source files | 31 | src/, daemon/, client/ |
| Built-in tools | 26 | From read_file to generate_capability |
| Test cases | 481 | Including 1 #[ignore] |
| Architecture Decision Records | 31 | docs/adr/ |
| Skills | 6 | skills/ directory |
| Capabilities | 1 | capabilities/ directory |
The architecture topology (simplified logical structure):
graph TB
subgraph "Client Layer"
CC[cc client<br/>Terminal TUI]
CI[CI scripts]
BG[headless runner]
end
subgraph "Daemon Layer"
LISTENER[Unix socket listener]
CMDS[cmd_tx<br/>command channel]
EVTS[event_rx<br/>event channel]
AGENT[AgentLoop<br/>event-driven kernel]
REG[Registry<br/>skills/ capabilities/]
end
subgraph "Tool Execution Layer"
TOOLBOX[Tool executor<br/>26 tools]
SUB[sub-agent<br/>read-only instances]
end
subgraph "External Resources"
FS[Filesystem]
SHELL[Shell process]
GIT[Git]
WEB[Web / GitHub]
end
CC -->|socket| LISTENER
CI -->|socket| LISTENER
BG -->|environment variables| LISTENER
LISTENER --> CMDS
LISTENER --> EVTS
CMDS --> AGENT
AGENT --> EVTS
REG -->|system prompt injection| AGENT
AGENT -->|tool calls| TOOLBOX
AGENT -->|sub-agent creation| SUB
TOOLBOX -->|read_file| FS
TOOLBOX -->|run_command| SHELL
TOOLBOX -->|commit| GIT
TOOLBOX -->|web_search| WEB
SUB -->|read-only tools| FS
SUB -->|read-only tools| WEB
style CC fill:#e1f5fe
style CI fill:#e1f5fe
style BG fill:#e1f5fe
style LISTENER fill:#fff3e0
style AGENT fill:#f3e5f5
style TOOLBOX fill:#e8f5e9
style REG fill:#fce4ec
The agent kernel is event-driven + OS threads. Each turn starts with a user message, goes through LLM call → parse tool call → execute tool → return result → loop until the agent autonomously decides output is complete. Tools are executed serially, but multiple agent instances (e.g., headless runner + interactive session) operate in different OS threads.
1.6 First Introduction of the "Filesystem as Self" Principle
In the prototype phase, the agent's "identity" was hard-coded as a constant — much like the system prompt in many other systems. Every time it started, the agent was told "you are a helpful assistant." This identity did not persist across sessions and could not be modified or audited by the user.
Starting from the pain point of "having to tell the agent the constraints every session," CodeCoder's first design decision was: extract the identity statement from a constant and put it into a file.
The core logic of this approach was surprisingly simple:
- Read
AGENTS.mdat startup — this file states the agent's role, code of conduct, and inviolable rules - Read
CONTEXT.md— this file states the domain terminology used by the project and its boundaries - Concatenate the contents of both files into the system prompt — the agent "knows" who it is at the start of every turn
This means users can change the agent's behavior by modifying these two files. No recompilation needed, no daemon restart needed — edit the file, trigger a hot reload, and it takes effect on the next turn.
This simple decision spawned additional identity files: skills/ (procedural knowledge), capabilities/ (executable artifacts), and memory/ (cross-session memory). Five types of files, five different "components of self" — this is the full content of the "filesystem as self" principle.
This principle will be fully developed in Chapter 2. For now, it is enough to remember its starting point: an agent's identity should not be a string constant in an API response; it should be a set of files on disk that are auditable, modifiable, and version-controllable.
ADR Deep Reading
The Rejected Approach: Single-Provider Binding
In CodeCoder's early design phase (before ADR 0015), there was an option that was seriously considered: directly binding to a single LLM provider's message format. Specifically, using OpenAI's Chat Completion API format as the kernel's message model, without an intermediate abstraction layer.
Arguments in favor:
- OpenAI's API format is the de facto standard — most agent projects are based on it
- One less layer of abstraction = one less layer of translation overhead and debugging complexity
- If only using one provider, provider-neutrality is unnecessary
Reasons for rejection:
- Once the kernel is bound to OpenAI's format, switching to Anthropic, Google, or another provider requires extensive modification of kernel code
- Provider API formats are evolving rapidly — if the kernel is too tightly coupled, provider changes force kernel changes
- Multi-provider support in agent systems is not a "nice-to-have" — it is "fault tolerance." When one provider's API is unavailable, the system can fall back to another
The final decision was to pursue provider neutrality: define a set of kernel-native message models (Message, MessageItem, ToolCall, ToolResult) and add a protocol adapter layer between the kernel and the provider. This decision was solidified in ADR 0017 (Provider Neutral Message Model).
ADR 0016 (Channel Topology and Event Model) addressed another early question: how events should flow within the kernel. The initial prototype used a single channel for all messages — instructions and events were mixed together. When cancellation instructions got blocked by token event streams, this design was scrapped in favor of dual-channel separation.
The common thread among these early ADRs is: they were not written to add features; they were written to fix a design flaw that had already surfaced. This is a notable characteristic of CodeCoder's Architecture Decision Records — most ADRs document real evolution, not "pre-designed" documentation. This pattern will recur throughout the ADR interpretations in subsequent chapters.
This chapter presented the book's backbone — three core questions. The next chapter answers the first question: identity.