Skip to main content
Version: 1.1.0

Semantic enrichment

Two independent semantic layers, both optional. Without them, the graph is purely structural (AST-extracted) — still fully queryable.

Local embeddings (no API key)​

astria run . --embed

Downloads a local model once (jina-embeddings-v2-base-code, ~615 MB, then offline forever) and computes vector embeddings for every node. This adds:

  • similar_to edges (INFERRED, cosine-scored) linking semantically related symbols across files — they flow into clustering, surprising connections, and every export
  • embedding-backed query recall: query merges semantic candidates with token matching, so conceptual questions with zero string overlap still find their symbols. When the question's identifying terms have no lexical evidence anywhere, a strong embedding match ranks like a label match instead of capping below partial word matches — the model is the only witness to what the question is about. The model loads once per process, so steady-state queries answer in ~100ms

Once embeddings exist, every run/update refreshes them incrementally (offline — the refresh never downloads), and query picks them up automatically.

Override the model cache location with ASTRIA_EMBED_CACHE_DIR — all variables in Environment variables.

LLM enrichment​

Select an LLM backend explicitly with --backend or ASTRIA_LLM_BACKEND to enrich documents, papers, and images. Credentials or endpoint variables alone never activate network enrichment. --backend none or ASTRIA_LLM_BACKEND=none disables it.

BackendEnv varsVision
Anthropic Claude (claude)ASTRIA_LLM_API_KEY✓
OpenAI-compatible (openai: OpenAI, DeepSeek, Ollama, LM Studio, custom)ASTRIA_LLM_BASE_URL + ASTRIA_LLM_API_KEY/OPENAI_API_KEY✓
Google Gemini (gemini)GEMINI_API_KEY or GOOGLE_API_KEY✓
  • ASTRIA_LLM_BACKEND selects the backend explicitly
  • ASTRIA_LLM_MODEL overrides the model
  • Per-run: astria run . --backend openai --model gpt-4o-mini
  • Images (png/jpg/webp/gif, ≤5 MB) go through each backend's vision API
  • ASTRIA_LLM_CONCURRENCY controls the parallel worker pool; long files are chunked and LLM output is validated

Once selected, a backend reads its configured credentials, including the generic provider environment variables listed above. Without explicit selection, the structural pipeline does not invoke an LLM. --label-communities and --deep also require explicit backend selection.

Jev judge layer​

astria run . --backend openai --judge jev keeps the engine backend as the generator and layers TypeSafe's Jev on top of it. Jev is a System One decision model — typed judgments with calibrated probabilities, not a text generator — so it never writes the extraction itself; it re-judges what the engine produced:

  • Gates trivial files before their first extraction with batched keep/drop judgments (≈1 request per 50 files), so empty or trivial files never cost an engine call. Gated files keep their structural extraction; the run summary reports the count ("N files gated by Jev"). Verdicts are cached per file content and judge configuration, so re-runs are reproducible and unchanged files cost no gate calls (ASTRIA_LLM_JEV_GATE_CACHE=off restores live re-judging).
  • Re-judges every extraction in one request per file: relations and node types are re-chosen from the schema allowlists (replacing the lossy clamps), and every edge gets a keep/drop existence verdict. Spurious edges are dropped; kept edges carry the judge's keep probability as a calibrated confidence_score in the graph — surfaced on query EDGE lines ([SEMANTIC:0.82]), explain/MCP neighbor listings, and wiki relation sections. Query traversal makes the signal load-bearing: nodes reached only through low-confidence semantic links sort after strongly-reached ones, and ASTRIA_QUERY_MIN_SEMANTIC_CONFIDENCE sets a hard floor that drops weak verified edges from traversal entirely (structural and inferred edges are never filtered).
  • Re-ranks the suggested questions in graph_report.md so the most useful one leads (only on runs that rebuilt the graph).

Judge decisions are cheap and batched, count toward ASTRIA_LLM_BUDGET like every other call, and fingerprint into the extraction cache — changing the judge or its configuration invalidates cached extractions. The judge requires an explicit backend (it wraps an engine; it cannot generate extractions — --backend jev is rejected with a pointer to --judge).

Cost: measured, capped, and cached​

Every response's usage block is counted across the whole run — extraction, gate, verification, community naming, and deep linking — and the run summary prints it:

LLM usage: 17 API calls, 3366 in / 1675 out tokens
  • ASTRIA_LLM_BUDGET (total tokens) stops extraction before the cap is exceeded; remaining files fail loudly instead of silently skipping. The cap is enforced with per-request reservations: every billable call (each extraction chunk, vision calls, complete() passes, judge requests) atomically claims its input estimate plus the full output allowance the wire request permits before it flies, so concurrent workers cannot collectively overshoot the cap. Reservations bound spend conservatively — recorded usage still comes from provider responses — so the budget is an enforced estimate, not a metered invoice.
  • ASTRIA_LLM_MAX_CHUNKS (default 8, clamped 1–64) caps how many chunks one file may cost. Content beyond the cap fails loudly — a truncated extraction is never returned or cached as if it covered the whole file. Raise it for oversized generated files (the effective cap participates in the cache fingerprint).
  • pipeline_runs records each run's llm_input_tokens / llm_output_tokens / llm_api_calls, so spend is queryable history, not a vibe.
  • Extraction caches include file content plus the effective backend, endpoint, model, and prompt configuration. Matching inputs reuse output; configuration changes invalidate it. Cached and fresh output follow the same merge path.
  • Failed semantic extraction leaves the core graph and successful-file manifest unadvanced, so a later update retries the work. Derived community-label and deep-link stages run after the core commit and can be retried separately on the next run.

Thematic community labels — --label-communities​

astria run . --backend openai --label-communities names communities with one LLM call per changed community:

Communities labeled: 6 (--label-communities)
...
Communities labeled: 0, 6 unchanged (--label-communities) # second run: all reused
  • The label ships with a one-line summary, and both are first-class data: MCP list_communities, graph_report.md, graph.json (communities array), wiki, and Obsidian all show them.
  • Provenance is explicit — label_source is llm or hub, so a themed label is always distinguishable from the deterministic thematic/hub term that names the community otherwise.
  • Caching includes community membership, prompt inputs, and effective backend configuration: changed members, prompts, endpoint, or model require fresh output. ASTRIA_LLM_COMMUNITY_MAX caps calls per run (default 48, largest communities first); communities under 3 nodes keep deterministic names.

--deep is the second extraction tier: one LLM call per file offers the file's code symbols against the graph's concept nodes and writes the meaningful matches as INFERRED edges tagged context='deep' — the cross-file concept mesh the AST cannot see.

  • Results are cached against file content, offered symbols and concepts, prompts, and effective backend configuration. Changes to any of those inputs can require a fresh call.
  • Source changes invalidate deep edges. Run with --deep again to replay valid cached links or regenerate links for changed inputs; ordinary updates do not restore them. --detail high continues to filter deep links as inferred evidence.
Deep concept links: 27 (--deep)