The Art of Reading Before Writing: How an AI Assistant Grounds Its Changes in Existing Code

Introduction

In the opencode session, message 4516 appears, at first glance, to be one of the most mundane actions possible: a file read. The assistant reads /tmp/czk/cmd/vast-manager/ui.html, requesting lines 1830 through 1840. There is no grand proclamation, no complex reasoning block, no tool execution with dramatic output. Just a simple [read] command targeting an HTML template file. Yet this single message sits at a critical juncture in the development of an autonomous fleet management agent, and it reveals profound truths about how AI-assisted coding actually works in practice. The assistant is not guessing, not hallucinating, not blindly generating code from thin air. It is doing something far more important: it is reading the existing codebase to understand the terrain before making a change. This message is the quiet, invisible foundation upon which correct implementation is built.

The Context: A User Request for Machine Notes

To understand why this message exists, we must trace the conversation that led to it. Just six messages earlier, at <msg id=4507>, the user asked a single, open-ended question: "Expose machine notes?" This query arrived immediately after the assistant had finished deploying a major fix to the agent's over-provisioning behavior. The agent had been launching too many instances because it only looked at running capacity (40 proofs/hour) while ignoring 7 loading instances that would soon contribute ~350 proofs/hour. The assistant had just fixed this by introducing projected_proofs_hr — a computed field that accounts for both running and loading capacity. The fleet summary now read: "1 running, 7 loading (~350 p/h when ready). Capacity: 40 p/h now, ~390 p/h projected."

The user's question about machine notes was a natural follow-up. The fleet now had a performance tracking system (the host_perf table and the fleet-performance.md file that the agent writes), but there was no way for a human operator to annotate specific machines with observations like "this machine had OOM issues," "good performer, prefer this one," or "RTX 5090 with 32GB — runs hot." The assistant, in its reasoning at <msg id=4508>, correctly interpreted "machine notes" as a persistent annotation system per machine, editable in the UI and readable by the agent.

But before the assistant could implement this feature, it needed to understand the existing UI structure. The Machine Perf tab already existed, showing per-machine benchmark data fetched from the /api/agent/perf endpoint. The assistant had just searched for the tab rendering code at <msg id=4515> using grep, finding two matches:

<div class="agent-tab ${agentTab==='perf'?'active':''}" data-tab="perf" onclick="switchAgentTab('perf')">Machine Perf <span class="badge">${machines.length}</span></div>

And the corresponding content div. But grep only shows matching lines — it doesn't show the surrounding context, the function structure, or the data flow. The assistant needed to see the actual code to understand how fetchAgentData() worked, how tabs were switched, and where to insert a new "Notes" tab. That is precisely why message 4516 exists.

What the Message Actually Reveals

The message shows the assistant reading lines 1830 through 1840 of ui.html. The content returned shows:

