RSS Amplifier

OffNote Labs Newsletter · Jun 12, 2026

How Coding Agents Find Their Way Through Code

0
Sign in to vote or save

Nishant Sinha · OffNote Labs Newsletter

Agents with terminals are suddenly everywhere. Devin, Cursor, Manus, Claude Code, Codex, and several newer agent systems all point in the same direction: give the model a workspace, give it tools, let it act.

At first glance this sounds like a leap forward in AI capability. But if you watch what these code agents actually do, their actions look quite primitive.

They list files. They search text. They read snippets. They run tests. They inspect errors. They edit a file and try again.

ls # list
rg "deleteDocument|delete_document" # grep
sed -n '1,160p' src/routes/documents.ts # read
npm test -- documents # test

This isn’t trivial. The terminal gives the agent a way to interact with the codebase through stable, named operations, bash commands. It does not have to hold the entire repository in context. It can ask the repo questions.

That makes code agents feel less like a single giant model “understanding” a project, and more like a fast junior developer with a very patient shell: look around, search, read, try something, observe the result.

They use the boring but extremely powerful and scalable UNIX tools to compose together an insightful trail that leads to code understanding and the final answer.

This is a cross-post from my other Substack, Engineering Agents — where I write specifically about building and shipping AI agents. If that's your thing, come check it out and subscribe to keep up with new posts.

Before an agent can fix, explain, or modify code, it has to localize the concern:
Where in this repo does the answer live?

A user asks:

How does this app send email?

The answer may be spread across:

mailer.py
send_email()
send_welcome_email()
settings.SMTP_HOST
users/views.py
auth/views.py

This is the localization problem: turn a human question into a searchable handle, the seed keywords. That might be a domain word, UI label, error message, route, function name, or config key.

The seed does not need to be final. It only needs to reveal the next one. Code search is not just lookup. It is translation from the user’s vocabulary into the repository’s vocabulary.

If you prefer, I created a short Code Search Demo video describing this example in more detail

To answer the above question, the agent starts with a broad concern, then narrows it into the specific files and functions where the behavior lives.

It might start wide, with the powerful tool recursive grep (ripgrep, rg):

rg -l "email" # all file mentions of email

result:

src/notifications/mailer.py
src/users/views.py
config/settings.py

mailer.py looks to be the key. The next question verifies that. What functions it defines:

rg -n "def " src/notifications/mailer.py
4:  def send_email(to, subject, body):
19: def send_welcome_email(user):
26: def send_password_reset(user, token):

Now the search has found a likely base function send_email:

email -> mailer.py -> send_email()

Reading just that function reveals the next names:

rg -A 8 "def send_email" src/notifications/mailer.py # read send_email 
with smtplib.SMTP(settings.SMTP_HOST) as s:
    s.send_message(msg)

Now the search terms are no longer guesses. They came from the code: smtplib, SMTP_HOST, send_email.

rg -n "send_email\\(" --type py
rg -C 1 "SMTP" config/settings.py #verify settings.py contains SMTP settings

The trace becomes:

email
-> mailer.py
-> send_email()
-> smtplib + SMTP_HOST
-> signup + password reset callers

After a few searches, the answer is localized and grounded:

The app emailer uses Python’s smtplib, configured through SMTP env vars, and is triggered by signup and password reset.

This is the key idea. ripgrep does not understand email. It helps the agent follow the names that the program already uses to connect the feature together.

The email example has the basic code-agent loop:

seed term -> search -> inspect -> discover better term -> search again

A result might reveal a filename, function name, import, route, error string, or test. Any of those can become the next query.

Good seed terms usually come from the terms closest to the user:

domain words: email, invoice, document
UI labels: "Send invite", "Delete document"
routes: /documents/:id, POST /login
errors: 403, forbidden, invalid token
config: SMTP_HOST, DATABASE_URL
tests: password reset, rejects empty name

Each step narrows the question: from rough domain language, to repo vocabulary, to the files where behavior actually lives.

Tests add another source of names. They are search amplifiers. A failing test might mention the exact route, expected status code, fixture, or error string:

test failure -> expected 403 -> search "403" -> find policy check

Finally, how do we know the loop is converging? The match set gets smaller. The same files keep recurring. The names in code start lining up with the user’s question.

ripgrep is popular with code agents for a simple reason: it is cheap enough to use repeatedly and scales to large files.

That matters because agentic search is many small queries, not one big query.

rg -l "email"
rg -n "def " src/notifications/mailer.py
rg -A 8 "def send_email" src/notifications/mailer.py
rg -n "send_email\\(" --type py
rg -C 1 "SMTP" config/settings.py

Each variant answers a slightly different localization question.

rg -l gives the shape of the match set: which files even matter? rg -n gives line numbers, so the agent can jump to the right place. -A, -B, and -C reveal nearby context without reading the whole file. Type filters and globs keep the search inside the relevant part of the repo. Respecting .gitignore usually keeps irrelevant artifacts out of the way.

This makes ripgrep useful in two ways at once.

  • First, it is localized content search: find symbols, strings, comments, routes, errors, and config keys.

  • Second, it navigates repo structure: which directories mention this? Which file types? Which tests? Which modules keep recurring?

ripgrep is not semantic search. It will not know that send mail and dispatch notification are related unless the code gives it a bridge. Codebases have plenty of bridges: function names, imports, tests, constants, routes, and error messages. In fact, Claude Code abandoned vector embedding based semantic search in favor of keyword based explicit, agentic search.

Why is exact search so powerful? In a repo, names point to dependencies: functions have callers, routes have handlers, modules are imported, errors have branches, and tests have expectations.

In practice, several more optimizations are needed to make code agents scale. Repeated ripgrep queries eat up a lot of tokens and do repeated work. How do we cache? A clever solution is to build a code-map of your entire repository and query it to localize before making explicit ripgrep calls. For example, the code-review-graph tool provides a MCP server to query the local code map.

Code is friendly to agents because it already has named, inspectable surfaces. The filesystem gives the agent a few reliable moves:

list what exists
search for a name
read the nearby context
run the thing
inspect the result

Interestingly, this search pattern is starting to show up outside code. Agent memory can be written to files. Plans, summaries, and observations can live in a workspace and be retrieved later with the same moves: ls to see what exists, cat or sed to read state, rg to recover something relevant.

But not everything agents need is naturally a file tree. Databases, APIs, conversations, logs, dashboards, and long-term memory all have structure that may not fit cleanly into normal files. Can we extend the same boring search pattern to them?

The design question here is whether they should expose filesystem-like surfaces:

  • What is ls for a database? Tables, rows, schemas, recent queries?

  • What is grep for agent memory? Content search, metadata search, embeddings, or all three?

  • Find some early ideas in the article below.

The lesson from code agents is that agents are effective when the knowledge base is exposed through named operations that allow inspection and localization.

The future of Agentic Search isn’t about giving models bigger prompts or more tools. It is more about giving every knowledge base a surface that allows the agent to localize and inspect at multiple resolutions.

This is a cross-post from my other Substack, Engineering Agents — where I write specifically about building and shipping AI agents. If that’s your thing, come check it out and subscribe to keep up with new posts.

No posts

Read the original on offnote.substack.com

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.