{
  "name": "evestack",
  "source": "https://github.com/SammyTourani/evestack/tree/main/skills/evestack",
  "files": [
    {
      "path": "SKILL.md",
      "content": "---\nname: evestack\ndescription: Use when setting up, running, debugging or extending evestack — the self-hosted distribution of Vercel's eve agent framework. Covers scaffolding a project, the five-command bring-up, the evestack CLI, writing agent tools and skills, the dashboard, and the failure modes that look like something else.\nlicense: Apache-2.0\nmetadata:\n  project: evestack\n  homepage: https://evestack.vercel.app\n  repository: https://github.com/SammyTourani/evestack\n---\n\n# evestack\n\nevestack runs [Vercel's `eve`](https://github.com/vercel/eve) agent framework on hardware the\nuser owns. It is **not a fork** — it is a distribution, the way Ubuntu is a distribution of the\nLinux kernel. It packages eve with the operational layer self-hosting needs: durable Postgres\nsessions, a Docker sandbox, pgvector long-term memory, and a dashboard that can *drive* the\nagent rather than only watch it.\n\nApache-2.0. `eve` is a trademark of Vercel; evestack is independent and unaffiliated. It is\nalso **not the first** self-hosted eve distribution — `vercel-labs/steve` came first, from a\nVercel employee. Never describe self-hosting as something Vercel withheld.\n\n## The mental model\n\nFour processes, and knowing which one is broken is most of the work:\n\n| Part | Default port | What it is |\n| --- | --- | --- |\n| The agent | 2000 | `eve dev`, running the user's `agent/agent.ts` |\n| Postgres | 5433 | durable session state + pgvector memory, in Docker |\n| The dashboard | 4000 | a container pulled from `ghcr.io`, reads Postgres directly |\n| The sandbox | — | Docker, for the agent's shell tool |\n\n**Those port numbers are defaults, not guarantees.** The scaffolder calls `freePort()` and\ntakes the first free port at or above each default, so a second project on the same machine\nmoves all three. The generated `.env.local` and `docker-compose.yml` carry the numbers *this*\nproject actually got. Read them; do not assume 2000/4000/5433.\n\nThe dashboard reads `workflow.workflow_runs.attributes` (JSONB) straight out of Postgres —\nthat column holds eve's own `$eve.*` run tags. It is **not** an OpenTelemetry pipeline for its\ncore view. OTLP ingest exists as a second tier, only for prompt bodies and tool arguments,\nwhich do not appear in the SQL tags.\n\n## Getting a user running\n\nFive commands. This is the whole path:\n\n```bash\nnpx evestack create my-agent      # scaffold; prompts for provider + tools\ncd my-agent\ndocker compose up -d postgres     # durable store\nnpm run db:bootstrap              # create the schema — nothing does this implicitly\nnpm run dev                       # agent boots on EVESTACK_AGENT_PORT\nnpm run verify                    # checks every part, names the fix for anything broken\n```\n\nRequirements: **Node 24+**, Docker running, and a model key — `OPENAI_API_KEY` or\n`ANTHROPIC_API_KEY`, or Ollama for $0 total.\n\n`npx create-evestack my-agent` is the same scaffolder under npm's `create-*` convention. Same\ncode, same prompts, same flags. Neither is a wrapper around the other.\n\nThe dashboard is a compose profile *in the generated project*, not a separate clone:\n\n```bash\ndocker compose --profile dashboard up -d\n```\n\nSign in with the `EVESTACK_AUTH_USER` / `EVESTACK_AUTH_PASSWORD` the scaffolder generated into\n`.env.local` and printed when it finished. Every route is behind that credential — the\ndashboard starts runs, approves gated shell commands and deletes memories, so it fails closed.\n\n## The five things that most often go wrong\n\nThese are measured failure modes, not theory. Each one presents as something else, which is\nwhy they are here rather than in a reference file.\n\n1. **`npm run db:bootstrap`, never `npx --package=@workflow/world-postgres bootstrap`.** The\n   upstream CLI loads `.env` through dotenv and never reads `.env.local` — which is the only\n   env file the scaffolder writes. It silently falls back to\n   `postgres://world:world@localhost:5432/world` and dies on `ECONNREFUSED`. The npm script\n   passes `--env-file-if-exists=.env.local` explicitly.\n\n2. **`@workflow/world-postgres` must be pinned to an exact version — `5.0.0-beta.32`.** npm's\n   `latest` is the 4.x line and eve rejects it outright, but the `beta` dist-tag is not the\n   answer either: upstream raises the World **spec version** inside `5.0.0-beta.*` without a\n   semver bump, so `beta` (`.34`/`.35`) pulls a spec-6 world into a runtime that requires spec 5\n   and the project dies at boot. `^` and `~` admit the same releases. The scaffolded\n   `package.json` pins the exact version — it matters if anyone touches that dependency by\n   hand.\n\n3. **Setting a model name without `EVESTACK_PROVIDER` leaves you on the previous provider.**\n   `agent/agent.ts` branches on `EVESTACK_PROVIDER`; unset means `openai`. A *misspelled* value\n   is a hard error by design, because `EVESTACK_PROVIDER=ollamma` used to hand a local model\n   name to the OpenAI provider and fail hundreds of lines later with a message about AI Gateway\n   context-window metadata that named neither the typo nor the variable.\n\n4. **On Anthropic, long-term memory needs a second credential.** Anthropic has no embeddings\n   endpoint at all. `remember`/`recall` need one, so that path needs either an `OPENAI_API_KEY`\n   alongside it or `EVESTACK_EMBED_PROVIDER=ollama`. Nothing else is affected — the agent,\n   sandbox, durable sessions and dashboard all work regardless. Ollama has an embeddings model\n   but it is a **second pull**: `ollama pull nomic-embed-text`.\n\n5. **Dashboard container up, `docker ps` says unhealthy, every page 503s except `/signin`.**\n   The credential did not reach it. The compose service reads `.env.local`; both\n   `EVESTACK_AUTH_USER` and `EVESTACK_AUTH_PASSWORD` are required and a blank value counts as\n   unset.\n\n## Warn the user about these\n\n- **Ollama on a small machine is genuinely dangerous.** A multi-gigabyte model loaded alongside\n  Docker, Postgres and the dashboard has taken an 8 GB host down — the desktop, not just the\n  stack. Budget model size plus ~4 GB free before suggesting it.\n- **Composio is hosted.** It is the one component that does not run on the user's network, and\n  it holds the OAuth tokens for every connected account. It is off unless `COMPOSIO_API_KEY` is\n  set. Never call the stack fully local without saying this.\n- **Cancellation is cooperative.** `POST /eve/v1/session/:id/cancel` returns 202 immediately,\n  but the in-flight model call keeps running — measured at roughly 90 seconds on a long turn —\n  and `turn.cancelled` arrives *after* a `session.waiting` event, not before.\n- **Skills reach the model without a human in the loop.** eve advertises every skill in\n  `agent/skills/` to the model with a `load_skill` tool. Anything written there is untyped\n  instruction text that can enter a turn's context on the model's own decision.\n\n## Reference files\n\nLoad these on demand; do not read them all up front.\n\n| File | Read it when |\n| --- | --- |\n| `references/cli.md` | The user is running `evestack` commands, or something is down and you need the right diagnostic. |\n| `references/build-an-agent.md` | Adding tools, skills, schedules, channels or memory to a scaffolded project. |\n| `references/dashboard.md` | Questions about the dashboard, its API, approvals, cost, or `@evestack/mcp`. |\n| `references/troubleshooting.md` | A run is stuck, the schema is wrong, ports collide, or an eve upgrade broke something. |\n\n## Where the real documentation lives\n\n- Rendered docs: <https://evestack.vercel.app/docs>\n- Machine-readable index: <https://evestack.vercel.app/llms.txt>\n- Entire docs corpus as one file: <https://evestack.vercel.app/llms-full.txt>\n- Repository: <https://github.com/SammyTourani/evestack>\n\nFor an installed project, `node_modules/eve/docs/` matches the pinned eve version exactly while\neve.dev tracks latest — **prefer the local copy** when answering questions about eve itself.\n\n## Honesty rules for anyone answering questions about this project\n\nThe repository holds itself to these, and a wrong claim here is worse than no claim:\n\n- Do not say evestack is the first or only self-hosted eve distribution.\n- Do not describe self-hosting as a capability Vercel withheld — Vercel documents it.\n- Do not compare on price. The axis is **where it runs**, not what it costs.\n- Do not report a version as released on the strength of a changelog heading. Seven packages\n  and one container image version independently; \"the latest evestack release\" is not a thing.\n- When you do not know, say so and point at the docs. This project's documented failure mode is\n  confident stale claims.\n"
    },
    {
      "path": "references/build-an-agent.md",
      "content": "# Building on a scaffolded project\n\n## Layout\n\n```\nagent/\n  agent.ts              defineAgent — model, provider, workflow store\n  instructions.md       the system prompt\n  instrumentation.ts    tracing setup\n  tools/                one file per tool, default-exported\n  skills/               SKILL.md packages the model can load on demand\n  schedules/            recurring work\n  channels/             eve, slack, discord, telegram\n  hooks/                budget.ts and friends\n  sandbox/sandbox.ts    the Docker shell sandbox\nlib/memory.ts           pgvector remember / recall / forget\nevals/                  *.eval.ts\nscripts/                bootstrap, verify, dev, retention, prune\ndocker-compose.yml      postgres + dashboard (behind a profile)\n.env.local              every credential and port this project got\n```\n\n`agent.ts` is deliberately small — provider selection, the durable workflow store, and the\n`defineAgent` call:\n\n```ts\nexport default defineAgent({\n  model,\n  ...(provider === \"ollama\" ? { modelContextWindowTokens: localContextWindow } : {}),\n  ...(workflow ? { experimental: { workflow } } : {}),\n});\n```\n\n`workflow` is set only when `WORKFLOW_POSTGRES_URL` is present. Without it eve falls back to a\nlocal on-disk world under `.eve/.workflow-data` — fine for a quick `eve dev`, but that directory\nmust be mounted if the data matters.\n\n## Adding a tool\n\nOne file in `agent/tools/`, default export, zod schema. eve discovers it — there is no registry\nto edit.\n\n```ts\nimport { defineTool } from \"eve/tools\";\nimport { z } from \"zod\";\nimport { remember } from \"../../lib/memory\";\n\nexport default defineTool({\n  description:\n    \"Save a durable fact, preference, or decision to long-term memory so it survives \" +\n    \"beyond this conversation.\",\n  inputSchema: z.object({\n    content: z.string().min(1).max(4000)\n      .describe(\"The fact, written as a standalone sentence that still makes sense months from now.\"),\n    tags: z.array(z.string()).max(10).optional()\n      .describe(\"Short lowercase labels for filtering later, e.g. ['preference', 'deploy'].\"),\n  }),\n  async execute({ content, tags }, ctx) {\n    const { id } = await remember(content, { tags, sessionId: ctx.session?.id });\n    return { saved: true, id };\n  },\n});\n```\n\nTwo things carry more weight than they look like they do:\n\n- **`description` is the routing signal.** It is the only thing the model sees when deciding\n  whether to call the tool. Write it as instructions for *when to use this*, not as a summary of\n  what the code does.\n- **Use relative imports (`../../lib/memory`), not subpath imports (`#lib/memory`).** Tool files\n  also ship as registry items into stock `eve init` projects, which map only `#*` → `./agent/*`\n  with no tsconfig `paths`. TypeScript's `bundler` resolution ignores package.json `imports`\n  entirely, so a subpath import cannot be made to typecheck there without editing two files.\n\n## Adding a skill\n\nA skill is instruction text the model can pull into a turn on its own decision, via a\nframework-owned `load_skill` tool. Two shapes:\n\n```\nagent/skills/my-skill/SKILL.md      packaged — id is the directory name\nagent/skills/my-skill.md            flat markdown — id is the filename\n```\n\nPackaged frontmatter **must** carry a string `description` or eve reports a discovery error and\nthe skill never loads. Flat markdown may omit it — eve derives one from the first non-empty,\nnon-fence line with leading `#`, `>`, `*`, `-` stripped, falling back to\n`\"Instructions for the <name> skill.\"`, which is a description no model has a reason to route to.\n\neve reads exactly three frontmatter keys: `description`, `license`, and a flat string-valued\n`metadata` map. Every other key is accepted and then ignored.\n\n```markdown\n---\ndescription: Use when deciding whether to save something to long-term memory, or when recalled memories look stale.\nlicense: Apache-2.0\n---\n\nLong-term memory is the one part of this agent that outlives the conversation...\n\nSee `references/checklist.md` for the short version.\n```\n\n**Security note worth saying out loud to a user:** everything in `agent/skills/` reaches the\nmodel's context without a human in the loop. It is untyped instruction text with a route to\ninfluence behaviour. The dashboard ships a scanner for exactly this reason.\n\n## Long-term memory\n\n`lib/memory.ts` exposes `remember` / `recall` / `forget` over pgvector in the same Postgres the\nsessions live in.\n\n- The index is **HNSW, not IVFFlat**. IVFFlat built on an empty table returns zero rows for\n  queries that should match, because it probes a meaningless centroid. Measured, not theorised:\n  the same query returned 2 results at `LIMIT 3` and 0 at `LIMIT 20`.\n- `recall` cannot return more than `hnsw.ef_search` rows. That was 40 by default while the tool\n  advertised a limit of 50, so `limit: 45` and `limit: 50` both silently returned 40. It now\n  widens `ef_search` per query.\n- Embeddings need a provider that has them. See the Anthropic caveat in `SKILL.md`.\n- `forget` is irreversible and gated on a human approval every time.\n\n## Schedules\n\nFiles in `agent/schedules/`. One trap that costs an afternoon:\n\n**`eve dev` does not fire schedules on a clock — it only registers them.** The timer exists\nonly in a built server. Dispatch by hand during development:\n\n```bash\ncurl -X POST http://127.0.0.1:2000/eve/v1/dev/schedules/<name>\n```\n\n## Human-in-the-loop approvals\n\nGated tools park the turn until someone answers. Approvals have **no dedicated HTTP endpoint** —\nthey resolve through the normal continuation route:\n\n```jsonc\n{ \"inputResponses\": [{ \"requestId\": \"…\", \"optionId\": \"approve\" }] }\n```\n\nThe dashboard exposes this as a UI, and `@evestack/mcp` exposes it as a tool that is withheld\nfrom `tools/list` entirely unless `EVESTACK_MCP_ALLOW_CONTROL=1`.\n\n## Evals\n\n`evals/*.eval.ts`, run by eve's eval runner. Three rules that are not guessable:\n\n- **Eval identity comes from the file path.** Authoring an `id` or `name` throws.\n- **Assert on the turn returned by `t.send()`**, not on `t`.\n- A denied tool needs **both** `{ status: \"rejected\" }` and session scope to match.\n\n## Channels\n\n`agent/channels/` ships `eve`, `slack`, `discord` and `telegram`. `channels/eve.ts` calls eve's\n`localDev()` directly, and on the pinned version that is correct — eve 0.30.0 fixed the\n`Host`-header auth bypass upstream by deciding the grant from the process rather than the\nrequest. **Do not re-add the old exact-match loopback wrapper**: on 0.30+ it adds no protection\nand wrongly rejects local dev over a LAN IP, a tunnel, or a container hostname.\n\n## Registry\n\nevestack ships primarily as an eve **registry**, so an existing eve project can adopt one piece\nwithout migrating:\n\n```bash\neve registry add @evestack=https://raw.githubusercontent.com/SammyTourani/evestack/main/registry/r/{name}.json\n```\n\nRegistry items pin dependency versions from `templates/default/package.json`, and CI enforces\nthe match. A bare unversioned name is a bug, not a style choice.\n"
    },
    {
      "path": "references/cli.md",
      "content": "# The `evestack` CLI\n\nOne command, eight verbs. `-h`/`--help` and `-V`/`--version` work anywhere, and **asking for\nhelp never writes, starts or opens anything.**\n\n```\nevestack create [name]     scaffold an agent, a database and a dashboard\nevestack status            is it up? what do I run?\nevestack tour              a guided first run, on a stack that is already up\nevestack open              the dashboard URL and its password, in a browser\nevestack verify            check every part and name the fix for anything broken\nevestack skills            teach your coding agent this project\nevestack attach [dir]      add evestack to an eve project you already have\nevestack doctor            a run stopped moving — read-only forensics\n```\n\nA bare `evestack` inside a project runs `status`. Outside one it prints the command list and\nexits `0` — typing the program's name is not an error. An unrecognised command gets one\nsuggestion when there is a close match (`evestack verfiy` → *Did you mean `evestack verify`?*)\nand never a guess when there is not.\n\n## Picking the right diagnostic\n\nThe three checking commands answer genuinely different questions. Choosing wrong wastes a\nround trip:\n\n| | Question it answers | Cost |\n| --- | --- | --- |\n| `status` | Is it running right now, and where? | four parallel probes, read-only |\n| `verify` | Is it *configured* correctly, part by part? | talks to Docker, Postgres and the ingest route |\n| `doctor` | Everything is up and a run still will not move — why? | reads Postgres, writes nothing |\n\n## `evestack create` / `npx create-evestack`\n\n```bash\nnpx evestack create [name] [--yes] [--verbose]\n```\n\n| Flag | Effect |\n| --- | --- |\n| `[name]` | Project directory. Prompted for if omitted, interactively. |\n| `--yes` / `-y` | Skip prompts, use defaults. Also triggered automatically when stdin is not a TTY (CI, a piped script), where it additionally declines to start containers — nobody is there to say no to a 200 MB pull. |\n| `--verbose` | Raw `npm` and `docker` output instead of one progress row each. |\n\nFour questions, all asked **before** any work starts, so the install and image pull are a wait\nthe user can walk away from: where, which model provider, whether to enable Composio, and\nwhether to bring the stack up.\n\nWhat it generates:\n\n- the agent project from `templates/default`\n- `.env.local` with a **uniquely generated** `EVESTACK_AUTH_PASSWORD` — never a shipped default\n- `.env` with the database password, the only file Compose interpolates from\n- `.gitignore` covering `.env*`\n- `docker-compose.yml` for Postgres, with the dashboard behind a profile\n\n**Exit codes:** `0` only when the project was created *and* its dependencies installed. If\n`npm install` fails, or `node_modules/eve` is missing afterwards, it exits `1` and prints what\nto run — so an `&&` chain stops there rather than walking into an empty `node_modules`. If the\nuser accepts the offer to start the agent, the exit code becomes the agent's.\n\nThe scaffolder has **zero dependencies** — Node's built-in `readline`, `crypto` and `fs` only.\n\n## `evestack status`\n\n```bash\nevestack status [--json]\n```\n\nFour parts: agent, Postgres and dashboard probed in parallel, plus the model configuration read\nfrom `.env.local` rather than called — a status command that spends money is one people stop\ntyping. The fix for anything down is printed under it.\n\nThe Postgres connection is pinned `default_transaction_read_only = on` by the same helper\n`doctor` uses, so it is safe to point at production. It reports two things beyond reachability:\nwhether the workflow schema was ever created (forgetting `db:bootstrap` is a distinct state with\na distinct fix, not a red tick), and how many runs and memories are in it.\n\nWhen Postgres *and* the dashboard are both unreachable it checks Docker before printing two\ncompose commands that would fail, and says that instead.\n\n| Code | Meaning |\n| --- | --- |\n| `0` | Everything this project needs is answering. |\n| `1` | Something is down, and it printed what to run. |\n| `2` | Not an evestack project. |\n\n## `evestack tour`\n\n```bash\nevestack tour [--yes] [--message=TEXT] [--no-open]\n```\n\nA guided first run on a stack that is already up. It sends **one real message** to the agent,\nstreams the reply, then links that same turn in the dashboard.\n\nThat one message is a real model call and costs real money — a fraction of a cent on\n`gpt-5-mini`. Nothing else in the tour calls a model, and it says so before sending.\n\n**With no terminal to ask, it refuses and exits `3`.** Under a pipe, in CI, or with stdin\nclosed, the confirmation has nobody to answer it, so the tour stops rather than treating\nsilence as consent. `--yes` accepts the charge up front.\n\n## `evestack verify`\n\nChecks every part in order and names the fix for whatever is broken. This is the command to\nreach for when a user says \"it isn't working\" without more detail — it turns a vague report into\na specific failing check.\n\nThe checks: config, docker, postgres, schema, pgvector, model, memory, agent, dashboard,\ntraces. The memory check only appears on providers that can embed.\n\n## `evestack doctor`\n\n```bash\nevestack doctor [options]\n```\n\nRead-only forensics for a durable job that stopped moving. It never writes to the database;\nwhen there is something to fix it prints the SQL and lets a human decide. Safe against\nproduction.\n\n| Flag | Default |\n| --- | --- |\n| `--schema=NAME` | `graphile_worker` |\n| `--workflow=NAME` | `workflow` |\n| `--url=URL` | `$WORKFLOW_POSTGRES_URL`, then `$DATABASE_URL` |\n| `--agent-url=URL` | `$EVESTACK_AGENT_URL`, then `http://127.0.0.1:2000` |\n| `--limit=N` | `50` rows per section; counts are never capped |\n| `--probes=N` | `25` sessions probed, `0` to skip |\n| `--idle=MINUTES` | `30` before a quiet session is worth probing |\n| `--timeout=MS` | `15000`, for both `statement_timeout` and HTTP |\n| `--sql` | print only the remediation SQL |\n| `--json` | the whole diagnosis as JSON |\n| `--verbose` | add the raw rows behind each finding |\n\n| Code | Meaning |\n| --- | --- |\n| `0` | Looked, found nothing costing a run right now. |\n| `1` | At least one fault — a stranded run, a wedged job, a wedged session. |\n| `2` | Could not look (no database, wrong schema, bad arguments). |\n\n## `evestack skills`\n\n```bash\nevestack skills [--dir=PATH] [--print] [--force] [--json]\n```\n\nInstalls this pack — the one you are reading — into the user's own agent, so it persists instead\nof living in one conversation. Suggest it once you have been pasted somewhere durable is useful.\n\n| Flag | Effect |\n| --- | --- |\n| `--dir=PATH` | Where to write it. Default: `agent/skills/evestack` inside an eve project, otherwise `.claude/skills/evestack`. |\n| `--print` | Write to stdout, touch no files. |\n| `--force` | Overwrite existing files. Without it, one existing file stops the run and nothing is written. |\n| `--json` | Report what was written, as JSON. |\n\nThe default target is chosen, not guessed: inside a scaffolded project `agent/skills` is a real\nruntime location that eve scans, so the pack becomes loadable by the agent being *built*.\n\nIt fetches the pack from the site rather than carrying a bundled copy, so it cannot go stale —\nwhich means it needs a network connection. Exit `1` names the URL if it cannot reach it.\n\n## `evestack attach`\n\nAdds evestack to an eve project that already exists, rather than scaffolding a new one. Use it\nwhen the user has a working eve app and wants the durable store, sandbox and dashboard around\nit.\n\n## Project scripts\n\nInside a scaffolded project these are npm scripts, not CLI verbs:\n\n```bash\nnpm run db:bootstrap    # create the workflow schema — required once, before first dev\nnpm run dev             # eve dev on EVESTACK_AGENT_PORT\nnpm run verify          # same checker as `evestack verify`\n```\n"
    },
    {
      "path": "references/dashboard.md",
      "content": "# The dashboard\n\nA Next.js app shipped as a container image, `ghcr.io/sammytourani/evestack-dashboard`, pinned in\nthe generated `docker-compose.yml` to the version tested with that template. Multi-arch\n(`linux/amd64` + `linux/arm64`, ~204 MB compressed), so the same command works on Apple Silicon\nand on an x86 server.\n\n```bash\ndocker compose --profile dashboard up -d\n```\n\nIt is a compose profile **in the generated project** — not a separate clone, no image to build,\nno credential to copy across. To run a fork or a private build, set `EVESTACK_DASHBOARD_IMAGE`\nin a `.env` beside the compose file.\n\n## What it reads\n\n`workflow.workflow_runs.attributes` (JSONB) directly over SQL. That column holds eve's own\n`$eve.*` run tags — the same data behind Vercel's Agent Runs. **There is no ingest step for the\nsession list.** OTLP ingest at `/api/ingest/v1/traces` is a second tier used only for prompt\nbodies and tool arguments, which do not exist in the SQL tags.\n\nCost is computed client-side from token counts (`lib/pricing.ts`), because eve only reports\n`gen_ai.usage.cost` for AI-Gateway-routed calls, which a self-hosted agent never makes.\n\nTwo facts that trip people reading the data directly:\n\n- **A failed turn still records `status='completed'`.** The absence of `$eve.model` is the only\n  failure signal.\n- **Rows without `$eve.type` are internal noise** and must be filtered out.\n\n## Pages\n\n`sessions` · `chat` · `costs` · `approvals` · `memory` · `skills` · `sandboxes` · `schedules` ·\n`integrations` · `evals` · `monitors` · `traces` · `charts`\n\n## Auth\n\nEvery route is behind `EVESTACK_AUTH_USER` / `EVESTACK_AUTH_PASSWORD` from `.env.local`. It\nfails closed — the dashboard starts agent runs, approves gated shell commands and deletes\nmemories, so serving a viewer to anyone who reaches the port is not an acceptable default.\n\nScripts use HTTP Basic:\n\n```bash\ncurl -u \"$EVESTACK_AUTH_USER:$EVESTACK_AUTH_PASSWORD\" localhost:4000/api/fleet\n```\n\n## HTTP API\n\nRead:\n\n| Route | Returns |\n| --- | --- |\n| `GET /api/health` · `/api/health/detail` | liveness; detailed state incl. the five most recent sessions |\n| `GET /api/fleet` | fleet overview |\n| `GET /api/budget` | caps, per-principal daily spend, stops, lifetime totals |\n| `GET /api/approvals` | who decided what, and how identity was established |\n| `GET /api/alerts` | alert state |\n| `GET /api/metrics/query` | metrics |\n| `GET /api/skills` · `/api/skills/[name]` | the scanned skills directory |\n| `POST /api/evals/promote/[id]` | generates eval source from a session; writes nothing |\n\nMutating:\n\n| Route | Effect |\n| --- | --- |\n| `POST /api/control/sessions` | starts a real run — spends money |\n| `POST /api/control/sessions/[id]/message` | another turn — spends money |\n| `POST /api/control/sessions/[id]/approve` | **runs the gated tool for real**; audited |\n| `POST /api/control/sessions/[id]/cancel` | cooperative stop; the in-flight call still bills |\n| `POST /api/control/sessions/[id]/fork` | fork a session |\n| `DELETE /api/memories/[id]` | irreversible |\n\n## The Skills page and its one honest caveat\n\nThe page scans a skills directory and reports which one it found and how. In the **published\nimage** the working directory is `/repo/packages/dashboard`, so `<cwd>/agent/skills` does not\nexist and it falls back to the template's skills bundled inside the image. That bundled skill is\nalso called `memory-hygiene` — the same name the scaffolder writes into a real project — so the\npage can look like it is reading the user's agent when it is not.\n\n`resolvedBy: \"bundled-template\"` and the absolute path are both rendered, which is what keeps\nthis honest rather than silent. To scan the real one, mount it and set the env var — the\ngenerated compose already gives the dashboard `env_file: .env.local`, so the mount is the\nmissing half:\n\n```yaml\nenvironment:\n  EVESTACK_SKILLS_DIR: /agent-skills\nvolumes:\n  - ./agent/skills:/agent-skills:ro\n```\n\n## `@evestack/mcp`\n\nThe same control plane spoken as MCP, so Claude Code or any MCP client can ask questions in\nEnglish. It is a **thin client over the dashboard's HTTP routes** — no database connection, no\nSQL, no price table, no copy of eve's protocol. If the dashboard has no route for something,\nthis package has no tool for it.\n\n```jsonc\n{\n  \"mcpServers\": {\n    \"evestack\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@evestack/mcp\"],\n      \"env\": { \"EVESTACK_MCP_DASHBOARD_URL\": \"http://localhost:4000\" }\n    }\n  }\n}\n```\n\nRead-only tools: `list_sessions`, `get_session`, `list_approvals`, `get_costs`,\n`promote_session_to_eval`.\n\n**The four mutating tools — `start_session`, `send_message`, `approve_or_deny`, `cancel_run` —\nare withheld from `tools/list` entirely** unless `EVESTACK_MCP_ALLOW_CONTROL=1`. A model cannot\nplan around a capability it has never been told exists, and the gate is an environment variable\nread once at launch, before any client input is parsed.\n\nNever advise a user to set that flag without saying what it means: it lets a model approve a\ngated tool call a human was asked to stand at, which is the entire reason eve pauses the turn.\n"
    },
    {
      "path": "references/troubleshooting.md",
      "content": "# When it will not run\n\nWork down this list. Most reports of \"it's broken\" are one of the first four.\n\n## First: run the checker\n\n```bash\nnpm run verify        # or: evestack verify\n```\n\nIt checks config, docker, postgres, schema, pgvector, model, memory, agent, dashboard and\ntraces, and names the fix for whatever failed. Turning a vague report into a named failing check\nis worth more than any guess made from a description.\n\nIf the stack is up but a *run* is stuck, that is a different command:\n\n```bash\nevestack doctor       # read-only forensics; prints SQL, writes nothing\n```\n\n## `ECONNREFUSED` on bootstrap\n\nAlmost always `npx --package=@workflow/world-postgres bootstrap` instead of\n`npm run db:bootstrap`. The upstream CLI reads `.env` through dotenv and never `.env.local`,\nwhich is the only env file the scaffolder writes, so it falls back to\n`postgres://world:world@localhost:5432/world`.\n\n## `npm run dev` starts but nothing persists\n\nThe `workflow` schema was never created. Nothing creates it implicitly —\n`@workflow/world-postgres` runs its migrations only from its own CLI and eve never invokes it.\n\n```bash\ndocker compose up -d postgres\nnpm run db:bootstrap\n```\n\n`evestack status` reports this as its own state rather than a generic red tick, because the fix\nis specific.\n\n## `EADDRINUSE` on boot\n\n`npm run dev` passes `EVESTACK_AGENT_PORT` to `eve dev` as `--port`, and **eve only scans for a\nfree port when no port is given at all**. So there is no auto-increment: if something grabbed\nthat port since scaffolding, the boot fails rather than moving.\n\nFree the port, or change `EVESTACK_AGENT_PORT` in `.env.local` — and if you change it, change\nthe `EVESTACK_AGENT_URL` default in `docker-compose.yml` to match, because the scaffolder wrote\nthat number in at generation time.\n\nRelated: a stale server can hold a port while looking dead. Kill by port, not by name —\n`pkill -f \"next start\"` does not match the process, which reports as `next-server`:\n\n```bash\nlsof -ti:3000 | xargs kill -9\n```\n\n## Two scaffolds fighting over one database\n\nEvery generated `docker-compose.yml` used to hardcode `name: evestack`. Compose treats `name:`\nas project identity, so a second scaffold recreated the first project's `evestack-postgres-1`\nand both agents shared one database. It is derived from the project directory now — but two\nscaffolds still both want host port 5433, which is a loud failure by design.\n\n**A related trap when cleaning up:** `docker ps --filter name=X` is a *substring* match, not an\nexact one. It will happily match and stop a container you did not mean.\n\n## Dashboard unhealthy, everything 503s except `/signin`\n\nThe credential did not reach the container. The compose service reads `.env.local`; both\n`EVESTACK_AUTH_USER` and `EVESTACK_AUTH_PASSWORD` are required and a blank value counts as\nunset.\n\nBlank values are a recurring shape here: `process.env.X ?? DEFAULT` does **not** fall back on an\nempty string. One blank line in `.env.local` was enough to set `EVESTACK_MODEL`,\n`OLLAMA_BASE_URL` and `EVESTACK_CONTEXT_WINDOW` (where `Number(\"\") === 0`) to nothing. Reads\nare `?.trim() || DEFAULT` now.\n\n## The agent dies at boot naming AI Gateway context-window metadata\n\n`EVESTACK_PROVIDER` is missing or misspelled. A model name without the provider leaves the agent\non the previous provider — usually `openai` — which then receives a local model name. Unset\nmeans `openai` because that is a choice; misspelled is a hard error because that is a mistake.\n\n## `remember` / `recall` do nothing\n\nThe provider has no embeddings model. Anthropic has none at all — that path needs an\n`OPENAI_API_KEY` alongside it, or `EVESTACK_EMBED_PROVIDER=ollama`. On Ollama, embeddings are a\n**second pull**: `ollama pull nomic-embed-text`. The first `remember` call names the variable\nthat fixes it.\n\nIf `recall` returns fewer rows than requested, it is bounded by `hnsw.ef_search`, not by the\ndata.\n\n## A denied tool approval kills the session\n\n`vercel/eve#1658` — denying a tool approval permanently fails the durable session on affected\ncombinations. The mechanism: `output.type=\"execution-denied\"` is unmappable by older\n`@ai-sdk/openai`, which yields `output: undefined` and an OpenAI 400.\n\nBisected: `@ai-sdk/openai` **2.0.117 reproduces**, **4.0.30 survives**. `execution-denied` is in\n`@ai-sdk/provider` 4.0.5 and absent from 2.0.3. eve declares no peer range on `@ai-sdk/openai`,\nso an app on `ai@7` can still resolve v2 and hit it. The template pins `^4.0.0`; the\n`wrapLanguageModel` middleware in `agent.ts` stays as a no-op on the shipped stack because older\npeers are still admissible elsewhere.\n\n## After upgrading eve\n\n**Contracts and typecheck going green is not sufficient.** They pin route strings and module\nexports, not response bodies. eve 0.31.x removed `continuationToken` from the HTTP session\nbodies — it moved to the `session.waiting` stream event — and that shipped as a live break while\nevery static check stayed green.\n\nOnly the live seam probes catch this class:\n\n```bash\nnode contract/runtime/run.mjs --require=dashboard,agent,postgres --only=seam\n```\n\nNever accept an eve bump on contracts + tsc green alone.\n\n## A run that will not move, with everything up\n\n```bash\nevestack doctor --verbose\nevestack doctor --sql        # remediation SQL only\n```\n\nOne shape worth recognising, because it bricks a deployment permanently rather than failing one\nrun: `status`, `completed_at`, `output_cbor` and `error_cbor` are **one value in four columns**.\n`WorkflowRunSchema` is a discriminated union whose pending/running branch declares all three of\nthe latter `undefined`, and startup parses *every* `status='running'` row before filtering —\noutside the try. So a single row with `running` + `completed_at` + `output_cbor` stops the world\nfrom starting.\n\nWrite the whole branch in one statement, or not at all. Repair SQL is in\n`docs/troubleshooting.mdx`, and it needs the `::workflow.status` cast because the column is an\nenum.\n\n## Ollama took the machine down\n\nNot a figure of speech. Loading a multi-gigabyte model alongside Docker, Postgres and the\ndashboard has exhausted an 8 GB host and shut the desktop down. Budget model size plus ~4 GB\nfree, and use a hosted key for any end-to-end test — the local path is not needed to verify\nevestack itself.\n"
    }
  ]
}