1830:     </div>`;
1831: }
1832: 
1833: // ── Agent Panel ────────────────────────────────────────────
1834: 
1835: async function fetchAgentData() {
1836:   try {
1837:     const [actR, alR, pfR] = await Promise.all([
1838:       fetch(API + '/api/agent/actions'),
1839:       fetch(API + '/api/agent/alerts'),
1840:       fetch(API ...

This is a pivotal moment. The assistant can now see the exact structure of fetchAgentData() — the function that loads data for the Agent Activity panel. It uses Promise.all to fetch three endpoints in parallel: actions, alerts, and perf. The data is truncated at line 1840, but the assistant now knows:

  1. The function name: fetchAgentData() — where to add a fetch for machine notes.
  2. The pattern: Promise.all with parallel fetches — the notes fetch should follow the same pattern.
  3. The tab structure: The Agent Panel section starts at line 1833 with a clear comment delimiter.
  4. The existing endpoints: /api/agent/actions, /api/agent/alerts, and /api/agent/perf — the notes endpoint should follow the same /api/agent/notes naming convention. Without this read, the assistant would be guessing at the code structure. It might write a fetchMachineNotes() function that doesn't integrate with the existing data flow. It might place the tab HTML in the wrong location. It might use a different fetch pattern that conflicts with the existing error handling. The read grounds the implementation in reality.

The Assumptions Embedded in This Message

Every read operation carries assumptions, and this one is no exception. The assistant assumes that:

  1. The UI is rendered client-side with JavaScript template literals: The ${...} syntax and the structure of the HTML (with agent-tab classes and switchAgentTab functions) strongly suggest a single-page application pattern where tabs are toggled by JavaScript, not by server-side rendering.
  2. The notes feature belongs in the Agent Activity panel: The assistant could have chosen to put machine notes in the main instance table, in a separate page, or as a popup modal. By reading the Agent Panel section, the assistant implicitly assumes this is the right location.
  3. The data fetch pattern is appropriate for notes: Using Promise.all with a new /api/agent/notes endpoint assumes that notes data is independent of other data and can be fetched concurrently — a reasonable assumption for a small annotation store.
  4. The file path is correct: The assistant assumes /tmp/czk/cmd/vast-manager/ui.html is the current, authoritative version of the UI. This is a development environment, and the file could have been edited elsewhere or have stale content.
  5. Line numbers are stable: The assistant requested lines 1830-1840 based on the grep results from &lt;msg id=4515&gt;. It assumes the file hasn't changed since that grep was performed (which was only one message earlier, so this is safe).

Input Knowledge Required to Understand This Message

A reader needs substantial context to understand why a simple file read is significant:

  1. The project architecture: The vast-manager is a Go HTTP server with a SQLite backend and a JavaScript-rendered HTML UI. The agent system consists of a Go API layer (agent_api.go) and a Python autonomous agent (vast_agent.py) that runs on a cron timer.
  2. The conversation history: The user's request for machine notes at &lt;msg id=4507&gt;, the assistant's reasoning at &lt;msg id=4508&gt;, the database schema changes at &lt;msg id=4509&gt;, the API endpoint additions at &lt;msg id=4510-4514&gt;, and the grep search at &lt;msg id=4515&gt; all precede this read.
  3. The existing tab structure: The Agent Activity panel has tabs for Actions, Alerts, and Machine Perf. The assistant is reading to understand how to add a fourth tab for Notes.
  4. The data flow pattern: fetchAgentData() is called to load all panel data. Understanding this function is essential to adding a new data source.

Output Knowledge Created by This Message

This read produces several forms of knowledge:

  1. Structural knowledge: The assistant now knows the exact line numbers and code structure of the Agent Panel section. It knows that fetchAgentData() starts at line 1835, uses Promise.all with three fetches, and returns rendered HTML.
  2. Pattern knowledge: The assistant can now replicate the existing pattern — add a fourth fetch to the Promise.all call, add a new tab div alongside the existing three, and add a new content div.
  3. Integration points: The assistant knows where to insert the tab button (alongside the Machine Perf tab at line 1924) and where to insert the content div (alongside the perf content at line 1928).
  4. Naming conventions: The existing endpoints use /api/agent/actions, /api/agent/alerts, /api/agent/perf. The notes endpoint should follow this pattern as /api/agent/notes.

The Thinking Process Visible in This Message

While the message itself contains no explicit reasoning block, the thinking process is visible through the sequence of actions. The assistant is methodically building up understanding before making changes:

  1. First, understand the domain: At &lt;msg id=4508&gt;, the assistant reasoned about what "machine notes" means, examined existing infrastructure (host_perf, bad_hosts, agent_actions, agent_alerts tables), and decided on a lightweight per-machine annotation store.
  2. Then, build the backend: At &lt;msg id=4509&gt;, the assistant added a machine_notes table to the SQLite schema. At &lt;msg id=4510-4514&gt;, it added GET and POST API endpoints for reading and writing notes.
  3. Then, research the frontend: At &lt;msg id=4515&gt;, the assistant grepped for the Machine Perf tab rendering to find where to add the Notes tab. At &lt;msg id=4516&gt; (this message), it reads the actual code to understand the full context.
  4. Next, implement the frontend: The assistant would go on to add the notes fetch to fetchAgentData(), add the Notes tab HTML, and render notes data. This is a classic "read before write" pattern that distinguishes effective AI-assisted coding from naive code generation. The assistant is not writing code in a vacuum — it is reading the existing codebase to understand patterns, conventions, and integration points before making changes.

Mistakes and Incorrect Assumptions

Were there any mistakes in this message? The read itself is correct — it returns the expected content. However, there are potential pitfalls:

  1. The truncated response: The content cuts off at line 1840 with fetch(API .... The assistant cannot see the full fetchAgentData() function. It might need to read more lines to understand the complete function, including error handling, the rendering logic, and how the returned HTML is inserted into the DOM.
  2. Missing the rendering functions: The grep at &lt;msg id=4515&gt; found the tab HTML at lines 1924 and 1928, but the assistant hasn't read those sections yet. It knows where the tabs are rendered but not the full rendering logic for the Machine Perf tab (which would serve as a template for the Notes tab).
  3. Assumption about tab switching: The assistant assumes that adding a new tab is as simple as adding a new div with the right class and data-tab attribute. But there might be additional JavaScript for tab switching, state management, or data caching that isn't visible in the read range. These are not errors in the message itself but rather limitations that the assistant would need to address in subsequent reads or edits.

Conclusion

Message 4516 is a quiet testament to how professional software development works in the AI era. It is not about generating code from scratch — it is about reading, understanding, and integrating. The assistant could have guessed at the UI structure and written code that "looks right." Instead, it took the time to read the actual file, understand the actual patterns, and ground its implementation in the reality of the existing codebase. This read operation, mundane as it appears, is the difference between code that merely compiles and code that correctly integrates with a complex, multi-layered system. In the architecture of autonomous coding, the read tool is not a passive information retrieval mechanism — it is the primary instrument of contextual understanding, and messages like this one are where the real work of integration begins.