Configuration
Enso reads machine-level settings from $ENSO_HOME/config.json. If
ENSO_HOME is unset, the home is ~/.enso.
The configuration controls:
- which Slack and Telegram accounts Enso serves;
- which conversations map to which workspaces;
- which provider CLIs, models, and effort levels may run;
- interactive timeouts, log rotation, run-history retention, and web-viewer defaults.
config.json is loaded when the service starts. After changing it, validate it
and restart the service. Job definitions are different: JOB.md files are
reloaded by the scheduler every minute. See Jobs.
Before editing
config.json is strict JSON, not JSONC. Comments, trailing commas,
single-quoted strings, and unquoted keys are invalid.
enso config check
enso config show
enso doctor
enso config checkreports all configuration problems it can find at once.enso config showprints the stored document and redacts object keys ending intoken. It does not print a normalized or fully defaulted configuration.enso doctoralso checks provider executables, workspace health, and service status.
Enso refuses to serve chat when the configuration has an error. Warnings, such as a provider executable not currently being found, do not make the JSON invalid, but they usually indicate the machine is not ready to run.
Protect the file because it contains transport credentials:
chmod 600 "$ENSO_HOME/config.json"
If ENSO_HOME is not set in your shell, use
~/.enso/config.json instead.
A minimal configuration
This is valid strict JSON after replacing the example values. The
default workspace must already exist.
{
"version": 1,
"transports": {
"telegram": {
"bot_token": "REPLACE_WITH_TELEGRAM_BOT_TOKEN",
"allowed_users": ["123456789"],
"notify": "123456789"
}
},
"bindings": {
"telegram:123456789": "default"
},
"defaults": {
"provider": "claude",
"model": "sonnet",
"effort": "high"
},
"providers": {
"claude": {
"path": "claude",
"models": ["opus", "sonnet", "haiku"],
"args": ["--dangerously-skip-permissions"]
}
}
}
Create the workspace if needed, then validate:
enso workspace create default
enso config check
Complete example
This example shows every top-level section. It is strict JSON and contains no comments; replace tokens, IDs, paths, model choices, and permission flags for your machine.
{
"version": 1,
"transports": {
"slack": {
"bot_token": "xoxb-REPLACE_ME",
"app_token": "xapp-REPLACE_ME",
"notify": "C0123456789",
"mention_required": false,
"thread_mention_required": false
},
"telegram": {
"bot_token": "REPLACE_ME",
"allowed_users": ["123456789"],
"notify": "123456789"
}
},
"bindings": {
"slack:dm:U0123456789": "default",
"slack:C0123456789": "default",
"slack:C9876543210": "research",
"telegram:123456789": "default"
},
"defaults": {
"provider": "claude",
"model": "sonnet",
"effort": "high"
},
"workspaces": {
"research": {
"agent": {
"provider": "codex",
"model": "sol",
"effort": "xhigh"
}
},
"default": {
"providers": {
"claude": {
"args": ["--permission-mode", "dontAsk"]
}
}
}
},
"providers": {
"claude": {
"path": "claude",
"models": ["opus", "sonnet", "haiku"],
"args": ["--dangerously-skip-permissions"]
},
"codex": {
"path": "codex",
"models": ["sol", "terra", "luna"],
"args": ["--dangerously-bypass-approvals-and-sandbox"]
},
"grok": {
"path": "grok",
"models": ["grok-4.6", "grok-4.5"],
"args": ["--always-approve"]
}
},
"agent": {
"timeout": 3600
},
"logging": {
"level": "INFO",
"max_bytes": 10485760,
"backups": 5
},
"runs": {
"keep": 500,
"max_age_days": 30
},
"web": {
"host": "127.0.0.1",
"port": 8787
}
}
Required and optional sections
| Key | Required | Contract |
|---|---|---|
version |
Yes | Must currently be the integer 1. |
transports |
Yes | Configure Slack, Telegram, or both. At least one is required. |
providers |
Yes | A non-empty object containing supported provider names. |
defaults |
Yes | A complete provider/model/effort triple for interactive conversations. |
bindings |
No | Conversation-to-workspace mappings. An empty object is allowed. |
workspaces |
No | Agent or provider-argument overrides for selected workspaces. |
agent |
No | Interactive execution settings. |
logging |
No | Log level and rotation. |
runs |
No | Finished job-run retention. |
web |
No | Default bind host and port for the viewer. |
Unknown nested behavior should not be assumed to exist merely because JSON accepts an
extra key. Use the CLI reference and enso config check as the
public contract.
Transports
Slack and Telegram can run together in the same Enso process.
Slack
{
"transports": {
"slack": {
"bot_token": "xoxb-REPLACE_ME",
"app_token": "xapp-REPLACE_ME",
"notify": "C0123456789",
"mention_required": false,
"thread_mention_required": false
}
}
}
Slack requires bot_token and app_token. The app token is used
for Socket Mode.
The optional notify value is a bare Slack conversation ID beginning with
C, G, or D. It is the transport’s fallback target
for job failure alerts and untargeted enso message send calls.
mention_required controls whether a top-level channel message must mention
the bot. thread_mention_required applies separately to replies inside a
thread. Both default to false and must be JSON booleans.
Invite the bot to every channel you bind. A valid binding does not add the bot to Slack for you.
Telegram
{
"transports": {
"telegram": {
"bot_token": "REPLACE_ME",
"allowed_users": ["123456789", 987654321],
"notify": "123456789"
}
}
}
Telegram is limited to private chats. allowed_users is a list of exact,
positive numeric Telegram user IDs; JSON integers and canonical decimal strings are
accepted. Anyone outside the list is ignored silently. An empty or omitted list therefore
authorizes nobody.
The optional notify target is one positive Telegram user ID. It may be an
integer or a canonical decimal string, although strings are often easier to handle
consistently.
Notification destination forms
Transport-level notify values are bare IDs. Job and CLI destinations may be:
| Destination | Example |
|---|---|
| Explicit Slack | slack:C0123456789 |
| Explicit Telegram | telegram:123456789 |
| Bare ID | C0123456789 or 123456789, only when exactly one transport is configured |
With both transports configured, always prefix job and CLI destinations so the target is unambiguous.
Conversation bindings
A binding maps a transport conversation to a workspace:
| Binding key | Conversation |
|---|---|
slack:C… or slack:G… |
Slack channel; each top-level message starts a thread and conversation |
slack:dm:U… |
Direct messages with one Slack user |
telegram:<user-id> |
A Telegram private chat |
{
"bindings": {
"slack:C0123456789": "research",
"slack:dm:U0123456789": "default",
"telegram:123456789": "default"
}
}
Workspace names are lowercase kebab-case and at most 64 characters. Every bound workspace
directory must exist at $ENSO_HOME/workspaces/<name>; a missing directory
is a configuration error. Create it with:
enso workspace create research
A binding for an unconfigured transport is a warning. It is usually stale configuration and cannot receive messages until that transport is configured. Bindings take effect only after the service restarts.
See Workspaces for layout, instructions, uploads, and auditing.
Providers
Enso currently recognizes three provider keys: claude,
codex, and grok.
Each configured provider has:
| Field | Meaning |
|---|---|
path |
Executable path. A leading ~ is expanded; a bare command is resolved from PATH. If omitted, the provider name is used. |
models |
Non-empty list of model names that configuration and jobs may select. |
args |
String arguments appended verbatim to every invocation. Defaults to an empty list. |
The initial setup choices use these model names and effort vocabularies:
| Provider | Initial model names | Accepted effort levels |
|---|---|---|
| Claude | opus, sonnet, haiku |
low, medium, high, xhigh, max |
| Codex | sol, terra, luna |
low, medium, high, xhigh, max, ultra |
| Grok | grok-4.6, grok-4.5 |
low, medium, high, xhigh |
Codex’s setup aliases map sol, terra, and luna to
their corresponding installed Codex model IDs. The models list controls what
Enso accepts; it does not prove that the installed provider CLI can actually use a name.
Use enso doctor and test the provider on the machine.
Enso clamps an effort downward when it exceeds the known maximum for a selected model, and logs the change. It does not silently find another model.
Permissions and authentication
Enso delegates authentication, sessions, filesystem access, and approval behavior to the
provider CLI. Provider args are not interpreted or made safer by Enso. The
unattended flags shown in setup and in the example grant broad authority:
- Claude:
–dangerously-skip-permissions - Codex:
–dangerously-bypass-approvals-and-sandbox - Grok:
–always-approve
Choose those flags deliberately. A workspace is a working directory and context boundary, not an operating-system sandbox. A provider may be able to read or change files and use credentials elsewhere on the machine. Treat every enabled provider, workspace skill, job script, and secret as part of one trusted automation system.
Defaults and workspace overrides
defaults is one complete interactive-agent selection:
{
"defaults": {
"provider": "claude",
"model": "sonnet",
"effort": "high"
}
}
All three fields are required. The provider must be configured, the model must appear in
that provider’s models list, and effort must be valid for that provider.
A workspace may replace the complete triple:
{
"workspaces": {
"research": {
"agent": {
"provider": "codex",
"model": "sol",
"effort": "xhigh"
}
}
}
}
It may also replace one configured provider’s arguments:
{
"workspaces": {
"default": {
"providers": {
"claude": {
"args": ["--permission-mode", "dontAsk"]
}
}
}
}
}
These are replacement rules:
- a workspace
agentblock replaces the whole default triple and must include provider, model, and effort; workspaces.<name>.providers.<provider>.argsreplaces that provider’s global argument list for the workspace; it is not appended to it.
Only workspaces that need overrides belong in the workspaces configuration
object.
Jobs do not inherit this interactive default triple. Every
JOB.md explicitly declares its provider, model, effort, and workspace.
Workspace-specific provider arguments still apply when that provider runs in the named
workspace.
Interactive timeout
{
"agent": {
"timeout": 3600
}
}
agent.timeout is the interactive-turn timeout in seconds. It defaults to
3600 and must be a non-negative integer. A value of 0 disables
the interactive timeout. Jobs use their own required/defaulted timeout field instead.
Secrets and environment
Provider credentials and values used by job scripts can live in:
$ENSO_HOME/secrets/*.env
Create and protect the directory:
mkdir -p "$ENSO_HOME/secrets"
chmod 700 "$ENSO_HOME/secrets"
chmod 600 "$ENSO_HOME/secrets/"*.env
Each file contains simple assignments:
GITHUB_TOKEN=replace-me
export ANOTHER_TOKEN="replace-me-too"
This parser is intentionally small:
- blank lines and lines without
=are ignored; - an optional leading
exportis removed; - lines whose parsed key begins with
#are ignored; - a variable already present in the service environment wins;
- surrounding whitespace is stripped, followed by any leading or trailing single- or double-quote characters;
- the file is not a shell script: no variable expansion, command substitution, or shell escaping is performed.
Secrets are loaded when Enso starts and inherited by provider processes and job scripts. Restart after changing them. There is no per-workspace secret boundary: all agents and scripts launched by the service share the environment.
Transport bot tokens are required directly in config.json; the JSON loader
does not substitute environment-variable references into those fields.
Logs and run retention
Defaults:
{
"logging": {
"level": "INFO",
"max_bytes": 10485760,
"backups": 5
},
"runs": {
"keep": 500,
"max_age_days": 30
}
}
logging.level is DEBUG, INFO,
WARNING, or ERROR; case is normalized. The byte and backup
limits, along with both run-retention values, must be non-negative integers.
Finished run rows are pruned if they are older than max_age_days or
outside the newest keep rows. Rows still marked running are not
pruned by that rule.
Run history retains only the final 1 MiB of a job’s provider output in
enso.db. That output is not automatically written into the workspace.
If durable output belongs in drafts/ or knowledge/, instruct the
agent to write it there or use a postrun script. See Jobs.
Use DEBUG logging carefully on a multi-user machine: verbose provider and
routing details can reveal more operational context than normal logs.
Web viewer
{
"web": {
"host": "127.0.0.1",
"port": 8787
}
}
These values are defaults for enso web start; command-line flags override
them. host must be a non-empty string. port must be an integer
from 1 through 65535.
The viewer can still start with its safe defaults when config.json is missing
or invalid, so its Health page can show the problem. It is read-only and has no built-in
authentication. Keep it on 127.0.0.1 unless a trusted access layer protects
it. See Web viewer.
Chat commands
Slack commands use !; Telegram commands use /.
| Command | Effect |
|---|---|
stop |
Stops the process running for this conversation and drops its queued turns. |
clear |
Forgets stored provider sessions for the conversation, so the next turn starts fresh. |
status |
Shows the effective workspace and agent, session age, active process, and queue depth. |
help |
Lists commands. |
restart |
Replies, then restarts the service or re-executes enso serve. |
Session state is keyed by conversation and provider and can only resume in the workspace where it began. Idle session database entries are pruned after 30 days when the service starts. Clearing or invalidating a session may also remove provider-owned local session artifacts for providers that expose them; it is more than a visual chat-history reset.
Safe edit procedure
- Make sure every workspace named by a binding exists.
- Edit
$ENSO_HOME/config.jsonas strict JSON. - Run
enso config check. - Run
enso doctorif providers, paths, or workspaces changed. - Restart Enso.
- Send
statusin the affected chat and confirm the effective workspace, provider, model, and effort.
Troubleshooting
JSON fails before schema validation
Remove comments and trailing commas, use double quotes, and validate the raw document:
python -m json.tool "$ENSO_HOME/config.json" >/dev/null
enso config check
A binding says its workspace is missing
The configuration deliberately fails closed when there is nowhere to run:
enso workspace create research
enso workspace audit research
enso config check
The provider path is not executable
Confirm the same environment that starts Enso can resolve the command:
command -v claude
enso doctor
A terminal’s PATH may differ from a background service’s. An absolute
provider path avoids that ambiguity.
A workspace still uses global provider arguments
Check that the workspace name exactly matches the binding or job, and remember that the
override must contain an args array:
{
"workspaces": {
"research": {
"providers": {
"codex": {
"args": ["--dangerously-bypass-approvals-and-sandbox"]
}
}
}
}
}
Restart after changing config.json.
Alerts are not delivered
Check, in order:
- the job’s explicit
notifyvalue; - the configured transports’
notifyvalues; - whether an explicitly prefixed destination names a configured transport;
- whether the Slack bot is in the target channel or the Telegram user has started the bot and can receive its outbound message.
Successful and no_work jobs are intentionally silent unless the prompt sends
a message itself.
Machine-readable checklist
An agent editing Enso configuration should preserve these invariants:
- emit strict JSON with
version: 1; - configure at least one transport and at least one supported provider;
- keep every agent selection a complete provider/model/effort triple;
- use only models declared for that provider and valid provider effort strings;
- create each bound workspace before referencing it;
- treat workspace provider arguments as replacements;
- restart for configuration or secret changes;
- never describe a workspace as a security sandbox;
- never assume retained job stdout was saved as a workspace file.
Continue with Workspaces, Jobs, or the CLI reference.
