# Eva documentation, in full
> Eva is an open-source, autonomous software factory. It runs coding work end to end, from a spec a machine can check to evidence that it was done.
Every documentation page, concatenated. The index is at https://docs.evafactory.co/llms.txt, and each page is served on its own at the same path with `.md` appended.
---
# What is Eva
Eva is an open-source, autonomous software factory. It runs coding work end to end, from a spec a machine can check to evidence it was done.
Eva runs coding work end to end: you give it a spec a machine can check, a
harness does the work, and Eva keeps the evidence that it was done.
It runs on your laptop as a CLI, and it runs as a service you reach from
anywhere. Those are the same program.
Today Eva is a terminal client on a plugin kernel. A small core loads plugins, and every
capability is one — the model, the surface, the trace, the themes. What ships today is on this
site. What comes next is on the [roadmap](/about/roadmap).
## The problem
You have subscriptions to Claude Code, Codex, and OpenCode. Each is a good
harness. None can tell you what a change cost, none can check another's work,
and none can run twenty tasks overnight and show you the evidence in the
morning.
Switching between them means switching tools, losing history, and starting the
accounting again. And all of them are tied to the machine you are sitting at.
Close the laptop and the work stops.
## What Eva does about it
**One contract over every harness.** Eva's native harness and every foreign one
implement the same interface, so a task runs on any of them without being
rewritten. Eva's own harness has no privileged position.
**One trace.** Everything every harness does lands in one event schema. What a
trace cannot rebuild is a bug.
**One verifier.** Acceptance criteria are checked by Eva, not claimed by the
agent that did the work. A claim is never evidence.
**One bill.** Cost is attributed per task, per merged change, and per harness.
That makes "use this harness for this kind of work" a measurement rather than a
preference.
## What Eva is not
Not a model. Not an IDE. Not a replacement for the harnesses you use — Eva
drives them, and its own harness is one entry in the same registry.
[What is an open-source autonomous software factory?](/software-factory) defines the
category these four properties belong to, and how a factory differs from a
harness.
## Start here
## If you are an agent
Every page here is also served as markdown: append `.md` to any path, so
`/install` is also [`/install.md`](/install.md). Each page's HTML advertises its
own twin with ``, and asking for
`Accept: text/markdown` returns the twin as well.
- [llms.txt](/llms.txt) — every page as one index, with what Eva is for and
when to reach for it.
- [llms-full.txt](/llms-full.txt) — every page's markdown, concatenated, for
reading the whole manual in one fetch.
- [auth.md](/auth.md) — how to obtain a credential, which is by not needing
one. There is no Eva account and no Eva API key.
- [pricing.md](/pricing.md) — zero, and what you do pay for.
- [CLI reference](/reference/cli) — every command and every global flag.
`eva -p ""` is the whole scriptable surface.
A scoped index exists per area — [use](/use/llms.txt),
[configure](/configure/llms.txt), [extend](/extend/llms.txt),
[reference](/reference/llms.txt), [about](/about/llms.txt) — for taking one
area into context instead of the manual.
---
This page as HTML: https://docs.evafactory.co
Every page as one markdown index: https://docs.evafactory.co/llms.txt
---
# Contact
Which channel answers what about Eva — questions, bugs, security reports, and patches — and what to include so the answer comes back once.
Every question about Eva is answered in public, on the issue tracker. There is
no support mailbox, and this page does not offer one: a published address that
bounces is worse for a reader than a page that says where the real door is.
## Questions, bugs, and requests
Open an issue at
[github.com/missingstudio/eva/issues](https://github.com/missingstudio/eva/issues).
A question answered in public is answered once, and the next person who asks it
finds the answer instead of asking again. That is why there is an issue tracker
and not a private queue.
Include four things, and the first reply can be the answer rather than a request
for detail:
- The version, from `eva --version`.
- The command you ran, in full.
- What happened instead of what you expected.
- **stderr as well as stdout.** Eva reports a Finding on stderr without
changing the exit code, so the explanation is often there and only there.
## Security
There is no separate security mailbox. Report a suspected vulnerability as an
issue that says what the impact is and which code path reaches it, and leave a
working exploit out of the first message.
If the finding needs to stay private until it is fixed, say so in the first line
rather than in the detail, and a private channel will be opened before anything
more is written down.
## Patches
The source takes patches. Read [contributing](/about/contributing) first: it
names the commit format, the branch naming, and the one command that runs every
check CI runs. A change that passes locally passes in CI, because it is the same
command.
Releases, with notes, checksums, and a provenance attestation, are at
[github.com/missingstudio/eva/releases](https://github.com/missingstudio/eva/releases).
## The company
missing studio publishes Eva. Its other work is on the same GitHub
organisation, [github.com/missingstudio](https://github.com/missingstudio), and
it posts as [@madebymissing](https://x.com/madebymissing).
Eva is MIT licensed and the whole tree is public. There is no paid tier that
unlocks capability, and self-hosting is not a downgrade path.
---
This page as HTML: https://docs.evafactory.co/about/contact
Every page as one markdown index: https://docs.evafactory.co/llms.txt
---
# Contributing
Clone Eva, run the one command CI runs, and know the conventions before you open a pull request.
Eva is MIT licensed and developed in the open at
[github.com/missingstudio/eva](https://github.com/missingstudio/eva).
## Get set up
Requires Bun 1.3 or newer.
```bash
git clone git@github.com:missingstudio/eva.git && cd eva && bun install
```
## The one command
```bash
bun run verify
```
That is exactly what CI checks on every change: types and lint, the tests, the
packaging, and a dependency audit, in that order. If it passes locally it
passes in CI.
Run a subset while you work:
```bash
bun run check # types, lint, and format in one pass
bun run test # the suite
bun run test packages/kernel # one package, by path
```
## Conventions that will fail review
**Commits are Conventional Commits.** `type(scope): description`, lowercase,
imperative, no full stop. Types: `feat`, `fix`, `refactor`, `chore`, `docs`,
`test`, `ci`, `build`. One logical change per commit.
```
feat(kernel): batch domain rebuilds during boot
```
**Branches are `type/kebab-case-description`.** For example
`fix/streaming-first-char-missing`.
**Prose is ASD-STE100 Simplified Technical English** — short sentences, active
voice, one instruction per sentence, and the same word for the same thing every
time. That applies to commits, plans, and documentation.
**Make the smallest correct change.** Follow the package boundaries that are
already there.
## Package boundaries are enforced
A plugin imports the contract packages only — never the kernel, and never
another plugin. That is a lint rule rather than a convention, so crossing a
boundary fails the build rather than a review.
## Before you open a pull request
Read [AGENTS.md](https://github.com/missingstudio/eva/blob/main/AGENTS.md).
---
This page as HTML: https://docs.evafactory.co/about/contributing
Every page as one markdown index: https://docs.evafactory.co/llms.txt
---
# Privacy
What this documentation site stores, what Eva itself writes to disk, and where a request goes when you follow a link off this page.
This site counts page views with Google Analytics, and does nothing else with
you. The marketing site at evafactory.co counts its own, in a separate
property; [its privacy page](https://evafactory.co/privacy) states the same
policy for that domain.
## What this site measures
Every page loads the Google Analytics tag, which reports the visit: the address
of the page, the page you arrived from, and what a browser tells any server
about itself — its language, its screen, and an approximate location worked out
from the network address. Google holds that record; missing studio reads the
totals.
Search is not part of it. The site downloads a search index once and searches
it in your browser, so a query never reaches a server, never enters an address,
and is never reported.
Block googletagmanager.com and every page still works the same.
## Cookies
Google Analytics writes two cookies, `_ga` and `_ga_DBW4DSZSDE`. They tell one
browser from another and one visit from the next, so a reader who comes back is
not counted as a new one.
Nothing else writes to your browser's storage. There is no theme control
because there is no theme to record: the site is dark only, the same surface
the program itself draws.
## What this site does not do
- No advertising, and no data sold or shared with an advertiser.
- No session recording, and no map of what you click.
- No account, and no form that asks for a name or an address.
- No third-party font. The typeface is served from this origin, so no other
company sees that request.
## Where a request does leave
Google receives the analytics request described above, and its own policy
governs what it does with it.
Links to GitHub, npm, and the license text go to those companies, and their own
policies apply once you follow one. Nothing is sent to them until you do.
This site is served by a CDN, which processes the request in order to answer it.
## Eva, the program
Eva runs on your machine. It writes its sessions to disk under your home
directory, and it reads a repository's configuration only after you grant it
with [`eva trust`](/configure/trust).
Your model provider's key is read from the environment and never reaches a
config file, a log, or the session record — see [keys](/use/keys). Eva sends
requests to the provider you configured and to nobody else. It does not proxy
that key, resell access to it, or report your usage anywhere.
## Questions
missing studio publishes this site. Open an issue on the repository and it will
be answered in public — see [contact](/about/contact).
---
This page as HTML: https://docs.evafactory.co/about/privacy
Every page as one markdown index: https://docs.evafactory.co/llms.txt
---
# Roadmap
What Eva is today, and the stages that take it from a terminal client on a plugin kernel to a software factory. Each stage has an exit test it can fail.
Eva is built in stages, and **each stage has an exit test it can fail**. A
stage is not done when it feels done; it is done when a named test stops
failing.
## Where Eva is today
A terminal client on a plugin kernel. A small core loads plugins, and every
capability is one — the model, the surface, the trace, the themes. The plugin
system, the event schema, the session store, and the cost accounting are real
and shipping.
Everything else on this site describes that. Nothing here describes a stage
that has not landed.
## Phase I — model to agent
| Stage | What it adds |
| ----- | -------------------------------------------------------------- |
| 0 | Wire. One provider, one surface, one trace. **Done.** |
| 1 | Workflow. A pipeline that takes a Spec and returns an Outcome. |
| 2 | Tools and the loop. The agent that can act, not only answer. |
## Phase II — agent to harness
| Stage | What it adds |
| ----- | --------------------------------------------------------------- |
| 3 | Context engine. Repomap, compaction, what conditions a request. |
| 4 | Environment. Workspaces, snapshots, isolation. |
| 5 | Verifier. Criteria Eva checks rather than the agent claims. |
| 6 | Memory and trace. |
| 6.5 | Extension distribution — third parties, trust, isolation. |
| 7 | Harness profiles. |
| 8 | Eval harness. |
## Phase III — harness to factory
| Stage | What it adds |
| ----- | ---------------------------------------------------------------------------- |
| 9a | Work items and a local scheduler. |
| 9b | Control plane and enrolment. |
| 9c | **Harness adapters.** Claude Code, Codex, and OpenCode, behind one contract. |
| 9d | Skills and MCP. |
| 10 | Integration — the merge queue and review routing. |
| 11 | Economics, dashboard, billing. |
| 12 | Continual learning. |
## Phase IV — factory to company
| Stage | What it adds |
| ----- | ----------------------------- |
| 13 | Intent — the demand side. |
| 14 | Authority and accountability. |
## The four "ones"
Eva's pitch is one contract, one trace, one verifier, one bill. One trace
exists today. The verifier arrives at stage 5, the contract over foreign
harnesses at stage 9c, and per-harness billing at stage 11.
The full plan, with the exit test for every stage, is
[roadmap.md](https://github.com/missingstudio/eva/blob/main/docs/roadmap.md) in
the repository.
---
This page as HTML: https://docs.evafactory.co/about/roadmap
Every page as one markdown index: https://docs.evafactory.co/llms.txt
---
# Troubleshooting
Fixes for what people actually hit with Eva — a setting that does nothing, a missing key, a chord the terminal cannot send, a plugin that misbehaves.
## A setting I wrote does nothing
Ask Eva what it resolved and where each value came from:
```bash
eva config show
```
Three common causes. The key is under the wrong plugin id. The key is spelled
differently from the one the plugin declares. Or the file is not in a layer Eva
reads — a `.eva` directory needs [`eva trust`](/configure/trust) first.
Eva also reports an unread key as a finding on stderr, naming the plugin whose
options it was written under. That message is usually the whole answer.
## Eva says it has no credential
Eva reads `ANTHROPIC_API_KEY` from the environment at the moment it opens a
request.
```bash
echo $ANTHROPIC_API_KEY | head -c 8
export ANTHROPIC_API_KEY=sk-ant-...
```
A key set in a shell profile is not visible to a shell that started before the
profile changed. Open a new terminal, or re-source the profile.
## Shift+Enter does not insert a newline
Most terminals cannot send `shift+enter` without the kitty keyboard protocol.
Rebind it to a chord your terminal does send:
```yaml
keymap:
input.newline: ctrl+j
```
Pasting a multi-line block works regardless — a paste arrives whole, and a
newline inside it is text rather than a key.
## The terminal looks wrong, or colours are unreadable
Try a different theme:
```
/theme contrast
```
`contrast` suits bright rooms and low-contrast displays. `mono` suits terminals
with a restricted palette. See [themes](/use/themes).
## A plugin is misbehaving
Turn it off for one run:
```bash
eva --without-plugin eva.themes
```
Eva keeps running with a plugin disabled. If disabling one fixes your problem,
that is worth an issue — a single disabled plugin should degrade the run, never
break it.
## My pipeline does not stop on failure
`--print` exits non-zero when the answer fails, but a shell pipeline reports
the last command's status by default:
```bash
eva -p "review the diff" > review.txt || exit 1
```
See [exit codes](/reference/exit-codes).
## Still stuck
Open an issue at
[github.com/missingstudio/eva](https://github.com/missingstudio/eva/issues).
Include the output of `eva --version` and `eva config show`.
---
This page as HTML: https://docs.evafactory.co/about/troubleshooting
Every page as one markdown index: https://docs.evafactory.co/llms.txt
---
# Concepts
The words Eva uses for its own machinery — Session, Run, Trace, Plugin, Harness, Cost, and the rest. One concept has one name.
Eva gives one concept one name, and uses that name everywhere: in the code, in
the traces, in the config, and on this site. This page is the list.
## The work
**Spec** — A statement of intent whose acceptance criteria a machine can check. A
description with no machine-checkable criteria is not a Spec.
**Unit** — Anything that takes a Spec and returns an Outcome. A model call, a workflow,
an agent, a harness, and a factory are all Units at different timescales.
**Outcome** — What a Unit returns: Done, Failed, NeedsHuman, or Exhausted. Escalation to a
human is an Outcome, not an error.
**Claim** — An assertion of success by whatever did the work. A Claim is never Evidence,
whatever its source.
**Budget** — What a Run may spend: tokens, money, wall clock, and Steps. Exhausting a
Budget is an Outcome, not an error, and the partial work is kept.
## Execution
Four systems Eva touches use the word "turn" for two different things, so Eva
does not use the bare word at all. These three replace it.
**Session** — The durable, resumable transcript. It survives `kill -9`. Resume, branch, and
rewind all act on it.
**Run** — One execution of a Unit against a Session, from the intent that opens it to
the Claim that closes it. A Session resumed twice has several Runs.
**Provider Turn** — One exchange with a Provider: a request starts, and a stream is read to its
end. It is not a unit of record.
**Stop Reason** — Why a Run ended: `end_turn`, `max_tokens`, `max_turn_requests`, `refusal`, or
`cancelled`. A refusal is a legitimate outcome and a cap is a budget fact.
Neither is a failure.
## Evidence
**Event** — One typed, versioned, sequence-numbered record of something observable. There
is exactly one Event schema.
**Trace** — The persisted Event stream of a Run, and the single source of truth. Every
projection is a fold over it. Anything a Trace cannot rebuild is a bug.
**Degraded** — A marker on a Run, an Event, or a field, saying this data is incomplete,
estimated, or unreported. Eva keeps Degraded data and marks it. Eva never
guesses a repair and never drops it in silence.
**Finding** — Something a run did not read, as data rather than a line of text — a key
nothing declares, or a key written in a shape nothing reads. A Finding stops
nothing, and is never passed over in silence.
## Money
**Cost** — What a Provider says a request cost, in Ticks — integers of 1e-10 USD.
Absent means the Provider did not report one, which is not zero.
**Price** — What a vendor publishes for a model, in Ticks per million tokens.
**Estimate** — What a Run's counters come to at Catalog Prices. It is a projection, and it
moves when a vendor reprices. Eva shows it marked, because an Estimate read as
a Cost is the mistake the pair exists to prevent.
## The plugin system
**Kernel** — The part of Eva that is not a plugin. It holds the plugin runtime, the four
extension points, the config source, and location resolution. Nothing else.
**Plugin** — A module that exports an id and an effect, and declares the config keys it
reads. Everything that is not the Kernel is one.
**Extension point** — One of exactly four ways to extend Eva: a Domain, a Slot, a Hook, or a
Broadcast. There is no fifth.
**Domain** — Shared state that many plugins build together. The Kernel rebuilds it by
replaying every registered Transform in order.
**Slot** — A typed key and its contract. Exactly one plugin fills a Slot at a time, and
a consumer reads it at the moment of use, so replacing the plugin behind it
takes effect at once.
**Hook** — A callback at a live operation boundary. A Hook may narrow what a Run does,
never widen it.
**Broadcast** — A typed, process-local notification a plugin subscribes to as a stream. It is
never written to the Trace.
[How plugins work](/extend/how-plugins-work) puts these together.
## Interfaces
**Console** — The interactive interface a person types into. It is not a Session; it holds
one while it runs.
**Surface** — A plugin that ships an interface a person or a program drives Eva through —
the terminal, `--print`, and later HTTP and the web.
**Harness** — Something that takes a Prompt and drives it to a Stop Reason. Eva's native
loop is one, and so are Claude Code, Codex, and OpenCode. Every one is a
plugin, and Eva's own holds no privileged position.
**Location** — A directory a Session belongs to, and the scope a request acts in. Eva holds
many and has no ambient one.
---
This page as HTML: https://docs.evafactory.co/concepts
Every page as one markdown index: https://docs.evafactory.co/llms.txt
---
# Configuration
Where Eva reads configuration from, how the layers resolve, every key you can set, and how to ask Eva which layer a value actually came from.
Eva reads configuration from several layers and merges them. To find out what
Eva actually resolved, and where each value came from, ask it:
```bash
eva config show
```
That command prints every resolved key beside its origin, and the plugins that
would load — before the kernel boots, so a config naming a plugin nobody has
still prints. It is the answer to "why is this setting not taking effect", and
it is faster than reasoning about precedence.
## The shape
Configuration is YAML, and every key is one of two kinds.
A top-level key sits at the root of the file. Each one belongs to exactly one
plugin, and a plugin declares the keys it reads. That declaration is what lets
Eva tell you about a key nothing reads.
A plugin option sits inside that plugin's own entry under `plugins:`, and only
that plugin takes it.
```yaml title="~/.eva/config.yaml"
model: anthropic/claude-sonnet-5 # top-level, read by eva.config
theme: contrast # top-level, read by eva.tui
plugins:
- id: eva.provider.anthropic
options:
maxTokens: 32000 # an option, taken by one plugin
```
## Where config lives
The layers, lowest precedence first. A later layer overrides an earlier one.
1. Built-in defaults.
2. `~/.eva` as a resource directory, then `~/.eva/config.yaml`.
3. `$EVA_CONFIG_DIR` as a resource directory, then its `config.yaml`.
4. Each trusted project `.eva` directory, then its `config.yaml`, nearest
last. A project directory is read only after [`eva trust`](/configure/trust).
5. Every `--config ` overlay, in the order the flags were given.
6. `EVA_CONFIG_CONTENT`, config carried inline in the environment.
7. The flags: `--model`, `--plugin`, `--without-plugin`.
Within one directory the resources come first and `config.yaml` second — the
file is the one place a person goes to override, so it wins over a resource
the same directory discovered.
The flags are a layer like the files are: they merge as one mapping whose
origin is the command line, so `eva config show` names the flag the way it
names a file.
## How the layers merge
A mapping merges key by key. A scalar replaces. A list replaces whole.
So a project file that names one agent changes that agent and leaves the
others alone, and one field of an agent can be overridden without restating
the rest.
The `plugins` list is the one exception: the layers concatenate it instead of
replacing it, because a list that replaced whole would drop every plugin a
lower layer named. A field of an entry still replaces as a unit — a later
`options` is the whole options, never a deep merge of two.
Every leaf remembers the file that set it, which is what `eva config show`
prints beside each key.
## The keys
Every top-level key that exists, the shape it wants, and the plugin that reads
it. A `name` accepts a bare string or a mapping with an `id`.
| Key | Shape | Read by | Default |
| ----------- | ------- | -------------- | ------------------------- |
| `model` | string | `eva.config` | `anthropic/claude-opus-5` |
| `theme` | name | `eva.tui` | `default` |
| `themes` | mapping | `eva.config` | — |
| `keymap` | mapping | `eva.config` | — |
| `agents` | mapping | `eva.config` | — |
| `commands` | mapping | `eva.config` | — |
| `prompts` | mapping | `eva.prompt` | — |
| `workflows` | mapping | `eva.workflow` | — |
| `posture` | name | `eva.web` | `local` |
| `plugins` | list | the kernel | the built-in plugin table |
Any other key at the root is reported as a key nothing reads. Disabling a
plugin removes its keys from that sweep — with `--without-plugin eva.tui`,
`theme` becomes a key that reached nothing, and Eva says so.
## model
The model every session starts with, as `provider/model`.
```yaml
model: anthropic/claude-sonnet-5
```
`--model` sets it for one run and `/model` inside a session. See
[models](/configure/models).
## theme
Which theme the console draws. The selector belongs to the terminal surface;
the definitions live under `themes`.
```yaml
theme: contrast
```
A `theme` that names no known theme keeps the default and says so, rather than
drawing nothing. See [themes](/use/themes).
## themes
Your own themes, or overrides of the built-in ones. A theme is a name and four
colors, and the colors merge onto a theme that already exists.
```yaml
themes:
dusk:
name: Dusk
colors:
foreground: "#e8e4d8"
muted: "#8a8a7a"
accent: "#d8a657"
warning: "#e78a4e"
```
[Themes](/use/themes) has the color keys and the file form.
## keymap
Key bindings, one row per key. A bare string is a binding; a mapping carries a
binding and the command it fires.
```yaml
keymap:
input.newline: ctrl+j
```
[Keys and bindings](/use/keys) has the spelling rules and the defaults.
## agents
Agent definitions, keyed by id. Each may carry a `prompt`.
```yaml
agents:
review:
prompt: You review changes for correctness, and nothing else.
```
An agent prompt would rather be a Markdown file than a YAML string —
`.eva/agents/review.md` is the same row, written as frontmatter and a body.
## commands
Slash commands, keyed by id. Config describes a command; the code that runs it
comes from a plugin. A described command nothing implements is shown as one
the build knows of but cannot run, rather than failing silently.
```yaml
commands:
deploy:
description: Ship the current branch.
```
## prompts
Prompt templates, keyed by id. A row needs a non-empty `text`, or it is
dropped.
```yaml
prompts:
release-notes:
text: Write release notes for the changes described below.
```
`.eva/prompts/release-notes.md` is the same row as a file: the body becomes
the text.
## workflows
Declared workflows for `eva run`, keyed by name. A workflow is a list of
Steps, each naming a prompt template; there is no agency in it.
```yaml
workflows:
notes:
steps:
- id: summarize
template: release-notes
```
`.eva/workflows/notes.yaml` is the same row as a file. A document with
problems still registers; the run refuses at its first prompt with every
problem named, so the refusal lands in the trace.
## posture
Whether `eva serve --web` presents itself as single-tenant or hosted: `local`
or `hosted`. It changes what the page reports, and no byte of what is served.
The bind address is not config — a non-local `--host` is refused until tokens
exist.
```yaml
posture: local
```
## plugins
Which plugins load, with what options. The one list the layers concatenate.
```yaml
plugins:
- eva.trace.jsonl
- id: eva.trace.sqlite
disabled: true
```
[Plugins](/configure/plugins) has the entry shapes, the wildcard, and the
load order.
## The .eva directory
A config directory holds resources beside its file, because an agent prompt
wants to be a Markdown file rather than a YAML string. Five directories are
read, and no others:
```
.eva/
├── config.yaml
├── agents/review.md frontmatter, then the prompt
├── commands/deploy.md the description, from frontmatter or the first line
├── prompts/release-notes.md
├── themes/dusk.yaml a name and its colors
└── workflows/notes.yaml
```
Each file becomes a row keyed by its base name and joins the same mapping the
config file produces — one merge law covers both, and the origin table names
either source. A directory that is not there holds nothing, which is not an
error.
A project's `.eva` is read only after you grant it. See
[trust](/configure/trust).
## Environment variables
| Variable | What it does |
| -------------------- | ------------------------------------------------------------------------------------ |
| `EVA_CONFIG` | replaces the user config path (`~/.eva/config.yaml`), and the trust record beside it |
| `EVA_CONFIG_DIR` | read as another `.eva`-shaped directory, resources included |
| `EVA_CONFIG_CONTENT` | inline YAML, layered over every file and under the flags |
| `ANTHROPIC_API_KEY` | the Anthropic credential — see [providers](/configure/providers) |
| `OPENAI_API_KEY` | the OpenAI credential — see [providers](/configure/providers) |
`EVA_CONFIG` replaces rather than overlays, because a hermetic run given a
named config must not inherit the machine's file. `--config` is the flag that
overlays.
## Overlay a file for one run
```bash
eva --config ./ci.eva.yaml
```
`--config` is repeatable. Later files overlay earlier ones.
## Unread keys are reported, not ignored
If you write a key nothing reads — a typo, a setting from an older version, a
key under the wrong plugin — Eva tells you. That is a **Finding**: it is
written to stderr, it names the origin that set the key and the key you most
likely meant, and **it does not change the exit code**.
```
eva: nothing reads "keybindings", did you mean "keymap"? (~/.eva/config.yaml)
eva: "themes" wants a mapping, so nothing read it, did you mean "theme"? (~/.eva/config.yaml)
eva: nothing reads "maxTokns" in eva.provider.anthropic's options, did you mean "maxTokens"?
```
The second line is `themes: dusk` — a mapping key given a bare string. The
key that would have taken the value as written differs by one letter: `theme`
selects, `themes` defines.
The sweep runs at both levels: top-level keys against what the loaded plugins
declare, and each plugin entry's options against what that plugin takes. A key
written in a shape nothing reads is named the same way, with the shape it
wanted spelled in words.
Eva reports a finding rather than failing, because a stale key should not stop
your work. Eva reports it rather than staying quiet, because a setting that
silently does nothing is worse than one that errors.
---
This page as HTML: https://docs.evafactory.co/configure/configuration
Every page as one markdown index: https://docs.evafactory.co/llms.txt
---
# Models
Set the model for one run with a flag, inside a session with the model command, or permanently with the model key. The Catalog holds what Eva knows.
A model reference is `provider/model`. Out of the box, Eva starts on
`anthropic/claude-opus-5`.
## Set the model for one run
```bash
eva --model anthropic/claude-sonnet-5
```
The flag works on every command, including `--print`.
## Set it inside a session
```
/model anthropic/claude-sonnet-5
```
With no argument, `/model` shows the current model and offers the ones Eva
knows about.
## Set it permanently
The top-level `model` key in config:
```yaml title="~/.eva/config.yaml"
model: anthropic/claude-sonnet-5
```
[Configuration](/configure/configuration) explains where that file goes and
which layer wins when several set it.
## What the catalog ships
| Reference | Context window |
| ---------------------------- | -------------- |
| `anthropic/claude-opus-5` | 1,000,000 |
| `anthropic/claude-sonnet-5` | 1,000,000 |
| `anthropic/claude-opus-4-8` | 1,000,000 |
| `anthropic/claude-haiku-4-5` | 200,000 |
| `openai/gpt-5.6` | 1,050,000 |
| `openai/gpt-5.4` | 1,050,000 |
| `openai/gpt-5.4-mini` | 400,000 |
| `openai/gpt-5.4-nano` | 400,000 |
| `openai/gpt-4o-mini` | 128,000 |
A model behind a [compatible endpoint](/configure/providers#compatible-endpoints)
joins this list when its entry names it, and runs either way when you type
its reference.
## The Catalog
The **Catalog** is the Domain holding providers, their models, and the
default. Provider plugins write to it and nothing owns it, so adding a
provider is adding a plugin rather than editing a list.
The Catalog also holds **Price** — what a vendor publishes per million
tokens, vendored into the binary so boot needs no network. Price feeds an
Estimate and never a Cost; [cost and usage](/use/cost) explains why those
stay apart.
## Credentials and retries
A model runs behind a provider, and the provider owns both. One API key in
the environment connects each of the first-party providers, and retries are
tunable per run. [Providers](/configure/providers) has the whole of it.
## More harnesses
The harnesses you already pay for — driven behind the same contract — arrive
with the multi-harness stage. [The roadmap](/about/roadmap) says when.
---
This page as HTML: https://docs.evafactory.co/configure/models
Every page as one markdown index: https://docs.evafactory.co/llms.txt
---
# Plugins
Every capability in Eva is a plugin. Turn one on or off in config or with a flag, give it options, and swap a default — the trace sink included.
Everything that is not the kernel is a plugin — the providers, the trace, the
session store, the console, the themes. This page is how you turn them on and
off and hand them options. [How plugins work](/extend/how-plugins-work) is the
model behind it, and [write a plugin](/extend/write-a-plugin) is the authoring
guide.
## What the binary carries
The built-in table, in load order. Order is precedence: a later plugin's
registrations win over an earlier one's, which is why `eva.config` — your
config — loads near the end.
| Plugin | Contributes |
| ------------------------- | ------------------------------------------------- |
| `eva.trace` | the one path events take to the trace |
| `eva.trace.sqlite` | the default trace store, one SQLite file |
| `eva.session.jsonl` | the session store, a fold over the trace |
| `eva.auth` | credentials, read from the environment |
| `eva.catalog.models` | the built-in model catalog |
| `eva.catalog.prices` | vendored prices — no network at boot |
| `eva.provider.anthropic` | the Anthropic provider |
| `eva.provider.openai` | the OpenAI provider |
| `eva.provider.compatible` | a provider per compatible endpoint you name |
| `eva.provider.retry` | backoff for calls that may be retried |
| `eva.usage` | normalizes usage numbers |
| `eva.budget` | token, time, and step ceilings |
| `eva.validator` | judges output against a JSON Schema |
| `eva.diff` | previews an edit, applies it, reverses it |
| `eva.commands` | `/model`, `/cost`, `/clear`, `/sessions`, `/help` |
| `eva.themes` | the three built-in themes, and `/theme` |
| `eva.keymap` | the default key bindings |
| `eva.prompt` | prompt templates |
| `eva.workflow` | declared workflows, for `eva run` |
| `eva.config` | projects your config into the domains |
| `eva.print` | the `--print` surface |
| `eva.tui` | the interactive console |
| `eva.api` | the read-only session API |
| `eva.web` | the page that watches a session |
Three more ride in the box without loading: `eva.trace.jsonl`,
`eva.trace.memory`, and `eva.trace.postgres`. They are alternative trace
stores, available by id the moment config names one.
## Turn one on
A string enables a plugin with no options.
```yaml
plugins:
- eva.trace.jsonl
```
## Give one options
An object carries options. A later `options` for the same id is the whole
options — never a deep merge — because half of two entries is a plugin nobody
wrote.
```yaml
plugins:
- id: eva.budget
options:
tokens: 200000
minutes: 15
```
## Turn one off
`disabled` removes a plugin, and it accepts a wildcard matched as a prefix. A
later entry for the same id sets the fields it names and keeps the rest, so
turning one back on restates nothing.
```yaml
plugins:
- { id: "eva.provider.*", disabled: true }
- { id: eva.provider.anthropic, disabled: false }
```
`{ id: "*", disabled: true }` boots the bare kernel: it starts, prints a
version, and exits cleanly. There is a CI job whose only purpose is to prove
that.
## Swap a default
The trace store is the worked example. Disable the default, name the one you
want, and every consumer follows — a consumer reads its slot at the moment of
use, so nothing holds the old store.
```yaml
plugins:
- { id: eva.trace.sqlite, disabled: true }
- id: eva.trace.postgres
options:
schema: eva
```
`eva.trace.postgres` reads its connection string from `EVA_POSTGRES_URL`,
because a URL holds a password and the environment is where a deployment puts
one.
## For one run
Both flags are repeatable, and both are ordinary config: they merge as one
layer whose origin is the command line.
```bash
eva --plugin eva.trace.jsonl
eva --without-plugin eva.themes
```
## When a plugin fails
A plugin that fails to load rolls back whole — its registrations are removed,
and no partial state survives. The run continues and says what degraded,
because a plugin failure should cost you that capability, not your work.
The same holds for a plugin you disable: whatever read its contribution
reports `degraded` naming what is missing, rather than guessing.
## Plugins from npm
Not yet. A plugin entry may carry a `package`, and the shape is parsed — but
this build loads only the plugins it carries, and an id it does not carry is
reported:
```
eva: no plugin named "acme.reviewer" is in this build
```
Today a new plugin is a package in the repository's `plugins/` directory and
a row in the built-in table — [write a plugin](/extend/write-a-plugin) is the
guide. The distribution channel that resolves a package from npm is on the
[roadmap](/about/roadmap).
---
This page as HTML: https://docs.evafactory.co/configure/plugins
Every page as one markdown index: https://docs.evafactory.co/llms.txt
---
# Providers
Eva ships Anthropic and OpenAI, and talks to any OpenAI-compatible endpoint you name in config. One API key each, and retries you can tune.
A provider is a model behind one contract, and every provider is a plugin.
Three ship in the binary: Anthropic, OpenAI, and a third that turns any
OpenAI-compatible endpoint you name — Ollama, llama.cpp, vLLM, LM Studio, a
hosted compatible — into a provider of its own.
Each provider writes into the **Catalog**, which is the domain holding
providers, their models, and the default. Adding a provider is adding a
plugin, not editing a list.
## Anthropic
One key in the environment is the whole setup.
```bash
export ANTHROPIC_API_KEY=sk-ant-...
eva
```
The plugin is `eva.provider.anthropic`, and model references are
`anthropic/` — the default model is `anthropic/claude-opus-5`.
[Models](/configure/models) lists what the catalog ships.
It takes one option:
```yaml
plugins:
- id: eva.provider.anthropic
options:
maxTokens: 32000
```
`maxTokens` caps one response. Unset, Eva sends 64,000.
## OpenAI
The same shape, one key over.
```bash
export OPENAI_API_KEY=sk-...
eva --model openai/gpt-5.4
```
The plugin is `eva.provider.openai`. It speaks the Responses API and asks the
server to store nothing, so a conversation lives in your session record and
not in the vendor's.
It takes one option, and here `maxTokens` unset sends no cap at all:
```yaml
plugins:
- id: eva.provider.openai
options:
maxTokens: 32000
```
## Compatible endpoints
`eva.provider.compatible` speaks Chat Completions to any endpoint you name.
One entry in its `providers` mapping is one provider in the catalog, and the
entry's key is the namespace your model references use.
```yaml
plugins:
- id: eva.provider.compatible
options:
providers:
ollama:
api: http://localhost:11434/v1
models: [qwen3-coder]
```
```bash
eva --model ollama/qwen3-coder
```
The fields an entry takes:
| Field | Default | What it does |
| ------------ | --------------- | ----------------------------------------------------------------------------- |
| `api` | required | the base URL a curl would take, version segment included; nothing is appended |
| `name` | the entry's key | the display name in the catalog |
| `credential` | `false` | `false` sends no Authorization header; `true` asks the credential store |
| `models` | `[]` | model ids written into the catalog so `/model` can list them |
| `maxTokens` | none | per-endpoint response cap; unset sends none |
| `usage` | `true` | ask the server to report token usage; set `false` for one that refuses |
An entry with no `api` is skipped entirely. A model the `models` list does not
name still runs when you type its reference — the list only feeds the picker.
An endpoint with a key needs two entries: the endpoint, and the environment
variable its credential comes from.
```yaml
plugins:
- id: eva.auth
options:
env:
groq: GROQ_API_KEY
- id: eva.provider.compatible
options:
providers:
groq:
api: https://api.groq.com/openai/v1
credential: true
models: [llama-3.3-70b]
```
An entry named `openai` replaces the first-party OpenAI provider, because the
compatible plugin loads after it and a later registration wins. That is the
supported way to point OpenAI-shaped traffic somewhere else.
## Credentials
Credentials belong to `eva.auth`, and today a credential is an API key read
from the environment at the moment a request opens.
| Provider | Variable |
| ------------- | ---------------------------------- |
| `anthropic` | `ANTHROPIC_API_KEY` |
| `openai` | `OPENAI_API_KEY` |
| anything else | named in `eva.auth`'s `env` option |
**Your key never reaches a settings file, a log, or the session record.** Eva
holds it nowhere else, which is why a session record is safe to share.
A provider with no credential still resolves, so the run closes saying
authentication failed rather than pretending the model does not exist.
## Base URLs
Eva passes no base URL to the two first-party providers, so the vendor SDKs'
own environment variables apply: `ANTHROPIC_BASE_URL` and `OPENAI_BASE_URL`
point them at a proxy. A compatible endpoint names its `api` explicitly, so
`OPENAI_BASE_URL` never leaks into one.
## Retries
A retry is an attempt that spent money and produced nothing, so Eva records
each one as an event of its own — with its delay — rather than hiding it
inside the attempt that succeeded. The vendor SDKs' silent retries are turned
off for the same reason: an attempt missing from the trace is a cost the
trace cannot explain.
`eva.provider.retry` decides whether a failed call is retried, with
exponential backoff:
```yaml
plugins:
- id: eva.provider.retry
options:
maxAttempts: 3
baseMs: 500
capMs: 30000
```
Only faults that can pass are retried: rate limits, overload, an unreachable
host, and server errors. A bad key, a missing model, and a billing problem
fail at once, because retrying those buys nothing.
## Turn a provider off
A provider is a plugin, so the plugin list is the switch. The wildcard
disables by prefix, and a later entry turns one back on without restating
anything.
```yaml
plugins:
- { id: "eva.provider.*", disabled: true }
- { id: eva.provider.anthropic, disabled: false }
```
See [plugins](/configure/plugins) for the entry shapes.
---
This page as HTML: https://docs.evafactory.co/configure/providers
Every page as one markdown index: https://docs.evafactory.co/llms.txt
---
# Trust and the .eva directory
A repository can ship a .eva directory with a team's Eva setup. Eva reads it only after you run eva trust there, because the grant is yours to give.
A repository can carry a `.eva` directory holding a team's Eva setup —
configuration, and later more. **Eva ignores it until you run `eva trust` in
that directory.**
```bash
cd some-repository
eva trust
```
## Why it is a verb you run
A repository you clone is code someone else wrote. If Eva read a checked-in
config file automatically, cloning a repository would silently change how your
tools behave — which model you call, which plugins load, what your runs cost.
So the grant is yours to give. It is a verb you type, not a file a repository
can ship. Nothing in a repository can grant itself trust.
## Withdraw it
```bash
eva untrust
```
The grant is recorded per directory, so untrusting one repository leaves the
others alone.
## Check what a grant actually did
```bash
eva config show
```
Every resolved key is printed beside where it came from, so you can see exactly
which values the `.eva` directory contributed.
## Exit codes
`eva trust` and `eva untrust` exit 0 when the grant is recorded or dropped.
See [exit codes](/reference/exit-codes).
---
This page as HTML: https://docs.evafactory.co/configure/trust
Every page as one markdown index: https://docs.evafactory.co/llms.txt
---
# Connect a model
One API key in the environment connects Anthropic or OpenAI, and your key never reaches a settings file, a log, or the session record.
Eva talks to Anthropic and OpenAI, so a key is the whole setup.
```bash
export ANTHROPIC_API_KEY=sk-ant-...
eva
```
That is it. There is no login step, no config file to edit, and no account.
For OpenAI models, export `OPENAI_API_KEY` instead and pick one:
```bash
export OPENAI_API_KEY=sk-...
eva --model openai/gpt-5.4
```
A local model behind Ollama, or any OpenAI-compatible endpoint, is one config
entry away — [providers](/configure/providers) shows it.
## Where your key goes, and where it does not
**Your key never reaches a settings file, a log, or the session record.** Eva
reads it from the environment at the moment it opens a request and holds it
nowhere else.
This matters because a session record is a file on your disk that you may
share, copy into an issue, or hand to another tool. It never contains a
credential.
## Choose a different model
Set it for one run with a flag:
```bash
eva --model anthropic/claude-sonnet-5
```
Or set it inside a session with a command:
```
/model anthropic/claude-sonnet-5
```
`/model` with no argument shows the current model and offers the ones Eva
knows about. See [models](/configure/models).
## What Eva costs
Eva reports what a provider says a request cost, and marks an estimate as an
estimate. [Cost and usage](/use/cost) explains the difference, and why Eva
refuses to guess.
Next: [your first run](/first-run).
---
This page as HTML: https://docs.evafactory.co/connect-a-model
Every page as one markdown index: https://docs.evafactory.co/llms.txt
---
# How plugins work
Eva is a small kernel that loads plugins. There are exactly four extension points — Domain, Slot, Hook, and Broadcast — and no fifth.
Eva is plugin-first. A small **Kernel** holds the plugin runtime, the four
extension points, the config source, and location resolution. **Everything that
is not the Kernel is a plugin** — the model, the surface, the trace, the
session store, the themes, and the harness.
Eva's own harness is a plugin with no privileged position.
## A plugin
A plugin exports an id and an effect, and declares the config keys it reads.
The effect runs once at load and registers into extension points.
```ts
export default {
id: "example.hello",
effect: () => {
// register into extension points here
},
}
```
## The four extension points
There are exactly four, and there is no fifth.
**Domain** — shared state that many plugins build together. The Kernel rebuilds
a Domain by replaying every registered Transform in order, then running the
domain's finalizer. A Transform describes a contribution rather than performing
one, which is why it can replay.
**Slot** — a typed key and its contract. Exactly one plugin fills a Slot at a
time. A consumer reads it at the moment of use and never captures it, so
replacing the plugin behind a Slot takes effect at once.
**Hook** — a callback at a live operation boundary. Hooks run in registration
order, and a later Hook sees what an earlier one changed. **A Hook may narrow
what a Run does, never widen it.**
**Broadcast** — a typed, process-local notification a plugin subscribes to as a
stream. It is never written to the Trace and carries no cross-version
guarantee. Control flow belongs in a Hook, not here.
## A Seam
A **Seam** is a complete swappable capability in three roles: the Slot that
defines it, the plugins that fill it, and the plugins that read it. The Seam is
the whole capability. Name a part by its role rather than calling the part a
Seam.
## What a plugin may import
A plugin imports the contract packages only. **Never the kernel, and never
another plugin.** That rule is enforced by lint rather than by convention, so a
violation fails the build rather than a review.
## Disabling one
```bash
eva --without-plugin eva.themes
```
A plugin that is disabled, or that fails, degrades the Run rather than stopping
it. The repository has CI jobs whose only purpose is to prove that: one starts
the binary with every plugin disabled, and another disables a single plugin and
asserts the Run still completes.
Turning plugins on and off in config, handing them options, and the wildcard
are [plugins](/configure/plugins).
Next: [write a plugin](/extend/write-a-plugin).
---
This page as HTML: https://docs.evafactory.co/extend/how-plugins-work
Every page as one markdown index: https://docs.evafactory.co/llms.txt
---
# Write a plugin
Author an Eva plugin — declare the config keys it reads, register into an extension point, clean up after itself, and fill or read a Slot.
The smallest plugin exports an id and an effect.
```ts
import type { Plugin } from "@missingstudio/eva-sdk"
export default {
id: "example.hello",
effect: () => {},
} satisfies Plugin
```
An id is namespaced. Eva's own plugins use `eva.*`; use your own prefix.
## Declare the config keys you read
A **Declaration** is the config keys a plugin reads, and the reader they
produce. One half goes beside the plugin's id and the other reads a key out of
config, **so a key is declared and read in one place**.
This is what lets Eva tell a person about a key nothing reads. The key sweep
asks the same reader, so a value it passes is a value the plugin can read.
A Declaration validates nothing and rejects nothing. It is not a schema.
## Clean up after yourself
Registering into an extension point returns a **Registration**. It is owned by
the registering plugin's scope, and disposing it is safe to repeat.
You rarely dispose one by hand — the scope does it when the plugin unloads. It
matters because a plugin must be able to unload and reload in one live process
without losing its position. The repository has a CI job that fails when it
cannot.
## Fill a Slot
Exactly one plugin fills a Slot at a time. Filling one replaces whatever was
there.
```ts
export default {
id: "example.store",
effect: (ctx) => {
ctx.fill(SessionStore, myStore)
},
} satisfies Plugin
```
## Read a Slot
**Read a Slot at the moment of use. Never capture it.**
```ts
// Correct: read at the moment of use.
const store = ctx.use(SessionStore)
// Wrong: captured at load, so a later replacement never takes effect.
const captured = ctx.use(SessionStore)
```
A Slot is late-bound on purpose. Capturing one at load time defeats the whole
point of the Seam, and it fails quietly rather than loudly.
## Ship it
A plugin ships as an npm package — a **Bundle**. A Bundle carries a config
layer that applies when a Profile lists it.
Package boundaries are enforced: a plugin imports the contract packages only,
never the kernel and never another plugin. Its own tests may also import the
testkit, which boots the plugin so its effect can be tested.
## Test it
```ts
import { boot } from "@missingstudio/eva-testkit"
test("the plugin registers", async () => {
const eva = await boot({ plugins: [plugin] })
expect(eva.has("example.hello")).toBe(true)
})
```
The testkit is a devDependency everywhere it is used, so nothing it reaches
ends up in a shipped plugin.
---
This page as HTML: https://docs.evafactory.co/extend/write-a-plugin
Every page as one markdown index: https://docs.evafactory.co/llms.txt
---
# Your first run
Open the Eva console, ask a question, read the answer, and see what it cost. Then do the same thing from a shell pipeline.
Open the console with no arguments:
```bash
eva
```
Type a question and press enter. Eva streams the answer as it arrives.
## See what it cost
Type `/cost`. Eva prints what this session has spent.
Commands that begin with a slash are answered by the console itself. They never
reach a model, so they cost nothing.
## Answer once and exit
`--print` runs one prompt, writes the answer to stdout, and exits. This is the
form to use in a script.
```bash
eva --print "what does this repository do?"
```
It is `-p` for short. The run exits non-zero when the answer fails, which makes
it safe in a pipeline:
```bash
eva -p "summarise the last commit" > summary.txt
```
## Stop a run
Press Esc to interrupt a run in progress. Press it again to confirm.
Press Ctrl+C twice to leave the console.
## What you have now
---
This page as HTML: https://docs.evafactory.co/first-run
Every page as one markdown index: https://docs.evafactory.co/llms.txt
---
# Install
Install Eva with Homebrew, the install script, npm, or from source, and verify the download you got before you run it.
Eva installs from four channels. Pick one. Every channel delivers the same
prebuilt binary for your platform.
## Homebrew, on macOS
```bash
brew install --cask missingstudio/tap/eva
```
It is a cask rather than a formula, because the release publishes a prebuilt
binary rather than something Homebrew compiles.
## The install script, on macOS and Linux
```bash
curl -fsSL https://raw.githubusercontent.com/missingstudio/eva/main/scripts/install.sh | sh
```
The script checks the download against the release's signed checksums before it
installs anything, and it says so either way. Read it first if you prefer.
Pass `--require-signature` to make an unverifiable download a refusal rather
than a warning:
```bash
curl -fsSL https://raw.githubusercontent.com/missingstudio/eva/main/scripts/install.sh | sh -s -- --require-signature
```
## npm
```bash
npm i -g @missingstudio/eva
```
A prebuilt binary for your platform. No runtime is needed.
## From source
Requires Bun 1.3 or newer.
```bash
git clone git@github.com:missingstudio/eva.git && cd eva && bun install && bun run eva
```
## Verify what you downloaded
Three artefacts back every release, and they answer different questions.
| Artefact | Proves |
| ------------------------------- | ------------------------------------------ |
| `checksums.txt` | the download is the bytes that were built |
| `checksums.txt.sigstore.json` | the checksums came from Eva's own workflow |
| a per-archive build attestation | which workflow built it, from which commit |
A checksum alone proves the download is intact, not that it is Eva's — whoever
serves a bad archive can serve a matching `checksums.txt`. The signature is
what ties the checksums to this repository.
Verify provenance directly:
```bash
gh attestation verify eva-darwin-arm64.zip --repo missingstudio/eva
```
## Check it worked
```bash
eva --version
```
Next: [connect a model](/connect-a-model).
---
This page as HTML: https://docs.evafactory.co/install
Every page as one markdown index: https://docs.evafactory.co/llms.txt
---
# CLI reference
Every Eva command and every global flag, with what each one does and what it returns.
## Commands
```
eva start the interactive console
eva --print answer once and exit
eva trust read this directory's .eva, and record the grant
eva untrust drop the grant for this directory
eva config show print the resolved config, and where each key came from
```
| Command | What it does | Page |
| ----------------- | -------------------------------------------------- | ----------------------------------------- |
| `eva` | open the interactive console | [The console](/use/console) |
| `eva --print` | answer once, print to stdout, exit | [Print mode](/use/print-mode) |
| `eva trust` | read this directory's `.eva`, and record the grant | [Trust](/configure/trust) |
| `eva untrust` | drop the grant for this directory | [Trust](/configure/trust) |
| `eva config show` | print the resolved config with each key's origin | [Configuration](/configure/configuration) |
## Global flags
Every flag below is valid on every command, before it or after it.
| Flag | Repeatable | What it does |
| -------------------------- | ---------- | -------------------------- |
| `--config ` | yes | overlay a config file |
| `--model ` | no | set the model for this run |
| `--plugin ` | yes | load a plugin for this run |
| `--without-plugin ` | yes | skip a plugin for this run |
| `-p, --print ` | no | answer once and exit |
| `-v, --version` | no | print the version and exit |
| `-h, --help` | no | print the help and exit |
## Notes on the surface
**The prompt is a flag, not a bare argument.** With a bare argument accepted,
`eva trsut` would be a valid prompt rather than a misspelling Eva can correct.
**`--without-plugin`, not `--no-plugin`.** A flag named `--no-plugin` is parsed
by Commander as the negation of `--plugin` and silently joins it, which is the
opposite of what it reads like.
**`-v` is version, not verbose.**
**`--` does not protect a value.** Quote a prompt that begins with a dash.
## Exit codes
See [exit codes](/reference/exit-codes).
---
This page as HTML: https://docs.evafactory.co/reference/cli
Every page as one markdown index: https://docs.evafactory.co/llms.txt
---
# Exit codes
What each Eva exit code means, why an unread config key never changes one, and how to make a shell pipeline stop when a run fails.
| Code | When |
| ---- | ------------------------------------------------------------------------- |
| 0 | the help, the version, a grant, `config show`, or a Run that ended `done` |
| 1 | a parse error, an unreadable config, or a Run that ended any other way |
| 130 | the second Ctrl+C |
## A finding does not change the exit code
An unread config key is a **Finding**. Eva writes it to stderr and the run
continues, and the exit code is whatever the run itself earned.
A stale config key should not break a pipeline. A silent one should not go
unmentioned. Reporting on stderr without touching the exit code is how Eva does
both.
## Use it in a script
```bash
eva -p "review the diff" > review.txt || exit 1
```
`--print` exits non-zero when the answer fails, so a pipeline stops rather than
carrying an empty answer forward. See [print mode](/use/print-mode).
---
This page as HTML: https://docs.evafactory.co/reference/exit-codes
Every page as one markdown index: https://docs.evafactory.co/llms.txt
---
# What is an autonomous software factory?
An autonomous software factory runs coding work from a machine-checkable spec to verified evidence, across every coding agent harness you already pay for.
An autonomous software factory is a system that runs coding work end to end:
you give it a Spec whose acceptance criteria a machine can check, a Harness
does the work, and the factory keeps the Evidence that it was done. It is
defined by four properties — one contract over every harness, one Trace of
everything they did, one Verifier that decides whether the work passed, and
one bill that prices it.
The word factory is doing real work in that sentence. A factory is not a
better machine; it is the thing that coordinates machines, checks their
output, and accounts for what they consumed. That is the difference between
running a coding agent and operating a fleet of them.
## Why the category exists
The problem is not that coding agents are bad at coding. It is that a single
agent cannot tell you anything about its own work that you should believe.
If you already use Claude Code, Codex, and OpenCode, you have three good
Harnesses and three separate worlds. None of them can check another's work.
None can tell you what a merged change cost. None can run twenty tasks
overnight and hand you the evidence in the morning. Switching between them
means switching tools, losing history, and starting the accounting again.
And each one is tied to the machine you are sitting at. Close the laptop and
the work stops.
Those are not model problems, and a better model does not fix any of them.
They are coordination, verification, and accounting problems — which is
exactly the set of problems a factory exists to solve.
## The four properties
These four are the whole definition. A system missing any of them is a
harness, an orchestrator, or a dashboard, but not a factory.
**One contract.** Every Harness — the factory's own and every foreign one —
implements the same interface, so a task runs on any of them without being
rewritten. No harness holds a privileged position, including the one the
factory ships.
**One Trace.** Everything every Harness does lands in one Event schema.
Anything the Trace cannot rebuild is a bug, which makes the Trace the single
source of truth rather than a log beside it.
**One Verifier.** Acceptance criteria are checked by the factory, not claimed
by the agent that did the work. A Claim is never Evidence, whatever its
source.
**One bill.** Cost is attributed per task, per merged change, and per
Harness. That turns "use this harness for this kind of work" from a
preference into a measurement.
## How a factory differs from a harness
A **Harness** takes a Prompt and drives it to a Stop Reason. Claude Code is a
harness. Codex is a harness. Eva's own native loop is a harness, and it sits
in the same registry as the rest.
A **factory** takes a Spec and returns an Outcome — Done, Failed,
NeedsHuman, or Exhausted — with the Evidence attached. It selects a Harness,
gives it a bounded environment to work in, checks the result against criteria
the Harness did not get to define, and records what the whole thing cost.
The practical test is who decides that work is finished. In a harness, the
agent decides and reports. In a factory, the Verifier decides and the agent's
report is just one more Event in the Trace. Every other difference —
scheduling, isolation, cost attribution, audit — follows from moving that one
decision out of the agent.
## What a factory is not
**Not a model.** A factory is model-agnostic by construction; the model is a
replaceable part.
**Not an IDE.** An IDE is where a person writes code. A factory is where work
runs when no person is watching.
**Not a replacement for your harnesses.** The subscriptions you already pay
for become the workers. A factory that resells model access instead of
driving the harnesses you own has kept the licensing problem and dropped the
coordination benefit.
**Not an agent framework.** A framework is a library for building an agent. A
factory is an operating system for running agents against real repositories
with budgets, verification, and an audit trail.
## Where Eva is today
Eva is an open-source implementation of this category, and it is early. Being
honest about which of the four properties has shipped is part of the
definition working at all.
| Property | Status |
| ------------ | -------- |
| One Trace | Shipping |
| One Verifier | Stage 5 |
| One contract | Stage 9c |
| One bill | Stage 11 |
What runs today is a terminal client on a plugin kernel: a catalog of
providers, a
durable Session that survives `kill -9`, Cost recorded as the Provider
reported it, and a `.eva` directory a repository earns access to. Eva drives
its own Harness today; the adapters that put your other harnesses behind the
same contract land at stage 9c.
The [roadmap](/about/roadmap) states every stage and the exit test it can
fail. Nothing above is claimed as shipped that the tree cannot demonstrate.
## Related reading
---
This page as HTML: https://docs.evafactory.co/software-factory
Every page as one markdown index: https://docs.evafactory.co/llms.txt
---
# Commands
Every slash command in the Eva console. They are answered locally, never reach a model, and cost nothing.
A command is a line you type at the console with a leading slash. **Commands
never reach a model, so they cost nothing.**
Type `/` to open the palette. Enter runs the selected row, and Tab completes it.
## The commands
| Command | Argument | What it does |
| ----------- | ---------------- | ----------------------------------------- |
| `/help` | — | list the commands |
| `/model` | `provider/model` | show or set the session model |
| `/cost` | — | show what this session has spent |
| `/theme` | `theme` | show or set the theme |
| `/clear` | — | open a new Session |
| `/new` | — | an alias for `/clear` |
| `/sessions` | — | show the Sessions Eva holds, and open one |
## Commands that take an argument
`/model` and `/theme` both name an argument, and both answer a bare line with a
choice of their own. Running `/model` with no argument opens the model picker
rather than complaining.
`/sessions` takes no argument and always asks: it opens a picker over the
Sessions Eva holds, and the row you take is the Session the console follows.
An argument hint says what an argument would look like. It never says one is
required.
## What is not a command
A flag is not a command. `--print`, `--model`, and `--config` start a process
and are read before the console exists. See the
[CLI reference](/reference/cli).
`eva trust`, `eva untrust`, and `eva config show` are commands of the
executable rather than of the console. You type them in your shell, not at the
Eva prompt.
---
This page as HTML: https://docs.evafactory.co/use/commands
Every page as one markdown index: https://docs.evafactory.co/llms.txt
---
# The console
The Eva console is the interactive surface you type into. It shows a live area while a Run happens, then replaces it with the committed record.
The console is the interactive interface Eva opens when you run `eva` with no
arguments. It is a plugin — `eva.tui` — like everything else in Eva.
```bash
eva
```
## What it shows
**The live area** shows a Run as it happens, from the stream rather than from
the record. When the Run closes, the fold over the committed records replaces
it. What you read afterwards is the record, not the stream.
**Notes** are lines the console says on its own — command output, a notice, a
question Eva asked. A note is not part of the transcript. It lasts until the
conversation moves on.
The console holds a Session while it runs. It is not itself a Session.
## Typing
Type a prompt and press Enter to send it. Type `/` to open the
command palette; see [commands](/use/commands).
| Key | What it does |
| ----------------------------------- | --------------------------------- |
| Enter | send the prompt |
| Esc | interrupt the Run |
| Esc again | confirm the interrupt |
| Ctrl+C, twice | leave the console |
| Tab | complete a command in the palette |
[Keys and bindings](/use/keys) covers how a binding is spelled and how to
change one.
## Pasting
A pasted block arrives whole, at the caret, on both renderers. A newline inside
a paste is text rather than a key, so pasting several lines does not send the
prompt.
## Two renderers, one frame
Eva draws the same frame two ways. Under Bun it uses a native renderer. Where
that renderer cannot load, it draws the same content as a stream of lines. Both
say the same words — that is a contract the repository tests on every change,
not a coincidence.
You do not choose between them. Eva picks the one the runtime supports.
## When output is piped
A run whose output is piped writes the conversation and no chrome. This is what
makes `eva | tee log.txt` produce something worth reading.
---
This page as HTML: https://docs.evafactory.co/use/console
Every page as one markdown index: https://docs.evafactory.co/llms.txt
---
# Cost and usage
Eva shows what a provider said a request cost, and marks an estimate as an estimate. It never multiplies tokens by a rate and calls the result a cost.
Type `/cost` in the console to see what the current Session has spent.
## Cost and Estimate are different things
**Cost is what the Provider said a request cost.** Eva records it in Ticks —
integers of 1e-10 USD — so no rounding creeps in across thousands of small
charges.
**An Estimate is what a Run's counters come to at published prices.** It is a
projection. It moves when a vendor reprices, because it is folded fresh each
time rather than stored.
Eva shows an Estimate marked as one. An Estimate read as a Cost is exactly the
mistake that keeping two words apart exists to prevent.
## Absent is not zero
If a Provider does not report a cost, Eva records absent. **Absent is never
rendered as zero**, and Eva never computes a number from tokens times a rate
and presents it as a Cost.
A Run that cannot report its cost says so in words. That is a Degraded outcome:
Eva keeps the data, marks it, and holds it out of any scoring.
## Why integers
A float of dollars accumulates error. At 1e-10 USD per Tick, a Run costing
three cents is 300,000,000 Ticks, and a million such Runs still add up exactly.
## What is coming
Cost attributed per task, per merged change, and per harness is the "one bill"
part of Eva's pitch, and it lands with the multi-harness work.
[The roadmap](/about/roadmap) says when.
---
This page as HTML: https://docs.evafactory.co/use/cost
Every page as one markdown index: https://docs.evafactory.co/llms.txt
---
# Keys and bindings
How Eva spells a key chord, the six bindings the console ships, and how to add a chord for a command or replace a default binding in config.
A **binding** is a key chord written as words joined by `+`, in one canonical
spelling. There is exactly one way to write any chord, so a config file cannot
name a key two ways.
## The spelling rules
| Rule | Correct | Wrong |
| -------------- | ------------------- | -------------- |
| Modifier order | `ctrl+meta+shift+k` | `shift+ctrl+k` |
| The enter key | `enter` | `return` |
| The plus key | `plus` | `+` |
The joiner cannot also be a key, which is why `plus` is spelled out.
A string Eva cannot parse names no key. The console says so — once — rather
than storing it and failing later.
## Default bindings
The keymap ships six rows, and these are the six commands the console acts
on.
| Chord | Row | Command | Does |
| --------------------------------- | --------- | ----------------- | ------------------------ |
| Enter | `submit` | `session.submit` | send the prompt |
| Shift+Enter | `newline` | `input.newline` | insert a newline |
| Ctrl+C | `cancel` | `session.cancel` | interrupt the Run |
| Ctrl+D | `quit` | `app.quit` | leave the console |
| Esc | `back` | `surface.back` | close the open panel |
| Ctrl+K | `palette` | `surface.palette` | open the command palette |
Tab completes the selected row inside the palette. It is the panel's own key, not a
keymap row.
## Add a chord for a command
The keymap is config: the top-level `keymap` mapping, one row per key. A row
keyed by a command name, with a bare string as its binding, adds a second
chord — the default stays.
```yaml
keymap:
input.newline: ctrl+j
```
Now Ctrl+J and Shift+Enter both
insert a newline.
`input.newline` is bound to `shift+enter` by default, and most terminals cannot send that chord
without the kitty keyboard protocol. If you want a multi-line prompt you can type rather than
paste, add a chord your terminal sends, such as `ctrl+j`.
## Replace a default binding
To change one of the six rows, restate it whole — the binding and the
command:
```yaml
keymap:
palette:
binding: ctrl+p
command: surface.palette
```
Restate `command` whenever you rewrite a default row. A bare string sets the
row's command to the row's own id — `palette: ctrl+p` would fire a command
named `palette`, which nothing answers.
## Conflicts
Two rows that bind the same chord collide, and they collide in the chord's
one spelling — `ctrl+c` and `C+ctrl` are the same key bound twice. The
console reports the collision and the last row wins, so a mistake costs a
notice rather than a dead keyboard.
---
This page as HTML: https://docs.evafactory.co/use/keys
Every page as one markdown index: https://docs.evafactory.co/llms.txt
---
# Print mode
Run one prompt, write the answer to stdout, and exit. Print mode exits non-zero when the answer fails, which makes Eva safe inside a shell pipeline.
Print mode answers one prompt and exits. It is how Eva belongs in a script.
```bash
eva --print "summarise the last commit"
```
`-p` is the short form.
## It is safe in a pipeline
**Print mode exits non-zero when the answer fails.** A pipeline that checks
exit codes therefore stops on a failed run rather than carrying an empty
answer forward.
```bash
eva -p "list the risky changes in this diff" > review.txt || exit 1
```
| Code | When |
| ---- | ------------------------------------------------------------------ |
| 0 | the Run ended `done` |
| 1 | a parse error, an unreadable config, or a Run that ended otherwise |
| 130 | the second Ctrl+C |
[Exit codes](/reference/exit-codes) has the full table, including the codes the
other commands return.
## The prompt is a flag, not a bare argument
`eva --print "…"` rather than `eva "…"`. The prompt stays behind the flag on
purpose: with a bare argument accepted, a misspelled verb becomes a valid
prompt. `eva trsut` would be sent to a model rather than corrected.
## Combine it with the global flags
Every global flag works in print mode.
```bash
eva -p "explain this error" --model anthropic/claude-sonnet-5
eva -p "review the diff" --config ./ci.eva.yaml
eva -p "what changed?" --without-plugin eva.themes
```
## A finding does not fail the run
An unread config key is a finding. Eva writes it to stderr and the run
continues, with the exit code unchanged. A finding is data about your setup,
not a failure of the work.
---
This page as HTML: https://docs.evafactory.co/use/print-mode
Every page as one markdown index: https://docs.evafactory.co/llms.txt
---
# Sessions
A Session is Eva's durable, resumable transcript. It survives kill -9, because everything Eva knows is folded from a Trace on disk rather than held in memory.
A Session is the durable, resumable transcript of your work with Eva.
**It survives `kill -9`.**
That is not a recovery feature bolted on afterwards. Eva writes every
observable thing to a Trace as it happens, and everything you see is a fold
over that Trace. There is no in-memory state that a crash could lose, because
there is no in-memory state that matters.
## Runs inside a Session
A **Run** is one execution against a Session, from the intent that opens it to
the Claim that closes it. A Session you resume twice has several Runs.
A Session has a Header — what to call it, and when it last moved. Nothing
stores a Header. It is a fold over the Trace, like every other projection.
## Start a new one
```
/clear
```
`/new` is the same command. It opens a new Session; it does not erase the old
one.
## Go back to one
```
/sessions
```
It lists the Sessions Eva holds, most recently moved first, and the one you
take is the one the console follows. A Session with nothing said in it yet is
listed too, named by when it moved.
## Where they live
Sessions are written under Eva's state directory, as JSONL. One file, appended
to, one Event per line. You can read one with `cat`, and Eva can rebuild
everything it shows from it.
A Session record never contains your API key. See
[connect a model](/connect-a-model).
## Locations
A Session belongs to a **Location** — the directory it was opened in. Eva holds
many Locations and has no ambient one, so a Session opened in one repository
does not follow you into another.
## What is coming
Resume, branch, and rewind all act on the Session, and land in a later stage.
[The roadmap](/about/roadmap) says when.
---
This page as HTML: https://docs.evafactory.co/use/sessions
Every page as one markdown index: https://docs.evafactory.co/llms.txt
---
# Themes
Eva ships three themes and takes custom ones. A theme is a name and four colors, written in config or dropped into a .eva/themes directory.
Eva ships three themes.
| Id | Name | For |
| ---------- | ------------- | ----------------------------------- |
| `default` | Default | most terminals |
| `contrast` | High contrast | low-contrast displays, bright rooms |
| `mono` | Monochrome | terminals with a restricted palette |
## Switch theme
```
/theme contrast
```
Running `/theme` with no argument opens the picker rather than complaining.
## Set one permanently
The selector is the top-level `theme` key:
```yaml title="~/.eva/config.yaml"
theme: contrast
```
[Configuration](/configure/configuration) explains where that file goes and
how Eva decides which layer wins. The key is read by the terminal surface —
which is why, in a run with the console disabled, Eva reports `theme` as a
key nothing read.
## Write your own
A theme is a name and exactly four colors. Define one under the top-level
`themes` mapping and select it:
```yaml title="~/.eva/config.yaml"
theme: dusk
themes:
dusk:
name: Dusk
colors:
foreground: "#e8e4d8"
muted: "#8a8a7a"
accent: "#d8a657"
warning: "#e78a4e"
```
Or ship it as a file — `.eva/themes/dusk.yaml` is the same row, keyed by its
base name, so a repository can carry a team's theme:
```yaml title=".eva/themes/dusk.yaml"
name: Dusk
colors:
foreground: "#e8e4d8"
muted: "#8a8a7a"
accent: "#d8a657"
warning: "#e78a4e"
```
The four keys, and what each paints:
| Key | Paints |
| ------------ | -------------------------------------------------------- |
| `foreground` | the words — yours and the agent's |
| `muted` | thoughts, hints, and secondary text |
| `accent` | the bar beside your words, the spinner, the selected row |
| `warning` | tool calls, and what needs your attention |
## Change one color of a built-in
Colors merge onto a theme that already exists, so overriding one key leaves
the other three alone:
```yaml
themes:
default:
colors:
accent: "#ff79c6"
```
## A theme with a gap
A row missing one of the four colors is not a theme yet. The console keeps
drawing the default and says so, rather than painting half a theme. The same
holds for a `theme` that names no row: the default is drawn, and a notice
names the theme that was not found.
## Themes are a plugin
`eva.themes` is a plugin like everything else in Eva, so you can turn it off:
```bash
eva --without-plugin eva.themes
```
Eva keeps running without it. A plugin that fails or is disabled degrades the
run rather than stopping it — the repository has a CI job whose only purpose
is to prove that.
---
This page as HTML: https://docs.evafactory.co/use/themes
Every page as one markdown index: https://docs.evafactory.co/llms.txt