$ 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

### 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 sessions or /sessions in the REPL.
  • - Prompt Inspection — See exactly what the model receives. /prompt budget shows token/character counts per layer; /prompt diff shows changes between turns. enchanter prompt --budget and --diff from the CLI.
  • - Session Recording & Replay — Record sessions to JSONL with --record, then replay with enchanter 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_command runs 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 the memory tool 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 exceeds summarize_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 (-p flag)
  • 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: false in 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
14 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.