Configuration¶
Overview¶
axiom-graph is configured through a single axiom-graph.toml file in your project root. Every section and key is optional: missing values fall back to sensible defaults, and if the file is absent entirely axiom-graph runs with all defaults. You only add the keys you want to change.
The file uses TOML syntax, and all settings live under the [axiom_graph] table (sub-tables like [axiom_graph.scan] group related keys). A small number of behaviors – logging in particular – are controlled by environment variables instead; see Environment Variables below.
Most of what you configure here shapes one thing: which files become nodes in the mesh, and how staleness flows across the edges between them. If you have not yet connected an agent, start with Connect your agent; the CLI commands referenced throughout this page are documented in Use the CLI.
What gets indexed: the [axiom_graph.scan] section¶
The [axiom_graph.scan] section decides which files axiom-graph discovers and turns into nodes. The fewer irrelevant files in the mesh, the less an agent has to wade through later – scanning configuration is the first lever on context reduction.
[axiom_graph.scan]
docs_dirs = ["docs", ".pev"]
config_dirs = [".claude"]
test_paths = ["tests/"]
js_paths = ["axiom_graph/viz/static/ts/*.ts"]
exclude_dirs = ["worktrees", "pev-worktrees"]
Key |
Type |
Default |
Purpose |
|---|---|---|---|
|
list of strings |
|
Directories scanned for Markdown and DocJSON files. The first entry is the primary write target for new docs. |
|
list of strings |
|
Directories scanned for agent/config artifacts (settings, commands, skills). |
|
list of strings |
|
Path prefixes that contain test code, e.g. |
|
list of strings |
|
Glob patterns for JS/TS files to scan. Empty means no JS/TS scanning. Requires the JS/TS extra (see below). |
|
list of strings |
|
Bare directory names to skip, in addition to the built-in set below. |
Built-in exclusions¶
axiom-graph ships with a built-in set of directories it always skips — version-control, virtualenv, build-output, cache, and worktree directories. The authoritative list lives in code (_BASE_SKIP_DIRS in axiom_graph/index/builder.py), not here, so this guide doesn’t duplicate it. Your exclude_dirs entries add to that set; they never replace it. Use exclude_dirs for project-specific data folders, scratch notebooks, or generated trees that would otherwise pollute the graph.
Multi-root docs and config¶
Because docs_dirs and config_dirs are lists, projects that scatter docs and config across several top-level directories do not have to consolidate them. axiom-graph scans each entry independently. Node IDs derive from each path’s form relative to its own root, so a file at .pev/cycles/foo.json becomes myproject::pev.cycles.foo, not myproject::docs.pev.cycles.foo. Relative paths resolve against the project root; absolute paths are honored as-is.
JavaScript / TypeScript scanning¶
By default only Python is scanned. Set js_paths to glob patterns to also index .js, .ts, .jsx, and .tsx files; this requires the JS/TS optional extra, which installs tree-sitter:
pip install "axiom-graph[js]"
The JS/TS scanner is what extends the semantic layer beyond Python: it picks up workflow(opts)(fn) / task(opts)(fn) envelopes and their Step / AutoStep markers, and – as proof the semantic layer is framework-aware – recognizes xstate v5 state machines (createMachine, setup().createMachine) as state/transition nodes. See Annotations for what those markers mean.
Project identity and database path¶
Two top-level keys live directly under [axiom_graph]:
[axiom_graph]
project_id = "my-project"
db_path = ".axiom_graph/graph.db"
Key |
Type |
Default |
Purpose |
|---|---|---|---|
|
string |
directory name |
Namespace prefix on every node ID (the part before |
|
string |
|
Location of the indexed mesh – the SQLite database that |
project_id is worth setting explicitly: it is baked into every node ID, so changing it later re-namespaces the whole graph. db_path rarely needs to change, but you can point it elsewhere if .axiom_graph/ is inconvenient for your layout.
Staleness propagation: the [axiom_graph.staleness] section¶
Staleness is not a bolt-on feature – it is a read on the same typed mesh that powers intent-scoped retrieval. Drift detection and “give me exactly the linked nodes” are two queries against one graph. The [axiom_graph.staleness] section tunes how a drift signal travels along the edges. For the full model, see Staleness.
[axiom_graph.staleness]
transitive_tags = ["consumer"]
frozen_tags = ["adr", "plan", "pev-cycle", "pev-instance", "pev-request"]
Including frozen docs on demand¶
When you do want to see frozen rows, the staleness read tools take an include_frozen switch. Pass include_frozen=true to axiom_graph_check or axiom_graph_drift_query and frozen sections are included, marked [frozen] in full-format output. The depth of the read is otherwise unchanged – this only widens which nodes are reported, not how far signals travel.
Rename detection: the [axiom_graph.rename] section¶
When a node disappears from one build and a similar node appears, axiom-graph tries to recognize that as a rename (identity moved) rather than a delete-plus-create. Welding the old identity to the new one preserves history, verification snapshots, and edges – so a symbol rename does not silently break every link pointing at it. The [axiom_graph.rename] section tunes the matcher.
[axiom_graph.rename]
code_threshold = 0.6 # min body-similarity ratio to auto-apply a code rename
prose_threshold = 0.5 # min ratio for prose (DocJSON) renames
pool_cap = 50 # max scoped-pool size before exact-hash fallback
Key |
Type |
Default |
Purpose |
|---|---|---|---|
|
float |
|
Minimum body-similarity ratio (0-1) for a lost code node to auto-weld to a newly-appeared one. |
|
float |
|
Minimum ratio for prose/DocJSON renames. |
|
integer |
|
Maximum number of candidate nodes in a scoped comparison pool. Above this, the matcher falls back to an exact-hash-only pass. |
Lower thresholds catch more renames but risk false welds; higher thresholds are conservative. The pool_cap keeps similarity scoring bounded – when a build touches a large number of nodes at once, exact-hash matching still catches cross-file moves without an expensive all-pairs comparison. If the matcher ever guesses wrong, the welds it makes are not permanent: review them with the rename CLI/MCP tools and undo with rename revert. See Use the CLI for the rename apply / rename revert commands.
Annotation validation¶
One more section governs the semantic layer – the annotated orchestration highways (@workflow / @task envelopes with Step / AutoStep markers). These are not a full call graph; they are the spots you deliberately annotate so an agent can read intent and step names instead of tracing every call. See Annotations.
Validation rules¶
At scan time, axiom-graph runs eight static rules (A1-A3, B1-B4, C1) over your annotations – checking that step numbers are well-formed, sequential, and so on. Findings flow into the build summary and check --format json. You can toggle the whole pass or individual rules:
[axiom_graph.validation]
enabled = true
[axiom_graph.validation.rules]
A1 = true
A2 = true
A3 = true
B1 = true
B2 = true
B3 = true
B4 = true
C1 = true
Key |
Type |
Default |
Purpose |
|---|---|---|---|
|
boolean |
|
Master switch. When |
|
boolean |
|
Per-rule toggle. An unknown rule ID raises a config error rather than being silently ignored. |
The rules, briefly: A1 step_num is a positive int/float; A2 Step(name, purpose) both non-empty; A3 AutoStep arg shape valid; B1 no duplicate step_num in an envelope; B2 major step numbers form 1, 2, 3 with no gaps; B3 non-integer step_num must sit inside a loop; B4 AutoStep must be immediately followed by a call to a decorated function; C1 @workflow / @task purpose is non-empty.
Size and age thresholds: [axiom_graph.thresholds]¶
These thresholds drive advisory flags during builds and the age-based staleness clock.
[axiom_graph.thresholds]
max_function_lines = 80
max_module_lines = 600
stale_days = 90
Key |
Type |
Default |
Purpose |
|---|---|---|---|
|
integer |
|
A function longer than this is flagged as oversized during builds. |
|
integer |
|
A module longer than this is flagged as oversized during builds. |
|
integer |
|
An unverified node older than this many days is considered stale. |
The line limits nudge toward atomic, function-granular code – which keeps each node small enough that reading one source range, not a whole file, is genuinely useful.
Publishing the docs site: [axiom_graph.site]¶
The [axiom_graph.site] section controls the axiom-graph render-site pipeline, which converts DocJSON documents into clean Markdown for a static site generator.
[axiom_graph.site]
nav_file = "site-nav.yml"
output_dir = "site"
Key |
Type |
Default |
Purpose |
|---|---|---|---|
|
string |
|
YAML file defining the site navigation and which docs to include. Resolved relative to the project root. Used as the implicit |
|
string |
|
Output directory for the implicit render when no targets are declared. |
Both can be overridden per invocation with render-site’s --nav and --output flags.
Configurable render targets: [[axiom_graph.site.targets]]¶
Declare one or more named render targets. When any targets are present, nav_file / output_dir are ignored for the default run and only the explicit targets apply. Use --target NAME (repeatable) to render a subset.
[[axiom_graph.site.targets]]
name = "guide"
output = "userdocs/guide"
format = "sphinx"
nav = "site-nav.yml"
[[axiom_graph.site.targets]]
name = "readme"
output = "README.md"
format = "plain"
doc = "myproject::docs.consumer.readme"
overwrite = true
[[axiom_graph.site.targets]]
name = "plugin-pev"
output = "pev_nexus_agents/pev/docs"
format = "plain"
nav = "docs/consumer/plugins/pev/nav.yml"
overwrite = true
Key |
Type |
Required |
Purpose |
|---|---|---|---|
|
string |
yes |
Unique target identifier used by |
|
string |
yes |
Output path relative to project root. For |
|
string |
|
|
|
string |
one of nav/doc |
Path to a |
|
string |
one of nav/doc |
A single DocJSON doc-id to render to |
|
bool |
|
When |
Validation rules (enforced at config load): exactly one of nav/doc must be set; format must be "plain" or "sphinx"; sphinx requires nav (never doc); target names must be unique.
Implicit target synthesis: when targets is empty or absent, a synthetic guide target is created pointing nav_file → userdocs/guide with format="sphinx" — preserving the pre-targets default behavior.
Hybrid manifest: subtree (nav) targets keep their co-located .render-manifest.json in the output directory (unchanged). Single-file (doc) targets are recorded in .axiom_graph/render-manifest.json keyed by repo-relative output path. Subset runs (--target NAME) merge their entries into the central manifest without erasing other targets’ entries.
For a worked, runnable walkthrough — declaring README, plugin-docs, and guide targets and rendering each — see the multi-target rendering tutorial.
This section is the publishing end of the docs-honesty loop. Consumer/published docs are themselves DocJSON nodes in the mesh; they link through a dev-doc proxy to the code, and — because "consumer" is listed in transitive_tags — they inherit LINKED_STALE when that code drifts. That staleness is the signal to update the prose; verifying or mark-clean-ing the section clears it; render-site republishes the corrected page. The site you are reading is built this way (axiom-graph dogfooding its own loop). For the full walkthrough, see the docs-honesty loop.
Environment Variables¶
Logging is controlled by environment variables rather than the TOML file. These apply to both the CLI and the MCP server (the primary integration surface for agents).
Variable |
Default |
Description |
|---|---|---|
|
|
Override the log level. Accepts standard Python levels: |
|
(none) |
When set, log output is also written to this file path (in addition to stderr). Applies to the MCP server only. |
When an MCP tool call behaves unexpectedly, setting AXIOM_GRAPH_LOG_LEVEL=DEBUG (and optionally AXIOM_GRAPH_LOG_FILE) is the fastest way to see what the server is doing.
Complete Example¶
A full axiom-graph.toml with every section. All keys are optional – include only the ones you want to change from their defaults.
[axiom_graph]
project_id = "my-project"
db_path = ".axiom_graph/graph.db"
[axiom_graph.scan]
docs_dirs = ["docs", ".pev"]
config_dirs = [".claude"]
test_paths = ["tests/"]
js_paths = ["src/**/*.ts"]
exclude_dirs = ["worktrees", "pev-worktrees"]
[axiom_graph.thresholds]
max_function_lines = 80
max_module_lines = 600
stale_days = 90
[axiom_graph.staleness]
transitive_tags = ["consumer"]
frozen_tags = ["adr", "plan", "pev-cycle", "pev-instance", "pev-request"]
[axiom_graph.rename]
code_threshold = 0.6
prose_threshold = 0.5
pool_cap = 50
[axiom_graph.validation]
enabled = true
[axiom_graph.validation.rules]
A1 = true
A2 = true
A3 = true
B1 = true
B2 = true
B3 = true
B4 = true
C1 = true
# Configurable render targets. With no --target, render-site regenerates all
# targets; --target NAME renders a subset. overwrite = true lets the first run
# replace hand-authored files; subsequent runs see the provenance stamp and
# regenerate cleanly.
[[axiom_graph.site.targets]]
name = "guide"
output = "userdocs/guide"
format = "sphinx"
nav = "site-nav.yml"
[[axiom_graph.site.targets]]
name = "readme"
output = "README.md"
format = "plain"
doc = "my-project::docs.consumer.readme"
overwrite = true
[[axiom_graph.site.targets]]
name = "plugin-docs"
output = "plugins/myplugin/docs"
format = "plain"
nav = "docs/consumer/plugins/myplugin/nav.yml"
overwrite = true
Note: a
[semantic]install extra and embeddings-based search existed in earlier versions. Semantic search is deprecated (2.1.0) and removed in 3.0 – there is no embeddings extra or config to set.Note: omitting
[[axiom_graph.site.targets]]entirely preserves the pre-targets default behavior: a syntheticguidesphinx target is created fromnav_file→userdocs/guide.