$ Enchanter
View on GitHub → · Runs on Linux, macOS, and Windows.
A focused AI agent harness. Single Rust binary, any OpenAI-compatible provider, zero runtime dependencies. Reads your SOUL, loads your memory, finds your skills, talks to your model. Nothing more.
quick start
git clone https://github.com/andrewthecodertx/enchanter.git && cd
enchanter && make install Contents
- Features
- Philosophy
- Platform Support
- Getting Started
- Configuration
- Named Providers
- Soul, Memory & Skills
- Knowledge Store
- Session Continuity & History
- Prompt Inspection
- Recording & Replay
- MCP Servers
- Security Sandbox
- Context Compaction
- TUI Mode
- Daemon Mode
- Running
- REPL Commands
- Environment Variables
- Data Directory
- Learn More
### features
- - Single Binary — No runtime dependencies. Build it, ship it, run it anywhere.
- - Any OpenAI-Compatible Provider — Works with OpenAI, Ollama, OpenRouter, Groq, or any provider that speaks the OpenAI API.
- - Named Providers — Define
multiple providers in config.yaml and switch between them mid-session
with
/model. - - SOUL-Driven Personas — Define your agent's personality, directives, and behavioral constraints in SOUL.md.
- - Knowledge Store — Structured key-value facts that persist across sessions. Unlike memory (narrative text), knowledge entries are typed, searchable, and organized by category. The agent learns once and never asks again.
- - Persistent Memory — Memories persist across sessions with automatic summarization when the store grows.
- - Session Summaries — On clean exit, Enchanter generates a concise session summary and saves it to memory so your next session picks up where you left off.
- - Session History — Every conversation
is automatically logged to JSONL files. List and review past sessions
with
enchanter sessionsor/sessionsin the REPL. - - Prompt Inspection — See exactly
what the model receives.
/prompt budgetshows token/character counts per layer;/prompt diffshows changes between turns.enchanter prompt --budgetand--difffrom the CLI. - - Session Recording & Replay — Record sessions to JSONL with
--record, then replay withenchanter replay. Swap models, verify determinism, or replay with stubbed tools. API keys are redacted by default. - - Skill Discovery — Drop in SKILL.md files to teach your agent new capabilities. Compatible with the agentskills.io format.
- - MCP Integration — Connect local (stdio) and remote (HTTP) MCP servers for external tools and data.
- - Built-in Tools — exec_command, read_file, write_file, edit_file, search_files, list_directory, memory, knowledge.
- - Interactive REPL — Full readline-powered REPL with slash commands for soul, memory, skills, config, and more.
- - Daemon Mode — Background process keeps MCP servers warm, eliminating 3–15s cold starts. Linux & macOS only.
- - TUI Mode — Multi-pane terminal UI with sidebar (skills + memory), chat area, and input bar. Keyboard-driven, all REPL slash commands work.
- - Security Sandbox — On Linux,
exec_commandruns under a Landlock kernel sandbox confined to allowed paths. File tools check the path allowlist on all platforms. - - Context Compaction — Rolling compaction keeps long sessions within the model's context window. Older turns are summarized; recent turns are always preserved verbatim.
### philosophy
Enchanter is a harness, not a framework. It doesn't try to be clever about what your agent should do — it gets out of the way and lets the model, the soul, and the tools do the work. The prompt is assembled from three explicit layers. The config is one file. The data directory is yours to edit.
Start simple: one model, one soul, no MCP servers. When you need more, add providers, add skills, add tools. The harness grows with you instead of front-loading complexity.
### platform support
| Platform | REPL & Inline | Daemon Mode |
|---|---|---|
| Linux | ✅ | ✅ |
| macOS | ✅ | ✅ |
| Windows | ✅ | ❌ |
Daemon mode requires Unix domain sockets and is only available on Linux
and macOS. On Windows, Enchanter runs entirely in inline mode — no --no-daemon flag or daemon subcommand is available.
### getting started
1. Build and install
Linux / macOS:
git clone https://github.com/andrewthecodertx/enchanter.git
cd enchanter
make install
Installs to ~/.local/bin/enchanter.
Windows:
git clone https://github.com/andrewthecodertx/enchanter.git
cd enchanter
cargo build --release
Binary at target\release\enchanter.exe. Copy it anywhere on your PATH. Requires Rust 1.85+.
2. Launch the REPL
enchanter
First run creates ~/.enchanter/ with defaults.
3. Configure a provider
Edit ~/.enchanter/config.yaml:
model:
default: gpt-4.1-mini
api_key: sk-...
Or use an environment variable: export ENCHANTER_API_KEY=sk-...
4. Start talking
Type a message and press Enter. Use /exit to quit cleanly (saves a session summary and history). Ctrl+D also works. All conversations are automatically saved to ~/.enchanter/sessions/.
### configuration
All config lives in ~/.enchanter/config.yaml. Environment variables override config values. First run creates
sensible defaults.
Minimal — OpenAI
model:
default: gpt-4.1-mini
api_key: sk-...
That's it. base_url defaults to OpenAI.
Ollama (local)
model:
default: qwen3
base_url: http://localhost:11434/v1
No API key needed for local providers.
OpenRouter
model:
default: anthropic/claude-sonnet-4
base_url: https://openrouter.ai/api/v1
api_key: sk-or-...
Groq
model:
default: llama-3.3-70b-versatile
base_url: https://api.groq.com/openai/v1
api_key: ${GROQ_API_KEY}
API keys support ${VAR} expansion from environment variables.
Full config with all options
model:
default: gpt-4.1-mini
base_url: https://api.openai.com/v1
api_key: ${OPENAI_API_KEY}
providers: # see "Named Providers" section
ollama:
model: qwen3
base_url: http://localhost:11434/v1
openrouter:
model: anthropic/claude-sonnet-4
base_url: https://openrouter.ai/api/v1
api_key: ${OPENROUTER_API_KEY}
security:
allowed_paths:
- ~/Projects
- /tmp
allow_unsandboxed_exec: false
agent:
max_turns: 30
summarize_on_exit: true
memory:
max_entries: 50
summarize_threshold: 40
context:
max_tokens: 96000
keep_last_turns: 20
mcp:
servers:
filesystem:
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem",
"/home/user/Projects"]
fetch:
command: uvx
args: ["mcp-server-fetch"]
my-remote:
url: https://mcp.example.com/api
headers:
Authorization: "Bearer
${MY_TOKEN}"
Config reference
| Key | Default | Description |
|---|---|---|
model.default | gpt-4.1-mini | Model ID sent to the API |
model.base_url | https://api.openai.com/v1 | API base URL |
model.api_key | — | API key (not needed for local providers) |
agent.max_turns | 150 | Max agent loop turns. Set to 0 for unlimited. |
agent.summarize_on_exit | true | Generate session summary on clean exit |
agent.memory.max_entries | 50 | Max memory entries loaded into prompt |
agent.memory.summarize_threshold | 40 | Summarize older entries when count exceeds this |
agent.context.max_tokens | — | Compact older turns when estimated tokens exceed this. No compaction if unset. |
agent.context.keep_last_turns | 20 | Always keep this many recent messages verbatim during compaction |
security.allowed_paths | home dir | Directories the agent may read/write. Defaults to home directory if empty. |
security.allow_unsandboxed_exec | false | Allow unsandboxed shell execution when Landlock is unavailable. Escape hatch — use with care. |
### named providers
Define multiple providers in config.yaml
and switch between them mid-session. Each provider can set model,
base_url, and api_key. Fields left blank inherit from the top-level model config or
environment variables.
Config
providers:
ollama:
model: qwen3
base_url: http://localhost:11434/v1
openrouter:
model: anthropic/claude-sonnet-4
base_url: https://openrouter.ai/api/v1
api_key: ${OPENROUTER_API_KEY}
groq:
model: llama-3.3-70b-versatile
base_url: https://api.groq.com/openai/v1
api_key: ${GROQ_API_KEY}
Switching providers in the REPL
/model ollama # Full provider switch: model, base_url, api_key
/model openrouter # Switches to Claude on OpenRouter
/model gpt-4.1 # Bare model ID keeps current provider
When you use a named provider, all three settings change at once. When you pass a bare model ID, only the model name changes — the base URL and API key stay the same.
### soul, memory & skills
The system prompt is assembled in seven layers, each with a distinct role:
- 1. SOUL — Your agent's persona from SOUL.md. Stable across turns, defines identity and behavioral directives.
- 2. CONTEXT — Environment info: model, user, working directory, host, platform.
- 3. SKILLS — Discovered SKILL.md files index.
- 4. INSTRUCTIONS — Tool usage guidance, canonical tool descriptions, and knowledge capture directives.
- 5. KNOWLEDGE — Structured key-value facts from the knowledge store. Compact and searchable.
- 6. VOLATILE — Memory entries and user profile. Changes frequently, fully replaceable.
- 7. SESSION — Current date and session start timestamp.
SOUL.md (~/.enchanter/SOUL.md)
Define your agent's personality and behavioral constraints. First run creates a default you can edit:
# Enchanter
You are Enchanter, a focused AI agent harness.
You are concise, helpful, and direct.
Memory — three files
- •
memories/USER.md— User profile entries (§-delimited). The agent uses thememorytool to add/replace these. - •
memories/MEMORY.md— Agent observations and notes (§-delimited). Auto-populated during sessions and on exit summaries. - •
memories/SUMMARY.md— Auto-summarized older entries. Generated when memory exceedssummarize_threshold.
Skills (~/.enchanter/skills/)
Drop in SKILL.md files organized by category. Compatible with the agentskills.io format:
~/.enchanter/skills/
software-development/
enchanter/SKILL.md
plan/SKILL.md
research/
arxiv/SKILL.md
creative/
image-generation/SKILL.md
### knowledge store
Unlike memory (free-form narrative text), the knowledge store captures
discrete, typed facts that persist across sessions. Keys use
dot-namespaced identifiers like
project.rust_version
or
user.email. Values are short strings organized into five categories:
- - environment — Runtime and system facts (OS, shell, tool versions)
- - project — Project-specific details (language, framework, conventions)
- - preference — User preferences and style (code style, commit conventions)
- - decision — Architectural or design decisions (chosen approach, rationale)
- - fact — General facts that don't fit other categories
Each entry tracks its source: observed (detected from tool output), told (explicitly stated by the user), or inferred (concluded by the agent from context).
The agent proactively stores facts it discovers during conversation.
When it learns a version number, a project path, a user preference, or a
bug workaround, it stores it with the
knowledge
tool so it never has to ask again. The INSTRUCTIONS prompt layer directs this
behavior automatically.
Store file
~/.enchanter/knowledge/kstore.json
Human-readable JSON. Edit directly, commit to git, or let the agent manage it. Persists to disk on every write so crashes don't lose data.
Project-level knowledge
When enchanter runs inside a project with a
.enchanter/
directory, project-level knowledge is merged on top of the global store.
Project entries override global entries with the same key.
my-project/
.enchanter/
knowledge/
kstore.json # project-specific
facts
Running enchanter init
in a project directory creates the knowledge directory automatically.
### session continuity
When you exit the REPL with /exit or Ctrl+D, Enchanter generates a
concise summary of your session and saves it to memory. Your next
session automatically loads this context so you can pick up where you
left off.
- • Skipped for single-shot mode (
-pflag) - • Skipped if the session was too short (no real exchange)
- • 10-second timeout; falls back to a simple message count on failure
- • Disable with
summarize_on_exit: falsein config.yaml
⚠ Ctrl+C is a force-quit. It bypasses the session summary hook, so no
summary is saved. Always use /exit or Ctrl+D for a clean exit.
session history
Every conversation is automatically saved to ~/.enchanter/sessions/
as a JSONL file. Each message is appended atomically — if the process crashes,
everything written up to that point is preserved. Each session gets a unique
ID and stores the model, timestamps, and full conversation including tool
calls and results.
List sessions
enchanter sessions
View a specific session
enchanter sessions <id>
Inside the REPL
/sessions
### prompt inspection
Enchanter assembles the system prompt in explicit layers: SOUL → CONTEXT → SKILLS → INSTRUCTIONS → KNOWLEDGE → VOLATILE → SESSION. You can inspect exactly what the model receives and how it changes between turns.
Token/character budget per layer
$ enchanter prompt --budget
The budget view shows approximate token counts per layer (chars÷4 heuristic), visual bar charts, and threshold warnings when a layer exceeds ~4,000 estimated tokens.
Diff the system prompt between turns (REPL only)
/prompt diff
The diff output highlights layer-level changes — memory blocks added/removed, skill index changes, tool list changes, provider/model changes, and SOUL changes. API keys and auth headers are never shown in diff output.
### recording & replay
Record full sessions to JSONL files for debugging, reproducibility, and model comparison. API keys and auth tokens are never included in recordings by default.
Record a session
enchanter --record session.jsonl
Record with additional redaction
enchanter --record session.jsonl --record-redact
Replay a recorded session
enchanter replay session.jsonl
Replay with a different model
enchanter replay session.jsonl --swap-model gpt-4
Replay with stubbed tools (deterministic)
enchanter replay session.jsonl --tools stubbed
Verify exact model match
enchanter replay session.jsonl --exact
Each recorded event includes a schema version, monotonic sequence number, UTC timestamp, and a typed payload. Recordings capture config snapshots (with redacted API keys), prompt layer hashes, user messages, assistant responses, tool calls, model changes, and session summaries.
### mcp servers
Connect external tools and data sources via Model Context Protocol servers. Two transport types are supported:
- - stdio — Local processes spawned by Enchanter. Auto-restarted on crash (up to 3 attempts).
- - HTTP — Remote servers via Streamable HTTP with SSE support. Session tracking with Mcp-Session-Id headers.
stdio — local process
mcp:
servers:
filesystem:
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem",
"/home/user/Projects"]
stdio — with environment variables
mcp:
servers:
images:
command: npx
args: ["-y", "@fal-serverless/mcp-server"]
env:
FAL_KEY: ${FAL_KEY}
http — remote server
mcp:
servers:
my-remote:
url: https://mcp.example.com/api
headers:
Authorization: "Bearer
${MY_TOKEN}"
Tools from MCP servers are namespaced as server_name:tool_name. Use /tools in the REPL to list all available tools.
### security sandbox
On Linux, exec_command
(shell execution) runs under a Landlock
kernel sandbox that confines the spawned shell to an allowlist of directories.
The sandboxed process can read/write within
allowed_paths
and read/execute system directories (/usr, /bin, /lib, etc.), but write access to system dirs is denied.
File tools (read_file, write_file, edit_file, search_files, list_directory) check allowed_paths
on all platforms.
Config
security:
allowed_paths:
- ~/Projects
- /tmp
allow_unsandboxed_exec: false
⚠ On macOS and Windows, Landlock is unavailable. If allow_unsandboxed_exec
is false
(the default), exec_command
will refuse to run. Set it to true
to allow unsandboxed shell execution — this means the LLM can run any command
your user can.
### context compaction
For long sessions, the conversation window can grow beyond the model's context limit. Rolling compaction keeps the live context within bounds:
Config
agent:
context:
max_tokens: 96000 # Compact older turns
when est. tokens exceed this
keep_last_turns: 20 # Always keep this
many recent messages verbatim
When the estimated token count exceeds max_tokens, older turns are summarized into a single compact message. The most
recent keep_last_turns
messages are always preserved verbatim. Both values are optional — without
them, no compaction occurs and the full conversation is sent.
### tui mode (optional feature)
The terminal UI provides a multi-pane interface with a sidebar (skills + memory), main chat area, and input bar. A header shows the model, provider, and session info; a footer shows tool/skill counts and quick key hints.
launch the tui
enchanter tui
All REPL slash commands work in the TUI (/help, /clear, /model, /undo, /retry, etc.). On exit, the TUI generates a session summary to memory just
like the REPL.
Focus and navigation
| Key | Action |
|---|---|
Tab | Cycle focus forward through panes |
Shift+Tab | Cycle focus backward |
1–4 | Jump to Skills / Memory / Chat / Input |
/ | Jump to input and start a command |
Esc | Return focus to input pane |
Input bar
| Key | Action |
|---|---|
Enter | Send message (multiline off) / newline (multiline on) |
Ctrl+Enter | Newline (multiline off) / send (multiline on) |
Ctrl+M | Toggle multiline mode |
Ctrl+A / Home | Move cursor to start |
Ctrl+E / End | Move cursor to end |
Ctrl+U | Clear input line |
← / → | Move cursor |
Backspace / Delete | Delete character |
Ctrl+C | Cancel streaming response |
Ctrl+Q | Quit |
Sidebar (Skills / Memory)
| Key | Action |
|---|---|
↑ / ↓ or j/k | Navigate items |
Enter | Show details in chat |
? | Show help in chat |
Chat pane
| Key | Action |
|---|---|
↑ / ↓ or j/k | Scroll up / down one line |
PageUp / PageDown | Scroll by page |
End | Jump to bottom / re-enable auto-scroll |
? | Show help in chat |
During streaming
| Key | Action |
|---|---|
Ctrl+C | Cancel streaming response |
Ctrl+Q | Quit |
Tab / Shift+Tab | Cycle focus (even while streaming) |
The TUI is enabled by default. Build without it: cargo build --no-default-features.
### daemon mode (Linux & macOS only)
Enchanter can run as a background daemon that keeps MCP servers warm. This eliminates the 3–15 second cold start on every invocation (most of which is spawning MCP server processes).
The daemon listens on a Unix domain socket at ~/.enchanter/sock
and writes its PID to ~/.enchanter/daemon.pid. It auto-shuts down after 10 minutes of inactivity (configurable
with --idle-timeout).
start the daemon
enchanter daemon start check status (model, MCP servers, uptime)
enchanter daemon status stop the daemon
enchanter daemon stop Auto-start: When you run enchanter -p "question" and the daemon isn't running, Enchanter starts it automatically, waits
for it to become ready, then sends your request through it.
Fallback: If the daemon can't
be reached, Enchanter falls back to inline mode. Use --no-daemon to skip the daemon entirely.
Streaming: The daemon streams responses as JSONL events over the Unix socket, so you still see content tokens as they arrive — not just a final blob of text.
### running
interactive session
enchanter single shot — ask one question and exit
enchanter -p "Explain Rust ownership in one paragraph" use a different model for this session
enchanter -m qwen3 override the system prompt
enchanter -s "You are a pirate. Always respond in pirate speech." disable streaming (wait for full reply)
enchanter --no-stream -p "Summarize this" disable all tools (built-in + MCP)
enchanter --no-tools -p "What is 2+2?" run inline, skip daemon auto-connect (Linux/macOS only)
enchanter --no-daemon -p "quick question" Info subcommands
| Command | Description |
|---|---|
enchanter soul | Show current SOUL.md |
enchanter memory | Show loaded memory |
enchanter skills | List discovered skills |
enchanter config | Show resolved configuration |
enchanter prompt | Show assembled system prompt |
enchanter sessions | List saved session history |
enchanter sessions <id> | Show a specific session's conversation |
enchanter daemon start | Start the background daemon (Linux/macOS) |
enchanter daemon stop | Stop the running daemon (Linux/macOS) |
enchanter daemon status | Show daemon status: model, MCP servers, uptime (Linux/macOS) |
enchanter init | Scaffold .enchanter/
overlay in current directory |
### repl commands
/help — Show available commands /clear — Reset conversation history /soul — Show SOUL.md content /memory — Show loaded memory /skills — List discovered skills /tools — List all available tools (built-in + MCP) /model <name> — Switch to a named provider or bare model ID /retry — Re-send the last user message /undo — Remove last exchange from history /config — Show resolved configuration /sessions — List saved session history /prompt — Show full assembled system prompt /prompt diff — Show diff of system prompt from previous turn /prompt budget — Show token/character budget per prompt layer /exit — Quit cleanly (also Ctrl+D) ### environment variables
All optional. Environment variables override config.yaml values.
| Variable | Overrides | Description |
|---|---|---|
ENCHANTER_MODEL | model.default | Model ID to use |
ENCHANTER_BASE_URL | model.base_url | API base URL |
ENCHANTER_API_KEY | model.api_key | API key (not needed for local providers) |
ENCHANTER_HOME | — | Data directory (default: ~/.enchanter) |
OPENAI_API_KEY | fallback | Used if ENCHANTER_API_KEY is not set |
OPENAI_BASE_URL | fallback | Used if ENCHANTER_BASE_URL is not set |
Provider api_key and base_url fields support ${VAR} expansion, so you can write
api_key: ${OPENROUTER_API_KEY} instead of hardcoding.
### data directory
By default, all data lives in ~/.enchanter/. Set ENCHANTER_HOME to point it elsewhere. First run auto-creates the directory with defaults.
~/.enchanter/
├── SOUL.md # Agent persona and directives
├── config.yaml # Model, providers, agent, MCP config
├── memories/
│ ├── USER.md # §-delimited user profile entries
│ ├── MEMORY.md # §-delimited agent memory entries
│ └── SUMMARY.md # Auto-summarized older entries
├── knowledge/
│ └── kstore.json # Structured key-value facts
├── sessions/
│ └── <uuid>.jsonl # Conversation history (auto-saved)
└── skills/
└── software-development/
└── enchanter/SKILL.md
### learn more
Read the source, explore the skills spec, or just run it and start talking. Or read about the time the AI agent that runs on Enchanter started helping build itself: Enchanter Meets Itself.