Query the symbol graph¶
The semantic layer keeps a live map of your code: every definition, every reference, the recorded
call and test edges, and the type hierarchies that connect them. This guide shows you how to ask it
questions — where a symbol lives, who calls it, and what depends on it — instead of reaching for
grep.
Built and default-on — optional and additive
The tovio semantic queries are part of the optional, additive semantic layer, which is
built and on by default — the commands below work today. The layer never sits on the critical
path of a commit or sync, and every query is served offline from HEAD's index. The same graph is
exposed to agents through the implemented symbol_context, symbol_relations, and
change_impact MCP tools.
Why query instead of grep¶
grep matches text. The symbol graph matches meaning. A query knows the difference between a function
named read and a comment that says "read," between the User type and a variable called user, and
between a definition and the dozen places that call it. Three questions it answers cleanly:
- Where is this defined? — jump straight to the definition, across files.
- Who calls this? — the recorded caller list, the basis for "is this safe to change?"
- What depends on this? — the transitive reach of a symbol, for planning a refactor.
Symbols are named by moniker¶
Every query except search takes a descriptor moniker — the symbol's qualified name in SCIP
style: a function is verifyToken()., a type is Claims#, a method is Shop#Widget#greet()., a
plain value is MAX_RETRIES.. When you don't know the exact spelling, start with search:
$ tovio semantic search verify
Symbols matching `verify`:
verifyToken(). auth/token.ts:42 [prefix 708, 4 ref(s)]
legacyVerify(). auth/legacy.ts:9 [word-boundary 502, 1 ref(s)]
Search ranks by match tier — exact, case-insensitive, prefix, word-boundary, substring, then fuzzy
subsequence — with a bounded bonus for how often a symbol is referenced. The tiers are spaced far
enough apart that the bonus reorders within a tier and never across one, so the ranking is an integer
total order and two machines with the same index print the same list. --limit <n> widens the
screenful (default 20, capped at 500).
Find where a symbol is defined¶
A moniker that resolves to several definitions lists each one. find-refs is the mirror image — every
recorded reference to the symbol, by file.
List who calls a symbol¶
$ tovio semantic callers 'verifyToken().'
Caller(s) of `verifyToken().`:
pruneExpired().
refreshSession().
requireAuth().
callers is a one-hop lookup over the recorded calls edges — it answers "who calls this," not "what
would break." Two siblings use the same shape: tested-by lists the test functions that exercise a
symbol, and types lists the supertypes it extends or satisfies (one hop upward only; "who implements
this" needs a reverse index the graph does not yet carry).
Edges are recorded, not proven
Call and test edges are extracted syntactically. A call through a trait object, a call from inside a macro body, or a cross-module call to a differently-monikered target is invisible to the parsers and therefore absent. An empty result means no edge was recorded — never that a symbol is uncalled or untested.
Find what depends on a symbol¶
callers is one hop. impact is the transitive reach — everything that would feel a change, directly
or through the chain — plus the tests that cover it:
$ tovio semantic impact 'verifyToken().' --depth 2
Impact of `verifyToken().` (2 hop(s)):
requireAuth(). api/middleware/auth.ts [1 hop(s)]
refreshSession(). api/routes/session.ts [1 hop(s)]
sessionRouter(). api/routes/index.ts [2 hop(s)]
Covering test(s):
rejectsExpiredToken(). covers requireAuth().
advisory: call edges are extracted syntactically — no recorded dependents is not proof of no impact
Use --depth to bound how far the walk goes (default 3). Omit the moniker entirely and impact seeds
itself from what this change altered — HEAD's first parent versus HEAD — which is the question people
actually have before landing: "what might my change break?"
Machine-readable output¶
Every query honors the standard --json contract — stable, decoration-free, the surface agents and CI
read. The one-hop queries share one envelope: the moniker plus a sorted, deduplicated list under
callers, tested_by, or supertypes.
{
"moniker": "verifyToken().",
"callers": ["pruneExpired().", "refreshSession().", "requireAuth()."]
}
find-def and find-refs return definitions / references as { "shard", "path" } pairs (the
content address of the defining shard and the repository path, or null for a path you cannot read).
impact returns seeds, affected (with distance in hops), tests, and truncated, and restates
the advisory in the machine surface so a consumer can't read an empty affected as proof.
Agents query this surface directly
The Node/MCP edge exposes the same graph as structured tools — symbol_context for definitions
and references, symbol_relations for the one-hop edges, and change_impact for the transitive
walk — so an agent can pull exact symbol context instead of guessing from a text search. See the
agents guide.
What the graph covers¶
| You can ask for | You get |
|---|---|
search <text> |
Ranked definitions matching what you typed — the query for when you can't spell the moniker |
find-def <moniker> |
The definition(s), with the defining file |
find-refs <moniker> |
Every recorded reference, by file |
callers <moniker> |
Direct callers — the one-hop calls set |
tested-by <moniker> |
Tests that exercise the symbol — the one-hop tests set |
types <moniker> |
Supertypes it extends or satisfies — one hop upward |
impact [<moniker>] |
Transitive dependents and their covering tests, bounded by --depth |
The graph indexes Rust, TypeScript/JavaScript, Python, and Go. A file in any other language simply isn't in the graph — queries skip it rather than failing, and you fall back to a text search for those files. Symbols under a policy-protected path are sealed to the file's recipients: a reader with the key sees them; anyone else, including a scope-limited agent token, sees only what they are cleared to read.
How it stays current¶
The graph updates incrementally on every commit — TOVIO re-indexes only the files that changed,
writing one content-addressed symbol-shard per file and stitching them into the commit's
symbol-xref, so a query always reflects your latest history without a full re-index. Both live as
ordinary objects in your store, so they sync and re-hash on receipt like any other object.
Where to go next¶
- Read a semantic diff — the change-shaped view of the same graph.
- Version AI behavior — the other half of the semantic layer.
- Concepts — how TOVIO models a repository as content-addressed objects.
Last reviewed September 9, 2026
Suggest an improvement to this page Not for security reports — see disclosure