Give Your Coding Agent Eyes

작성자

카테고리:

← 피드로
DEV Community · Nour Mohamed Amine · 2026-09-06 개발(SW)

Every coding-agent session starts blind: your agent rediscovers how your features work, and sometimes guesses wrong.

AGENTS.md is the usual fix, but it loads every fact into every session — too thin to answer real questions, or too fat to afford.

So give the agent a catalog instead. Each feature in your project gets one small note. At startup the agent loads only a table of paths and descriptions — no content — and reads a note when the description matches the task. You regenerate the catalog from your code every 20 commits or at the end of each sprint, so it never drifts from what the code does today.

The menu, not the meal

Here’s what the agent sees at startup:

<context-files>
  <instruction>If the current task requires knowledge from a file below,
  call the read tool on it before proceeding. Skip files whose description
  does not match the task.</instruction>
  <available>
    <file>
      <path>/app/.pi/notes/back.settings.md</path>
      <description>Load when working on per-organization settings for
      notification rules and other global defaults.</description>
    </file>
    <file>
      <path>/app/.pi/notes/back.notification-engine.md</path>
      <description>Load when working on how queued notifications are
      batched, retried, and dispatched.</description>
    </file>
    <!-- ...18 more -->
  </available>
</context-files>

Enter fullscreen mode Exit fullscreen mode

Paths and descriptions. No content. Twenty features cost me about 300 tokens — roughly one mid-sized note’s worth — and every feature in the project is reachable.

When I ask for a change to the settings page, the agent reads back.settings.md and nothing else. When I ask about retry behavior, it reads the notification engine note. The LLM is the relevance filter, and it turns out to be a very good one — provided the descriptions are written as triggers.

That’s the whole idea: the agent starts with a table of contents for the codebase, and pulls the chapters it needs.

How the block gets built

This is a pi extension, pi-context. It hooks before_agent_start:

pi.on("before_agent_start", async (event, ctx) => {
  const rootFiles = await walkUpContextFiles(fs, cwd);
  if (rootFiles.length === 0) return;

  const linked = await collectConfigDirFiles(fs, projectRoot, visited);

  const block = formatContextFilesBlock(linked);
  if (!block) return;

  return { systemPrompt: `${event.systemPrompt}\n\n${block}` };
});

Enter fullscreen mode Exit fullscreen mode

Four steps: walk up from cwd to find the project root via AGENTS.md / CLAUDE.md, recursively scan .pi/, .claude/, and .agents/ for .md files, keep only the ones with a description frontmatter field, inject the block. It runs over SSH too, against the remote filesystem, which is why the fs is abstracted.

Note the filter. A markdown file without a description is invisible. That’s the opt-in — your READMEs and scratch notes don’t leak into the system prompt.

The description is the router

This is the one thing that decides whether the whole system works, so it gets its own rule: the description is a trigger condition, not a summary.

# Bad — describes the file
description: This file covers the settings feature.

# Good — describes when to read it
description: Load when working on per-organization settings for
  notification rules and other global defaults.

Enter fullscreen mode Exit fullscreen mode

The agent never sees the content when it makes the decision. It sees one sentence and has to answer “does this match my task?” A summary makes that a guess. A trigger makes it a lookup. Every description starts with “Load when” or “Use when” — the constraint is enforced by the skill that writes them.

Notes are grouped by feature

The notes live in .pi/notes/, one file per feature, flat, prefixed by layer:

.pi/notes/
├── _index.md
├── .last-sync              → a88fd19e683fa8d75...
├── back.settings.md
├── back.notification-engine.md
├── back.auth-tenancy.md
├── back.prisma-schema.md
├── ops.dev-workflow.md
└── test.harness.md

Enter fullscreen mode Exit fullscreen mode

One topic per file, hard-capped around 5 KB. The feature is the unit of perception: it’s the granularity at which you actually ask for changes, so it’s the granularity at which the agent should be able to load knowledge. A 40 KB architecture doc is useless here — it can’t be selectively read.

A real note looks like this, in full:

---
description: "Load when working on per-organization settings for"
  notification rules and other global defaults.
---

# Settings Feature

<scope>
  <rule>
    <requirement>The settings row is a per-org singleton reached at
    `/api/v1/settings`; GET returns the org's settings, PATCH creates
    or updates them.</requirement>
    <example>`GET /api/v1/settings` returns the current notification
    configuration for the active org.</example>
  </rule>
  <rule>
    <requirement>Defaults are `notificationsEnabled = true` and
    `notificationRules = null` when no row exists yet.</requirement>
    <example>The first GET for a tenant without settings still returns
    a usable config.</example>
  </rule>
</scope>

