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

```text
~/.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:

```bash
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:

```text
~/.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](/docs/workspaces/) for the full layout and audit contract.

## Agent

An agent is always a complete triple:

```json
{
  "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:

```json
{
  "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](/docs/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](/docs/customizing/) for authoring examples and [Workspaces](/docs/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

1. A Slack or Telegram message arrives.
2. Its location resolves to a binding key. If no binding exists, no agent work starts.
3. The location and thread resolve to a conversation key.
4. Chat commands such as `stop`, `clear`, `status`, `help`, or `restart` are handled without
   starting a provider.
5. 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.
6. The turn takes the conversation lock or waits in that conversation's queue.
7. Enso resolves the full agent triple from the workspace override or defaults and clamps
   effort if necessary.
8. It assembles background messages, transport context, attachment paths, user text, and
   any transport-specific formatting contract.
9. The provider CLI runs with the workspace as its working directory and resumes its
   compatible session when one exists.
10. Provider events drive a live status message; Enso splits and delivers the final text
    according to the transport's limits.
11. 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

1. The scheduler ticks on the minute and reloads every `JOB.md`.
2. A due job is skipped if disabled, invalid, already running, or outside its allowed
   catch-up window.
3. The trigger takes the per-job lock; an overlapping trigger is skipped, never queued.
4. Enso opens a run row with status `running`.
5. The optional prerun executes from the job directory. Exit `0` opens the gate and makes
   stdout available to the prompt, exit `1` records `no_work`, and any other failure records
   `prerun_error`.
6. The provider runs in batch mode from the configured workspace under the job timeout.
7. Enso closes the run row with status, exit code, duration, error, and retained output.
8. The optional postrun receives the retained output on stdin and the outcome in environment
   variables. It cannot change the recorded run status.
9. 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.
- `--debug` logging 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](/docs/installation/) when this model matches what you want to
run.
