Guides

Saving tokens

Reading a repository costs an AI assistant tokens, and most of that is avoidable because most questions need a small part of it. Ananke answers those questions from its own graph of your code, with nothing else to install.

ananke context enable                       # this project now uses all of this by default
ananke context map                          # a small picture of the repository
ananke context outline src/shop/orders.py   # what's in a file, without reading it
ananke context symbol place_order           # one function with its source
ananke context callers place_order          # what calls it

What it saves

Measured on Ananke itself, about 790,000 tokens of source, counted with tiktoken:

QuestionCommandReading insteadWith the command
Where do I start?ananke context map790,0001,162
What’s in this big file?ananke context outline8,480462
The same, with signaturesananke context outline --signatures8,4801,150
A short function from a big fileananke context symbol8,480115
The longest function in itananke context symbol8,4801,625
What’s in a small file?ananke context outline98061
Who calls this?ananke context callers167 by grep191

Your numbers will differ, so measure your own repository with ananke context benchmark.

Using it

Start with the map

ananke context map

You get the languages, components, routes, tables, largest files and most complex functions, usually in under 2,000 tokens. --budget sets roughly how many it may use, and a map that had to cut something says how much:

ananke context map --budget 500

Look inside a file before reading it

$ ananke context outline src/ananke/plexus/execution/scheduler.py --signatures
src/ananke/plexus/execution/scheduler.py (about 1077 tokens to read)
24-25 function def _noop_handler(step: ExecutionStep, repository_root: Path) -> dict[str, object]
28-31 function def _no_executor(step: ExecutionStep, repository_root: Path) -> dict[str, object]
34-82 function def run_plan(repository_root: Path, plan: ExecutionPlan, *, handlers: dict[...
85-116 function def _execute_step(step: ExecutionStep, repository_root: Path, handlers: dict[...

Each line starts with line numbers, so if you do need the code you can read just those lines. Leave off --signatures for names only, which is cheaper. A file with nothing to list, such as Markdown, tells you to read it directly and what that costs.

Get one function

$ ananke context symbol context_map
src/ananke/plexus/app.py:679-686 method AppFacade.context_map
    def context_map(self, *, budget: int = 1500, refresh: bool = False) -> dict[str, Any]:
        """A small picture of the repository, rebuilt only when its sources change."""
        ...

A method can be named with its class, as in Cart.total. If several things share a name you get up to three, and --path picks one:

ananke context symbol total --path src/shop/cart.py
ananke context symbol Cart.total

See what calls it

$ ananke context callers run_plan
5 resolved, from the code graph:
src/ananke/plexus/workflow_run.py:206 execute (calls)
tests/unit/test_assembly.py:250 test_compiled_plan_runs_on_the_existing_scheduler (calls)
...
7 other mentions of run_plan, not resolved (text only):
CHANGELOG.md:707
docs/reference/three-repository-audit.md:96

The two groups are kept apart on purpose. The first is what the graph worked out. The second is every other place the word appears, so a call made through a variable or from documentation isn’t lost, and you can judge it for yourself.

From a script or an assistant’s tools

Add --json for structured output. If your assistant prefers MCP tools, ananke.context.map, outline, symbol and callers give the same answers and are read-only. Plain commands cost nothing until they’re used, whereas each tool’s description is sent with every chat, so use commands where you can.

It stays current by itself

You don’t run an update step, and no background process watches your files.

Every question first works out the state of your sources: the current commit, plus a hash of every changed or new file that Git sees. If that matches the saved graph, the answer takes about half a second. If it doesn’t, the graph is rebuilt, which took about two and a half seconds on Ananke, and then answered.

You did thisThe next question sees it
Edited a file and didn’t commitYes
Added a file, even in a new folderYes
Deleted a fileYes
Switched branch or pulledYes
Changed a file Git ignoresNo, ignored files aren’t part of the graph

Because the check runs when you ask, an answer can’t be out of date, and nothing runs while you aren’t asking.

Use it by default

ananke context enable --local --dry-run   # see what would change
ananke context enable --local             # just for you
ananke context enable                     # for everyone who works on this project

enable adds a short note where each assistant looks for project instructions, telling it to use these commands before reading files. It works out which assistants you use, and you can name them with --client:

ananke context enable --client kiro --client copilot
ananke context enable --client all
Assistant--clientWhere the note goesWith --local
Claude Codeclaude-codeCLAUDE.mdCLAUDE.local.md, which Claude Code also reads
OpenAI CodexcodexAGENTS.mdAGENTS.md, unless Git tracks it
GitHub Copilotcopilot.github/copilot-instructions.mdThe same file, unless Git tracks it
Kirokiro.kiro/steering/ananke-context.mdThe same file
Cursorcursor.cursor/rules/ananke-context.mdcThe same file
Amazon Qamazon-q.amazonq/rules/ananke-context.mdThe same file

Many other tools read AGENTS.md, so --client codex often covers them too.

Sharing it, or keeping it to yourself

Without --local, these are ordinary project files: they show up in git status, and once committed, everyone who works on the repository gets them. That suits a team that has agreed to use this, but it needs Ananke installed for everyone.

With --local, nothing reaches a commit. Each file is listed in .git/info/exclude, which works like .gitignore but never leaves your checkout. If Git already tracks a file, such as a team’s AGENTS.md, it’s left alone, and the result names it under skipped.

Running enable again changes nothing, and ananke context status shows who’s enabled. To undo either kind:

ananke context disable

That removes only what was added, and leaves your own text, hooks and exclude lines as they were.

What it costs

WhatTokensWhen
The noteabout 80Every chat
Each lookup60 to 1,600Only when used
The mapabout 1,200 hereOnly when used
The map with --session-mapabout 1,200 hereEvery Claude Code chat, needed or not
Serena connected, for comparisonabout 1,100Every chat, used or not

So the cheapest setup is the default: the note, with the map run only when it helps. One avoided read of a large file pays for about 100 chats’ worth of the note. --session-map is worth adding only if nearly every chat starts by exploring.

The note is an instruction, so an assistant usually follows it but isn’t forced to. Nothing stops it reading a file, which is sometimes the right thing to do.

The skill for your assistant

The repo-context skill teaches an assistant the same routing, for every repository and not just the ones you’ve enabled. It’s in Dex Trove:

dex install neo-pragnya/repo-context --target claude
dex install neo-pragnya/repo-context --target copilot

Use enable for a project you want set up for everyone who works on it, and the skill for your own habits across all of them.

Notebooks

A folder of notebooks works too. A file a notebook writes with %%writefile counts as a file at that path, even before anyone runs the notebook, so you can look inside it:

ananke context outline Week-1/app/service.py
ananke context symbol load_orders

It all runs offline

The map, outline, symbol, callers, enable and disable make no network connection, and a test checks that. They need no account, key or licence, and your code stays on your machine until an assistant sends what it reads to its own provider.

How it compares with Serena

Serena is a very good tool, built on language servers, and the comparison is worth being straight about. Same machine, same repository, same questions:

QuestionSerenaAnanke
Names in a big file277462Serena, though Ananke’s include line numbers
The longest function in a big file4,4271,625Ananke
A small file1,191403Ananke; reading it costs 980
Who calls this?1,730130Ananke; grep costs 167

Serena returns more around each answer, which is why it costs more. On a small file its overhead costs more than the file. Connecting it also costs about 1,100 tokens per chat in its default mode, or about 8,000 with --agent-interface tools. Ananke’s commands cost nothing until used.

Here’s what each does:

AnankeSerena
Outline a file, find a symbolYesYes
Find what calls a symbolYes, inferred from names and imports, plus text matchesYes, following types
Stay current with editsYesYes
Whole-repository map, routes, tables, componentsYesNo
Works with no language server or downloadYesNo
LanguagesPython, JavaScript, TypeScriptMany, through language servers
Rename a symbol across a projectNoYes
Edit by symbol: replace a body, insert before or afterNoYes
Compiler diagnosticsNoYes
Memory across chatsNo, use your notes or CLAUDE.mdYes
LicenceApache-2.0GPL-3.0-or-later

So Ananke is the better default for reading and saves more on most questions. If you work in another language, need references that follow types through a large refactor, or want symbol-level editing, Serena does things Ananke doesn’t. Those gaps are on the roadmap.

Adding Serena if you want it

Ananke never imports or bundles Serena, because of its licence. It’s a separate program that you connect to your assistant yourself. ananke context status prints this, pinned to a version we tested, with its dashboard and its usage ping off:

claude mcp add --scope user serena --env SERENA_USAGE_REPORTING=false -- \
  uvx --python 3.12 --from serena-agent==2.1.0 serena start-mcp-server \
  --context claude-code --project-from-cwd --enable-web-dashboard false --open-web-dashboard false

Each repository then needs a project file, or lookups quietly find nothing:

uvx --python 3.12 --from serena-agent==2.1.0 serena project create . --language python

Your code stays on your machine either way. Left on, Serena’s ping sends its version, your operating system and its settings, but no code, to its authors. Its language servers may be downloaded the first time.

Other tools for other jobs

Both are optional.

repomix: many files at once. For when you need to read a lot of source together. It’s an optional extra, pip install "ananke-plexus[context]":

ananke context pack --include 'src/**/*.py'

--include is required. On a whole repository, documentation can dominate and compression saved as little as 5%, while on source alone it saved 68%: 288,740 tokens down to 92,766. repomix’s secret check can leave out files, which once silently cost us app.py, so the result now says what was dropped. repomix has no way to stay current on its own: you pack when you ask.

Graphify: structure across repositories. For how things hang together across a large repository or several. It needs graphify update to stay current, and ananke context status tells you when it’s behind. Put **/*.min.js and similar in .graphifyignore. Don’t read graph.json directly, because it can be hundreds of thousands of tokens. Read GRAPH_REPORT.md, or use graphify query and graphify explain.

Your assistant’s chat

Most of the cost of a long chat is the chat itself, because everything said so far is sent with each turn. Start a new chat for a new task, and let the map and your notes carry what matters across.

Ask a question, get just the code

ananke context ask "where is the gold discount applied"
ananke context ask "how is a learning candidate promoted" --budget 1500 --json
ananke context eval tests/fixtures/retrieval-questions.json     # recall and tokens against reading every match

ask plans from the question’s words to symbols: names first (orderTotal and order_total both count as order and total, and promoted matches promote), then docstrings and paths. Test code ranks lower unless the question is about tests. It returns the best symbols’ source, then what calls them and what they call, until the budget is spent; overlapping passages are merged, and what didn’t fit is counted. Each candidate says which words it matched. The answer is kept for this snapshot of the repository, uncommitted edits included, so asking again costs nothing until the code changes.

eval measures it. On Ananke’s own repository, the six questions in tests/fixtures/retrieval-questions.json found the expected symbol in the top three for five, using 5,353 tokens in all, against 3.45 million for reading every Python file that contains one of the question’s words: 99.8% fewer.

References and edits by symbol

An assistant that knows a symbol’s name doesn’t need to read whole files to change it:

ananke context refs RankingEngine.rank_symbols                 # where it's used
ananke context replace Cart.total --with new_total.py          # a dry run: prints the diff
ananke context replace Cart.total --with new_total.py --write  # applies it
ananke context insert --with count.py --after Cart.total --write
ananke context rename Cart.total grand_total --write

refs resolves references by type with Jedi (MIT) when the typed extra is installed (pip install 'ananke-plexus[typed]'); without it, references come from the graph’s call edges and are labelled inferred. The difference matters: on a real repository the graph missed self.ranking_engine.rank_symbols(...), a call through an attribute, which Jedi resolved.

Every edit is a dry run until --write, and before writing: each changed file must still parse, a replacement must define the name it replaces, and a file that changed since it was read is refused. A rename changes name tokens only, never text in strings or comments. Without Jedi, a rename that leaves uses of the name the graph couldn’t resolve is refused, rather than half-applied; the dry run lists them.

Only Python is edited. Serena isn’t used: its application is GPL-3.0-or-later from v2.0.0.

Edit this page on GitHub ↗