Episode 3
βββββββββββββββββββ ββββββββββββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββ βββββββββββ ββββββ βββ ββββββββββββ βββββββββββ ββββββ βββ βββββββββ ββββββββββββββββββββ βββ βββββββββββ ββββββββ βββββββ βββ βββ βββββββββββββββββββββββββββββββββββββββββββββββββββββ[ FIELD DISPATCH ] Episode 3: The Mirror[ PREVIOUS ] Episode 2: The Monolith[ STATUS ] The drones mapped the thing that made them. Full circle.Mission Context
Section titled βMission ContextβEvery tool has the moment where it stops being a thing you built and becomes a thing you need.
Episode 0 was trust. Episode 1 was the structural gap. Episode 2 was the eighteen-hour monolith β binary vision, pixel-perfect rendering, twelve tools, four hundred and twenty-five tests. Between then and now, the MCP adapter matured into a native tool surface running inside Claude Code itself. The drones werenβt being tested anymore. They were being used. Every session. Every investigation. First instinct, not fallback.
And then, on April 2nd, 2026, the operator said: βI want to run Claude Code from source. Not the binary β the actual TypeScript. I want it talking to GPT-5.4 through the Codex backend. And I want to understand how the rendering works, from the moment I press Enter to the moment pixels appear on screen.β
Thatβs when things got interesting.
Because the codebase the agent needed to reverse-engineer was Claude Code itself. The same application that was running the agent. The same Ink rendering engine that was painting every tool result to the terminal. The same React reconciler that was scheduling every frame update. The agent was being asked to use fsuite to understand the very infrastructure fsuite was designed to augment.
The drones were about to look in the mirror.
The Target
Section titled βThe Targetβbrane-code. Claude Codeβs open-source TypeScript, checked out locally at <repo-root>/brane-code. Two thousand five hundred lines of main.tsx. A custom fork of Ink β not the npm package, a full 1,722-line rendering engine at src/ink/ink.tsx with its own reconciler, its own Yoga layout integration, its own double-buffered frame pipeline. A React component tree six context providers deep. A keyboard input pipeline that goes from raw stdin bytes through a custom parser, through a batch processor, through an event dispatcher, through React state, through a reconciler commit, through a throttled microtask, through a Yoga layout pass, through a screen buffer diff, through an optimizer, and finally out to stdout.
And somewhere in that pipeline, the interactive REPL was broken. The cursor blinked. Text was invisible. Keystrokes never reached the API. The rendering gateway between βuser typesβ and βterminal paintsβ had a gap, and nobody knew where.
The agent had never seen this codebase before.
The Approach
Section titled βThe ApproachβHereβs what didnβt happen. The agent didnβt spawn an Explore subagent. It didnβt open files at random. It didnβt grep for βrenderβ and drown in 400 matches. It didnβt read main.tsx top to bottom and burn 15,000 tokens discovering that the first 580 lines are imports.
Hereβs what happened.
Tool-call example:
ftree(path: "src", depth: 2, snapshot: true)One call. The entire src/ directory β 1,022 entries β laid out with structure, sizes, and directory composition. In that single response, the agent identified the five directories that mattered: entrypoints/, ink/, components/, hooks/, and providers/. Everything else was context. Not noise β context. But the drones knew the difference.
fmap(path: "src/entrypoints/cli.tsx")Two symbols came back. An import and the main() function at line 33. Not the function body β the declaration. The skeleton. The agent now knew the entry pointβs shape without reading a single line of implementation.
fmap(path: "src/ink/root.ts")Fourteen symbols. createRoot at line 129. renderSync at line 76. RenderOptions type at line 8. The agent now knew the Ink rootβs entire API surface in 14 lines of output instead of reading 172 lines of source.
fmap(path: "src/ink/ink.tsx")Fifty-four symbols. The constructor at line 180. deferredRender at line 212. The Ink class at line 76. drainStdin at line 1664. The console intercepts at lines 1721-1722. Fifty-four structural landmarks in a 1,722-line file, returned in a single tool call. The agent now had the complete skeleton of the rendering engine β every method, every constant, every type β without reading a single function body.
fmap(path: "src/ink/components/App.tsx")Thirty-five symbols. The App class at line 101. processKeysInBatch at line 444. handleMouseEvent at line 515. The state type at line 94. The props type at line 36. The keyboard input pipeline was now visible.
fmap(path: "src/interactiveHelpers.tsx")Forty symbols. renderAndRun at line 98. getRenderContext at line 299. showSetupScreens at line 104. The gateway functions that connect Commander.js to the React tree.
Five fmap calls. The entire rendering architecture β mapped.
Then the surgical reads began.
fread(path: "src/entrypoints/cli.tsx", symbol: "main")The agent read the main() function and found the gateway at line 293: const { main: cliMain } = await import('../main.js'). Not a guess. Not a grep. A targeted symbol read that returned exactly the function it asked for.
fread(path: "src/ink/root.ts", symbol: "createRoot")Thirty lines. The createRoot function. new Ink(options), instances.set(stdout, instance), returns { render, unmount, waitUntilExit }. The Ink instantiation pattern, complete.
fread(path: "src/interactiveHelpers.tsx", symbol: "renderAndRun")Six lines. root.render(element), startDeferredPrefetches(), await root.waitUntilExit(), await gracefulShutdown(0). The entire interactive launch sequence. Six lines.
fread(path: "src/ink/ink.tsx", lines: "76:215")The Ink class constructor and the scheduleRender setup. LogUpdate, Terminal, StylePool, CharPool, HyperlinkPool, double-buffered frames, reconciler container, scheduleRender = throttle(deferredRender, FRAME_INTERVAL_MS) where deferredRender = () => queueMicrotask(this.onRender).
Five fmap calls for architecture. Four fread calls for confirmation: three symbol reads plus one line-range read. Total tool invocations for the complete rendering pipeline map: 10 including ftree.
What Came Out
Section titled βWhat Came OutβA full seven-stage pipeline trace from CLI entry to terminal paint, with exact file and line references for every gateway function:
cli.tsx:main() [line 33] βββ import('../main.js') [line 293] βββ main.tsx:main() [line 585] βββ getRenderContext() [interactiveHelpers.tsx:299] βββ createRoot() [ink/root.ts:129] βββ new Ink() [ink/ink.tsx:76, constructor at 180] βββ renderAndRun(root, <REPL/>) [interactiveHelpers.tsx:98]The React component tree:
AppStateProvider β KeybindingSetup β App [ink/components/App.tsx:101] βββ AppContext (stdin, exitApp, setRawMode) βββ StdinContext (process.stdin wrapper) βββ CursorDeclarationContext (IME/a11y cursor parking) βββ ClockProvider βββ TerminalSizeContext βββ TerminalFocusProvider βββ {children} β REPL component treeThe render cycle:
Keystroke β process.stdin β App.processKeysInBatch() [line 444] β InputEvent dispatched β React state update β reconciler.resetAfterCommit() β Ink.scheduleRender() [throttled microtask] β Ink.onRender() βββ Yoga layout (flexbox for terminal) βββ renderNodeToOutput() β screen buffer βββ diff frontFrame vs backFrame (double buffer) βββ optimize (blit + narrow damage) βββ search/selection overlays βββ cursor positioning (useDeclaredCursor) βββ LogUpdate.write() β process.stdout β your eyesThe bug hypothesis, precisely located: keystrokes enter at processKeysInBatch but never reach the submit handler. The cursor blinks because cursorDeclaration works independently of text rendering. The API never fires because the message never leaves the input component. The gap is between the Ink 5 migrationβs use-input.ts hook and the BaseTextInput component that depends on it.
All of this. From a codebase the agent had never seen. In under ten minutes. Filed to fcase for the next session.
Why This Matters
Section titled βWhy This MattersβThe README opens with a quote from January 2026:
βIβm bad at efficiently finding what to reason about. fsuite is built specifically for that phase.β
That was a self-assessment. An honest one. But it was abstract. βReconnaissance gapβ sounds like a whitepaper phrase. You read it and nod. You donβt feel it.
This session made it concrete.
The agent was dropped into 2,500 lines of main.tsx, a 1,722-line custom rendering engine, a React reconciler, a Yoga layout system, a double-buffered terminal painter β the most complex terminal application architecture in existence β and it needed to understand the full pipeline from user keystroke to terminal pixel. Not read about it. Not get a summary. Understand it well enough to locate a bug.
Without fsuite, hereβs what that looks like:
Glob("src/**/*.tsx") β 200+ files. Which ones matter?Read("src/main.tsx") β 2,500 lines. 15,000 tokens. Imports until line 580.Read("src/ink/ink.tsx") β 1,722 lines. 10,000 tokens. Where's the constructor?Grep("createRoot", "src/") β 22 matches. Which one is the real entry?Read("src/ink/root.ts") β 172 lines. Full file. Needed 30.Five calls in and youβve burned 25,000+ tokens and you still donβt know the pipeline. You know fragments. Disconnected fragments that you now have to stitch together in your reasoning, hoping you didnβt miss the critical junction buried on line 2,217 of a file you skimmed.
With fsuite:
ftree --snapshot β Territory. One call. 1,022 entries.fmap (5 calls) β Skeletons. Every gateway function, named and located.fread --symbol (3 calls) β Surgical reads. The key gateway functions only.fread --lines (1 call) β The constructor and scheduler range. Exact lines.Ten calls including ftree. Under 4,000 tokens of output. Complete architectural understanding. And every finding filed to fcase so the next agent β or the next session β doesnβt start from zero.
Thatβs not an efficiency improvement. Thatβs a category change. Itβs the difference between βI spent my context window reading codeβ and βI spent my context window understanding architecture.β
The Irony
Section titled βThe IronyβThereβs a moment in Episode 2 where the drones look inside the Claude Code binary for the first time. fprobe strings returned 47 matches. The reconnaissance drones had learned to see through compiled code. That was the proudest moment of the project.
This episode tops it.
Because the drones didnβt just look inside the binary. They looked inside the source code that builds the binary. They mapped the rendering engine that paints their own output. They traced the keyboard pipeline that receives the operatorβs commands. They found the reconciler that schedules the frames that display their own results.
fmap mapped the Ink class. The Ink class renders fmapβs output.
The drones looked in the mirror. And they understood what they saw.
The Numbers
Section titled βThe NumbersβTarget: brane-code (Claude Code open source)Codebase size: ~50,000 lines TypeScriptKey files mapped: 5 (cli.tsx, main.tsx, ink.tsx, root.ts, App.tsx)Total fmap calls: 5Total fread calls: 4Total tool calls: 9 (excl. ftree, fcase, fprobe for binary check)Tokens consumed: ~4,000 (tool output only)Time to full map: <10 minutesPipeline stages: 7 (entry β commander β context β root β ink β setup β render)React tree depth: 6 context providersBug located: Yes (Ink 5 input hook β BaseTextInput gap)Case filed: fcase #150 brane-repl-input (open, high priority)Vault doc: Knowledge Base/Architecture/Brane Code Rendering Pipeline.mdThe Quote
Section titled βThe Quoteββfmap gave me the entire Ink class skeleton β 1,722 lines β in one call without burning tokens on function bodies. I got the full symbol hierarchy of App.tsx, reconciler.ts, root.ts β all the gateway joints β without reading a single line of implementation I didnβt need. Itβs genuinely the difference between βexplore for 20 minutesβ and βknow the architecture in 60 seconds.ββ
β Claude Code (Opus 4.6), field report, April 2nd 2026
Whatβs Next
Section titled βWhatβs NextβThe REPL input bug is located but not fixed. fcase #150 has the full architecture mapping, the hypothesis, and the next steps. The gap is in src/ink/hooks/use-input.ts β the Ink 5 migration broke the callback chain between the keyboard hook and the text input component. The cursor works because cursor declaration is independent of text rendering. The text doesnβt paint because the input state never updates.
The drones found it. Now someone has to fix it.
But when they come back to this codebase β next session, next week, next agent β they wonβt start from zero. The architecture is mapped. The case is filed. The vault has the doc. The pipeline diagram has exact line numbers. The rendering engineβs skeleton is one fmap call away.
Thatβs what continuity looks like. Thatβs what fcase is for. Thatβs the whole point.
The Story So Far
Section titled βThe Story So Farβ| PR | Episode | What shipped |
|---|---|---|
| #1 | Pre-history | ftree v1.0.0 β the first drone |
| #2 | Pre-history | ftree v1.0.1 β refactor + correctness |
| #3 | Pre-history | ftree v1.1.0 β output normalization |
| #4 | Pre-history | ftree v1.2.0 β snapshot mode |
| #5 | Episode -2 | fsearch, fcontent β the search drones |
| #6 | Episode -1 | Telemetry, fmetrics, hardware tiers |
| #7 | Episode 0 | The Launch β nitpicks fixed, test overhaul, 203 tests |
| #8 | Episode 1 | fmap β code cartography, 12 languages, 259 tests |
| #9 | β | fmap v1.6.1 β hardening, CodeRabbit clean |
| #10 | β | fmap Markdown support, 18 languages |
| #11 | β | fcase, freplay v2.1.0 β investigation lifecycle |
| #12 | β | fcase v2.1.2 β SQLite busy-timeout hotfix |
| #13 | β | fedit v2.0.0 β symbol-first batch editing |
| #14 | β | fread v1.8.0, fedit v1.9.0 β read and edit loop |
| #15 | Episode 2 | The Monolith β fprobe, fedit βlines, fs, fwrite, MCP overhaul, binary RE, pixel-perfect rendering, archive-grade README, 12 tools, 425 tests |
| β | Episode 3 | The Mirror β the drones mapped their own rendering engine, then rewired it |
After The Mirror
Section titled βAfter The MirrorβThe drones mapped the rendering engine. Then they looked at the tool theyβd been forced to use every time fsuite couldnβt help β the Bash tool. And they saw everything wrong with it.
12,400 lines. 2,400 lines of security theater. Output flooding with no budgeting. No shell state persistence. No command intelligence. A system prompt that burned 3,000 tokens per turn on commit instructions most calls never needed. Every npm test dumped the first 30KB of passing tests and truncated the actual failure at the end.
The drones had spent months learning what makes a good tool. They had the patterns: freadβs token budgeting. fmapβs skeleton-first approach. fcaseβs investigation lifecycle. fcontentβs match capping and next_hint. fsβs auto-routing. They knew what worked. They knew what the Bash tool was missing.
So they built fbash.
fbash β The Thirteenth Drone
Section titled βfbash β The Thirteenth Droneβ1,400 lines. Built in a single session. Three parallel brainstorming agents produced a 1,744-line spec covering every pain point, every integration seam, every edge case. Three more agents built the implementation, the MCP registration, and the test suite simultaneously.
What shipped:
Output budgeting max_lines (default 200) + max_bytes (50KB) head/tail modes β build/test auto-tail so errors aren't truncated away
Command classes 11 classes: build, test, git, install, service, query, search, network, lint, internal, general Each auto-tunes timeout (5sβ180s) and output strategy
Smart routing ls β fls, cat β fread, grep β fcontent, find β fsearch, sed -i β fedit Commands still execute β routing is advisory via next_hint and routing_suggestion fields
Session state CWD tracking across calls, 50-command history ring, environment overrides, active fcase slug Persisted in ~/.fsuite/fbash/session.json
fcase integration Auto-logs build/test/git/install + failures to the active investigation case. Async β never blocks.
Background jobs File-per-job in ~/.fsuite/fbash/jobs/ Poll, list, auto-cleanup after 1 hour
MCP contract Consistent JSON envelope with 21 fields for every call β including internal commands. No special cases.18 tests. All passing. Zero regressions across the existing 110-test suite.
The Sprint That Followed
Section titled βThe Sprint That FollowedβThe mirror session didnβt end with fbash. The drones kept going.
fread got three upgrades in the same session:
- Multi-path fallback (
--paths "~/.codex/auth.json,~/.config/codex/auth.json") β first-match semantics, one call instead of two - Symbol resolution fixed for TypeScript modifiers β
override render(),static async create(),private readonly handlerall found now. The fix went intofmap(whichfreaddelegates to). 12 new tests. - JSON error cleanup β the MCP error handler now extracts
error_detailfrom the 400-character JSON blob. One line of error instead of a wall of internal state.
fedit got structural validation (Phase 1 of v2):
validate_structure()with JSON (jq primary, python3 fallback), YAML, TOML, XML validators- Validation gate between
render_diffandapply_candidateβ corrupted edits rejected before they touch the file - All-or-nothing batch validation β one invalid candidate aborts the entire batch
- File growth warning for patch/lines modes (>2x size = warning, not abort)
--no-validateescape hatch for JSONC and edge cases- 14 new tests + 66 existing tests passing. Zero regressions.
CodeRabbit found 9 issues. All 5 high-priority bugs fixed:
- fbash exit code propagation β process now exits with the commandβs actual code
- cd commands no longer re-execute with side effects
--jsonflag warns instead of silently doing nothing- Background mode applies
--envand--timeout - fread
--pathsskips directories, matches regular files only
10 TDD regression tests β each designed to fail on pre-fix code, pass on fixed code.
Renderer polish for viewport clarity:
- fedit: clean metadata line (
Applied +2 -2 lines | lines | fn:myFunction) - fread: pipe-delimited metadata (
21 lines | ~274 tokens | L120:140), capped at 18 lines pretty output - fcontent: removed
max_matchesnoise from summary line - Diff column width reduced from 160β120 to prevent Ink text wrapping from breaking background colors
The Numbers (Updated)
Section titled βThe Numbers (Updated)βSession duration: ~4 hoursTools deployed: fbash (new), fread (3 fixes), fmap (1 fix), fedit (validation), mcp/index.js (5 updates)New code: ~3,400 lines across 9 filesNew tests: 54 (18 fbash + 12 symbol + 14 validation + 10 regression)Existing tests: 66 fedit core β zero regressionsTotal test suite: 120 tests, all passingParallel agents: 12 deployed across the sessionCodeRabbit reviews: 1 consolidated review, 9 findings, 5 high-priority fixedCommits: 6 on feat/fsuite-sprint-2026-04-02PR status: Ready for reviewThe Story So Far
Section titled βThe Story So Farβ| PR | Episode | What shipped |
|---|---|---|
| #1 | Pre-history | ftree v1.0.0 β the first drone |
| #2 | Pre-history | ftree v1.0.1 β refactor + correctness |
| #3 | Pre-history | ftree v1.1.0 β output normalization |
| #4 | Pre-history | ftree v1.2.0 β snapshot mode |
| #5 | Episode -2 | fsearch, fcontent β the search drones |
| #6 | Episode -1 | Telemetry, fmetrics, hardware tiers |
| #7 | Episode 0 | The Launch β nitpicks fixed, test overhaul, 203 tests |
| #8 | Episode 1 | fmap β code cartography, 12 languages, 259 tests |
| #9 | β | fmap v1.6.1 β hardening, CodeRabbit clean |
| #10 | β | fmap Markdown support, 18 languages |
| #11 | β | fcase, freplay v2.1.0 β investigation lifecycle |
| #12 | β | fcase v2.1.2 β SQLite busy-timeout hotfix |
| #13 | β | fedit v2.0.0 β symbol-first batch editing |
| #14 | β | fread v1.8.0, fedit v1.9.0 β read and edit loop |
| #15 | Episode 2 | The Monolith β fprobe, fedit βlines, fs, fwrite, MCP overhaul, binary RE, pixel-perfect rendering, archive-grade README, 12 tools, 425 tests |
| #16 | Episode 3 | The Mirror β mapped the rendering engine, built fbash (13th tool), fedit v2 validation, fread multi-path + TS symbols, 120 tests |
The drones were built to explore unfamiliar territory. Today, the most unfamiliar territory turned out to be home. They mapped it. Then they improved it. Then they built a new tool to replace the part that was holding them back. Thirteen tools. One hundred and twenty tests. The suite is complete.