Skip to content

Appendix 01

Reading Index for Part II · Pi in Source: Files Worth Reading Line by Line ​

Xia A and Lao Z use a magnifying glass to find a few key entry files in layered shelves and connect them along a main call path.

Xiao A finished Part II · Pi in Source and wanted to pick files in pi-mono to study closely on his own. He opened a directory at random: thousands of files, and no idea where to start. So he went back to Lao Z.

"Don't read everything, and don't read at random." Lao Z said. "The biggest trap in reading source code is treating every file the same. Some files are entry points: read one and you understand a main path. Others are only adaptation details: a glance to learn what problem they solve is enough. Generated models, third-party dependencies, and the various README files should never be read line by line — reading them would not stick anyway. This appendix is built on that idea: each path gets one table, and each table is followed by notes on how to read it and how not to. The 'What to Read First' column tells you the entry point; the notes tell you where misreading usually happens. The cost of reading in the wrong direction is usually higher than the cost of reading slowly. When time is short, read the notes before the table — not the other way round."

This appendix serves Part II · Pi in Source. Consult it once you have finished a round of chapter reading and want to go deep on one call chain. Every path and symbol was verified against the upstream pi tag v1.0.0 (commit a13d35a742c6ef8462812a28fbe1d8c8b7431c32). It lists only files where understanding one means understanding a main trunk; it does not try to cover the whole repository.

"One more thing matters," Lao Z said. "This index maps to the v1.0.0 snapshot. If you check out a different version, the paths may still exist, but line numbers drift and interfaces get renamed. The value of an index rests on a matching baseline. Between 0.83.0 and 1.0.0, the whole harness directory moved. So run git rev-parse HEAD in your upstream clone first to confirm the baseline, or read the pi/ snapshot in this book's repository directly, and only then place the entries. With the wrong baseline, every 'What to Read First' is a hint, not a coordinate."

How to Order Reading Priorities ​

Lao Z gave Xiao A three ordering principles:

  1. Entry points before details: come in through the export table in index.ts and one create* or run* function; see the main path clearly before diving into branches.
  2. Contracts before implementations: read a module's types.ts or interface definitions before the module itself; you need implementation details only when you suspect a bug.
  3. State before logic: which members a class has, and who changes them when, matter ten times more than whether one if is written cleverly.

Xiao A: "Of the three principles, which one gets broken most often?"

"The second one." Lao Z said. "Many people enter a module and go straight for the implementation functions, reading line by line at the ifs and fors. When they finish, they still cannot say what the module guarantees to the outside, because they skipped types.ts. An interface is a promise; an implementation is that promise kept. If you look at the keeping before the promise, you see a pile of code written 'by coincidence', and you cannot tell which shapes exist only to satisfy the contract."

"Let me add one rule of thumb." Lao Z continued. "When you see a member variable being assigned, note down who assigns it, and where. State is assembled from 'who changed it, when'. Take a currentSession field: if you cannot find every place that assigns it, you will never know when the session gets replaced. This sounds plain, and it saves the most time when you debug."

Xiao A filtered the repository by those three principles. The files worth reading line by line, grouped by which main path they unlock, follow below.

The Model Request Path ​

Path and SymbolRelated TopicWhat to Read FirstBoundary
packages/ai/src/models.ts: ModelsImpl, createProviderPart II Chapter 2: Directories and the Registry — pi-aiHow applyAuth merges credentials; how streamSimple delegates to a ProviderDoes not expand each Provider
packages/ai/src/api/lazy.ts: lazyStreamPart II Chapter 3: Issue the Ticket First, Do the Work After — pi-aiHow the work splits between delayed initialization and the error entry pointlazyStream only handles timing; it does not define any vendor's event format
packages/ai/src/auth/resolve.ts: resolveProviderAuthModel authenticationThe credential source chain and failure propagationDoes not audit secure storage in credential backends

Lao Z singled one out: models.ts is the facade of the whole pi-ai package. It is not the longest file, but ModelsImpl concentrates Provider mapping, model lookup, credential merging, and delegation to the stream entry point in one place. Read the dozen lines of streamSimple and you have read every main path pi-ai exposes to the outside. As for each Provider's adaptation details (anthropic, openai-responses, openai-completions, google, and the rest), pick one and read it deeply during the Source Walkthrough; compare the others for their differences.

