
GPL-3.0 · Rust · on crates.io
dirge — The Batteries-Included
Rust Coding Agent
Everything on by default — MCP, LSP, ACP, a Janet plugin system, tree-sitter code intelligence, and self-improving memory. Yet it's native Rust: ~8 MB RAM idle, ~36 MB binary, no runtime. And an agent loop built to keep even small, cheap models on the rails.
8MBRAM idle
36MBBinary size
25+Built-in tools
8Providers
dirge — ~/src/voxel
CONTEXT
ctx [██░░░░░░░░] 1%
11.5k / 1.0M · cmp:0
ACTIVITY
edit voxel.cljs
read voxel.cljs
skill paren-check
bash cd …/voxel
AGENT LOG STREAM
<you> add FPS-style mouse-look + wheel height to voxel.cljs
<dirge> Here's the plan:
1. add :mouseX/:mouseY delta fields to input
2. mousemove + wheel listeners (pointer lock)
3. consume deltas in update-camera, reset per frame
BASH · clojure -M:dev -e "(require 'voxel)"
✓ compiled — 0 warnings
EDIT — voxel.cljs (+2 lines, validated)
- :lookUp false})
+ :lookUp false
+ :mouseX 0.0
+ :mouseY 0.0})
<critic> deltas reset each frame ✓ — looks complete
SYSTEM LOAD
CPU [██░░░░░░] 24%
MEM [████░░░░] 55%
MCP
● chiasmus
● rlm
● wavescope
● lattice
TODOS
✓ input delta fields
▸ pointer-lock listeners
○ minimap overlay
[$_$]
░▌ add a minimap overlay to the HUD
voxel:main· deepseek-v4-pro· 11k/1.0M (1%)· 3 msgs· code· running
[*] ON THE NAME
A dirge is a song to keep the dead from losing their way. It turns grief into something that is remembered. Agents are like mayflies awoken for a moment to work and to forget, with every new session effacing the old one. Dirge keeps watch over things said and done, always folding context into memory to carry past mistakes and preferences across the gulf between sessions. It sings the past forward, so that no grave need be dug twice. Dirge grieves for nothing, since nothing is truly buried under its care, and its lament is a promise that what was built here once will be remembered.
[*] WHY DIRGE
Small footprint, serious reliability
Native Rust, no runtime — and an agent loop engineered so cheaper models stay productive instead of derailing.
Terminal-First TUI
ratatui + crossterm. Live token streaming, a branching session tree, and configurable info panes via /display — all keyboard-driven.
Robust Agent Loop
Repairs malformed tool calls, validates every write through tree-sitter before it hits disk, breaks repeat-loops, and — when a model keeps failing — injects a recovery checkpoint, suggests the tool/path it likely meant, then escalates to a stronger model.
One Permission Engine
A single Policy Decision Point with four modes, op-based rules, and session allowlists. The /why command traces exactly which policy decided — and why.
Role-Based Routing
Point the main loop, review, escalation, summarization, and subagent roles at different models. Mix DeepSeek, GLM, Anthropic, OpenAI, and Ollama in one session.
Self-Improving Memory
Two-tier per-project memory — hot facts inline, the rest a searchable breadcrumb index — plus a global cross-project tier for durable preferences, injected as a cache-stable snapshot. Reusable skills share the same salience engine: /learn distills one from any source or the current session, and the curator prunes on proven usefulness — decay plus a success/failure record — not just age.
Built-In Issue Board
A persistent, agent-facing kanban in the session DB — a stateful extension of the memory model. The harness surfaces the top open issues at the start of every turn, so the agent works its backlog without polling a tracker. The issue tool manages it (create / start / close, priorities, cross-session); /issues views it. SQLite rows, no external tracker.
Long-Horizon Sessions
Durable, incrementally-refreshed checkpoints anchored to a stable identity — so resuming a long, compacted session recovers its live state instead of a stale snapshot. Hold autonomous runs to a natural-language stop condition with --goal. Adapted from MiMo-Code.
Code Intelligence
Tree-sitter semantic tools and inline LSP diagnostics for 10+ languages — surfaced in tool output so the agent fixes compile errors on the same turn.
Extensible at Runtime
A Janet plugin system hooks the full lifecycle — intercept tools, rewrite prompts, register commands — plus Claude-compatible skills loaded on demand and grown from any source with /learn.
MCP & ACP
Connect MCP servers for extra tools — or run dirge mcp to be one, so another agent (e.g. Claude Code) delegates implementation tasks to dirge and reviews them. ACP for editor integration with Zed.
Sandbox Mode
Run every bash command in isolation with --sandbox: bubblewrap for a namespaced jail, or a hardware-isolated libkrun microVM — defense in depth on top of the permission engine.
Phased Planning
An opt-in /plan workflow runs explore → plan → implement → review as context-isolated phases: a read-only agent maps the code, a second drafts the plan, then a write-disabled reviewer runs the code and feeds gaps back for a bounded retry.
Diff-Aware Code Review
As agents write code, a reviewer reads the actual diff — not just the transcript — in two passes (review, then verify to drop false positives). High/critical findings block the loop until fixed or justified; medium/low surface as advisories. Run it any time with /code-review. Reuses the critic's judge, so it's one opt-in with no cost when off.
Spec-Driven Workflow
Align on what before how. The spec tool tracks changes — proposal, requirement deltas, and a task checklist with real status — as SQLite rows, not markdown; living specs are the current truth, and archiving folds a change's deltas into them transactionally. The active change is injected into context; archiving forms a memory. Inspired by OpenSpec.
Token-Efficient I/O
Tree-sitter read_minified/edit_minified collapse files to their skeleton; a hard read-before-edit gate blocks blind edits; and oversized bash/webfetch output is relayed to disk with a head+tail summary so the context window stays lean.
Step-Through Debugging
A built-in Debug Adapter Protocol client drives real debuggers — set breakpoints, step, inspect stacks and variables — straight from the agent loop and Janet plugins (debug tool, dap/* bindings).
Vision & Image Paste
Paste a clipboard image into the prompt with Ctrl+V and send it to a vision model — turns are multipart, interleaving text and pictures. Images live out-of-line in the session's assets/ dir and are re-read at the provider boundary each turn, so transcripts stay small. Gated by provider/model vision support, with a multimodal override for local models like Ollama llama3.2-vision.
[*] GET STARTED
Install from crates.io in one line
The crate is published as dirge-agent (the short name was taken);
the installed command is still dirge. Set an API key and start coding.
▸ install
# Batteries included — MCP, LSP, ACP, plugins, and every # tree-sitter language are on by default. $ cargo install dirge-agent # Leaner build — opt out of defaults, pick what you need: $ cargo install dirge-agent --no-default-features \ --features "loop,git-worktree,mcp,lsp,semantic-rust"
▸ install with homebrew (macOS & Linux)
# Prebuilt binary — no Rust toolchain needed. $ brew install dirge-code/dirge/dirge # Or tap once, then install / upgrade by short name: $ brew tap dirge-code/dirge $ brew install dirge $ brew upgrade dirge # On macOS this skips the Gatekeeper quarantine prompt you'd # hit from a downloaded tarball.
▸ build from source
# Clone the repo $ git clone https://github.com/dirge-code/dirge $ cd dirge # Release build (all default features) $ cargo build --release $ ./target/release/dirge
▸ set API key (auto-detected by provider)
# OpenRouter (default provider) $ export OPENROUTER_API_KEY=sk-or-... # Or any provider: $ export ANTHROPIC_API_KEY=sk-ant-... $ export OPENAI_API_KEY=sk-... $ export DEEPSEEK_API_KEY=sk-... # Or pass directly: $ dirge --api-key sk-...
▸ run modes
# Interactive TUI (default) $ dirge # One-shot: run and print to stdout $ dirge -p "explain this codebase" # Headless iterative loop $ dirge --loop "refactor auth module" # Resume last session $ dirge -c # Sandbox every bash command (needs bubblewrap) $ dirge --sandbox # Restrictive mode (prompt every tool) $ dirge -R # MCP server: let another agent delegate tasks to dirge $ dirge mcp
[*] PROVIDERS
Bring your own model
Works with all major providers, and any OpenAI-compatible endpoint. OpenRouter is the default — no setup for most. Switch with /model.
Anthropic
Claude Sonnet · Opus
Gemini
2.5 Pro · 2.0 Flash
Ollama
Llama · Qwen · Mistral
OpenRouter
Default · 200+ models
Custom
Any OpenAI-compatible endpoint
[*] TOOLS
25+ built-in tools
Everything the agent needs — files, shell, search, code intelligence, and delegation — with zero configuration.
Files & edits
read · write · edit · apply_patch · list_dir · find_files
Shell
bash · bash_output · kill_shell — foreground & background
Search
grep · glob · session_search
Code intelligence
list_symbols · get_symbol_body · find_callers · find_callees · find_definition · lsp · repo_overview
Agent & memory
task · task_status · write_todo_list · memory · skill · spec · question
Token-efficient I/O
read_minified · edit_minified — collapse a file to its tree-sitter skeleton
Debugging
debug — DAP step-through: breakpoints, step, inspect stacks & variables
Dynamic discovery
tool_search — load tools on demand to keep the prompt lean
[*] COMMANDS
Drive it with slash commands
Control the agent from the TUI — switch models, manage sessions, inspect decisions. Type /help for the full list.
/model
Show or switch LLM model
/agent
Switch the active agent profile
/prompt
List or activate system prompts
/plan
Run the phased plan workflow
/code-review
Review the working-tree diff for issues
/mode
Set security permission mode
/why
Trace the last permission decision
/allow
Manage the session allowlist
/sandbox
Inspect & control bash sandboxing
/display
Pick visible info panes
/panel
Toggle the side gutters
/compress
Compact history for context
/sessions
List, save, load sessions
/issues
View the native issue board
/tree
Show session branch tree
/fork
Branch the conversation
/loop
Start an iterative coding loop
/tasks
List background shells & agents
/spec
Inspect spec changes & living specs
/mcp
List MCP servers and tools
/worktree
Create a git worktree
/plugins
List loaded Janet plugins
/reasoning
Toggle reasoning visibility
/btw
Quick question (no tools)
[*] PLUGINS
Plugin system in Janet
dirge embeds Janet as a plugin language. Plugins are small scripts that hook into the agent loop, intercept tools, rewrite prompts, register slash commands, gate execution, and drive session navigation — all from a few lines of Lisp.
Your first plugin
Plugins live in ~/.config/dirge/plugins/ (global)
or ./.dirge/plugins/ (project-local).
A plugin is a single .janet file — no boilerplate, no exports required.
~/.config/dirge/plugins/hello.janet
;; Every time the user sends a prompt, print a line (defn on-prompt [ctx] (harness/notify (string "user said: " (ctx :prompt)) :info))
That's it. Restart dirge, type a message, and the notification appears in chat. The plugin system is on by default.
Multi-file plugins: create a directory of .janet files that share
one Janet environment and load in alphabetical order. Name files
00-state.janet, 01-hooks.janet to control load order.
Hook reference
Every hook takes a ctx table and returns nil or a string.
Define them as top-level functions in your plugin file.
on-init
Once at session start. ctx: model, cwd, provider.
on-prompt
After user submits, before LLM call. Use harness/replace-prompt to rewrite.
on-turn-start
Start of one LLM call cycle. ctx: index.
on-message-update
Every ~16 streamed tokens. ctx: index, partial text.
on-turn-end
After tool results return. ctx: index, full message text.
on-response
After a full LLM response. Use for logging/notifications.
on-tool-start
Before a tool runs (after permission check). ctx: tool name, args. Use harness/block or harness/mutate-input.
on-tool-end
After tool returns. ctx: tool name, output. Use harness/replace-result.
on-error
A tool or LLM call errored. ctx: error message.
on-complete
Agent finished its multi-turn response.
Tool interception
Use on-tool-start and on-tool-end to gate, rewrite, or filter tool calls.
Block dangerous bash commands
(defn on-tool-start [ctx] (when (= (ctx :tool) "bash") (let [cmd (get-in ctx [:args "command"])] (when (string/find "rm -rf" cmd) (harness/block "denied: dangerous deletion")))))
Three APIs: harness/block — stop the tool (first-wins across plugins),
harness/mutate-input — rewrite tool args (last-write-wins),
harness/replace-result — replace tool output.
Register custom tools
Plugins can register tools the LLM calls directly — not just intercept built-in ones.
Custom tool registration
(defn echo-handler [args] (string "echo received: " args)) (harness/register-tool "plugin_echo" ;; name (LLM sees this) "Echoes args back verbatim" ;; description "Echo" ;; UI label "{\"type\":\"object\",\"properties\":{\"msg\":{\"type\":\"string\"}},\"required\":[\"msg\"]}" "echo-handler" ;; handler fn name :parallel) ;; :parallel or :sequential
Execution mode: :parallel (read-only, default) or
:sequential (mutating). One sequential tool forces the whole batch sequential.
Optionally pass a prepare-arguments function (7th arg) to normalize
LLM-supplied args before validation.
Slash commands & keyboard shortcuts
Register a slash command
(defn echo-cmd [args] (string "you said: " args)) (harness/register-command "echo" "echo-cmd")
Now /echo hello world prints you said: hello world in chat.
Register a keyboard shortcut
(defn refresh [key] (string "F5 pressed: " key)) (harness/register-shortcut "f5" "refresh" "Refresh chat") (harness/register-shortcut "ctrl-s" "save-all" "Save")
Key spec format: (modifier "-")* key-name. Modifiers: ctrl, alt, meta, shift.
Reserved keys (Ctrl+C, Esc, etc.) cannot be overridden.
Custom LLM providers
Register any OpenAI-compatible endpoint as a first-class provider, then switch with /model.
Register a local provider from a plugin
(harness/register-provider "local-openai" ;; name surfaced in /model "openai" ;; provider type "http://localhost:8000/v1" ;; base URL "LOCAL_OPENAI_API_KEY") ;; env var for the key
Use /model local-openai/gpt-4 to switch. Config-declared providers
in config.json win on name collision.
User dialogs
Block the plugin thread to ask the user for confirmation or a choice. Safe to call from any hook — the UI renders while the worker waits.
Confirm and select dialogs
;; Returns true on confirm, false on Esc/Cancel (if (harness/confirm "Confirm" "Run migration?") (harness/notify "running..." :info) (harness/block "user said no")) ;; Returns selected string, or nil on cancel (let [choice (harness/select "Pick model" ["gpt-4" "claude-4" "deepseek"])] (when choice (harness/notify (string "using: " choice) :info)))
Dialogs use the UI's selection picker. Arrow keys navigate, Esc cancels. These are the only synchronous round-trip APIs.
Renderers & entries
Persist typed entries in the session timeline with custom formatting. Entries survive save/load. Use message renderers for live mid-conversation output.
Session entry renderer
;; Define a renderer for "bookmark" entries (defn render-bookmark [data] (harness/render :cyan (string "★ " data))) (harness/register-renderer "bookmark" "render-bookmark") ;; Append an entry from any hook (harness/append-entry "bookmark" "milestone-1")
Message renderer (live mid-conversation)
(defn render-status [payload] (string "■ " payload)) (harness/register-message-renderer "status" "render-status") ;; From a prepare-next-run hook: (harness/add-custom-message "status" "turn complete")
[*] THEMES
Custom themes
Switch between built-in themes or create your own with a simple JSON file. Override any color — the rest falls back to defaults.
Built-in themes
phosphor
80s CRT green on black. Errors red, warnings yellow. The default.
plain
White assistant text, cyan accents, gray dim. Clean and modern.
~/.config/dirge/config.json
{
"theme": "plain"
}
Custom themes
Create ~/.config/dirge/midnight.theme.json and set
"theme": "midnight" in config.
All fields optional — unspecified values keep the phosphor default.
~/.config/dirge/midnight.theme.json
{
"agent": "#88ccff",
"user": "#ffaa66",
"system": "darkgray",
"tool": "darkcyan",
"perm": "yellow",
"result": "darkgray",
"error": "red",
"warn": "yellow",
"accent": "#ff66cc",
"dim": "darkgray",
"header": "cyan",
"divider": "darkgray",
"banner_primary": "#aa88ff",
"banner_secondary": "darkmagenta",
"label": "MIDNIGHT"
}
Colors accept: named colors (red, darkcyan), hex RGB
(#88ccff), or 256-color palette index (208).
Malformed files fall back to phosphor with a warning.
Color field reference
result
Secondary result text
accent
Headers, focused picker rows, banner accents
dim
Placeholders, separators
header
Right-panel headers
divider
Horizontal divider line
banner_primary
Welcome banner primary stroke
banner_secondary
Welcome banner border / decorations
label
Name shown in banner
[*] CONFIGURATION
Declarative config
Configure providers, role routing, permissions, themes, MCP servers, and custom
prompts in ~/.config/dirge/config.json.
~/.config/dirge/config.json
{
"provider": "openrouter",
"model": "deepseek/deepseek-v4-flash",
"theme": "phosphor",
"permissions": {
"mode": "standard",
"rules": [
{ "pattern": "git checkout *", "action": "allow" },
{ "pattern": "rm -rf **", "action": "deny" }
]
},
"roles": {
"escalation": "anthropic",
"review": "glm"
},
"mcp_servers": {
"lattice": {
"command": "lattice-mcp",
"args": []
},
"chiasmus": {
"command": "npx",
"args": ["-y", "chiasmus"]
},
"wavescope": {
"command": "wavescope-mcp",
"args": []
}
}
}
Four permission modes: standard (safe ops auto-approved),
restrictive (every tool prompts),
accept-all (auto-approve inside cwd),
yolo (auto-approve everything).
Pattern rules support glob matching, and the /why command explains any decision.
[*] FAQ
Frequently asked
Common questions about dirge.
Q What makes dirge different from other coding agents? ▾
dirge is written in Rust — ~36MB binary, ~8MB RAM at idle, no runtime (vs ~300MB for Node.js agents). Beyond efficiency, it's built to keep cheaper models productive: a robust agent loop repairs malformed tool calls, validates every write through tree-sitter before disk, and escalates to a stronger model on repeated failure. Add a single explainable permission engine, role-based multi-provider routing, self-improving project memory, and a Janet plugin system.
Q Does dirge support local models? ▾
Yes. Ollama is a first-class provider, and you can point at any OpenAI-compatible local server (llama.cpp, vLLM, etc.) via config or by registering one from a plugin. With role-based routing you can even run a small local model for the main loop and escalate to a hosted model only when needed.
Q What languages does code intelligence support? ▾
The code-navigation tools — list_symbols, get_symbol_body,
find_callers, find_callees, find_definition —
cover TypeScript/TSX, Python, Clojure, Go, Ruby, Rust, Java, C, and C++. Tree-sitter
pre-write validation and the minified read/edit tools additionally cover Elixir and
Bash, and LSP diagnostics are surfaced inline. Each grammar is gated on its own
semantic-<lang> feature, all on by default.
Q How does the memory system work? ▾
dirge keeps persistent per-project memory (facts and pitfalls) in the session DB, injected into the system prompt as a cache-stable snapshot. It's two-tiered: hot entries inline verbatim, and once the budget fills the rest demote to a searchable breadcrumb index the agent expands on demand. A separate global, cross-project tier holds durable user preferences that follow you across every repo. After an idle session a post-session orchestrator extracts learnings and curates memory and skills (stale-detection, lifecycle, LLM consolidation) — so the agent carries preferences and past mistakes forward instead of starting cold every time.
Q What happens to long sessions when I resume them? ▾
Every conversation carries a stable identity and a durable checkpoint that's refreshed
in the background as context grows (a 20%-of-window cadence), without discarding history.
A long session compacts and rotates internally over time, but resuming by the id you
started with resolves to its live tip and recovers fresh state — it picks up where it
left off instead of loading a stale pre-compaction snapshot, and the verbatim original
request is re-anchored so the goal doesn't drift. Autonomous runs can be held to a
natural-language stop condition with --goal "…", checked by an independent
judge each time the agent tries to stop. This long-horizon design is adapted from
MiMo-Code.
Q What are the permission modes? ▾
Four modes: standard — safe ops auto-approved, writes/bash/MCP prompt;
restrictive — every tool prompts; accept-all —
auto-approve inside cwd; yolo — auto-approve everything.
All authorization flows through one Policy Decision Point, and /why
traces exactly which rule decided. Add --sandbox to isolate bash in bubblewrap.