Browsing and Reading Code¶
The previous chapter found things. This chapter is about reading them -- seeing the actual source, understanding file structure, and navigating a codebase without wasting tokens on code you do not need.
Reading a symbol with read_symbol¶
After find_code returns a match, you have a symbol_id. Use it to get the
exact source:
{
"symbol_id": "src/config/parser.py::parse_config#function",
"name": "parse_config",
"kind": "function",
"file": "src/config/parser.py",
"signature": "def parse_config(path: Path, strict: bool = False) -> Config",
"source": "def parse_config(path: Path, strict: bool = False) -> Config:\n \"\"\"Parse a YAML config file...\"\"\"\n ...",
"line_start": 42,
"line_end": 78,
"_meta": {
"savings": {
"returned_tokens": 220,
"total_file_tokens": 1840,
"tokens_avoided": 1620,
"method": "tiktoken_cl100k"
}
},
"_hints": {
"edit": {
"read_file": "src/config/parser.py",
"read_offset": 37,
"read_limit": 46
},
"next": {
"find_callers": "who_depends_on_this(repo, 'src/config/parser.py')",
"blast_radius": "what_breaks_if_i_change('src/config/parser.py::parse_config#function')",
"dependency_graph": "import_graph(repo, 'src/config/parser.py')"
}
}
}
This returns only the lines that belong to parse_config -- not the 1,800 other
tokens in that file. The _meta.savings block shows exactly what was avoided.
Why this beats reading the file directly:
- A 500-line file might contain 20 functions. You need one.
read_symbolreturns just that one. - The response includes the signature, docstring, decorators, and source in a structured format -- no parsing needed.
- The
_hintsblock tells you what to do next (see below).
Use context_lines to include surrounding code when you need to see what is
above or below the symbol:
Use verify=true to confirm the indexed content still matches the file on disk.
If the file has changed since indexing, the response will flag the drift.
The _hints system¶
Every read_symbol response includes _hints with two sections:
_hints.edit -- If you need to edit this symbol using a file-reading tool,
these are the exact offset and limit to pass:
This means: read src/config/parser.py starting at line 37, reading 46 lines.
You get exactly the symbol and its surrounding context, nothing more.
_hints.next -- Suggested follow-up tool calls based on what you just read:
"next": {
"find_callers": "who_depends_on_this(repo, 'src/config/parser.py')",
"blast_radius": "what_breaks_if_i_change('src/config/parser.py::parse_config#function')",
"dependency_graph": "import_graph(repo, 'src/config/parser.py')"
}
These are ready-to-use parameter suggestions. If you plan to change the function,
use blast_radius. If you want to understand who uses it, use find_callers.
Understanding a file with whats_in_file¶
Before reading any file, check its outline. This shows every symbol in the file with signatures and line numbers -- without returning any source code.
{
"file": "src/config/parser.py",
"outline": [
{
"symbol_id": "src/config/parser.py::ConfigError#class",
"name": "ConfigError",
"kind": "class",
"signature": "class ConfigError(Exception)",
"line_start": 10,
"line_end": 15,
"children": []
},
{
"symbol_id": "src/config/parser.py::parse_config#function",
"name": "parse_config",
"kind": "function",
"signature": "def parse_config(path: Path, strict: bool = False) -> Config",
"line_start": 42,
"line_end": 78,
"children": []
}
],
"_meta": {
"symbol_count": 2,
"token_efficiency": {
"returned": 180,
"equivalent_file_read": 1840,
"reduction_percent": 90.2
}
}
}
Classes show their methods as children, so you can see the full structure at a
glance. Use this to decide which symbols to fetch with read_symbol.
Exploring the repo with project_structure¶
To understand how a project is organized:
This returns a compact indented tree (like the tree command) with file counts
and language breakdowns. Directories deeper than max_depth are collapsed with
a count of their contents.
Getting the big picture with repo_overview¶
Start here when you first encounter a repository:
{
"repo": "my-project",
"files": 208,
"symbols": 1542,
"sections": 87,
"languages": {"python": 180, "typescript": 28},
"symbol_kinds": {
"function": 420,
"class": 180,
"method": 890,
"constant": 52
}
}
This tells you the scale, the languages involved, and the distribution of symbol
types. From here, use project_structure to see the structure, or find_code
to start finding things.
Navigating documentation with doc_table_of_contents and doc_tree¶
If the repository has documentation files, use doc_table_of_contents for a flat list of all
headings:
Or doc_tree for a nested hierarchy grouped by document:
Both return section_id values you can pass to read_doc_section to read individual
sections without loading entire documents.
Getting the full picture with understand_symbol¶
When you need to understand a symbol in context -- not just its source, but what
it imports, what else is in the same file, and who calls it -- use
understand_symbol instead of making multiple calls:
understand_symbol(
symbol_id="src/config/parser.py::parse_config#function",
include_imports=true,
include_callers=true
)
This returns the symbol's source, the file's imports, sibling symbols in the same file, and callers from other files -- all in one response. It replaces what would otherwise be 3-5 separate tool calls.
Batch operations¶
When you need to read multiple things at once, use the batch variants:
| Instead of calling... | Use... |
|---|---|
read_symbol three times |
read_symbols(symbol_ids=["id1", "id2", "id3"]) |
read_doc_section three times |
read_doc_sections(section_ids=["id1", "id2", "id3"]) |
whats_in_file three times |
whats_in_files(repo="x", file_paths=["a.py", "b.py", "c.py"]) |
Each batch call is a single round-trip and returns all results together.
The workflow¶
A typical browsing session looks like this:
repo_overview-- understand the scaleproject_structure-- see the directory layoutfind_code-- find what you are looking forwhats_in_file-- understand the file it lives inread_symbol-- read the exact source you need- Follow
_hints.next-- check callers, blast radius, or dependencies
Each step returns only what you asked for, and each response tells you what to do next. You never need to read an entire file to find a 20-line function.