"When you read streamSimple, the easiest mistake is assuming it handles authentication for you." Lao Z said. "Credential merging happens in applyAuth. streamSimple only wires the authentication result and the Provider choice onto the stream. It is an assembly point, not the logic itself. To find the root cause of an authentication error, go down into applyAuth and resolveProviderAuth. Do not circle the assembly point."

"Then lazyStream must just be a lazy-loading optimization."

"That word is only half right." Lao Z said. "Its job is to delay stream creation until subscription, and to unify the error entry point. That makes it a performance design and an error-handling design at the same time: errors become observable stream errors before subscription, instead of scattering across the creation phase. When you read lazyStream, do not only notice that it acts one step later. Notice what shape it gives failure."

"Isn't resolveProviderAuth easy to misread too?"

"Easily. Some people think it audits credential security. What it actually does is resolve and merge credentials: follow the configuration chain to find which credentials apply, then throw the failure upward. Secure storage of credentials is another layer's business, and not its concern. The boundary column says 'does not audit secure storage in credential backends' precisely to warn you against reading past that line." Xiao A wrote the three misreadings down beside the table.

The Loop and Session Path ​

Path and SymbolRelated TopicWhat to Read FirstBoundary
packages/agent/src/agent.ts: Agent, subscribePart II Chapter 4: The Two-Layer Loop — pi-agent-coreThe state contract of the transcript, the listeners, the message queue, and the abort controllerEvents are not persistence
packages/agent/src/agent-loop.ts: runAgentLoop, runToolCallThe loopHow stop reason is decided, how tool results are collected, and the turn hooks such as finishTurnDo not infer business turns from nested loops
packages/coding-agent/src/core/agent-session.ts: AgentSessionPart II Chapter 5: The Dispatch Desk — pi-coding-agentThe subscribe call and the _install* hooks in the constructorVery long; skip through it by hook
packages/coding-agent/src/core/session-manager.ts: SessionManagerSessionsHeader version 3, entry types, and _persist's lazy creation and synchronous appendJSONL stores more than conversation messages
packages/coding-agent/src/core/agent-session-runtime.ts: forkSession forkThe at/before boundary and createBranchedSessionDoes not create a Git worktree
packages/coding-agent/src/core/compaction/compaction.ts: compact, getSummarizationFailureCompactionHow summarization fails: error or lengthSummarization is not lossless recovery
packages/coding-agent/src/core/skills.ts: loadSkillsFromDir, formatSkillsForPromptSkillsDiscovery rules, diagnostics, and listing only name and descriptionTextual description is not an authorization constraint

"The trap most people step in, in this group, is here." Lao Z pointed at agent-loop.ts and agent-session.ts. "runAgentLoop is a stateless loop. AgentSession is the stateful assembly layer. Many people treat the loop as the master control function and think that reading it means understanding sessions. That reverses the direction: the loop does not know how far the session has been written, does not know when to compact, and does not know which skills are available. v1.0.0 draws this line more clearly still — session code is not in the agent package at all. Read the two layers separately, or you will mistake an assembly decision for a core mechanism."

"I get it," Xiao A cut in. "subscribe is the event log. If you miss something, replay it."

"That guess is off. subscribe looks like an event hub, but events are not persistence. What a listener receives is a one-time observable fact, not a log you can replay. Replaying needs JSONL, not subscribe."

"What about session-manager.ts?"

"Storing the conversation is only part of it. Beyond header version 3 and the entry types, it also stores configuration changes, usage, compaction records, context edits, and navigation information. Read the JSONL as a 'message archive' and you miss most of it. Also note that malformed lines are skipped when reading — no error is raised."

"The misreading of compaction.ts hides deeper." Lao Z continued. "Many people think compaction is lossless tidying. It is actually a separate request that can fail: if the summarization request errors, or gets truncated by a token limit, compaction fails, and an incomplete summary is never written into the session. Read it as silent truncation and you will not handle the failure branch — and the failure branch is exactly one chapter of this book's failure handbook. Read compaction expecting it to fail."

"The boundary of skills.ts points the other way." Lao Z said. "Textual description is not an authorization constraint. A skill is a package of instructions that influences the model through text, and the model may not follow it. It even puts only the name and description into the prompt; the model has to read the body itself with read. When you read skills.ts, do not treat it as a security mechanism."

The Product and Terminal Path ​

