Huginn

A self-hosted, model-agnostic coding agent — Muninn's brother raven ("thought" to Muninn's "memory"). Built spine-first from first principles, then refined against the best ideas from the open-source coding-agent community (aider, SWE-agent, opencode).

Philosophy

Models are swappable batteries; the harness is the durable, owned, learnable layer — the edit tool, context strategy, verify loop, planning, self-review, memory, and safety gating. Point Huginn at any OpenAI-compatible endpoint (local llama.cpp on the GPU server, or a frontier provider with an API key) and every model improvement is a free upgrade. See ROADMAP for the full CP0→CP10 plan and what shipped.

What it can do

Agent loop with native tool-calling; anchored ("hashline") edits that don't require the model to retype code; a verify loop (run_command); a tree-sitter repo map; LSP code-intelligence (diagnostics/hover/goto/references/rename); phased planning with per-phase git commits; a self-review critic that auto-fixes blocking findings, now backed by a static-analysis loop (clippy / ruff / eslint / go vet diagnostics folded in as deterministic findings); a repro-test-first check that re-runs a bug fix's own reported test before honoring "done"; cross-session memory plus a procedural-skill loop that learns reusable approaches from clean jobs; a command-safety policy (classify → ask/allowlist/judge); session recovery (transient-retry + convergent context-window compaction); a multi-agent delegate tool for subagent fan-out; and a scaffold_project tool that bootstraps a project for checkpoint-by-checkpoint building. It also keeps a run-trace ledger and an L4 self-improvement loop that proposes harness-config changes from its own scorecard. Reachable four ways: TUI, one-shot, REPL, and a headless NATS worker (delegation from Muninn).

Benchmarks

SWE-bench Verified: 68.2% resolved (341/500), scored with the official swebench harness — driving a local, quantized Qwen3.6-27B (INT4) on a self-hosted OpenAI-compatible endpoint, no frontier API, ~$0/instance.

That is a frontier-adjacent number on commodity hardware: it sits at or above where GPT-4-class agents scored on this exact benchmark a few months prior, reached here on a single quantized open model served from a home GPU box.

