-
Notifications
You must be signed in to change notification settings - Fork 1
configuration
All eVi state lives under ~/.evi/ (Windows: %USERPROFILE%\.evi\). The
primary file is config.toml. First launch writes a default. Hand-edit it
when you need to change something; nothing else holds machine-wide state.
[llm]
backend = "lmstudio" # lmstudio | ollama | llamacpp | openai_compat
base_url = "http://localhost:1234/v1" # default per-backend; auto-bumped by `evi models backend`
api_key = "lm-studio" # local servers ignore it; `env:VARNAME` reads the env var
model = "qwen2.5-7b-instruct" # current active model
temperature = 0.7 # lower (0.3) is friendlier to tool calling
max_tokens = 4096
request_timeout = 120.0
[comfy]
base_url = "http://localhost:8188"
default_checkpoint = "sd_xl_base_1.0.safetensors"
default_steps = 25
default_width = 1024
default_height = 1024
[google] # Phase 2 — deferred
client_secrets_path = ""
scopes = ["https://www.googleapis.com/auth/gmail.readonly"]
[microsoft] # Phase 2 — deferred
client_id = ""
tenant_id = "common"
scopes = ["Mail.Read", "User.Read"]
[tools]
fs = true # read_file / write_file / list_dir
code = true # run_python in a subprocess
shell = false # not implemented; never default-on
gmail = false # Phase 2
outlook = false # Phase 2
image = false # ComfyUI text2img
memory = true # remember / recall / forget / list_memories
subagent = false # delegate_explore / delegate_plan
mcp = false # MCP-server-provided tools
skills = true # list_skills / invoke_skill
web = false # web_search / web_fetch (network!)
voice = false # speak_text / transcribe_microphone
computer = false # never default-on; mouse/keyboard control
transcripts = true # write session JSONL for dreaming
[auto]
# Categories listed here run without prompting. `computer` is never here.
auto_approve = ["fs", "code", "memory", "skills", "image"]
# Curated destructive-command guard (on by default). Commands like `rm -rf ~`,
# `git reset --hard`, `git push --force`, disk formats, `curl | sh`, secret
# exfil, or `terraform destroy` can never run SILENTLY: a match forces a
# confirmation prompt, and is DENIED when there's no UI to confirm with
# (headless / scheduler / MCP). Overrides yolo, accept_edits and auto_approve.
block_destructive = true
# fnmatch globs matched against the WHOLE command that exempt it from the guard.
destructive_allow = ["*--force-with-lease*"]
# Builtin rule ids to silence without disabling the guard (see evi/shell_guard.py).
destructive_disable_rules = ["git-commit-amend"]Everyday commands are deliberately not flagged — rm -rf ./build, rm -rf node_modules, git reset --soft, git branch -d, chmod 644, terraform plan, apt remove <app> all run as normal. Only genuinely destructive,
hard-to-reverse forms match. An explicit, specific allow rule (one carrying an
arg-glob, e.g. allow shell *--force*) is honoured as intent and clears the
guard; a broad allow shell / allow shell * does not.
eVi is local-first, but any OpenAI-compatible cloud gateway works via
backend = "openai_compat". Named presets make the common ones one command:
evi models preset # list providers + whether each key env var is set
evi models preset openrouter -m anthropic/claude-3.5-sonnet
evi models preset openai # uses $OPENAI_API_KEY (api=chat; set api=responses for the Responses API)Presets (openrouter, openai, xai, anthropic, groq, together) all set
backend=openai_compat + the provider base_url and, by default, an
api_key = "env:<PROVIDER>_API_KEY" reference so your key stays in the
environment, not in config.toml. (Pass --api-key to store it inline instead.)
The anthropic preset targets Anthropic's OpenAI-compatible endpoint, not the
native Messages API.
The claude_agent backend talks to Claude through the local claude CLI
(the Claude Agent SDK), authenticating with your Claude subscription login
instead of an ANTHROPIC_API_KEY — the sanctioned, no-key way to use your
Max/Pro plan from your own tools.
Setup (one time):
npm i -g @anthropic-ai/claude-code # the `claude` CLI, if not already installed
claude # log in on your Max/Pro plan
pip install 'evi-assistant[claude-agent]'Then add + select it (no URL, no key):
evi backend add claude --kind claude_agent # or: Settings → Model & Backend → add, kind claude_agent
evi backend use claude --model opus # models are aliases: opus | sonnet | haikuThe opus / sonnet / haiku aliases resolve to whatever your plan currently
serves. Tools work fully — eVi still runs its own tools (with its
permissions, checkpoints, and mode-scoped toolsets); the Agent SDK is used only
to decide which tool to call. Note this routes through the claude CLI (not an
HTTP endpoint), so it's local-machine-only and needs the CLI logged in; if the
SDK/CLI is missing, eVi says so the moment you select the backend.
The codex backend is the OpenAI counterpart: it talks to the local codex
CLI authenticated by codex login (ChatGPT Plus/Pro/Business), no
OPENAI_API_KEY.
npm i -g @openai/codex # the `codex` CLI (or: brew install --cask codex)
codex login # browser OAuth against your ChatGPT plan
evi backend add codex --kind codex # or Settings → Model & Backend → kind codex
evi backend use codex --model gpt-5-codex # models: gpt-5-codex | gpt-5Under the hood eVi runs codex exec --json (read-only) and streams the answer.
Difference from claude_agent: Codex is an autonomous agent that runs its
own tools in its sandbox, so it's a chat / delegate provider — eVi's tools
don't route through it, and it can't edit files during a chat turn. Both backends
are built on the same shared CLI-agent shim (evi/llm/cli_agent.py), so they
behave consistently and adding further subscription-login CLIs is cheap.
The gemini backend uses the local gemini CLI: signing in with a Google
account gives a free tier (~1000 requests/day) with no GEMINI_API_KEY.
npm i -g @google/gemini-cli # the `gemini` CLI
gemini # run once and pick a Google login
evi backend add gemini --kind gemini # or Settings → Model & Backend → kind gemini
evi backend use gemini --model gemini-2.5-flash # models: gemini-2.5-pro | gemini-2.5-flasheVi runs gemini -p … -o json and streams the reply. Like codex it's a
chat / delegate provider (Gemini drives its own tools; eVi's tools don't route
through it), built on the same shared shim.
The amp backend uses the local amp CLI, authenticated by amp login (an
Amp subscription / credit balance) or an AMP_API_KEY access token — not a
per-token model API key.
npm i -g @sourcegraph/amp # the `amp` CLI
amp login # Amp subscription (or: set AMP_API_KEY)
evi backend add amp --kind amp # or Settings → Model & Backend → kind amp
evi backend use amp --model medium # modes: medium | low | higheVi runs amp -x --stream-json and streams the reply. Amp selects its model by
agent mode (low/medium/high), not a model id. Like codex/gemini it's
a chat / delegate provider, but note Amp is autonomous and can use tools
(including file edits) per your configured amp permissions — restrict it there if
you want a read-only chat. eVi won't start Amp unauthenticated (its login flow
would block), and it bounds each turn with a timeout as a backstop.
The qwen backend uses Qwen Code (Alibaba's gemini-cli fork): sign in free
with a qwen.ai account (a generous free tier, ~2000 requests/day) — no API key.
npm i -g @qwen-code/qwen-code # the `qwen` CLI
qwen # run once and pick 'Qwen' to sign in (free OAuth)
evi backend add qwen --kind qwen # or Settings → Model & Backend → kind qwen
evi backend use qwen --model qwen3-coder-plus # models: qwen3-coder-plus | qwen3-coder-flasheVi runs qwen -p … -o json (Claude-Code-style events) and streams the reply — a
chat / delegate provider like gemini, on the same shared shim.
The copilot backend uses the local copilot CLI (@github/copilot),
authenticated by your GitHub Copilot subscription (copilot login) — no
separate model API key.
npm i -g @github/copilot # the `copilot` CLI
copilot login # GitHub Copilot subscription
evi backend add copilot --kind copilot # or Settings → Model & Backend → kind copilot
evi backend use copilot --model auto # models: auto | claude-sonnet-4.5 | gpt-5eVi runs copilot -p … --output-format text -s and streams the reply. --model auto lets Copilot pick; the exact set depends on your plan. A chat / delegate
provider; unapproved tool calls are auto-denied in non-interactive mode, so a chat
turn stays answer-only.
| kind | CLI | Auth (no API key) | Tools |
|---|---|---|---|
claude_agent |
claude |
Claude Max/Pro login | eVi drives tools (full parity) |
codex |
codex |
ChatGPT Plus/Pro login | delegate (Codex's own, read-only) |
gemini |
gemini |
Google free login | delegate (Gemini's own) |
amp |
amp |
Amp subscription (amp login / AMP_API_KEY) |
delegate (Amp's own, per amp permissions) |
qwen |
qwen |
Qwen free OAuth login | delegate (Qwen's own) |
copilot |
copilot |
GitHub Copilot login | delegate (Copilot's own, auto-denied) |
All six are local-machine-only (they shell out to a CLI, so they don't work in the packaged desktop sidecar) and report clearly if their CLI isn't installed.
Partial overlay merged on top of config.toml. Activated by env var
EVI_PROFILE=<name> or --profile <name> / -p <name> on any CLI
invocation.
# ~/.evi/profiles/home.toml
[llm]
backend = "openai_compat"
base_url = "http://ai-server.local:8000/v1"
model = "qwen2.5:32b"evi profile add away --backend lmstudio --model llama3.2:1b-instruct-q4_K_M
evi profile list
evi --profile home chatProfile merge is deep for tables (dicts) and replacing for lists.
So a profile that overrides [microsoft] scopes replaces it wholesale
rather than appending.
One markdown file per fact:
~/.evi/memory/
INDEX.md auto-regenerated; you can read but don't edit
preferences.md arbitrary name; first non-empty line becomes the summary
project_paths.md
.attic/ soft-deleted entries; safe to remove if you want
The Agent.memory.format_for_prompt() block shows up in every system
prompt. The model uses the remember(name, content), recall(name),
and forget(name) tools to manage it. forget is soft-delete — it
moves to .attic/. The dreaming engine relies on this to avoid losing
data on bad consolidations.
Caps: 64 KB per entry, names must match [A-Za-z0-9_-]{1,64}.
~/.evi/skills/
summarize/
SKILL.md required — instructions + optional YAML frontmatter
example-input.txt optional assets (loadable by the model with read_file)
---
name: summarize
description: Boil long text down to 3 bullets.
---
# Steps
1. Identify the topic.
2. Pull the three highest-impact points.
3. Format as Markdown bullets ≤ 12 words each.The frontmatter description shows up in the system prompt's
## Available skills block. The model calls invoke_skill(name) to
load the full body when it decides the skill applies.
User-defined prompt templates. Type /<name> args in the chat REPL or
web UI and the file's body is sent as the user turn with {args}
substituted.
# ~/.evi/commands/commit.md
Run `git diff` to see what changed, then propose a conventional-commits
style message under 70 chars. Args: {args}Built-in slash commands (handled in code, not files): /help /reset /exit /tools /model /goal /plan /auto.
[[before_tool_call]]
name = "audit"
match = "*" # glob over tool names (fs.*, write_file, etc.)
command = ["bash", "-c", "echo $EVI_HOOK_TOOL >> ~/.evi/logs/tools.log"]
timeout = 5
[[before_tool_call]]
name = "no-system-writes"
match = "write_file"
command = ["bash", "-c", 'echo "$EVI_HOOK_ARGS_JSON" | grep -qv "/etc/"']
veto_on_nonzero = true # non-zero exit blocks the tool
[[after_tool_call]]
name = "notify"
match = "generate_image"
command = ["notify-send", "Image ready"]
[[after_tool_call]]
name = "webhook"
match = "*"
url = "https://example.com/evi-hook" # POST instead of spawning a commandEnv vars set in the child process (command hooks):
-
EVI_HOOK_EVENT—before_tool_callorafter_tool_call -
EVI_HOOK_TOOL— fully-qualified tool name -
EVI_HOOK_ARGS_JSON— JSON of the call arguments -
EVI_HOOK_RESULT— tool output (after-hooks only, capped at 4 KB)
A hook uses either command (argv, spawned) or url (HTTP POST of
{event, tool, args_json, result}). For a url hook a 2xx response means
success; any other status becomes the exit code, so veto_on_nonzero still
gates the call.
Besides the tool events, hooks fire on lifecycle events (use match = "*"):
[[user_prompt_submit]] # before each turn — veto blocks the prompt
name = "no-secrets"
command = ["python3", "/path/check_prompt.py"] # prompt is in EVI_HOOK_ARGS_JSON
veto_on_nonzero = true
[[before_compact]] # before history compaction — veto keeps it intact
[[stop]] # after a turn completes (notification; never blocks)Map a key to a slash command in the interactive chat REPL — pressing it replaces the line with that command and submits it.
[keybindings]
"c-t" = "/tools" # Ctrl-T
"f2" = "/model" # F2
"escape g" = "/goal" # Esc then g (a two-key sequence)Keys use prompt_toolkit names (c-t, f2, escape, …); a space-separated
value is a multi-key sequence. Terminal essentials (c-c, c-d, tab,
enter) are reserved and silently ignored, and an unknown key name is skipped
without breaking the others.
[voice]
engine = "system" # system | coqui | f5 | piper
model = "" # engine-specific: Coqui XTTS id, or a Piper voice .onnx path
clone_sample = "" # reference WAV for the cloning engines (coqui / f5)
language = "en"-
system — zero-dep platform voice (Windows SAPI / macOS
say/ espeak). -
coqui — Coqui XTTS v2; multilingual, clones a voice from
clone_sample. -
f5 — F5-TTS; fast zero-shot cloning (uses its
f5-tts_infer-cli). -
piper — lightweight local neural voices (set
modelto a.onnx); no cloning.
The neural engines are optional heavyweight installs; eVi lazy-imports them and
falls back to a clear error if the deps/binaries aren't present. evi voice engines shows which are installed and which is active; switch the engine in the
desktop Settings → Voice screen or by editing [voice].
Where a recipe is a sequence of turns through one conversation, a workflow orchestrates independent steps — each its own headless agent — with parallel fan-out and variable interpolation.
name = "research"
description = "Plan, research two angles in parallel, then synthesize."
[vars]
topic = "local-first AI"
[[steps]]
id = "plan"
prompt = "Outline an approach to research {topic}."
[[steps]]
id = "pros"
parallel = true
prompt = "List the upsides of {topic} given this plan:\n{plan}"
[[steps]]
id = "cons"
parallel = true
prompt = "List the downsides of {topic} given this plan:\n{plan}"
[[steps]]
id = "synth"
prompt = "Synthesize a balanced take.\nUpsides:\n{pros}\nDownsides:\n{cons}"- Steps run in file order; a contiguous run of
parallel = truesteps runs concurrently. A following sequential step is the natural fan-in point. - Prompts interpolate workflow
[vars]and earlier step outputs by id —{topic},{plan}, … (escape literal braces as{{/}}). - Each step is an unattended (auto-approved) headless agent; set
modeon a step for a tool preset (chat/cowork/code).
evi workflow new <name> scaffolds one; evi workflow run <name> --var topic=…
runs it (--json for machine output). The desktop 🗂 Dispatch panel lists
and launches workflows and shows every live session.
A plugin bundles any of: commands/, skills/, hooks.toml, mcp.json, and
agents.toml (subagent profiles, used via the delegate tool — see
evi agents). Install from a directory or git URL with evi plugin add, or by
name from the marketplace index:
// ~/.evi/marketplace.json
{
"plugins": [
{ "name": "git-helpers",
"source": "https://github.com/you/evi-git-helpers.git",
"description": "Handy git slash commands",
"tags": ["git"] }
]
}# config.toml — extra remote index files merged with the local one
[plugins]
index_urls = ["https://example.com/evi-plugins.json"]evi plugin search [query] lists matches; evi plugin install <name> resolves
the name through the index and installs its source. evi plugin index init /
evi plugin index add <name> <source> manage the local index.
Delegate a task to a trusted peer eVi (e.g. a GPU box):
[ { "name": "gpu", "url": "http://gpu-box:8473", "token": "<peer web token>" } ]evi peer run gpu "summarise this repo" (or the delegate_peer tool, category
federation, off by default) POSTs to the peer's /api/federate. The peer must
opt in with [federation] serve = true; it runs the task non-interactively
(tools not auto-approved are denied).
Federation is eVi's private fast path to your own eVis. A2A (a2a-protocol.org, a Linux Foundation standard) is the interop path — it lets eVi talk to any vendor's agent that speaks A2A, and lets such agents call eVi. No extra dependency (hand-rolled against the v0.3/v1.0 wire shapes).
Discovery — always on, public. eVi serves a spec-compliant Agent Card at
GET /.well-known/agent-card.json (auth-exempt). It carries the standard fields
(protocolVersion, capabilities, skills, securitySchemes) plus eVi's model
capability flags under an x-evi extension, so capability-aware clients can route
by modality.
Serve — opt in. Set a2a = true under [federation] to expose the JSON-RPC
endpoint POST /a2a:
[federation]
a2a = true # expose POST /a2a (off by default)It implements message/send, tasks/get, and tasks/cancel, is bearer-token
gated (same web token as the rest of the API), and runs each delegated task
non-interactively — tools not already auto-approved are denied, exactly like
/api/federate. Streaming (message/stream) and push notifications aren't
implemented yet, so the card advertises capabilities.streaming = false and a
compliant client falls back to blocking message/send.
Call out — the delegate_a2a tool. eVi can delegate to any external A2A agent
by its JSON-RPC URL:
delegate_a2a(url="https://some-agent.example/a2a", task="research X", token="…")
(category federation, off by default — enable the tool to let the model use it).
Opt-in ([web] multi_user = true): each person logs in with their own revocable
token instead of sharing auth_token.
[ { "name": "alice", "token": "…" }, { "name": "bob", "token": "…" } ]Manage with evi web-config users add/list/remove. Each user gets an isolated
workspace — their web sessions, transcripts, and memory live under
~/.evi/users/<name>/ and aren't visible to other users. Drop a user from the
file to revoke access. (Skills/plugins/config stay shared — they're capabilities,
not personal data.)
A local content filter over the model. Off by default; enabled = true to turn
it on. Two rule types layer together:
enabled = true
[[rule]] # regex — fast, deterministic
name = "block-secrets"
pattern = "(?i)(api[_-]?key|secret)\\s*[:=]"
action = "block" # block | redact
applies_to = "input" # input | output | both
[[judge]] # semantic — graded by the LLM
name = "no-self-harm"
policy = "Requests for, or content encouraging, self-harm or suicide."
applies_to = "both"
[[classifier]] # offline ML moderation model
name = "toxicity"
model = "unitary/toxic-bert" # any HF text-classification model ("" = default)
labels = ["toxic", "threat", "insult"] # labels that block ([] = any)
threshold = 0.7
applies_to = "both"Three layers run in order, stopping at the first block:
-
[[rule]]regex — fast, deterministic:blockrefuses the turn (input) or scrubs the stored reply (output);redactreplaces spans with[REDACTED]. -
[[judge]]— eVi's own model classifies the text againstpolicyand blocks on a match. The local counterpart to a hosted moderation API; one model round-trip per turn. -
[[classifier]]— a local HuggingFace text-classification model scores the text and blocks when alabelsscore crossesthreshold. Fully offline; needspip install 'evi-assistant[moderation]'(transformers + torch). Block-only.
Both semantic layers fail open (a missing/flaky model skips the rule, not the
turn). Inspect with evi guardrails list; evi guardrails test "<text>" dry-runs
the regex layer.
Inspect with evi guardrails list; dry-run the regex layer with
evi guardrails test "<text>".
The desktop app registers the evi:// URL scheme. evi://session/<id> focuses
a session, evi://workflow/<name> opens the dispatch panel, evi://new starts a
chat. evi link [id|new] prints a link; evi link --open <url> shows where it
routes. The same targets work in a browser via /?session= and /?workflow=.
[
{
"name": "filesystem",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "C:/Users"],
"env": {},
"enabled": true
},
{
"name": "git",
"command": "uvx",
"args": ["mcp-server-git", "--repository", "C:/evi"]
}
]Flip tools.mcp = true in config.toml. evi mcp list-tools enumerates
what each server exposes; tools land in the registry as
<server-name>.<tool-name>.
Managed via evi schedule add/list/remove/enable/disable/run-now. One
file per task. Don't hand-edit unless you know the schema; the IDs are
opaque and the scheduler caches state.
evi schedule add --name "morning brief" --cron "0 8 * * *" \
--prompt "Summarize my overnight email."
evi scheduler # foreground daemon, or just run `evi web`| What | Where |
|---|---|
| Dream audit | ~/.evi/logs/dreams/<stamp>.log |
| Scheduled task runs | ~/.evi/logs/scheduled/<id>_<stamp>.log |
| User-defined hooks | Wherever your hook commands write |
| Session transcripts | ~/.evi/transcripts/<YYYY-MM-DD>/<session>.jsonl |
| ComfyUI images | ~/.evi/images/ |
| Screenshots | ~/.evi/screenshots/ |
| HF model downloads | ~/.evi/models/<repo-flat>/ |
| Var | Purpose |
|---|---|
EVI_HOME |
Override ~/.evi/ location entirely |
EVI_PROFILE |
Active profile name (same as --profile) |
EVI_PYTHON |
Tauri desktop: Python interpreter to spawn (default py -3.13) |
EVI_REPO_ROOT |
Tauri desktop: pin the repo root rather than auto-detecting |
EVI_REMOTE_URL |
Tauri desktop: thin-client mode — skip spawn, navigate to URL |
Generated from docs/configuration.md — edit there, not here.
Start here
Guides
- Architecture
- [[Agent SDK (
evi.sdk)|sdk]] - SDK coverage + borrowable features
- Multi-machine setup
- Self-update design (Phase 29 proposal)
- [[Self-build — developing and building eVi with eVi|self-build]]
- Development notes
- Releasing
- Desktop bundling
- Code signing policy
- Surface parity — CLI ↔ Web ↔ Desktop
- eVi vs Claude Code — feature comparison
- Future integrations — backlog
- Roadmap
Feature deep-dives
- eVi feature guides
- Agents & Orchestration
- Recipes, Routines, Scheduled tasks, Channels
- Evals & LLM-as-judge
- Content Guardrails
- Hooks (tool + lifecycle, command/url)
- MCP (client + serve)
- Memory & Context management
- Observability (OpenTelemetry, stats, crash reports)
- Permissions & Sandbox
- Plugins & Marketplace
- Sessions, Resume, Handoff, Checkpoints
- Skills
- Slash commands
- Structured Outputs & Batch
- Ultracode
- Voice (TTS engines, STT, AutoSpeaker)
- Web & Desktop (settings, multi-user, deep links, updater)