Concepts
Enso is one long-running service on your machine. It connects chat transports and a cron scheduler to agent command-line tools you already have installed, and it runs every request inside a workspace.
This guide defines the words used throughout the rest of the documentation and traces the two paths through the system: an interactive chat turn and an unattended job run.
The primitives
| Primitive | Meaning |
|---|---|
| Home | ~/.enso, or the directory in ENSO_HOME: configuration, state, logs, workspaces, jobs, skills, and secrets |
| Transport | A Slack or Telegram connection. Both may run in the same service process. |
| Binding | A mapping from a chat location to one workspace |
| Workspace | The agent’s working directory, file layout, and local context |
| Agent | An explicit provider + model + effort triple |
| Conversation | A serialized queue of turns with resumable provider sessions |
| Job | A JOB.md containing a cron schedule, an agent, a workspace, and a prompt |
| Run | One execution of a job, including status, duration, exit code, and retained output |
| Skill | Instructions an agent may load, resolved across workspace, Enso, and user scopes |
| Message | An out-of-band send recorded so a later turn can learn what happened |
| Table | A registered ordinary SQLite table in enso.db for structured data |
| Viewer | The optional read-only web interface over Enso’s files and state |
The distinctions matter. A binding chooses a workspace, but a conversation owns its queue and session. A job describes recurring work, while a run is one historical attempt. A workspace organizes context, but it is not a security boundary.
Home
Enso-owned content lives below one home directory:
~/.enso/
├── .git/ # empty Git root used for instruction and skill discovery
├── config.json # transports, bindings, agents, providers, and operational defaults
├── AGENTS.md # instructions that apply to every Enso execution
├── CLAUDE.md -> AGENTS.md
├── skills/ # Enso-wide skills
├── .claude/skills -> ../skills
├── .agents/skills -> ../skills
├── workspaces/<name>/ # one fixed-layout directory per workspace
├── jobs/<name>/JOB.md # scheduled jobs and their optional scripts
├── secrets/*.env # environment variables loaded by the service
├── enso.db # sessions, runs, messages, job state, and registered tables
├── enso.log # rotating operational log
├── launchd.log # service stdout/stderr on macOS
├── web.log # viewer output while it runs
├── web.pid # viewer process lock while it runs
└── cache/slack.json # Slack user and channel directory cache
Set ENSO_HOME when you need a different root, especially for development or testing:
ENSO_HOME=/tmp/enso-demo enso setup
The home is initialized as a Git repository, but Enso does not create commits, push, or
read history. The Git root gives provider CLIs a common boundary for walking upward from a
workspace to find AGENTS.md and skills.
The statement “Enso lives in its home” describes Enso-owned content, not every side effect. Installing the background service writes a launchd or systemd unit in the platform’s normal user-service directory. Clearing a conversation can also remove the provider-owned session artifact needed to prevent that session being resumed.
Workspace
A workspace is where an agent actually works. Its path is always:
~/.enso/workspaces/<lowercase-kebab-case-name>
Every conversation binding and every job names exactly one workspace. That directory
becomes the provider process’s working directory, and its AGENTS.md contributes the
workspace-specific context for the run.
For example, a newsletter workspace might hold editorial rules in AGENTS.md, source
notes in knowledge/, generated copy in drafts/, attachments from chat in uploads/,
and a fact-checking skill in skills/.
A workspace is a content root, not a sandbox. The provider CLI may be able to read or change files anywhere its configured permissions allow. Use provider permission flags, operating-system isolation, and careful instructions according to your risk tolerance.
See Workspaces for the full layout and audit contract.
Agent
An agent is always a complete triple:
{
"provider": "claude",
"model": "opus",
"effort": "xhigh"
}
There is no partial agent and no inferred model. defaults in config.json supplies the
triple for interactive conversations unless a workspace replaces it. A workspace override
must replace all three values. Jobs do not inherit that default: every JOB.md states its
provider, model, effort, and workspace explicitly.
If an effort value exceeds a model’s supported maximum, Enso only clamps it downward and writes a log entry. It does not silently choose another model or provider.
Enso currently supports Claude Code, Codex, and Grok as providers. Other CLIs mentioned in skill-discovery research are not runnable Enso providers.
Binding and conversation
A binding maps a chat place to a workspace:
{
"slack:C012EXAMPLE": "product-research",
"slack:dm:U012EXAMPLE": "default",
"telegram:123456789": "default"
}
An unbound location does not receive agent work. A direct message or an explicit mention in an unbound Slack channel receives one explanation; ordinary unbound channel traffic is ignored.
A conversation is more specific than a binding:
- Each top-level message in a Slack channel starts a new thread and a new conversation.
- A Slack direct message is one continuous conversation.
- A Telegram private chat is one continuous conversation.
- Each conversation has its own FIFO queue and at most one active provider process.
- Later messages wait behind the current turn; the queue accepts up to ten waiting messages for that conversation.
- Different conversations may run at the same time.
Provider sessions are stored per conversation and per provider. A session can only resume
inside the workspace where it was created. The clear chat command forgets the
conversation’s sessions; sessions idle for 30 days are pruned when Enso starts.
Binding is not session state
If you change a channel binding from alpha to beta, the next turn runs in beta. A
provider session created in alpha is not resumed in beta, because sessions never cross
workspace boundaries. This prevents a chat route change from carrying hidden working
context into the wrong directory.
Job and run
A job is a directory below ~/.enso/jobs/ with a JOB.md. Its YAML frontmatter names the
schedule, full agent triple, workspace, and execution controls; the Markdown body is the
prompt.
The scheduler reloads job files every minute. A job may have:
- a prerun script that decides whether any model work is needed;
- a postrun script that archives or reacts to the outcome; and
- a notification target for scheduled failures.
A run is one trigger that passed the per-job lock. Its database row records the final
status, agent, exit code, duration, error, and up to the final 1 MiB of provider output.
That retained output stays in enso.db; it becomes a workspace file only if the agent or
a postrun script writes it there.
Different jobs may run concurrently, and jobs do not wait behind chat conversations. The same job never overlaps itself: a second trigger is skipped instead of queued.
See Jobs for the full schedule and script contracts.
Skill
A skill is a directory containing SKILL.md. Its frontmatter gives the skill a name and a
description of when to use it; its body tells the agent how to perform the task.
Enso resolves skills across three scopes:
| Scope | Location | Ownership |
|---|---|---|
| Workspace | <workspace>/skills/ |
You, for one workspace |
| Enso | ~/.enso/skills/ |
You, plus the bundled Enso skills |
| User | A provider’s own user-level skill directory | You, outside Enso |
Workspace and Enso skill names must be unique. Reusing an Enso-wide name inside a workspace is an audit error rather than an override, because provider CLIs resolve collisions differently. A collision with a user-level skill is a warning.
Enso creates the project-level links that each supported provider discovers. It may read user-level configuration to report what the provider can see, but it does not manage those user-level files.
See Customizing for authoring examples and Workspaces for discovery and auditing.
Message
The normal result of a chat turn goes directly back to the conversation. Anything sent
outside that final reply—such as progress from enso message send or a job alert—is also
recorded in the message outbox.
At the start of the next turn in that conversation, unread rows appear in the provider
prompt under [Background messages] and are marked consumed. Messages sent by the current
turn are retired when that turn ends because the agent already knows what it sent.
This closes an important context gap. An unattended job can report “the import finished” to a channel, and the next human follow-up reaches an agent whose prompt includes that background event.
Table
Registered tables are ordinary SQLite tables in enso.db. Enso catalogs them so agents
can discover their names and descriptions, while SQLite remains the source of truth for
their columns, constraints, and indexes.
Enso does not provide a CRUD framework. An agent uses SQLite normally, then registers an
eligible table with enso table register so future agents can find it. Enso’s internal
tables and names beginning _enso_ or sqlite_ are reserved.
How a chat turn flows
- A Slack or Telegram message arrives.
- Its location resolves to a binding key. If no binding exists, no agent work starts.
- The location and thread resolve to a conversation key.
- Chat commands such as
stop,clear,status,help, orrestartare handled without starting a provider. - Supported attachments download into
<workspace>/uploads/<8-hex-upload-id>/, one fresh directory for the inbound attachment batch, and their local paths are added to the prompt. - The turn takes the conversation lock or waits in that conversation’s queue.
- Enso resolves the full agent triple from the workspace override or defaults and clamps effort if necessary.
- It assembles background messages, transport context, attachment paths, user text, and any transport-specific formatting contract.
- The provider CLI runs with the workspace as its working directory and resumes its compatible session when one exists.
- Provider events drive a live status message; Enso splits and delivers the final text according to the transport’s limits.
- Enso stores the provider session id against the conversation, provider, and workspace.
Slack downloads are limited to 100 MiB per file. Telegram downloads are limited to 20 MiB per file and support documents, photos, audio, voice, video, and video notes in private chats.
How a job run flows
- The scheduler ticks on the minute and reloads every
JOB.md. - A due job is skipped if disabled, invalid, already running, or outside its allowed catch-up window.
- The trigger takes the per-job lock; an overlapping trigger is skipped, never queued.
- Enso opens a run row with status
running. - The optional prerun executes from the job directory. Exit
0opens the gate and makes stdout available to the prompt, exit1recordsno_work, and any other failure recordsprerun_error. - The provider runs in batch mode from the configured workspace under the job timeout.
- Enso closes the run row with status, exit code, duration, error, and retained output.
- The optional postrun receives the retained output on stdin and the outcome in environment variables. It cannot change the recorded run status.
- A scheduled failure alerts its target. A successful run is silent unless its prompt or postrun explicitly sends something.
Concurrency at a glance
| Work | Serialized with | Can overlap with |
|---|---|---|
| A chat turn | Later turns in the same conversation | Other conversations and jobs |
| A scheduled job | Another trigger of the same job cannot start | Other jobs and chat turns |
| Web viewer request | Nothing in the service; the viewer is separate and read-only | Service, chats, and jobs |
Trust boundaries and non-goals
- Enso is not a sandbox; provider permissions are the actual authority boundary.
- Files in
~/.enso/secrets/are available to every agent and job in that Enso home. - The database and workspace files are shared operator data, not tenant-isolated storage.
--debuglogging includes complete prompts and raw provider events and may contain private conversation or tool content.- The viewer has no authentication and should remain on localhost or behind a trusted private access layer.
- Enso is for one operator, not a multi-user or hosted control plane.
Continue with Installation when this model matches what you want to run.
