~/.chanto/config.toml (global), ./.chanto/config.toml (project), ./.chanto/config.local.toml (personal). Run chanto --example-config for a template.
The Chanto directory (~/.chanto throughout these docs: config, sessions, logs, memory) is $CHANTO_HOME when set. Otherwise an existing ~/.chanto is used; if there is none and XDG_CONFIG_HOME is set (to an absolute path), Chanto uses $XDG_CONFIG_HOME/chanto instead.
provider = "ollama"
model = "qwen3-coder:30b"
num_ctx = 32768
think = true # if reasoning starts repeating itself, that step is retried without thinking
effort = "high" # reasoning effort: low | medium | high | xhigh | max (default: the provider's). Also --effort, /effort, /think <level>
mode = "default"
input_border = "block" # TUI frames: block (default; thin, can't leave gaps) | line (rounded outline) | solid
theme = "auto" # auto | dark | light | dracula | nord | solarized-dark | gruvbox | colorblind | ~/.chanto/themes/<name>.toml
screen_reader = false # screen-reader mode: line-based output, no animation, a bell when a prompt waits (also --ax-screen-reader)
animations = true # TUI animation (spinner, live counters); false skips it (also --no-animations); unset follows macOS Reduce motion
notifications = "auto" # long turns and waiting prompts: auto | osc9 | osc777 | system | bell | off
shell_snapshot = true # Bash gets your shell's PATH, exported variables, aliases and functions (see below)
bash_diff = true # show files a Bash command changed as a diff (ctrl+o expands)
away_summary = true # one-line recap when you return to the TUI after 3+ minutes away
keep_awake = "working" # no idle sleep while chanto works: working | never | always
notify_after_secs = 30 # a turn this long notifies when it ends (0: never)
classifier_model = "qwen3:4b" # optional faster model for auto-mode checks
fallback_model = ["ollama/qwen3:8b"] # when the model is overloaded or unavailable (see Fallback models)
tool_search = "auto" # defer MCP tool schemas behind ToolSearch: auto | auto:N | true | false
stream_idle_timeout = 120 # a started model response silent this many seconds fails and is retried (0: no limit)
mcp_tool_timeout = 600 # seconds an MCP tool call may take (a server's tool_timeout_sec wins; else MCP_TOOL_TIMEOUT, in ms)
[permissions]
allow = ["Bash(cargo test:*)", "Bash(npm run lint:*)"]
deny = ["Bash(git push:*)"]
auto_mode_rules = ["Never modify files under migrations/"]
[web]
search_backend = "auto" # duckduckgo by default; brave/tavily/searxng with keys
search_min_gap_ms = 1500 # key-less searches are paced per engine; a blocked engine cools down (0 disables pacing)
[browser]
headless = false
[embeddings] # SemanticSearch's embedding endpoint
provider = "ollama" # ollama (default) | openai
model = "nomic-embed-text" # ollama: nomic-embed-text; openai: text-embedding-3-small
base_url = "http://localhost:11434" # override the endpoint (Ollama's /api/embed, or /v1/embeddings when provider = "openai")
api_key = "${OPENAI_API_KEY}" # openai provider only
batch = 16 # chunks embedded per request
[providers.lmstudio] # use as model = "lmstudio/<model-id>"
kind = "openai"
base_url = "http://localhost:1234/v1"
[providers.claude] # model = "claude/claude-opus-5-5"
kind = "anthropic" # key from api_key or ANTHROPIC_API_KEY
[sandbox]
enabled = true # macOS: Bash writes limited to project + temp/caches
auto_allow = true # auto mode: sandboxed commands skip the classifier
[mcp_servers.github]
command = "github-mcp-server"
args = ["stdio"]
[mcp_servers.remote] # streamable HTTP transport
url = "https://example.com/mcp"
headers = { Authorization = "Bearer ${MY_TOKEN}" }
[mcp_servers.linear] # OAuth: log in with /mcp login linear
url = "https://mcp.linear.app/mcp"
# oauth = { client_id = "..." } # only for servers without dynamic client registration
[[hooks.PostToolUse]]
matcher = "Edit|Write|MultiEdit"
command = "cargo fmt"Your shell setup in Bash
Bash commands run under bash -c, which reads no startup files. So that tools that work in your terminal work for the agent too (nvm, pyenv, asdf, Homebrew's shellenv, your aliases), Chanto takes a shell snapshot once per session, the first time a command runs: it starts your $SHELL (zsh or bash) as an interactive login shell, so it reads ~/.zprofile/~/.zshrc or ~/.bash_profile/~/.bashrc as a terminal would (a login bash whose profile doesn't mention .bashrc gets ~/.bashrc too), and records what they set up in a private file (mode 600, in a new mode-700 directory under the temp dir) that every command sources first:
- exported variables your startup files added or changed.
PATHis merged: directories only Chanto's own environment has (an activated virtualenv) stay in front, then your login shell'sPATH. Left out: secret-named variables (the ones[secrets] scrub_envkeeps from commands, unlesspass_envlets them through), values that look like secrets, terminal and prompt state (PS1,TERM,TMUX, history settings, …), proxy variables, the ones Chanto sets for commands itself (PAGER,NO_COLOR, …), and ones the config'senv(or Claude Code's settingsenv) sets, which win; - functions, except completion, prompt and hook functions (names starting with
_,prompt_,precmd,preexec, …) and ones that would replace builtins Chanto relies on (cd,pwd,exit, …). Each is defined through its owneval, so one bash can't parse (zsh-only syntax) is skipped; functions and aliases whose text looks like it holds a secret are left out too; - aliases, with
expand_aliaseson.
The startup files run with no terminal, stdin from /dev/null and their output thrown away, so ones that print, fail or wait for input don't matter, and with CHANTO_SHELL_SNAPSHOT=1 set, which they can test to skip slow work. If they take more than 5 seconds, Chanto warns once and commands run with Chanto's own environment. Commands still run under bash (not your shell) so quoting and the tool's behavior are the same for everyone; variables always carry over from zsh, and functions do when bash can parse them. fish isn't captured. The snapshot is sourced inside the command's process, so the sandbox applies to it, and CLAUDE_ENV_FILE files run after it, so they can build on it. Not in a background run's container, and not on Windows, where commands run in Git Bash or PowerShell as before.
Turn it off with shell_snapshot = false, CHANTO_SHELL_SNAPSHOT=0, or Claude Code's CLAUDE_BASH_NO_LOGIN=1.
After a Bash command changes files in the project, its result shows Changed N files: … with each file's diff (collapsed; ctrl+o expands it), as an edit would. The model gets only the list of changed paths. Ignored files and build outputs (the same ones /rewind leaves out) aren't counted; binary files and files over 256 KB are listed without a diff. Only the main conversation's foreground commands in the session's project are tracked, and commands Chanto knows are read-only skip it. Turn it off with bash_diff = false (Claude Code's bashEditDiffEnabled: false maps to it) or CHANTO_BASH_DIFF=0.
Fallback models
fallback_model (a spec, a comma-separated list or an array; --fallback-model a,b overrides it for one session) names models to switch to when the session's model can't serve a request. They're tried in order, at most three, and the session's own model is skipped if listed.
- What switches: an overloaded or failing server (HTTP 500, 502, 503, 504, 529, or an
overloadederror), a model the server doesn't have (404, Ollama's "model not found"), and a server that isn't running (connection refused, typical of a local Ollama or LM Studio that was closed). Hosted providers retry 429/5xx a few times first; with a fallback waiting, Chanto adds just one retry of its own before switching. - What doesn't: request errors (400, 413), authentication (401, 403), billing and quota errors, rate limits (429), timeouts, and failures after the response started streaming. These fail as usual.
- How long: a warning says which model took over. The fallback serves the rest of that turn, tool steps included, and subagents use the same chain. The next message tries the session's model first again. If a fallback fails the same way, the next entry takes over.
- Smaller windows: if the conversation has reached
compact_thresholdof the fallback's context window, it is compacted first (by the fallback, since the model it replaces is the one failing) instead of failing on the request.
Stalled streams. A response that starts and then goes silent for stream_idle_timeout seconds (default 120; 0 turns the limit off) fails with a message saying so. If nothing had been shown yet it's retried like a dropped connection; otherwise the turn ends with that error. The wait before the first byte isn't limited, since a local server can spend minutes reading a long prompt before it answers. Esc interrupts the wait either way.
Customizing Chanto
Ask /customize <request> in a session, or run chanto customize "<request>" from the shell. The agent gets a guide to Chanto's extension formats, chooses a small extension, asks about user (~/.chanto/) versus project (.chanto/) scope when needed, writes it with normal approval, and validates it with chanto check-extensions. For example: /customize add a /release-check command that reviews the changelog and runs tests in this project.
chanto check-extensions [-C <project>] checks config and Markdown extensions and lists loaded commands, skills, agents, output styles and hooks. It does not execute hooks or connect servers. /reload refreshes commands, skills, agents, output styles, instructions and hooks without losing the conversation. Other configuration, provider/tool settings, MCP and LSP servers require a restart. Plugin Markdown components and hooks are refreshed by /reload. Project hooks and other risky settings still require folder trust.
For behavior that extensions cannot express, use chanto customize --source "<request>". It uses the source checkout recorded at build time, or offers to clone the repository into ~/.local/share/chanto/src. The checkout must be clean. It creates a custom/<slug>-<id> branch, enables verified mode, requires cargo test to pass, then asks before building release and installing into ${PREFIX:-$HOME/.local}/bin. Installation removes the old binary before copying the new one, as install.sh --from-source does. Source edits use the same approval rules as other agent work; other repositories are not used as the checkout.
Plugins
Chanto manages Claude Code-compatible plugins without requiring Claude Code:
chanto plugin marketplace add owner/repo
chanto plugin marketplace add https://example.com/team/plugins.git
chanto plugin marketplace add /path/to/local-marketplace
chanto plugin marketplace list
chanto plugin marketplace update [name]
chanto plugin marketplace remove <name>
chanto plugin list
chanto plugin install <plugin>@<marketplace> [--yes]
chanto plugin uninstall <plugin>
chanto plugin enable <plugin>
chanto plugin disable <plugin>Marketplaces contain .claude-plugin/marketplace.json with name, owner, and
plugins[] entries (name, source, optional description and version). A
plugin source can be a path relative to the marketplace, a GitHub object
({"source":"github","repo":"owner/repo","ref":"tag"}), or a git URL object
({"source":"url","url":"https://example.com/plugin.git"}). Git marketplaces
update with git pull --ff-only; local marketplaces are copied snapshots (remove
and add again to refresh). Local copies reject symlinks, and relative plugin
sources must stay inside the marketplace.
Checkouts live in ~/.chanto/plugins/marketplaces/, installed copies in
~/.chanto/plugins/installed/, and the install registry in
~/.chanto/plugins/installed.json. Installed copies retain their marketplace
manifest, so removing a marketplace does not uninstall its plugins. These
user-level installs load alongside existing Claude Code plugins; project
enabledPlugins overrides are read only for trusted folders. Use
plugin@marketplace to disambiguate uninstall, enable, or disable.
Install reports commands, skills, agents, hooks, MCP servers and LSP servers.
Hooks and server definitions can run code and require explicit confirmation,
and so do skills and commands with !`cmd` blocks, frontmatter hooks or
allowed-tools (the summary lists each one under "runs when invoked");
non-interactive installs require --yes when those components are present.
Plugins cloned from git are refused if they contain symlinks.
/plugin lists plugins and accepts the same subcommands, including interactive
confirmation. Changes apply after /reload for Markdown components and hooks;
restart for MCP/LSP connections. /plugins lists active plugins.
To undo source changes, commit or stash your work, switch to the previous branch (printed when customization starts), and run ./install.sh; alternatively commit the customization, git revert that commit, and rebuild with ./install.sh. Restart Chanto after installing.
Profiles, output styles, API key helper
- Profiles (like Codex's
--profile): define[profiles.<name>]sections in any config file and select one withchanto --profile <name>orprofile = "<name>". A profile is applied on top of every other layer. A profile in an untrusted project can't set risky keys.toml[profiles.local] model = "qwen3:8b" num_ctx = 32768 [profiles.hosted] provider = "openai" base_url = "https://openrouter.ai/api/v1" model = "qwen/qwen3.5-9b" - Output styles (like Claude Code's
/output-style):Concisekeeps replies short, result first;Proactivedoes the obvious follow-up work (callers, tests, docs) without asking, except for anything destructive or outward-facing;Explanatoryadds short notes on why choices were made; andLearningleaves smallTODO(human)pieces for you to write.- Add your own as Markdown files in
~/.chanto/output-styles/or.chanto/output-styles/, withnameanddescriptionfront matter. Claude Code'soutput-stylesfolders are read too. - Switch with
/output-style <name>, or setoutput_stylein config (Claude Code'soutputStyleis imported).
- Add your own as Markdown files in
api_key_helper: a command that prints the API key, run at startup when no key is set, for exampleop read op://dev/openai/key. Claude Code'sapiKeyHelperis imported. Only user or trusted config can set it.
MCP servers
- Managing servers: from the shell, like
claude mcp:chanto mcp add <name> -- <command> [args]adds a stdio server;chanto mcp add <name> <url>adds an HTTP one. Use-e KEY=VALUEfor env vars and-H 'Name: value'for headers.-s local(default,./.chanto/config.local.toml),-s project(./.chanto/config.toml, shared via git) or-s user(~/.chanto/config.toml).chanto mcp add-json <name> '<json>'adds a server from a JSON entry in Claude Code's format ({"type":"http","url":...,"headers":{...}},{"command":...,"args":[...],"env":{...}},oauth.clientId/callbackPort;--client-secretreadsMCP_CLIENT_SECRETor asks). Pass the server's object, not themcpServerswrapper. Aurlmust start withhttp://orhttps://(or a${VAR}that supplies it).chanto mcp add-from-claude-desktoplists the servers in Claude Desktop's config (macOS~/Library/Application Support/Claude/, Windows%APPDATA%\Claude\, Linux~/.config/Claude/, and a Windows install from WSL) and imports the ones you pick (--all, or names, to skip the question). Names with characters other than letters, digits,-and_are reported and skipped; a name already in use gets a suffix (server_1).chanto mcp list,chanto mcp get <name>andchanto mcp remove <name>. In a folder you haven't trusted,liststill shows the servers its own config files define, marked "(untrusted folder: not loaded until you trust it)". Edits keep the rest of the file, comments included. Names must be letters, digits,-and_.
- In a session:
/mcpopens the server panel at the bottom (or, in print/plain mode, prints the same list). Each server shows its state (connected, needs login, failed with the reason, disabled, blocked by policy, or a.mcp.jsonserver pending approval); ↑/↓ moves, enter opens the server's actions (authenticate, reconnect, enable/disable, clear authentication), and esc closes. Like/status, it runs immediately even while a turn is running, it is never queued behind the model. A server that drops mid-session stops counting as connected: the panel shows it failed, and the tool call that hit it says so and names/mcp reconnect <server>./mcp reconnect <server>reconnects one;/mcp reconnect allretries every server that failed or needs a login./mcp disable <server>|alldisconnects servers and/mcp enable <server>|allconnects them again; the choice is remembered per project (disabledMcpServersin~/.chanto/mcp-projects.json, Claude Code keeps the same list in~/.claude.json, which Chanto also honors). The footer showsmcp <connected>/<configured>and turns amber when a server needs attention. - Tool approval: MCP tools ask before running in default and accept-edits mode and go to the classifier in auto mode, even when a local server marks a tool read-only (
readOnlyHint): the hint is the server's own word, and a read-only fetch can still send data out. In plan mode a tool marked read-only asks and the others are refused. An allow rule (mcp__server__tool, ormcp__serverfor all of a server's tools) lets a tool run without asking, and a tool marked read-only in plan mode too. - Project
.mcp.jsonapproval: trusting the folder in Chanto approves all of its servers. Otherwise a server loads once approved, as in Claude Code: byenabledMcpjsonServersorenableAllProjectMcpServersin~/.claude/settings.json, managed settings,~/.claude.json's entry for the project, or/mcp enable <name>(which records it in~/.chanto/mcp-projects.json); approvals in the project's own.claude/settings*.jsoncount only once the folder is trusted in Claude Code.disabledMcpjsonServersfrom any of those rejects a server. - Organization policy:
allowedMcpServersanddeniedMcpServersfrom managed settings and Claude Code settings are enforced, with Claude Code's matching:serverName(exact),serverCommand(exact command and arguments) andserverUrl(*wildcards; a host written in full matches only the default port). The denylist always wins and merges from every scope; the allowlist merges too unless managed settings setallowManagedMcpServersOnly. With an allowlist, a remote server must match aserverUrlentry and a stdio server aserverCommandentry (aserverNamecounts only when the list has none of that kind). An untrusted project's settings can add denylist entries but not allowlist ones. Blocked servers are listed in/mcpand never started. - Transports: stdio (
command), streamable HTTP (url) and the legacy HTTP+SSE transport (urlplustransport = "sse", or"type": "sse"in.mcp.json;chanto mcp add -t sse). An HTTP server that refuses the streamable POST with a 4xx is retried as SSE automatically. - Sampling: a server can ask to run a prompt on your model, for example to summarize what it fetched. You see the prompt and choose Allow or Deny each time; allowed requests run on the session's current model with no tools, count toward your budget, and are limited to the server's
maxTokens(at most 16k). Only text is supported. In print mode they're refused. - Elicitation: a server can ask you for input mid-call. Each field of its form is asked in turn as a question, required fields first (pick an option, type a value, or skip optional fields). Then you see exactly what would be sent and choose Send or Decline. Esc cancels at any point. While a question is open, the tool call waiting on it gets up to an hour instead of its usual limit. In print mode there's nobody to ask, so the request is declined.
- OAuth: a remote server that answers 401 shows as "needs login" in
/mcp; startup isn't blocked. To log in:- Run
/mcp login <name>. It opens your browser, waits for the redirect back to a local callback, then connects the server immediately. - Discovery follows the MCP authorization spec (protected-resource and authorization-server metadata), with dynamic client registration, PKCE and the
resourceparameter. - Tokens are stored in
~/.chanto/mcp-auth/(mode 0600) and refreshed automatically./mcp logout <name>deletes them.
- Run
- Resources: when a connected server offers resources, the model gets
ListMcpResourcesandReadMcpResourcetools. You can attach one to a prompt with@server:uri, e.g.@docs:file:///guide.md. - Prompts: server prompts become slash commands named
/mcp__<server>__<prompt> [args]. Arguments fill the prompt's declared parameters in order. They're listed in/helpand slash completion. - Updates: when a stdio server reports that its tool list changed, the list is refreshed before the next model call.
- Environment variables:
${VAR}and${VAR:-default}are expanded incommand,args,env,cwd,urlandheaders, in Chanto's config and in imported.mcp.json/ Codex servers.${VAR:-default}uses the default whenVARis unset or empty. A server that references an unset variable with no default is skipped with a warning naming the variable; the literal${VAR}is never sent. (Claude Code instead starts the server with the text unexpanded.) - Secrets: stdio servers don't inherit secret-looking variables (
*_TOKEN,*_API_KEY, ...) from Chanto's environment, except ones the server's config references as${VAR}or lists inenv_vars = ["GITHUB_TOKEN"], and ones set in itsenv. - Tool search: when MCP tool definitions would take 10% or more of the context window, Chanto stops sending their schemas. The model sees each tool's name and a one-line hint in a
<system-reminder>attached to the next message, and loads full definitions with theToolSearchtool:select:A,Bfor exact names, keywords (github issue) for the best matches (max_results, default 5), or+slack sendto requireslackin the name. Loaded tools are added to the end of the tool list and stay callable for the rest of the session. Earlier tool definitions and messages are never rewritten, so a local server's prompt cache stays valid. Newly connected servers' tools are announced the same way. With the text tool-call protocol, the definitions come only in theToolSearchresult, so the system prompt doesn't change.tool_search = "auto"(default),"auto:N"(defer at N% of the context),"true"(always defer) or"false"(always send every schema). TheENABLE_TOOL_SEARCHenvironment variable (orenvin settings) takes the same values and overrides the config.always_load = trueon a server (alwaysLoadin.mcp.json) keeps its tools upfront;always_load = falsedefers all of them. A tool the server marks_meta["anthropic/alwaysLoad"]: trueloads upfront unless its server setsalways_load = false.- A deny rule for
ToolSearchturns tool search off.
- Per-server options:
cwd(working directory of a stdio server),startup_timeout_sec(default 30, 10 for imported servers),tool_timeout_sec(how long one tool call may take; defaultmcp_tool_timeoutfrom the top level of the config, elseMCP_TOOL_TIMEOUTin milliseconds as in Claude Code, else 10 minutes. A call that runs out comes back to the model as an error naming the server and tool; esc stops the wait at any time. Either way the server is sentnotifications/cancelledfor the request, over stdio, streamable HTTP or legacy SSE),enabled_tools = [...](only expose these) anddisabled_tools = [...](hide these; applied afterenabled_tools). Names are the server's own tool names, without themcp__server__prefix.