Your codebase is a graph. Stop reading it like a pile of files.
Aracne scans your source tree into a topology — every package, file, function,
method, type and interface, plus the edges between them — and hands coding agents an
enhanced queryable graph instead of a directory to grep through.
All in your machine.
Aracne records how your code is wired — a class that inherits, a function that calls
a method, a module that imports another — as typed edges an agent can
walk and search on. Below is a real arac scan of a small
three-module Python project; click any node to see what the
read tool hands back.
Click any node · 9 of the 18 nodes this scan found — every one comes back enriched
arac read
Every tag on those edges is a type Aracne really stores —
calls, inherits,
uses_class, imports_module,
has_class, has_function,
methods. Note that process_order resolves
to StripeGateway.charge: the graph already knows which implementation
is wired up, which is the question grep cannot answer.
Save tokens with descriptions
A description is what lets the graph answer a question without opening the file it
came from. Same task, same agent, both ways — the only difference is whether it can ask
for a symbol instead of a filename.
What you needWhat you pay for anyway
Without Aracne
Two whole files to answer one question
Read orders.py34 lines · 11 useful
import jsonimport loggingfrom datetime import datetimefrom typing import Optionalfrom money import Moneyfrom payments import StripeGatewaylogger = logging.getLogger(__name__)class Order: """A customer order and the line-item prices it contains.""" def __init__(self, prices: list, note: Optional[str] = None): """Create an order from a list of line-item prices.""" self.prices = prices self.note = note def to_json(self) -> str: """Serialise the order for the audit log.""" logger.debug("serialising order") return json.dumps({"prices": self.prices, "note": self.note}) def total(self) -> Money: """Sum the line items into a single Money total.""" return Money(sum(self.prices))def process_order(order: Order, api_key: str, placed_at: datetime) -> str: """Charge an order and return a transaction id stamped with the order time.""" gateway = StripeGateway(api_key) txn = gateway.charge(order.total()) return f"{txn}@{placed_at.isoformat()}"
Needs yet another full file read
Read payments.py40 lines · 16 useful
import hashlibimport loggingfrom abc import ABC, abstractmethodfrom money import Moneylogger = logging.getLogger(__name__)class PaymentError(Exception): """Raised when a gateway refuses to take the money."""class PaymentGateway(ABC): """Contract every payment backend must satisfy.""" @abstractmethod def charge(self, money: Money) -> str: """Charge the customer and return the provider's transaction id.""" ...class StripeGateway(PaymentGateway): """PaymentGateway backed by the Stripe API.""" def __init__(self, api_key: str): """Store the Stripe secret key used to sign requests.""" self.api_key = api_key def _signature(self, cents: int) -> str: """Sign the request body with the account's secret key.""" return hashlib.sha256(f"{self.api_key}{cents}".encode()).hexdigest() def charge(self, money: Money) -> str: """Charge the card via Stripe and return the transaction id.""" cents = money.to_cents() if cents <= 0: raise PaymentError("charge amount must be positive") logger.info("charging %s cents", cents) return f"ch_{self._signature(cents)[:12]}"
```orders.pyimport datetime.datetimedef process_order(order: Order, api_key: str, placed_at: datetime) -> str: """Charge an order and return a transaction id stamped with the order time.""" gateway = StripeGateway(api_key) txn = gateway.charge(order.total()) return f"{txn}@{placed_at.isoformat()}"```# CONTEXT:## orders.Order: A customer order and the line-item prices it contains. orders.Order.total: Sum the line items into a single Money total.## payments.StripeGateway: PaymentGateway backed by the Stripe API. payments.StripeGateway.charge: Charge the card via Stripe, rejecting any non-positive amount, and return the transaction id.
15 lines in context · nothing wasted
Automatically detect errors
Edits can tell you what you just broke in the the same turn. No need for building or testing.
arac edit — reply, unpromptedno test run, nothing asked
2 edits applied across 2 file(s)Topology warnings (functions that may need manual review): - [signature_changed] charge changed signature, verify caller orders.process_order (source: payments.StripeGateway.charge, target: orders.process_order) - [signature_changed] to_cents changed signature, verify caller payments.StripeGateway.charge (source: money.Money.to_cents, target: payments.StripeGateway.charge)
Enhance your navigation
A plain grep only matches characters that are physically in a
file. Aracne answers the same command from the graph, so a search also matches
descriptions — and every hit is named by the node it came from.
The question: “where does a payment get turned down?” Same three modules
you clicked through above, same command typed both times — and the word
reject appears nowhere in the source.
Without Aracne
The word isn’t in the file, so there is nothing to match
grep -rn "reject" .3 files · 0 hits
(no output — exit 1)
So it guesses at whatever vocabulary the code might have used instead
grep -rn "Error" .2 hits · unlabelled
payments.py:10:class PaymentError(Exception):payments.py:38: raise PaymentError("charge amount must be positive")
2 searches, then a file read anyway · and on a real repo Error returns hundreds
With Aracne
The same command, answered from the graph
grep -rn "reject" .intercepted → arac cmd
# payments.PaymentError — Raised when a gateway rejects a charge instead of taking the money.payments.py:10:class PaymentError(Exception):# payments.StripeGateway.charge — Charge the card via Stripe, rejecting any non-positive amount, and return the transaction id.payments.py:34:def charge(self, money: Money) -> str:
1 search · both places named, each with what it does
Quickstart
Two steps to a queryable codebase.
1 Install it
Go 1.25+ and a C compiler (the tree-sitter scanners bind through CGO). SQLite is pure Go — nothing else to install. Prefer no toolchain? Take a release binary instead.
go install github.com/Rhuan-Marques/aracne/cmd/arac@latest
arac --version
2 Set it up
One interactive flow. It asks a couple questions and sets up the rest for you
cd /path/to/your/project
arac init
That's it, it's done!
You can quickly generate descriptions using /descriptions-generate
in your harness of choice. This uses sub-agents to do the same job as an
arac descriptions generate command, uses your credits from your subscription
instead of an API key, and shows you the process inside your harness.
You can look at it using:
arac viz serve
You can also try it by hand, the CLI drives the same engine with no LLM in the loop:
Every command, flag, MCP tool, hook and config key is in the
full documentation →
Where it goes
The graph is the primitive. Now it goes to work.
Next up
Scheduling
Description generation and cleanup are the two jobs that cost tokens and need nobody
watching. Scheduling gives them a window — overnight, or any hour you have stepped away
— so a repo warms itself and stays tidy on the plan you already pay for,
instead of spending your attention and your rate limit in the middle of a session.
In the works
Bug management
A hunter, a judge and a solver: one pass sweeps for defects, a second weighs each one
against the topology around it rather than the diff alone, and a third fixes
what survives. What is left is running that on a schedule and reporting back — findings
you approve, not patches that appear behind you.
In the works
Dead code & graph analytics
A complete call graph can answer reachability, cycles, choke points and the blast radius of
a change. An earlier Go-only pass at this shipped and was pulled: the analysis has to work for
every language aracne scans, or it is a command that fails for most of the people who try it.
On the horizon
Uniting nodes into functionalities
A feature rarely lives in one function. The plan is to let models group related nodes into a named
functionality as they work — and maintain it as the code moves — so that
grep and friends can jump straight to "the auth flow" instead of reassembling it
from seven files every session. A graph that remembers what its clusters mean.
On the horizon
Aracne’s harness
Every mode so far bends someone else’s harness into topology-shaped answers — a contract to
inject, a guard to intercept, a filesystem metaphor to translate. A harness built on the graph from the
start has none of that to undo, which is where the topological capabilities stop being a
retrofit and start being the interface.
Give your agent the map.
Aracne is open source, self-hosted, and brings no model of its own. Clone it, scan something big, and watch the token counter.