Path and SymbolRelated TopicWhat to Read FirstBoundary
packages/tui/src/tui-main-screen.tsPart II Chapter 6: The Dispatch Desk and the Workstation — pi-tuiMain-screen differential updates, the previous frame previousLines, and width checksRead it together with the terminal layer
packages/coding-agent/src/main.ts, cli.tsPart II Chapter 7: The Address in the Household Register — pi-coding-agentEntry point, argument parsing, mode selection, cwd resolutionNot a single master control function
packages/coding-agent/src/core/agent-session-services.tsProduct service assemblyThe order of resources, model runtime, and Provider registrationServices come before sessions
packages/coding-agent/src/core/tools/file-mutation-queue.ts: withFileMutationQueueTool concurrencySerializing writes to the same path, released in finallyDoes not cover all external resources

Xiao A stared at the early-return branches in main.ts: "What is this pile of if (... return; for?"

"They are all commands that never start a session: package management, configuration, --version, export, help, the model list." Lao Z said. "Do not read every return as a failed call. The logic that really creates the session comes after them. The trick to reading main() is to skip every early return first and go straight to the createSessionManager() line. That is where the product's main path actually starts."

Xiao A looked at the two files a while longer: "They are this long. All the session logic can't be living here, can it?"

"That step walks straight into this group's misreading." Lao Z said. "They look like master control, but they are the entry point and argument parsing — not the core session logic. agent-session-services.ts assembles long-lived services; agent-session.ts assembles one session. Do not merge those two similar-sounding kinds of 'assembly' into one concept. While reading the coding-agent package, keep the boundary column's 'Not a single master control function' in mind. The main path is spread across several files; it does not sit inside one big function."

"One more thing to watch in file-mutation-queue.ts." Lao Z added on his own. "Some people think it makes every write safe. withFileMutationQueue only serializes writes to the same path, and finally guarantees the release. Different paths are out of scope, network requests are out of scope, other external resources are out of scope too. Do not inflate serialized file writes into general concurrency safety."

The Protocol, Server, and Evaluation Path ​

Path and SymbolRelated TopicWhat to Read FirstBoundary
packages/protocol/src/protocol.tsPart II Chapter 9: Shipment and Customs — pi-protocolThe envelope union type, additionalProperties: false, PROTOCOL_VERSIONA schema does not replace authorization; the payload is Unknown
packages/protocol/src/codec.ts: ValidatedMessageDecoderprotocolThe chaining of frame → CBOR → validationA decoder cannot be reused after it fails
packages/protocol/src/framing.ts: FrameDecoderprotocolThe 4-byte big-endian length prefix, a trailing partial frame, and a half frame at EOFCheck the limit against the configuration
packages/server/src/server.ts: ServerPart II Chapter 10: The Blackboard and the Sticky Notes — pi-server + pi-clientThe handshake phases, and handleRequest's routing and error codesDoes not prove automatic reconnection
packages/server/src/session-router.ts: SessionRouterSession attachmentOne attachment per connection, attachmentId, release on disconnectNot a distributed transaction guarantee
packages/evals/src/harness.ts: runPiCodingAgentPart II Chapter 11: A New Exam Room for Every Test — pi-evalsTemporary directory mkdtemp, environment isolation, artifact collection, and cleanupReal models are non-deterministic

"Read these three protocol files in reverse order." Lao Z said. "Start with protocol.ts to see what objects the protocol accepts, then codec.ts to see how objects become bytes, and last framing.ts to see how bytes are cut into frames. These are the three layers of protocol reliability. Get the order backwards and you will take a framing boundary for a schema constraint."

"This group has one more misreading worth stating separately: taking a schema for authorization." Lao Z said. "additionalProperties: false in protocol.ts is a shape constraint, not an authorization constraint. It guarantees that the message structure conforms to the protocol. It does not guarantee who is entitled to send that message."

"What about codec.ts? If the decoder fails, skip it and use the next message."

"The mistake is in 'use the next message'. ValidatedMessageDecoder chains three layers — frame → CBOR → validation — and it is stateful. Once one layer fails, that state is no longer trustworthy, and you must build a new decoder. When you read codec, treat the recovery path after a failure as the main text, not as exception handling in a corner."

"The limit is written in the source. Just use what it says." Xiao A pointed at framing.ts.

"Not necessarily — it may be configurable. FrameDecoder handles the 4-byte big-endian length prefix, a trailing partial frame, and a half frame at EOF, but you must check the length limit against the configuration. While reading, trace where the limit comes from. Do not assume the value in the source is the only answer."

