$ 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. Ships with a REPL, an optional web UI (enchanter serve), a daemon mode, and a Landlock security sandbox.

quick start

git clone https://github.com/andrewthecodertx/enchanter.git && cd enchanter && make install && enchanter

or grab a prebuilt binary from thelatest release.

### download

Grab a prebuilt v0.8.0 binary directly — no GitHub account, no Rust toolchain required. Pick your platform:

Linux · x86_64download →
Linux · arm64download →
macOS · x86_64download →
macOS · arm64download →
Windows · x86_64download →
Windows · arm64download →

Verify integrity with theSHA256SUMSfile:

sha256sum -c SHA256SUMS

ARM builds are cross-compiled (experimental). The same files are always available onGitHub Releases.

### 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 with bar charts and threshold warnings. enchanter prompt --budget does the same 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.
  • -Web UI — Opt-in browser interface (enchanter serve) with streaming chat, tool-call side panel, session resume, model switching, and token/context display. Binds to 127.0.0.1 by default; optional bearer-token auth.
  • -Tool Approval — Opt-in human-in-the-loop gate (security.require_tool_approval). Asks y/N in the REPL or Approve/Reject cards in the web UI before running dangerous tools.
  • -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

PlatformREPL & InlineDaemon 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

KeyDefaultDescription
model.defaultgpt-4.1-miniModel ID sent to the API
model.base_urlhttps://api.openai.com/v1API base URL
model.api_key—API key (not needed for local providers)
agent.max_turns150Max agent loop turns. Set to 0 for unlimited.
agent.summarize_on_exittrueGenerate session summary on clean exit
agent.memory.max_entries50Max memory entries loaded into prompt
agent.memory.summarize_threshold40Summarize 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_turns20Always keep this many recent messages verbatim during compaction
security.allowed_pathshome dirDirectories the agent may read/write. Defaults to home directory if empty.
security.allow_unsandboxed_execfalseAllow unsandboxed shell execution when Landlock is unavailable. Escape hatch — use with care.

### named providers

Define multiple providers in config.yamland 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 likeproject.rust_versionoruser.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), orinferred (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 theknowledgetool 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 initin 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

resuming a session

Pass --resume <id> at startup to reload a previous conversation and continue from where it left off. The session is reopened, its history is reconstructed (tool calls included), and the REPL starts with the conversation already loaded.

enchanter --resume a1b2c3d4

### 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 much each layer costs.

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. It is the fastest way to spot a layer that is quietly consuming your context window.

### 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 Landlockkernel sandbox that confines the spawned shell to an allowlist of directories. The sandboxed process can read/write withinallowed_pathsand 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_pathson all platforms.

Config

security:
  allowed_paths:
    - ~/Projects
    - /tmp
  allow_unsandboxed_exec: false

⚠ On macOS and Windows, Landlock is unavailable. If allow_unsandboxed_execis false(the default), exec_commandwill refuse to run. Set it to trueto 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_turnsmessages are always preserved verbatim. Both values are optional — without them, no compaction occurs and the full conversation is sent.

### web ui (optional)

The web UI is a self-hosted browser interface served by the Enchanter binary itself — no separate app, no deployment needed. It provides streaming chat, a left-side panel showing live tool calls, session resume, model switching, and a token/context status line.

launch the web ui

enchanter serve

By default the server binds 127.0.0.1 and opens your browser. Configure the port, host, and browser behavior with flags:

enchanter serve --port 3005 --host 127.0.0.1 --no-browser

Endpoints under /api/* cover status, sessions, history, model switching, chat (SSE streaming), undo, clear, stop, and tool approval. Chat streams server-sent events, so tool calls and results appear live.

Token auth

On a shared machine, require a bearer token on all API routes:

enchanter serve --token "$(openssl rand -hex 32)"

or set web.auth_token in config.yaml

When set, every /api/* route requires Authorization: Bearer <token>. SSE endpoints also accept ?token=. The frontend stores the token inlocalStorage (use the 🔑 button).

### tool approval (opt-in)

set security.require_tool_approval to true and the agent pauses before running dangerous tools —exec_command, write_file, edit_file, memory, knowledge, and all MCP tools. Read-only tools (read_file, search_files, list_directory) run freely.

The REPL prompts y/N; the web UI renders Approve/Reject cards. Calls are rejected (fail-closed) if no approval channel is connected — a timeout or rejection denies the call.

### 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/sockand 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.

Stateless one-shots: Each-pcall through the daemon runs in isolation. Conversation history and tool-cache state do not bleed between requests, matching inline behavior.

SIGHUP: Send SIGHUP to the daemon's PID to make it shut down gracefully (exit code 0), picking up any config, SOUL, memory, or skill changes on the nextdaemon start.

### 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

resume a previous session (see enchanter sessions)

enchanter --resume a1b2c3d4

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

CommandDescription
enchanter soulShow current SOUL.md
enchanter memoryShow loaded memory
enchanter skillsList discovered skills
enchanter configShow resolved configuration
enchanter promptShow assembled system prompt
enchanter sessionsList saved session history
enchanter sessions <id>Show a specific session's conversation
enchanter daemon startStart the background daemon (Linux/macOS)
enchanter daemon stopStop the running daemon (Linux/macOS)
enchanter daemon statusShow daemon status: model, MCP servers, uptime (Linux/macOS)
enchanter initScaffold .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 budget— Show token/character budget per prompt layer
/exit— Quit cleanly (also Ctrl+D)

### environment variables

All optional. Environment variables override config.yaml values.

VariableOverridesDescription
ENCHANTER_MODELmodel.defaultModel ID to use
ENCHANTER_BASE_URLmodel.base_urlAPI base URL
ENCHANTER_API_KEYmodel.api_keyAPI key (not needed for local providers)
ENCHANTER_HOME—Data directory (default: ~/.enchanter)
OPENAI_API_KEYfallbackUsed if ENCHANTER_API_KEY is not set
OPENAI_BASE_URLfallbackUsed if ENCHANTER_BASE_URL is not set

Provider api_key and base_url fields support ${VAR} expansion, so you can writeapi_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

Current release:v0.8.0— prebuilt binaries for Linux, macOS, and Windows (x86_64 + arm64).

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.