# Runar Forge

> Persistent, semantically-searchable memory and a symbol-level code graph for AI coding agents. One static Rust binary — SQLite or PostgreSQL, MCP-compatible.

- **Type:** MCP server
- **Install:** `agentstack add mcp-crlome-runar-forge`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [crlome](https://agentstack.voostack.com/s/crlome)
- **Installs:** 0
- **Category:** [Databases](https://agentstack.voostack.com/c/databases)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [crlome](https://github.com/crlome)
- **Source:** https://github.com/crlome/runar-forge
- **Website:** https://runar-forge.com

## Install

```sh
agentstack add mcp-crlome-runar-forge
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# RunarForge — Huginn & Muninn

[](https://github.com/crlome/runar-forge/releases/latest)
[](https://www.npmjs.com/package/@runar-forge/cli)
[](https://github.com/crlome/runar-forge/actions/workflows/ci.yml)
[](LICENSE)

> Persistent, semantically-searchable memory for AI coding tools.
> One static Rust binary. SQLite or PostgreSQL. MCP-compatible.

**[runar-forge.com](https://runar-forge.com)**

**`runar`** is a single-binary CLI that gives Claude Code, VS Code,
OpenCode, Codex, Cursor, Windsurf, Trae, Gemini CLI, Continue.dev, Zed,
Goose, Kimi — any MCP-aware AI tool — a memory that survives session
closes, travels across tools, and understands codebase structure.

Named after Odin's two ravens from Norse mythology:

- **Huginn** ("thought") — the **scout**: crawls codebases, builds
  dependency graphs, scores file importance, extracts patterns and
  tech debt.
- **Muninn** ("memory") — the **librarian**: stores, retrieves,
  ranks, and graduates entries through tiered memory layers
  (working → episodic → semantic → archival).

A third subsystem, the **Curator**, answers natural-language
questions by assembling context from memory.

---

## Features

- **One static binary.** No Node, no Python, no runtime deps.
  ~16 MB on macOS arm64; ~70 MB with bundled local embeddings.
- **Two storage backends.**
  - SQLite (bundled via `rusqlite` `bundled-full` + FTS5) — zero
    setup, single-machine, default for solo use.
  - PostgreSQL 16+ with `pgvector` — multi-user, full semantic
    search, shared team memory.
- **Hybrid local + remote sync.** Outbox + reconcile pattern with
  LWW + verified tiebreaker. Run sqlite or local-pg as a fast
  primary and a remote PG as the team store; `runar sync push|pull`
  drains the outbox and pulls deltas.
- **MCP server.** `runar mcp-muninn` speaks the Model Context
  Protocol over stdio. 28 tools exposed
  under `muninn_*` (13), `huginn_*` (10), `curator_*` (5).
- **Symbol-level code graph.** `runar crawl` extracts definitions, call
  sites and per-function complexity into a dedicated `codegraph.db`
  (Rust, TypeScript/JS, Python, Go), then resolves calls into edges
  through a confidence-ranked tier chain. Query it with
  `runar graph search|symbol|trace|status` or the `huginn_search_graph`
  / `huginn_symbol` / `huginn_trace` MCP tools. An edge is evidence or
  it is absent: unmatched calls are counted, never attached to a
  plausible-looking definition.
- **A code view for humans.** `runar graph serve` opens a browser on
  localhost; `runar graph export` writes the same interface to a single
  HTML file that works offline. No bundler, no CDN, no vendored
  JavaScript, and no new dependency — see [Code view](#code-view).
- **A graph that says when it is stale, and can fix itself.** Every index
  records the commit and working-tree state it read, so `runar doctor` and
  the session code map report when the graph has fallen behind instead of
  answering confidently from an old picture. `runar graph refresh` re-indexes
  the graph alone in well under a second — only changed files are re-parsed —
  and the opt-in hooks keep it current on their own. See
  [Keeping the graph current](#keeping-the-graph-current).
- **Tiered memory.** Entries graduate through 4 layers based on age,
  citation count, verification, and confidence; `runar gc` enforces
  retention.
- **Auto-capture.** Optional Claude Code hooks queue PostToolUse
  payloads, then summarize them at session end (Claude Haiku if
  `ANTHROPIC_API_KEY` is set, otherwise heuristic fallback).
- **Health checks.** `runar doctor` runs 21 read-only probes — storage,
  schema, migrations, FTS parity, sync state, hook log, code-graph coverage
  and freshness, and the auto-refresh record. A degraded subsystem is
  reported as skipped with the reason and the fix, never as a silent pass.
- **Self-update.** `runar update --check|--rollback` fetches signed
  release manifests; previous binary preserved at `runar.previous`.

---

## Install

### A. Build from source

```bash
git clone https://github.com/crlome/runar-forge.git
cd runar-forge
cargo build --release -p runar-muninn
# Binary lands at target/release/runar
sudo mv target/release/runar /usr/local/bin/runar
```

With bundled local embeddings (no external embedding API required):

```bash
cargo build --release -p runar-muninn --features local-embeddings
```

### B. Pre-built binary

Pick the matching tarball from
[GitHub Releases](https://github.com/crlome/runar-forge/releases):

| Target                          | Asset                                          |
|---------------------------------|------------------------------------------------|
| Linux x86_64                    | `runar-x86_64-unknown-linux-gnu.tar.gz`        |
| Linux aarch64                   | `runar-aarch64-unknown-linux-gnu.tar.gz`       |
| macOS arm64 (Apple Silicon)     | `runar-aarch64-apple-darwin.tar.gz`            |
| macOS x86_64 (Intel)            | `runar-x86_64-apple-darwin.tar.gz`             |
| Windows x86_64                  | `runar-x86_64-pc-windows-msvc.zip`             |

Each tarball/zip is paired with a `.sha256` checksum.

### C. npm

```bash
npm install -g @runar-forge/cli
```

`@runar-forge/cli` is a tiny launcher; npm pulls the binary as the matching
per-platform optional dependency (`@runar-forge/cli--`). **No install
scripts, no install-time network** — so it installs cleanly even with
`ignore-scripts=true`.

---

## Quick start

```bash
runar init                          # creates ~/.runar-forge/.env
runar config wizard                 # interactive storage + DB setup
runar doctor                        # 21 read-only health checks
runar setup claude-code -p my-proj  # wire MCP tools into Claude Code
```

Optional auto-capture (PostToolUse queue + SessionEnd summarizer):

```bash
runar setup claude-code -p my-proj --with-auto-capture
```

Other editors — same `mcp-muninn` server, different config file. `vscode`,
`opencode`, and `codex` auto-write their config; `cursor` and `windsurf` print
it for manual paste. Hooks (context injection, auto-capture) are Claude Code
only — the rest are MCP tools only.

```bash
runar setup vscode      # writes .vscode/mcp.json   (servers.muninn)
runar setup opencode    # writes opencode.json      (mcp.muninn, type=local)
runar setup codex       # writes ~/.codex/config.toml ([mcp_servers.muninn])
runar setup cursor      # prints MCP config for manual paste
runar setup windsurf    # prints MCP config for manual paste
```

---

## Code view

Every other surface on the graph targets an agent. This one targets you.

```bash
runar graph serve                 # opens a browser on 127.0.0.1
runar graph export -p myproject   # one self-contained HTML file
```

Two altitudes of one tool, with a toggle between them. Selecting a symbol never
changes altitude — clicking a tower shows you its card where it stands.

### City

Directories are blocks, symbols are buildings, height is cyclomatic complexity,
so the thing most likely to hurt you is the tallest thing on screen. Selecting
one arcs its calls across the city; the dash pattern is the resolver's own
confidence tier, so a weak guess looks like one.

Only symbols that *have* complexity get a building — on a large codebase most
symbols are fields and type declarations, and drawing them buries the towers
that matter. The header always states what was drawn against what exists.

### Symbol

Search on the left, the neighbourhood in the middle, the definition on the
right. Never the whole graph: one symbol's callers and callees, and a click
moves you to any of them.

Note the card shows **call sites** and **distinct callers** separately. They are
different questions, and on real code they differ by a factor of three or more —
a function called ten times from one test is not a hub.

### Projects

One `codegraph.db` holds every project you have crawled. Served, the switcher
reads it live.

> Screenshots are taken against generated fixtures, not real code —
> see `tools/fixtures/demo_graph.py`.

**On `serve` and safety.** It binds `127.0.0.1` only, puts a 128-bit random
token in the URL path so a page you visit cannot reach it by guessing the port,
validates the `Host` header against DNS rebinding, answers `GET` only, and
opens the store read-only. No CORS headers are sent.

---

## Keeping the graph current

The graph is built by `runar crawl`, so between crawls it describes the code as
it was, not as it is. That matters more than ordinary staleness: `graph
search|symbol|trace` and the search-hint hook answer *confidently* from the old
picture, and a stale symbol looks exactly like a current one.

Every index now records two things about the tree it read — the commit at
`HEAD`, and a signature over `git status --porcelain` with each listed path's
size and mtime. Any reader can compare them without opening the graph writable.

```bash
runar graph refresh --project myproject --check   # 0 current, 2 stale, 3 cannot tell
runar graph refresh --project myproject           # re-index; ~0.3s on a 160-file project
```

`runar doctor` reports the same verdict, and the session code map adds one
`STALE:` line when the graph is genuinely behind. Nothing is claimed when it
cannot be known: a project without git, or a graph built before this feature
existed, reports "cannot judge" rather than a false all-clear.

### Automatic refresh (opt-in)

```bash
runar setup claude-code --project myproject --with-graph-autorefresh
```

Installs two triggers — after a file write, and at session start. Neither does
any work in the hook itself: it reads one timestamp file and, if a refresh is
due, starts a detached child, so the tool call that triggered it never waits.

The child refuses more often than it runs, and deliberately so. It will not
build a graph that does not exist, will not touch a project without git, and
skips entirely when the recorded signals have not changed — it fires on
*change*, never on "the working tree is dirty", because a repository that stays
dirty all afternoon is not getting staler. After each pass the next one is held
off for twenty times what that one cost, floored at 30s and capped at 10
minutes.

Every decision — including every refusal — is recorded, because a background
task that quietly does nothing is indistinguishable from one that is broken:

```
$ runar doctor
✔ graph auto-refresh  myproject: done (4s ago, 379ms)
     3 fire(s): 1 started a refresh, 2 debounced; 100% of runs had work;
     1 refresh(es), 373ms median / 373ms p95
```

Raw rows land in `~/.runar-forge/graph-refresh-stats.tsv`. Remove the hooks
again with `--no-graph-autorefresh`.

> Enable it per project as you return to work on each one. It only helps where
> you are actively editing, and it requires that project to have been crawled
> at least once — `runar graph refresh` is maintenance, never construction.

---

## CLI reference

| Command                      | Purpose                                                                         |
|------------------------------|---------------------------------------------------------------------------------|
| `runar mcp-muninn`           | Start MCP server over stdio (called by AI tools, not humans).                  |
| `runar init`                 | Create `~/.runar-forge/.env`. `--interactive` runs the wizard.                 |
| `runar config `      | Manage `~/.runar-forge/.env`: `path`, `show`, `get`, `set`, `unset`, `wizard`. |
| `runar doctor`               | 21 read-only health checks. `--db`, `--json`, `--quiet`, `--timeout-ms`.       |
| `runar setup `         | Wire MCP into `claude-code`/`vscode`/`opencode`/`codex`/`cursor`/`windsurf`. `--with-auto-capture`, `--with-search-hints`, `--with-graph-autorefresh`, `--all-projects`, `--configure`. |
| `runar update`               | Self-update binary via release manifest. `--check`, `--channel`, `--rollback`. |
| `runar search `       | CLI-side semantic search; `--limit`.                                           |
| `runar save  `  | Save a memory entry from the shell. `--project`, `--type`, `--tags`, `--topic-key`. |
| `runar stats`                | Memory system statistics (entries by type, sessions, layers).                  |
| `runar crawl []`       | Crawl a project. `--project`, `--mode auto|full|incremental`. Builds the symbol graph by default; `--no-deep` skips it. |
| `runar graph search ` | Full-text search over symbol names. `--project`, `--label`, `--limit`.         |
| `runar graph symbol `  | Definition site, signature, metrics and one-hop callers/callees. `--project`.  |
| `runar graph trace `   | Walk the call graph. `--direction callers|callees`, `--depth` (max 5), `--limit`. |
| `runar graph status`         | Symbol-graph coverage: files indexed, skipped by language, symbols, edges, unresolved calls. |
| `runar graph serve`          | Open the code view on localhost. `--project`, `--port` (0 = pick one), `--no-open`. |
| `runar graph export`         | Write the code view to one self-contained HTML file. `--project`, `--output`.  |
| `runar graph refresh`        | Re-index an already-built graph so it matches the working tree. `--project`, `--path`, `--full`, `--check` (report only; exits 0 current / 2 stale / 3 cannot tell). |
| `runar graph autorefresh`    | Hook entry point: start a background refresh if one is due. `--silent` for hook use; opt in via `setup claude-code --with-graph-autorefresh`. |
| `runar hint`                 | PreToolUse `Grep|Glob` search hints from the symbol graph. `--silent` for hook use; opt in via `setup claude-code --with-search-hints`. |
| `runar architecture`         | Latest architecture summary for a project.                                     |
| `runar techdebt`             | List TODO/FIXME/HACK/XXX markers. `--type`.                                    |
| `runar ask `       | Curator Q&A from the CLI. `--project`.                                         |
| `runar onboard`              | Multi-section onboarding report. `--json`.                                     |
| `runar benchmark`            | Memory-quality benchmark (deterministic). `--mode quick|full`.                 |
| `runar gc`                   | Tier graduation + stale eviction. `--project`, `--dry-run`.                    |
| `runar export`               | Export entries as JSONL. `--project`, `--type`, `--output`, `--limit`.         |
| `runar import `        | Import entries from JSONL.                                                     |
| `runar sync init`            | Validate local + remote pair. `--force` overrides schema mismatch.             |
| `runar sync push`            | Drain local outbox to remote. `--limit`, `--dry-run`.                          |
| `runar sync pull`            | Incremental pull (remote → local). `--limit`, `--dry-run`, `--since`.          |
| `runar sync bootstrap`       | First-time / full-table pull. `--project`, `--page-size`, `--yes-i-know`.      |
| `runar sync status`          | Read-only sync health summary. `--json`.                                       |
| `runar sync enable\|disable` | Toggle background auto-sync.                                                   |
| `runar sync gc`              | Outbox retention sweep (default 7 days). `--dry-run`.                          |
| `runar session ping\|list`   | Session heartbeat / recent-session listing.                                    |
| `runar context`              | Print recent context (PreToolUse hook target).                                 |
| `runar nudge`                | UserPromptSubmit hook (idle-save reminder).                                    |
| `runar save-ack`             | PostToolUse acknowledgement hook.                                              |
| `runar extract`              | Passive PostToolUse extraction (gated by `RUNAR_PASSIVE_LEARNING=true`).       |
| `runar enqueue`              | Enqueue raw PostToolUse payload onto observation queue.

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [crlome](https://github.com/crlome)
- **Source:** [crlome/runar-forge](https://github.com/crlome/runar-forge)
- **License:** MIT
- **Homepage:** https://runar-forge.com

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-crlome-runar-forge
- Seller: https://agentstack.voostack.com/s/crlome
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