"The server and evaluation group has two boundaries too." Lao Z said. "server.ts handles the handshake, request routing, and cleanup on disconnect, but it does not prove automatic reconnection. SessionRouter in session-router.ts manages attachment and release, but it is not a distributed transaction guarantee: after a disconnect, whether an in-flight request ran is still unknown. Both boundaries are 'looks like it, isn't it'. Read them against the boundary column, and do not let the name draw the conclusion for you. In evals/src/harness.ts, watch out for real-model non-determinism: it creates a temporary directory, collects artifacts, and runs cleanup, but results from a real model are not proof of determinism. That is the baseline condition of evaluation, not a bug."

What Not to Read Line by Line ​

Xiao A asked: "So which files don't I need to read?"

Lao Z listed three kinds:

  • Generated models: type code produced by TypeBox. It is a machine artifact; reading the schema definition beats reading it.
  • Manifests and configuration: package.json, tsconfig.json, and the various lock files. A glance to learn the versions and dependencies is enough.
  • Third-party dependencies: what is inside node_modules is not pi-mono's design. If you want to study it, go to that project's own repository.

"Three kinds to skip, for three different reasons." Lao Z said. "Generated models are written by a machine; do not read them as if a person wrote them. Their shape comes from the schema, so reading the schema costs less. Manifests and configuration are state, not logic: they tell you who you depend on, not how the system runs. Third-party dependencies are somebody else's boundary — before you enter node_modules, ask one question: is this line a pi-mono decision or upstream behavior? If you cannot answer it, stop reading. Telling apart 'who made the decision' matters more than telling apart 'where the code is'."

"Beyond that list there is one more kind: test files." Lao Z continued. "Tests are the most underrated instruction manual — they state 'how this module should behave' more concretely than any document. When the implementation confuses you, open the matching test first. A test's name is the behavioural contract; a test's assertion is the boundary. The question 'why is it written this way?' is usually answered in the tests, not in the docs."

"If tests are the manual," Xiao A asked, "in what order should I read tests and implementation against each other?"

"One convention: when you cannot follow the implementation, read the test first; when the test makes sense, go back to the implementation." Lao Z said. "A test gives you observable behaviour: give this input, get that result. An implementation gives you the internal mechanism: how it gets there. Learn what it is supposed to look like first, then how it does it. Reverse the order and you will guess behaviour from implementation details, and get it wrong nine times out of ten."

"And before I start changing things?"

"Run the matching test group first. Do not judge behaviour from what you read alone." Lao Z said. "Reading tells you 'it should be this way'. Tests tell you 'it is this way'. Between the two sit side effects and configuration you did not notice. Reading produces hypotheses: you read that 'this function returns empty when given an empty array' — that is a hypothesis. It becomes knowledge only after a test confirms it. Skip the test and change the code, and when you break something you cannot tell whether you were wrong or whether you misread."

Suggested Reading Route ​

If time is short, Lao Z recommends one main line: models.ts → agent-loop.ts → agent-session.ts → main.ts. Chained together, these four files give the full shape of one complete request: it starts at the model request, runs through the loop, is organized by the assembly layer, and is finally wired in from the command-line entry point. Finish this main line, then branch into protocol, tui, or evals as your interest directs.

"Why this order?" Xiao A asked.

"It is ordered by data flow, not by lines of code." Lao Z said. "A request is produced at the model layer, runs in the loop layer, is organized by the assembly layer, and enters from the entry layer. Read along the data flow and each layer's output is the next layer's input; your understanding stays continuous. Read it backwards — from entry to model — and it still works, but you see 'who calls it' before 'how it is implemented', and it is easy to get lost in the assembly noise at the entry point. Following the data flow is the least effortful way to read among these four paths."

"Of those four files, which one should I master first?"

"agent-session.ts." Lao Z said. "It is the most intertwined one: the Agent, the model, the tools, the prompts, session writes, compaction, and extensions are all wired together there. Master it and half your questions about the other three files disappear, because it lays their relationships out in the open. It is the most roundabout read, and it is the thread that sews these four paths together."

"After the main line there is a supplementary line." Lao Z said. "session-manager.ts and compaction.ts deserve a separate read. They decide session persistence and context management: outside 'one complete request', but they decide the quality of a long session. The main line answers how one request runs. These two files answer how state is stored and trimmed after many runs. For short sessions, read the main line; for long sessions, read these two. In the end, reading source code is about building a verifiable map: next time you hit a bug or want to extend something, you know which file to open."

Xiao A drew this main line on the first page of the index: model → loop → assembly → entry, with a small note beside it: "read the data flow first, the details after".