# 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