Skip to main content

Command Palette

Search for a command to run...

When Your Agent Summarizes Your Codebase, It Quietly Drops the Types

An agent reads your file, writes a summary for the next session, and in that paraphrase the generic constraint, the discriminated union, the branded ID all collapse into a vague noun. The next session then builds on a version of your code that never existed.

Updated
•5 min read•View as Markdown

You open a new agent session for a task that touches three existing modules. You point it at the repo and say "read the relevant files first." It reads them. It writes a 12-line summary of what each module does. You approve the summary. It starts editing.

Three days later you're debugging a bug that only shows up when a user_id is actually an admin_id, and you realize the summary the agent was working from had collapsed both into the word "user."

The summary is a translation, and translations lose type information

Read the summary the agent wrote about your auth module. Then open the actual file.

Your file says: type UserId = Brand<string, 'user'> and type AdminId = Brand<string, 'admin'>, and the function signature is getPermissions(actor: UserId | AdminId): Permission[] with a runtime branch that treats them differently.

The summary says: "the auth module resolves a user and returns their permissions."

Every type-level distinction — the brand that prevents you from passing a UserId where an AdminId is required, the union that the compiler uses to force you to handle both cases, the constraint that getPermissions only accepts IDs minted by the identity service — is gone. In its place is the noun "user," which is technically true and semantically wrong in exactly the way that produces bugs.

This isn't the agent being lazy. It's what summarization does. A summary is a compression algorithm, and compression works by discarding information that looks redundant. To the model, a branded string and a plain string are both "an identifier," and the brand is the redundant bit. The compiler knows the brand matters. The summary doesn't, because the summary isn't compiled.

The compiler can't help, because the summary isn't the code

Here's the part that surprised me. You have type checking on save, you have CI, you have review. None of it catches the wrong assumption, because the wrong assumption lives in the agent's working memory, not in the diff.

The agent writes code that calls getPermissions(someString). TypeScript complains that string isn't assignable to UserId | AdminId. The agent "fixes" it by casting: getPermissions(someString as UserId). The cast is the summary's fault, but the cast is also what silences the compiler.

Now the build is green. The code ships. The runtime branch that was supposed to treat admins differently never fires, because the cast threw away the information that would have routed correctly. Six months later, someone with an admin_id in a user_id slot gets the wrong permissions, and you're lucky if it's a permissions bug rather than a data leak.

The type system worked exactly as designed. It just wasn't pointed at the right artifact. The artifact the agent was reasoning from was the prose summary, not the type definitions.

What actually helps

I went through three stages before something stuck.

Stage 1: longer summaries. I told the agent "be more detailed, don't omit types." It did, for one session. The next session it re-summarized from scratch and collapsed everything again. Longer summaries are also slower to load and they rot the moment you edit a file.

Stage 2: paste the type definitions into context. Better. But I was hand-copying interfaces between files, and I kept pasting the wrong version after a refactor. The types in my context drifted from the types in the repo, which is a worse bug than the one I was trying to prevent.

Stage 3: let the agent read the types directly, and forbid prose summaries of them. This is the one that held. The rule I put in my CLAUDE.md / agent instructions file is blunt: when you need to know what a function accepts, open the file and read the signature. Do not paraphrase it into English. If you write "this function takes a user," I will ask you to redo it with the actual type.

The effect is small but consistent. The agent stops inventing types it wishes you had. When it needs to know whether a field is optional, a string union, or a branded ID, it opens the file instead of guessing. The casts drop out of the diff because the agent already knows the correct type and writes to it.

There's a parallel in API work I keep coming back to. You can write a prose description of an endpoint — "returns a user object" — or you can point a tool at the OpenAPI schema and read the actual response shape. The prose version always sounds the same; the schema version is the one that doesn't lie. We ended up building Powerduck around that instinct: local-first, read the spec/schema as the source of truth, don't trust the markdown that describes it. The same instinct applies inside your codebase. The .ts file is your schema. The prose summary of it is the marketing copy.

The cheap check before you merge

When you're reviewing an agent PR, one question catches most of these: where did the agent learn the type it just used?

If the answer is "it opened the file," fine. If the answer is "it read its own summary of the file," go open the file yourself. You'll usually find the cast, the collapsed union, or the optional-field-it-assumed-was-required. That's the bug the compiler won't flag, because the compiler trusts the cast the agent wrote to make the summary's mistake go away.

Summaries are useful for orientation. They are not a substitute for the artifact they summarize. The moment you treat the summary as the source of truth, you've handed the agent a version of your codebase that exists only in English, and English is the one language your type checker can't read.

More from this blog

P

Powerduck Blogs

141 posts

Essays on local-first API tooling, OpenAPI contracts, MCP, and agentic coding failures. We build Powerduck, a local-first OpenAPI studio where the spec stays the source of truth. Topics: AI code review trust, testing AI-generated code, context engineering, and what actually breaks when AI writes your code.