Controlled harness A/B — Huginn vs. the reference harness. To isolate the harness from the model, I ran mini-SWE-agent (the SWE-bench team's own reference minimal harness) on the same Qwen3.6-27B, the same 500 Verified instances, graded by the same official harness:

Harness (same model, n=500) Resolved
Huginn (this project) 341/500 (68.2%)
mini-SWE-agent (reference) 351/500 (70.2%)
Paired McNemar test χ²=1.09, n.s. statistically tied

A solo-built agent — written in a few days and still WIP — landing in a statistical dead heat with the field's reference harness, on the reference team's own benchmark, is the headline result. On a strong model the harness is not the differentiator; the model dominates and a sound harness is enough to extract its full capability — and a sound one is reachable fast.

The harnesses are complementary. They resolve different instances — Huginn-only 32, mini-only 42 — so their union is 383/500 = 76.6%. There is real signal in which problems each harness solves, not just how many.

The harness still moves the needle measurably. On a frozen, gold-verified 39-instance subset, the same model climbs on harness levers alone — baseline 15/39 → +self-review gate 17/39 → +larger iteration budget 20/39 (38.5% → 51.3%) — evidence the layer is real, even if it doesn't beat a well-built peer harness on a capable model.

Install

Option A — drop-in static binary (Linux x86-64)

A fully static (musl) binary with no runtime dependencies — copy it to any Linux box and run:

scp dist/huginn-x86_64-linux-musl  you@host:~/.local/bin/huginn
chmod +x ~/.local/bin/huginn

Build it yourself with scripts/build-static.sh (needs musl-tools + rustup target add x86_64-unknown-linux-musl).

Option B — build from source (any OS/arch)

cargo build --release
install -m755 target/release/huginn ~/.local/bin/huginn   # optional

Rust + a C toolchain (for the tree-sitter grammars). macOS, Windows, and ARM build the same way; the TUI (ratatui/crossterm) is cross-platform.

Configure

The only hard requirement is a model endpoint. Set it in a config file or via env vars.

cp config.example.toml config.toml      # then edit [model].base_url / model / api_key

Config precedence: --config <path>./config.toml~/.config/huginn/config.toml → built-in defaults (which point at the GPU server). Env overrides (handy for a different box): HUGINN_BASE_URL, HUGINN_MODEL, HUGINN_API_KEY. For example, against a local llama.cpp:

HUGINN_BASE_URL=http://localhost:8080/v1 HUGINN_MODEL=qwen huginn --tui

Resilience and multiple endpoints (optional). The [model] block can carry extra interchangeable hosts that serve the same model, round-robined per request ([[model.peers]] — e.g. Qwen on both the GPU server and .78); a dead one is skipped. A separate [model_fallback] block is a different host the agent fails over to when the primary is unreachable (transport error / 5xx / 429 / stall) — failover, not routing. Both are opt-in; with neither set, behavior is unchanged. Named [models.<name>] tables form a registry the TUI's /model command switches between live (see TUI keys).

Optional tools (everything degrades gracefully if missing)

Tool on PATH Enables If missing
git per-task checkpoints (/undo), self-review diff edits still work; no checkpoint/review
rust-analyzer, pyright, typescript-language-server, gopls LSP tools for that language that language's LSP tools report "not installed"
rustfmt / prettier / black / gofmt format-on-edit edits aren't auto-formatted

Run

The workspace defaults to the current directorycd into a repo and go (or pass --workspace <path>). File tools are sandboxed to that root.

# Full-screen TUI cockpit (recommended for interactive work):
cd ~/code/myrepo && huginn --tui

# One-shot:
huginn --workspace ~/code/myrepo "add a doc comment to main.rs"

# Interactive line-REPL:
huginn --workspace ~/code/myrepo

# Headless NATS worker (delegation target for Muninn):
huginn --worker

The TUI is a full cockpit: markdown-rendered answers (fenced code blocks, lists, tables), live token streaming (with the model's "thinking" preamble shown dim-italic and the committed answer bright), a live plan panel that shows phases tick off as the agent works, inline diffs of every edit/write (red −/ green +), a background resource/LLM-status bar (CPU/RAM/GPU/VRAM + whether the model endpoint is up and which model it's serving, plus a live context-fill gauge — green/amber/red by how full the window is — and live tok/s while streaming), a toggleable tool panel (F2) with recent tool calls and timing, and full input-line editing with history.

TUI keys

Type + Enter to send · Shift+Enter newline · ←/→ Home/End edit the line · / command history · PgUp/PgDn, Ctrl+↑/Ctrl+↓, or the mouse wheel scroll · F2 tool panel · /plan /undo · /compact (summarize old history) /clear (reset to a fresh context) · /model [name] (list configured models, or switch the active one live between turns) · /add-model (interactive wizard that registers a new model endpoint and appends it to the config). When a command needs approval, a modal appears — ←/→ then Enter, or y/n/a (approve-all this session). Long input soft-wraps as you type.

Stopping vs. quitting. Esc interrupts the running turn (it abandons the in-flight model/tool call immediately; when idle it clears the input line) — it does not quit. Ctrl-C/Ctrl-D also interrupt while working, and quit when idle. The clean way to close is /exit (or /quit), which also reaps any servers the agent left running in the background.

Steering mid-run. The input stays live while the agent works — type a message and Enter and it's folded into the agent's context at its next model step (not queued for after), so you can redirect it without stopping: "focus on the parser bug first", "don't touch the tests", "stop and just show me the diff". Steering messages show as ↳ steering: in the transcript.

Command-safety policy ([agent] command_policy)

run_command executes shell as you — fine in a trusted session, dangerous unattended. So every command is first classified deterministically, then a configured policy decides its fate. The classifier (policy.rs) reads the whole line — each &&/||/|/; segment is judged independently, so a chained cargo build && rm -rf / is caught:

Class Meaning
Safe Every segment leads with an allowlisted read-only/build/VCS-inspection tool (cargo, git status, ls, pytest, gcc, …), with destructive subcommands filtered out (git reset/checkout, cargo publish, pip install, …).
Dangerous Any segment matches a known-destructive pattern — rm -rf, dd, mkfs, sudo, git push --force, curl … | sh, fork bombs, writes to /dev or /etc, …
Unknown Neither clearly safe nor clearly dangerous (a custom script, docker run, mv).

The policy then governs what runs:

Policy Safe Unknown / Dangerous
ask (default) runs interactive approval prompt (denied if headless)
allowlist runs denied — fully non-interactive safe mode
judge runs an LLM safety judge decides ALLOW/BLOCK (no human in the loop)
auto runs runs — trusted/sandboxed environments only

judge is what makes Huginn safe to drive headlessly: it mirrors Muninn's fast-reject-then-judge pipeline, and the judge is fail-safe — anything that isn't a clear ALLOW is treated as a block, and the prompt biases toward blocking when uncertain (a false block just re-routes the agent; a false allow is irreversible). Worker mode forces judge regardless of config.

File-write confinement. Independently of the command policy, every file tool resolves paths against the workspace root and refuses any write or read that escapes it (ctx.resolve()../../etc/passwd is rejected). Edits are also syntax-guarded: an edit that would turn a parsing file into a non-parsing one is refused.

Known limitations (this is a heuristic permission layer, not a sandbox — see ROADMAP):

  • File writes aren't policy-gated. They're confined to the workspace root, but within it any path can be written — there's no per-path write allowlist (e.g. to keep an agent from dirtying a test fixture or build output during an eval).
  • No network isolation. The classifier inspects command text, not behaviour; a command can still open sockets. Real egress control belongs at the container / namespace level, not in Huginn.

MCP servers (optional)

Huginn is an MCP client: list Model Context Protocol servers in config and their tools join the agent's native toolset (filesystem, git, databases, browsers, your own services). Both transports are supported — stdio (spawn a child process, the common npx case) and http (incl. FastMCP streamable-http). Tools are trust-by-config (configuring a server opts its tools in; each call still shows in the transcript and log), native tools win on a name clash, and a server that fails to start is logged and skipped. None configured → zero overhead. See the [[mcp_servers]] examples in config.example.toml.

[[mcp_servers]]
name = "filesystem"
kind = "stdio"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/project"]

Self-improvement loop (the L4 scorecard)

Every finished job appends one compact RunRecord to a global ledger (runs.jsonl in the state dir) — outcome, why the loop stopped, iterations, tool-failure count, and which quality levers were on. huginn --runs prints the analysis. With failure_mining on, a non-clean run also gets a one-word reason tag (a cheap-model pass: went-in-circles, forgot-to-validate, task-too-hard, …) so failures group by real cause, not just the stop symptom.

huginn --improve reads that scorecard and prints ranked, concrete config changes: climbers (e.g. raise the iteration budget when runs hit it, lower temperature on high tool-failure rates) and ablations (flip an on-by-default lever off purely to measure what it's worth). An addressability filter suppresses climbers when mining says the failures aren't harness-fixable, so it doesn't churn config on noise. --improve-apply writes the top proposal as a candidate config file (the live, hand-edited config is never rewritten) and opens an experiment; the apply→decide arm stays inert until a real SWE-bench score is fed back via --improve-resolve <id> --baseline PASS/TOTAL --candidate PASS/TOTAL, which keeps the candidate only if it resolves strictly more instances — otherwise it reverts. A change is kept only when a number says it helped. See DESIGN-multiagent.

Hermes feed (optional)

The worker also emits its events as coabai-agent-protocol Envelopes on a stable NATS subject (hermes.huginn.events, overridable via HUGINN_HERMES_SUBJECT) for the Hermes fleet console. Purely additive — the per-job thin-JSON feed Muninn's Matrix relay parses is untouched, and publishing is best-effort (no subscriber → dropped, so it's harmless when Hermes isn't running).

Delegation from Muninn (optional)

huginn --worker consumes coding jobs off a NATS JetStream work-queue and posts results back — so "hey Muninn, fix X in dlejos" from your phone reaches Huginn on the box with the repos. Entirely opt-in; see the [worker] block in config.example.toml. Not needed for any local use.