<updates>
  <rule>
    <requirement>PATCH only accepts `notificationsEnabled` and
    `notificationRules`; the use case upserts the singleton for the
    active org.</requirement>
    <example>`PATCH /api/v1/settings { notificationsEnabled: false }`
    disables global notifications.</example>
  </rule>
  <rule>
    <requirement>The notification engine reads these rules when a job
    opts into `useGlobalRules`.</requirement>
    <example>Global notifications disabled means the engine drops every
    queued job silently. See [[back.notification-engine]].</example>
  </rule>
</updates>

<self-verification>
  <check>Settings are tenant-scoped by `organizationId`.</check>
  <check>GET returns defaults when no row exists yet.</check>
  <check>The engine, not the settings feature, decides how rules apply.</check>
</self-verification>

Enter fullscreen mode Exit fullscreen mode

Roughly 1.4 KB. Every line is a fact an agent would otherwise have to infer from three files, and the <example> under each requirement pins down the ambiguous ones. Notes cross-link with [[wiki-style]] references, so an agent reading about settings knows where the consuming behavior lives.

Bootstrapping: one cold start

Nobody hand-writes twenty of these. The doc-routing skill (doc-skills) does a cold start on a repo with no notes:

Cold start detected — no previous sync found.
Root: /app
Skipping: **/*.lock, **/dist/**, **/*.generated.*, **/build/**
Files in scope: ~137 files (will be grouped into topics)
Proceed? [yes / cancel]

Enter fullscreen mode Exit fullscreen mode

It runs git ls-files, filters the noise, groups the rest by feature, and splits any group bigger than three files into sub-topics — recursively, until every topic is small enough that one note can honestly cover it. Then it reads the source files for each topic and writes a note.

That last part matters. It does not read the git log. It reads the code.

Keeping the eyes open

Cold start is the expensive run. After that there’s .last-sync — a single commit hash sitting in the notes directory.

I type sync notes and get:

Last sync: a88fd19 — 2026-08-16 — feat: add rule editor
Range: a88fd19..HEAD
Proposed sync — 14 commits:
  · 3f2b1ac refactor: extract dispatch queue
  · 91cd004 fix: tenant scoping on PATCH
  ...
Confirm? [yes / provide different range]

Enter fullscreen mode Exit fullscreen mode

Confirm, and it groups the changed files by feature, classifies each group as UPDATE, CREATE, or SKIP, rewrites only the affected sections, and stores the new hash. Cost is proportional to what changed, not to repo size.

Cadence: every ~20 commits, or at the close of a sprint. That’s not arbitrary — the workflow warns you above 20 and tells you to batch into 10–20 commits per run. Below that you’re syncing noise. Far above it, the diff spans so many features that grouping gets sloppy and you’re really doing a cold start again. A sprint boundary happens to land almost exactly in that window, which makes it a natural ritual: merge the sprint, run one sync, review the diff on the notes, commit.

It costs a couple of minutes. What you get back is that every session for the next two weeks starts with accurate knowledge of every feature in the project.

Behavior, not history

The one rule that makes this a perception layer instead of a changelog:

The doc must describe what the code currently does — not what changed.

Diffs and commit messages are explicitly banned as note content. The sync uses the diff only to decide which notes to touch — then it reads the current source and writes the current truth. The extraction guide draws the line sharply:

Keep (behavioral) Drop (structural) “Returns 401 when the token is missing.” “Has a token validation function.” “MAX_RETRIES=3 — requests fail hard after 3 attempts.” “PORT=3000 — default HTTP port.” “// FIXME: rate limiter not enforced for admin tokens.” “// TODO: add pagination.”

A changelog tells an agent what moved. Eyes tell it what is. An agent handed a history has to replay it to learn the present state; an agent handed the present state can just work. Same reason you don’t onboard a new teammate by having them read six months of PRs.

The writing itself

A third piece, doc-writing, owns how each note is written: format selection (ordered steps get numbered <step> blocks, keyword routing gets a trigger table, hard constraints go before soft ones), a token budget of 10–15 lines per section, and the description: contract. It’s what keeps twenty notes written by twenty different sessions looking like one system.

Try it

pi install git:github.com/zeflq/pi-context

Enter fullscreen mode Exit fullscreen mode

Add the doc-skills skills, then in your repo:

sync notes

Enter fullscreen mode Exit fullscreen mode

No .last-sync, no notes → it offers the cold start. After that, run it every sprint.

The setup is maybe twenty minutes on a real codebase. The change is hard to go back from: the agent stops asking you where things live.

원문에서 계속 ↗

추출 본문 · 출처: dev.to · https://dev.to/zeflq/give-your-coding-agent-eyes-4mmj