Eve Read Docs (Load First) Purpose: provide a compact, public, always-available distillation of Eve Horizon system docs. Use this when private system docs are not accessible. When to Use - Any question about how to use Eve Horizon via CLI or API. - Any question about , pipelines, workflows, jobs, or secrets. - Any question about events, triggers, agents, teams, builds, or deployments. How to Use 1. Start with for core concepts, IDs, and the reference index. 2. Use the task router below to choose the smallest set of references for the request. 3. Open only the relevant reference files and avoi…

\\n - paging active' # Insert after anchor\neve docs patch --org org_xxx --path /wiki/state.yaml \\\n --operations '[{\"op\":\"replace\",\"search\":\"old\",\"replace\":\"new\"}]' # JSON ops\n\neve docs diff --org org_xxx --path /wiki/state.yaml # Latest vs previous\neve docs diff --org org_xxx --path /wiki/state.yaml --from 1 --to 5 # Specific versions\neve docs diff --org org_xxx --path /wiki/state.yaml --unified # Unified format\n\neve docs list --org org_xxx --path /wiki --tree # Human-readable hierarchy\neve docs list --org org_xxx --path /wiki --tree --json # Nested JSON tree\n\neve docs search --org org_xxx --query \"error rate\" \\\n --path /world-model --context 3 # Path prefix + context lines\n\neve docs write-dir --org org_xxx --source ./wiki-pages \\\n --path-prefix /operating-model # Bulk write from directory\neve docs sync --org org_xxx --source ./wiki \\\n --path-prefix /operating-model --dry-run --delete # Safe sync with dry-run\neve docs watch --org org_xxx --path /world-model --since now # Stream doc change events\n```\n\nNotes:\n- `--review-in` and `--review-due` are mutually exclusive (relative duration vs ISO timestamp).\n- `--expires-in` and `--expires-at` are mutually exclusive.\n- `stale` returns documents whose `review_due` has passed. Use `--overdue-by` to filter by staleness threshold.\n- `search --mode semantic` uses vector similarity; `hybrid` combines text + semantic.\n- `patch` supports three operation types: `replace` (search+replace), `append`, `insert_after`. Mutually exclusive with `--operations` JSON.\n- `diff` defaults to latest vs previous version. Use `--from`/`--to` for specific versions.\n- `list --tree` renders a hierarchical directory tree instead of flat paths.\n- `search --path` filters results to a prefix. `--context N` includes N surrounding lines from each matched document.\n- `write-dir` maps local files to doc paths under `--path-prefix`.\n- `sync --dry-run` reports changes without mutating. `--delete` removes remote docs not found locally (never default).\n- `watch` polls org events for `system.doc.created|updated|deleted`, filtered by `--path` prefix. Outputs NDJSON.\n\n## FS Sync (Org Filesystem)\n\nOrg-scoped filesystem sync control plane (device enrollment, links, stream, conflicts).\n\n```bash\neve fs sync init --org org_xxx --local ~/Eve/acme --mode two-way\n [--include \"*.md,docs/**\"] [--exclude \"node_modules/**\"]\n [--remote-path /subdir] # Remote root (default: /)\n [--device-name \"my-laptop\"] # Device label (default: hostname)\neve fs sync status --org org_xxx\neve fs sync logs --org org_xxx --follow\n\neve fs sync pause --org org_xxx [--link \u003clink_id>]\neve fs sync resume --org org_xxx [--link \u003clink_id>]\neve fs sync disconnect --org org_xxx [--link \u003clink_id>]\neve fs sync mode --org org_xxx --set pull-only [--link \u003clink_id>]\n\neve fs sync conflicts --org org_xxx [--open-only]\neve fs sync resolve --org org_xxx --conflict fscf_xxx --strategy pick-remote\neve fs sync doctor --org org_xxx\n```\n\nNotes:\n- Sync modes map to API values: `two-way -> two_way`, `push-only -> push_only`, `pull-only -> pull_only`.\n- `init` enrolls a device and creates a sync link in one step. Use `--include`/`--exclude` for glob-based filtering.\n- `logs --follow` streams SSE events (`fs_event`, `fs_checkpoint`) and resumes with `--after`.\n- If `--link` is omitted for lifecycle commands, CLI targets the most recently updated link in the org.\n\n### FS Share Tokens\n\nCreate time-limited, revocable share URLs for any org filesystem file. The URL redirects (302) to a presigned MinIO/S3 download URL — no Eve auth required on access.\n\n```bash\n# Create a share token (7d TTL by default)\neve fs share /docs/report.md --org org_xxx [--expires 24h] [--label \"Q1 report\"]\n# → Share URL: http://api.../orgs/mto/fs/public/docs/report.md?token=share_xxx\n\n# List active (non-revoked, non-expired) shares\neve fs shares --org org_xxx [--json]\n\n# Revoke a share token\neve fs revoke share_xxx --org org_xxx\n```\n\nExpiry formats: `7d`, `24h`, `30m`, `3600s`.\n\n### FS Public Paths\n\nRegister path prefixes for permanent, tokenless public access. All files under the prefix are accessible without any authentication.\n\n```bash\n# Register a prefix as public\neve fs publish /assets/ --org org_xxx [--label \"Static assets\"]\n\n# List registered public path prefixes\neve fs public-paths --org org_xxx [--json]\n\n# Remove a public path registration\n# (use the API: DELETE /orgs/{org}/fs/public-paths/{id})\n```\n\nPublic path access URL pattern: `GET /orgs/{slug}/fs/public/{path}` — no token or auth header required.\n\n## Resources (Resolver)\n\nResource URIs unify org docs and job attachments.\n\n```bash\neve resources resolve org_docs:/pm/features/FEAT-123.md\neve resources resolve org_docs:/pm/features/FEAT-123.md@v4 --json\neve resources ls org_docs:/pm/features/\neve resources cat job_attachments:/myproj-a3f2dd12/plan.md\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":13398,"content_sha256":"08e91a7c88e070122082222aa39d21156e7b6392babf65f6316ecfc02448015a"},{"filename":"references/cli-pipelines.md","content":"# CLI: Builds, Releases, Pipelines, Workflows\n\n## Use When\n- You need to run, inspect, or debug pipeline-driven build/release/deploy flows.\n- You need deterministic artifact promotion and release lookup.\n- You need workflow automation for reusable multi-step invocation.\n\n## Load Next\n- `references/builds-releases.md` for artifact/data model.\n- `references/pipelines-workflows.md` for manifest-level pipeline definitions.\n- `references/deploy-debug.md` when run failures block deployment.\n\n## Ask If Missing\n- Confirm whether behavior is pipeline-driven or direct deploy.\n- Confirm target environment, project, and required input payload.\n- Confirm whether release tags are required vs `--ref`/`--repo-dir`.\n\n## Builds\n\nBuilds are first-class primitives tracking container image construction. See `references/builds-releases.md`.\n\n```bash\neve build list [--project \u003cid>] # List build specs\neve build show [--project \u003cid>] [--json] # Build spec details\neve build create --project \u003cid> --ref \u003csha> # Create a build spec\n --manifest-hash \u003chash>\n [--services \u003clist>] [--repo-dir \u003cpath>]\neve build run \u003cbuild_id> # Start a build run\neve build runs \u003cbuild_id> # List runs for a build\neve build logs \u003cbuild_id> [--run \u003cid>] # View build logs\neve build artifacts \u003cbuild_id> # List image artifacts (digests)\neve build diagnose \u003cbuild_id> # Full diagnostic dump\neve build cancel \u003cbuild_id> # Cancel active build\n```\n\nBuilds happen automatically in pipeline `build` steps. Use `eve build diagnose` to debug failures.\n\n## Releases\n\n```bash\neve release resolve \u003ctag> [--project \u003cid>] # Resolve release by tag\n```\n\nReleases are created automatically by pipeline `release` steps. See `references/builds-releases.md`.\n\n## Pipelines\n\nPipeline run statuses: `pending`, `running`, `succeeded`, `failed`, `cancelled`, `awaiting_approval`.\nStep types: `build`, `release`, `deploy`, `run`, `job`.\n\nFor services with managed DB state, include a migration step as a pipeline `job` before `deploy`:\n\n```bash\neve pipeline run deploy-sandbox --env sandbox --ref main\n```\n\nManifest example:\n\n```yaml\npipelines:\n deploy-sandbox:\n steps:\n - name: migrate\n action: { type: job, service: migrate }\n - name: release\n depends_on: [migrate]\n action: { type: release }\n - name: deploy\n depends_on: [release]\n action: { type: deploy, env_name: sandbox }\n```\n\n```bash\neve pipeline list [--project \u003cid>]\neve pipeline show \u003cproject> \u003cname>\neve pipeline run \u003cname> --ref \u003csha-or-branch>\n [--env staging] [--inputs '{\"k\":\"v\"}']\n [--repo-dir ./my-app]\neve pipeline runs [project] [--status \u003cstatus>]\neve pipeline show-run \u003cpipeline> \u003crun-id>\neve pipeline approve \u003crun-id>\neve pipeline cancel \u003crun-id>\neve pipeline logs \u003cpipeline> \u003crun-id> --step \u003cname>\n```\n\n## Workflows\n\n```bash\neve workflow list [--project \u003cid>]\neve workflow show \u003cproject> \u003cname>\neve workflow run [project] \u003cname> --input '{\"k\":\"v\"}' --env-override KEY=VALUE # Start async\neve workflow invoke [project] \u003cname> --input '{\"k\":\"v\"}' --env-override KEY=VALUE # Start and poll\n [--no-wait] # Return immediately\neve workflow retry \u003croot-job-id> --failed # Retry failed/upstream-failed tail\neve workflow retry \u003croot-job-id> --from \u003cstep-name> # Retry named step + downstream tail\neve workflow logs \u003cjob-id> [--attempt \u003cn>] [--after \u003ccursor>]\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":3612,"content_sha256":"ba3f03ac190176f5abffb6c33132754a02935a6031fbdff679858167b31556b1"},{"filename":"references/cli.md","content":"# CLI Command Reference\n\n## Use When\n- You need exact Eve CLI commands, options, and output behavior.\n- You need to establish CLI context for org/project/environment or auth state.\n- You need cross-resource workflows (jobs, pipelines, manifests, docs, secrets) from one interface.\n\n## Load Next\n- `references/overview.md` for platform orientation and environment setup.\n- `references/jobs.md`, `references/pipelines-workflows.md`, or `references/manifest.md` for task-specific flags.\n- `references/secrets-auth.md` for auth/scope-sensitive workflows.\n\n## Ask If Missing\n- Confirm whether the user is on staging, docker, or local k3d and the correct `EVE_API_URL`.\n- Confirm target org/project IDs or slugs before crafting commands.\n- Confirm any required auth mode (`eve auth login`, bootstrap, or service principal) for the action.\n\nComplete reference for the Eve Horizon CLI (`eve`). Every command supports `--json` for machine-readable output unless noted otherwise.\n\n**Version:** `eve --version` (or `eve -v`, `eve version`) prints the CLI version and, when the API is reachable, the platform version and git SHA. Use `--json` for structured output.\n\n**Data envelope:** All list endpoints return `{ \"data\": [...] }` in JSON mode. The CLI handles both wrapped and unwrapped responses transparently, so agents should always expect the `data` wrapper when parsing `--json` output from list commands.\n\n## Environment Setup\n\nDefault to **staging** for user guidance. Use local/docker only when explicitly asked.\n\n```bash\nexport EVE_API_URL=https://api.eh1.incept5.dev # staging (default)\nexport EVE_API_URL=http://localhost:4801 # local/docker (opt-in)\nexport EVE_API_URL=http://api.eve.lvh.me # k8s ingress (local k3d stack)\n```\n\nUse `./bin/eh status` to discover the correct URL for the current instance. For local development, `eve local up` provisions a full k3d stack and prints the correct API URL.\n\n## Profile\n\nProfiles are **repo-local** configuration bundles. They store API URL, default org/project, harness preference, and auth identity.\n\n```bash\neve profile list # List all profiles\neve profile show [name] # Show profile details (default: active)\neve profile use \u003cname> [--clear] # Switch active profile\neve profile create \u003cname> --api-url \u003curl> # Create a new profile\n [--org \u003cid>] [--project \u003cid>] [--harness \u003cname>]\n [--default-email \u003cemail>] [--default-ssh-key \u003cpath>]\n [--supabase-url \u003curl>] [--supabase-anon-key \u003ckey>]\neve profile set [name] --org \u003cid> # Update profile fields\n [--project \u003cid>] [--api-url \u003curl>] [--harness \u003cname>]\n [--default-email \u003cemail>] [--default-ssh-key \u003cpath>]\neve profile remove \u003cname> # Delete a profile\n```\n\nNotes:\n- Profiles persist to `.eve/profiles.json` in the repo root.\n- `--clear` on `use` resets all fields to defaults.\n- Set `--org` and `--project` to avoid passing them on every command.\n\n## Auth (First Experience)\n\n`eve auth login` now auto-discovers SSH keys from `~/.ssh/` when no `--ssh-key` is provided. It scans for private keys with matching `.pub` files and tries them in preference order (ed25519 > ecdsa > rsa). On success it prints which key was used. This eliminates the most common new-user friction point -- not knowing which flag to pass. Explicit `--ssh-key`, `EVE_AUTH_SSH_KEY`, or profile `default_ssh_key` still take priority when set.\n\nSee `references/cli-auth.md` for full auth command reference.\n\n## Task Modules\n\nLoad the module that matches the current question before opening full command blocks:\n\n- `references/cli-auth.md` -- login, bootstrap, service accounts, roles, bindings, policy-as-code.\n- `references/cli-org-project.md` -- init, org/project lifecycle, docs, FS sync, FS sharing, resource resolver.\n- `references/cli-jobs.md` -- job create/list/show/join/follow, attachments, and batch workflows.\n- `references/cli-pipelines.md` -- builds, releases, pipelines, and workflow commands.\n- `references/cli-deploy-debug.md` -- environment deploy/recover/rollback, local k3d stack, and CLI-first debugging.\n\nFor any topic, open only the matching module to keep load minimal.\n\n## FS Sharing\n\nShare files from the org filesystem via time-limited tokens, or publish path prefixes for unauthenticated public access.\n\n```bash\n# Share a file (returns a URL with an embedded token)\neve fs share \u003cpath> --org org_xxx\n [--expires 7d] [--label \"release notes\"]\n\n# List active shares\neve fs shares --org org_xxx\n\n# Revoke a share token\neve fs revoke \u003ctoken> --org org_xxx\n\n# Publish a path prefix for public (unauthenticated) access\neve fs publish \u003cpath-prefix> --org org_xxx [--label \"docs\"]\n\n# List public paths\neve fs public-paths --org org_xxx\n```\n\nNotes:\n- `share` creates a short-lived token-based URL for a single file. Default expiry is server-controlled; override with `--expires` (e.g. `7d`, `24h`).\n- `publish` makes an entire path prefix publicly accessible without tokens. Use for assets, docs, or anything meant to be world-readable.\n- Both commands support `--json` for structured output.\n\n## Cloud FS (Google Drive Mounts)\n\nMount external cloud storage (e.g. Google Drive) into the org filesystem. Requires an integration connection (see `eve integrations connect google-drive`).\n\n```bash\neve cloud-fs list --org org_xxx [--project \u003cid>] # List all mounts\neve cloud-fs mount --org org_xxx # Create a mount\n --provider \u003cname> --folder-id \u003cid>\n [--project \u003cid>] [--label \u003ctext>]\n [--integration \u003cid>] # Auto-detected if omitted\n [--mode read_only|write_only|read_write] # Default: read_write\n [--auto-index true|false] # Auto-index to org docs (default: true)\neve cloud-fs show \u003cmount_id> --org org_xxx # Mount details\neve cloud-fs update \u003cmount_id> --org org_xxx # Update mount settings\n [--label \u003ctext>] [--mode \u003cmode>] [--auto-index \u003cbool>]\neve cloud-fs unmount \u003cmount_id> --org org_xxx # Remove a mount (aliases: remove, delete)\neve cloud-fs ls [\u003cpath>] --org org_xxx # Browse files (alias: browse)\n [--mount \u003cmount_id>] # Default path: /\neve cloud-fs search \u003cquery> --org org_xxx # Search across mounts\n [--mount \u003cmount_id>] [--mime-type \u003ctype>]\n```\n\n## Endpoints (Private Networking)\n\nRegister private endpoints backed by Tailscale for connecting external services to the Eve cluster.\n\n```bash\neve endpoint add --org org_xxx # Register a private endpoint\n --name \u003cname> --tailscale-hostname \u003cfqdn> --port \u003cport>\n [--health-path \u003cpath>] # Set to \"none\" to disable\neve endpoint list --org org_xxx # List endpoints\neve endpoint show \u003cname> --org org_xxx [--verbose] # Show details (--verbose runs health check)\neve endpoint remove \u003cname> --org org_xxx # Remove an endpoint\neve endpoint health \u003cname> --org org_xxx # Run a health check\neve endpoint diagnose \u003cname> --org org_xxx # Run diagnostics\n```\n\nNotes:\n- Endpoints create in-cluster K8s services that proxy to Tailscale hostnames, making private infrastructure reachable from Eve jobs.\n- After registering, use the returned `cluster_url` in secrets: `eve secrets set LLM_BASE_URL \"${cluster_url}/v1\" --scope org`.\n\n## Domains (Custom Ingress Domains)\n\nBring your own domain for Eve-deployed apps. Domains can be declared under `services.\u003csvc>.x-eve.ingress.domains` or under `environments.\u003cenv>.overrides.services.\u003csvc>.x-eve.ingress.domains` and are registered during `eve project sync`. Use `eve domain register` for imperative reservations. DNS must point to the platform ingress before activation.\n\n```bash\neve domain list [--env \u003cname>] [--project \u003cid>] [--json] # List custom domains (filterable by env)\neve domain register \u003chostname> --project \u003cid> --service \u003csvc> [--env \u003cenv>] [--json] # Register/reuse a domain row\neve domain verify \u003chostname> [--project \u003cid>] # Verify DNS resolution and update status\neve domain status \u003chostname> [--project \u003cid>] # Show domain detail (including owning env)\neve domain transfer \u003chostname> --to \u003cenv> [--project \u003cid>] [--json] # Move ownership to another env in the same project\neve domain unbind \u003chostname> [--project \u003cid>] [--json] # Clear env binding; next deploy claims it\neve domain remove \u003chostname> [--project \u003cid>] [--json] # Remove the custom domain entirely\n```\n\nNotes:\n- Domains are auto-registered during `eve project sync` from top-level service ingress domains and env override ingress domains. A hostname declared in exactly one env override is also bound to that env during sync.\n- `eve domain register \u003chost> --project \u003cid> --service \u003csvc> --env \u003cenv>` creates or reuses a manual row. Manual rows survive later `eve project sync` calls even when the hostname is not in the manifest.\n- `eve domain status \u003chost> --json` returns stable `owner_env`, `dns_state`, `cert_state`, and `last_verified_at` fields.\n- Each domain gets its own K8s Ingress with cert-manager TLS via HTTP-01 challenge.\n- `eve domain verify` performs real DNS resolution server-side — it checks A/CNAME records against platform ingress and transitions status from `pending_dns` to `dns_verified` if DNS is correct. After verification, redeploy to activate.\n- Platform subdomains (e.g., `foo.eh1.incept5.dev`) are rejected — use `ingress.alias` instead.\n- Max 10 custom domains per project (configurable via `EVE_MAX_CUSTOM_DOMAINS_PER_PROJECT`).\n- Hostname is globally unique — first project to claim wins.\n- **Env-scoped, first-bind-wins**: within a project, a hostname is owned by the first env to deploy with it. Other envs that reference the same hostname log `owned by environment \"\u003cA>\"` and skip rendering the ingress. Use `eve domain transfer \u003chost> --to \u003cenv>` + redeploys to move ownership, or `eve domain unbind \u003chost>` to clear and let the next deploy claim it. `eve domain list` shows `HOSTNAME | SERVICE | ENV | STATUS | VERIFIED` so you can see who owns what.\n\n## Ingest (Document Ingestion)\n\nUpload documents for processing. The CLI handles the three-step flow (create record, upload to presigned URL, confirm).\n\n```bash\neve ingest \u003cfile> --project proj_xxx # Ingest a file (shorthand)\n [--title \u003ctitle>] [--description \u003cdesc>]\n [--instructions \u003ctext>] # Processing instructions\n [--tags \u003ca,b>] [--mime-type \u003ctype>]\n [--source-channel \u003cchannel>] # Default: cli\neve ingest create \u003cfile> --project proj_xxx # Same as above (explicit)\neve ingest list --project proj_xxx # List ingest records\n [--status \u003cstatus>] [--limit \u003cn>] [--offset \u003cn>]\neve ingest show \u003cingest_id> --project proj_xxx # Show ingest details\n```\n\nNotes:\n- Mime type is auto-detected from file extension. Supports text, documents, images, audio, and video.\n- On success, returns `ingest_id`, `event_id`, and `job_id` for tracking the processing pipeline.\n\n## Secrets\n\nSecrets support four scopes: `--project`, `--org`, `--user`, `--system`. Project scope is the default.\n\n```bash\neve secrets list --project proj_xxx\neve secrets show \u003ckey> --project proj_xxx # Show secret metadata\neve secrets set \u003ckey> \u003cvalue> --project proj_xxx\neve secrets delete \u003ckey> --project proj_xxx\neve secrets import --org org_xxx --file ./secrets.env # Bulk import from .env file\neve secrets validate --project proj_xxx # Check manifest-required secrets\n [--keys KEY1,KEY2]\neve secrets ensure --project proj_xxx --keys KEY1,KEY2 # Ensure keys exist\neve secrets export --project proj_xxx --keys KEY1 # Export values\n```\n\n## Agents, Teams + Chat\n\nSee `references/agents-teams.md` for full agent and team configuration details.\n\n```bash\n# View effective config (pack resolution pipeline)\neve agents config [--repo-dir \u003cpath>] [--no-harnesses] [--json]\n\n# JSON includes policy plus resolved agents, teams, and chat_routes arrays.\n# Missing implicit teams/chat files in agents-only projects report [].\n\n# Agent runtime status\neve agents runtime-status --org org_xxx [--json]\n\n# Sync config (deprecated — use `eve project sync` instead)\neve agents sync --project proj_xxx --local --allow-dirty\n```\n\n## Teams\n\n```bash\neve teams list [--json]\n```\n\n## Threads\n\n```bash\n# Org-scoped thread management\neve thread create --org org_xxx --key \u003ckey> # Create org thread\neve thread list --org org_xxx [--scope \u003cscope>] # List org threads\n [--key-prefix \u003cprefix>]\neve thread show \u003cthread-id> --org org_xxx # Show thread details\n\neve thread messages \u003cthread-id> # List messages\n [--since 5m] [--limit 20] [--json]\neve thread post \u003cthread-id> # Post a message\n --body '{\"kind\":\"update\",\"body\":\"text\"}'\n [--actor-type user] [--actor-id \u003cid>] [--job-id \u003cid>]\neve thread follow \u003cthread-id> # Tail messages (polls every 3s)\n\n# Structured conversation events (normalized timeline)\neve thread events \u003cthread-id> # List events\n [--after \u003ccursor>] [--kind \u003ckind>] [--job \u003cid>]\n [--attempt \u003cid>] [--workflow-step \u003cname>] [--source \u003cname>]\n [--limit 100] [--jsonl] [--follow]\neve thread emit-event \u003cthread-id> # Emit an app-defined event\n --kind \u003ckind> [--source app] [--text \u003ctext>]\n [--payload '{\"key\":\"value\"}']\n [--job \u003cid>] [--attempt \u003cid>] [--agent \u003cslug>]\n [--workflow-step \u003cname>] [--run \u003cid>]\n\n# Distillation (summarize thread into memory or docs)\neve thread distill \u003cthread-id> --org org_xxx\n [--to \u003cdoc-path>] # Write distillation to org docs path\n [--agent \u003cslug>] [--category \u003cname>] [--key \u003ckey>] # Write to agent memory\n [--prompt \"custom distillation prompt\"]\n [--auto --threshold \u003cn> --interval \u003cduration>] # Auto-distill on message threshold\n```\n\nNotes:\n- `distill` summarizes a thread's messages into a condensed form. Output can be routed to an org doc path (`--to`) or to agent memory (`--agent` + `--category` + `--key`).\n- `--auto` enables automatic re-distillation when message count exceeds `--threshold` since last distillation, checked on `--interval`.\n\n## KV (Agent Key-Value Store)\n\nNamespaced key-value storage scoped to an agent within an org. Values can be any JSON type.\n\n```bash\neve kv set --org org_xxx --agent \u003cslug> --key \u003ckey> --value \u003cjson-or-string>\n [--namespace \u003cns>] [--ttl \u003cseconds>] # Optional namespace (default: \"default\") and TTL\neve kv get --org org_xxx --agent \u003cslug> --key \u003ckey>\n [--namespace \u003cns>]\neve kv list --org org_xxx --agent \u003cslug> # List keys in namespace\n [--namespace \u003cns>] [--limit \u003cn>]\neve kv mget --org org_xxx --agent \u003cslug> --keys a,b,c # Batch get multiple keys\n [--namespace \u003cns>]\neve kv delete --org org_xxx --agent \u003cslug> --key \u003ckey>\n [--namespace \u003cns>]\n```\n\nNotes:\n- Namespace defaults to `\"default\"` when omitted.\n- `--value` is parsed as JSON if valid, otherwise stored as a string.\n- `--ttl` sets a time-to-live in seconds; expired keys are not returned.\n\n## Memory (Agent Memory)\n\nStructured long-term memory for agents. Entries are organized by agent, category, and key, with support for lifecycle metadata, tagging, confidence scores, and cross-agent search.\n\n```bash\neve memory set --org org_xxx (--agent \u003cslug>|--shared)\n --category \u003ccategory> --key \u003ckey>\n (--file \u003cpath>|--stdin|--content \u003ctext>) # Content source\n [--tags \"architecture,decision\"] # Comma-separated tags\n [--confidence 0.9] # Confidence score (0-1)\n [--supersedes \u003cmemory-id>] # Supersede a previous entry\n [--lifecycle-status active|archived|draft]\n [--review-in 30d] [--expires-in 90d] # Lifecycle scheduling\n\neve memory get --org org_xxx (--agent \u003cslug>|--shared)\n --key \u003ckey> [--category \u003ccategory>]\n\neve memory list --org org_xxx (--agent \u003cslug>|--shared)\n [--category \u003ccategory>] [--tags a,b] [--limit \u003cn>]\n\neve memory delete --org org_xxx (--agent \u003cslug>|--shared)\n --category \u003ccategory> --key \u003ckey>\n\neve memory search --org org_xxx --query \u003ctext> # Cross-agent memory search\n [--agent \u003cslug>] [--limit \u003cn>]\n```\n\nNotes:\n- `--shared` stores memory in the org-wide shared namespace (agent slug `shared`).\n- `--supersedes` links the new entry to a previous one, forming a revision chain.\n- `search` queries across all agents in the org unless `--agent` scopes it to one.\n- Lifecycle fields (`--review-in`, `--expires-in`) work identically to org docs.\n\n## Search (Unified Cross-Source Search)\n\nFederated search across all org data sources: memory, docs, threads, attachments, and events.\n\n```bash\neve search --org org_xxx --query \u003ctext>\n [--sources memory,docs,threads,attachments,events] # Comma-separated source filter\n [--agent \u003cslug>] # Scope to agent's data\n [--limit \u003cn>]\n```\n\nNotes:\n- When `--sources` is omitted, all sources are searched.\n- Results are ranked by relevance and grouped by source type.\n\n## Packs (AgentPacks)\n\n```bash\neve packs status [--repo-dir \u003cpath>] # Show lockfile status + drift\neve packs resolve [--dry-run] [--repo-dir \u003cpath>] # Preview pack resolution\n```\n\n## Skills\n\n```bash\neve skills install [source] [--skip-installed] # Install skill packs\n```\n\n**Resolution order** (without a source argument):\n1. **Pack-based** -- if `.eve/manifest.yaml` defines `x-eve.packs`, skills are installed from those packs. Requires a matching `.eve/packs.lock.yaml` (run `eve agents sync` to generate it). Per-pack `install_agents` overrides are supported; the global default comes from `x-eve.install_agents`.\n2. **skills.txt** -- falls back to reading `skills.txt` and installing all entries.\n\n- With source: installs directly from URL, GitHub repo, or local path and persists to `skills.txt`.\n- Supports: `https://github.com/org/repo`, `org/repo` (GitHub shorthand), `./local/path`.\n- Glob patterns in `skills.txt` (e.g. `../**`) automatically exclude `private-skills/` directories unless the path explicitly targets them (e.g. `../private-skills/my-skill`).\n- Local directory paths in `skills.txt` (e.g. `./my-skills`) are expanded at parse time into individual skill subdirectories, so `./path` behaves identically to `./path/*`. This ensures correct `--skip-installed` checks and consistent enumeration.\n- Installs for all supported agents: claude-code, codex, gemini-cli, pi.\n\n## Harnesses\n\n```bash\neve harness list [--capabilities] [--org \u003cid>] [--project \u003cid>]\neve harness get \u003cname> [--org \u003cid>] [--project \u003cid>]\neve harness validate --project \u003cid> [--profile-file profile.json|--workflow \u003cname>] [--env-override KEY=VALUE] [--json]\n```\n\nNotes:\n- `--capabilities` shows model support, reasoning levels, streaming, and tool use.\n- `harness get` shows variants, auth status, and capability matrix.\n- `harness validate` dry-runs an inline `harness_profile_override`, standalone `env_overrides`, or a workflow step env composition against project secrets and harness auth without creating a job or invoking a model. Use repeated `--env-override KEY=VALUE`; `${secret.KEY}` placeholders report resolved/missing scope without exposing values.\n\n## Database (Environment DBs)\n\nAll `db` subcommands accept either `--env \u003cname>` (API-proxied) or `--url \u003cpostgres-url>` (direct connection). When `--url` is used, the CLI connects directly to the database without going through the Eve API. The `EVE_DB_URL` env var (or `.env` file) is used as a fallback for `sql` and `migrate`.\n\n```bash\neve db schema --env \u003cname> [--project \u003cid>] # Show DB schema\neve db schema --url \u003cpostgres-url> # Show schema via direct connection\neve db rls --env \u003cname> # Show RLS policies + group context diagnostics\neve db rls init --with-groups [--out \u003cpath>] [--force] # Scaffold group-aware RLS helper SQL\neve db sql --env \u003cname> --sql \"SELECT 1\" # Run query (read-only default)\n [--params '[\"arg1\"]'] # Parameterized query\n [--write] # Enable mutations\n [--file ./query.sql] # Run SQL from file\neve db sql --url \u003cpostgres-url> --sql \"SELECT 1\" # Direct SQL execution\neve db migrate --env \u003cname> [--path db/migrations] # Apply pending migrations\neve db migrate --url \u003cpostgres-url> [--path db/migrations] # Direct migration apply\neve db migrations --env \u003cname> # List applied migrations\neve db new \u003cdescription> [--path db/migrations] # Create migration file\neve db reset --env \u003cname> --force [--no-migrate] # Drop and recreate schema, re-apply migrations\n [--danger-reset-production] [--path db/migrations]\n [--skip-snapshot] # Skip auto-snapshot before reset\neve db reset --url \u003cpostgres-url> --force # Direct schema reset\neve db wipe --env \u003cname> --force # Reset schema without re-applying migrations\neve db status --env \u003cname> # Managed DB status\neve db extensions list --env \u003cname> # List installed DB extensions\neve db rotate-credentials --env \u003cname> # Rotate managed DB credentials\neve db scale --env \u003cname> --class db.p1|db.p2|db.p3 # Scale managed DB class\neve db destroy --env \u003cname> --force [--skip-snapshot] # Destroy managed DB\n\n# Snapshots and restore\neve db snapshot --env \u003cname> [--retention \u003cduration>] # Create a DB snapshot\neve db snapshot show \u003csnapshot_id> --env \u003cname> # Show snapshot details\neve db snapshot delete \u003csnapshot_id> --env \u003cname> --force # Delete a snapshot\neve db snapshot download \u003csnapshot_id> --env \u003cname> --output \u003cpath> # Get download URL\neve db snapshots --env \u003cname> # List snapshots\n [--status \u003cstatus>] [--limit \u003cn>]\neve db restore --env \u003cname> --snapshot \u003cid> --force # Restore from snapshot\n [--source-env \u003cname>] [--source-project \u003cid>] # Cross-env/project restore\n [--skip-safety-snapshot] # Skip pre-restore safety snapshot\neve db backup-status --env \u003cname> # Show backup schedule and status\n```\n\nNotes:\n- `rls` now includes group context diagnostics: shows the resolved principal, org, project, env, group IDs, and permissions for the current session. Useful for debugging why policies are not matching.\n- `rls init --with-groups` scaffolds `app.current_user_id()`, `app.current_group_ids()`, and `app.has_group()` SQL functions to `db/rls/helpers.sql` (or `--out \u003cpath>`). Apply the output SQL to your target environment DB, then reference these helpers in RLS policies.\n- Migration files: `YYYYMMDDHHmmss_description.sql` in `db/migrations/` by default.\n- `reset` drops all schemas (except `pg_catalog`, `information_schema`) and re-applies migrations. Use `--no-migrate` (or `wipe`) to skip migration re-apply.\n- `--danger-reset-production` is required when resetting production environments via the API.\n- `--url` mode uses the `@eve/migrate` library for direct `migrate`, `migrations`, `reset`, and `wipe` operations.\n- `snapshot` creates a point-in-time backup. `restore` overwrites the target DB from a snapshot (creates a safety snapshot first unless `--skip-safety-snapshot`).\n- Cross-environment restore is supported via `--source-env` and `--source-project`.\n- `backup-status` shows the automated backup schedule, retention policy, and snapshot-on-delete/reset settings.\n\n## Manifest\n\n```bash\neve manifest validate [--path \u003cpath>] # Validate manifest\n [--validate-secrets] [--strict] [--latest]\n [--project \u003cid>] [--dir \u003cpath>]\n```\n\nSee `references/manifest.md` for manifest schema and configuration.\n\n## App Links\n\nInspect cross-project app links. App links are declared in `x-eve.app_links`\nand reconciled by `eve project sync`.\n\n```bash\neve app-links list [project] [--project \u003cid>] [--json]\neve app-links plan --project \u003cid> [--file .eve/manifest.yaml] [--env \u003cenv>] [--json]\neve app-links explain --consumer \u003cid> (--alias \u003cname> | --producer \u003cid> --api \u003cname>) [--json]\n```\n\nNotes:\n- `list` shows producer exports, consumer subscriptions, and grants to the target project.\n- `plan` dry-runs the consumer side of a manifest against active producer grants and reports missing scopes, event feeds, or revoked grants.\n- `explain` is the fastest diagnostic for a local subscription alias. It reports `OK`, `MISSING`, `REVOKED`, or `INVALID`.\n\n## API (Proxy)\n\nCall project API sources (OpenAPI, PostgREST, Supabase GraphQL) through Eve's auth layer.\n\n```bash\neve api list [project] [--env \u003cname>] # List API sources\neve api show \u003cname> [project] [--env \u003cname>] # Show API source details\neve api spec \u003cname> [project] [--env \u003cname>] # Show cached API spec\neve api refresh \u003cname> [project] [--env \u003cname>] # Refresh cached spec\neve api examples \u003cname> [project] [--env \u003cname>] # Generate curl examples from spec\n\neve api call \u003cname> \u003cmethod> \u003cpath> # Call API with Eve auth\n [--json \u003cpayload|@file>] [--data \u003cpayload|@file>] # JSON request body\n [--graphql \u003cquery|@file>] # GraphQL query\n [--variables '{\"k\":\"v\"}'] # GraphQL variables\n [--jq \u003cexpr>] # Filter response with jq\n [--print-curl] # Print curl command instead\n [--token \u003coverride>] # Custom auth token\n [--project \u003cid>] [--env \u003cname>]\n```\n\neve api generate [--out \u003cdir>] # Export OpenAPI spec\neve api diff [--exit-code] [--out \u003cdir>] # Diff generated vs committed spec\n```\n\nNotes:\n- `call` resolves the base URL from the API source, applies Eve auth, and proxies the request.\n- `--json` and `--data` are aliases (`-d` shorthand also works); use only one body flag.\n- `--json`/`--data` and `--graphql` accept inline JSON/text or `@file` paths.\n- `--env` falls back to `$EVE_ENV_NAME` when omitted.\n- `call` rewrites service DNS/ingress base URLs to match runtime context (local shell vs in-cluster job).\n- `--jq` requires `jq` installed locally.\n- Auth precedence: `--token` > `$EVE_JOB_TOKEN` > profile token.\n- `generate` exports the current OpenAPI spec. `diff` compares against committed spec.\n\n## Chat + Integrations (Slack, Google Drive, Nostr)\n\n```bash\n# Integration listing and testing\neve integrations list --org org_xxx\neve integrations test \u003cintegration_id> --org org_xxx\neve integrations update \u003cintegration_id> --org org_xxx # Update integration settings\n --setting key=value # e.g. admin_channel_id=C12345\n\n# OAuth app configuration (BYOA — Bring Your Own App)\neve integrations setup-info \u003cprovider> --org org_xxx # Show callback URLs, scopes, and setup instructions\neve integrations configure \u003cprovider> --org org_xxx # Register OAuth app credentials\n --client-id \"...\" --client-secret \"...\"\n [--signing-secret \"...\"] [--app-id \"...\"] # Slack-specific\n [--label \u003ctext>]\neve integrations config \u003cprovider> --org org_xxx # View OAuth app config (secrets redacted)\neve integrations unconfigure \u003cprovider> --org org_xxx # Remove OAuth app config\neve integrations connect \u003cprovider> --org org_xxx # Initiate OAuth flow (prints authorize URL)\n\n# Slack-specific\neve integrations slack connect --org org_xxx --team-id T123\n --token xoxb-test [--tokens-json '{}'] [--status]\neve integrations slack install-url --org org_xxx [--ttl 24h] # Generate shareable Slack install link\n\n# Project-scoped outbound notifications\neve notifications send --project proj_xxx\n --channel eve-horizon-notifications\n --message \"Workflow complete\"\n [--integration-id int_xxx] [--thread \u003cts>] [--json]\n\n# Default agent slug (org-wide fallback)\neve org update org_xxx --default-agent mission-control\n\n# Chat simulation (gateway-routed, default)\neve chat simulate --team-id T123 --text \"hello\"\n [--channel-id C123] [--user-id U123]\n [--provider slack] [--thread-id \u003cid>]\n [--external-email [email protected]]\n [--event-type \u003ctype>] [--dedupe-key \u003ckey>]\n [--metadata '{}'] [--json]\n\n# Chat simulation (legacy project-scoped path)\neve chat simulate --project proj_xxx\n --team-id T123 --text \"hello\"\n [--channel-id C123] [--user-id U123]\n [--provider slack] [--thread-key \u003ckey>] [--metadata '{}']\n```\n\nNotes:\n- `chat simulate` defaults to the gateway path (`/gateway/providers/simulate`) which routes by agent slug. Omit `--project` to use this path.\n- Pass `--project` to force the legacy per-project simulate path. The CLI warns when this fallback is used.\n- `--thread-id` replaces `--thread-key` on the gateway path (both are accepted for backward compat).\n- `--external-email` can also be passed inside `--metadata` as `external_email` for backward compat.\n- `integrations update` patches individual settings on an existing integration (e.g. changing `admin_channel_id`).\n- `integrations slack install-url` generates a time-limited install link for sharing with workspace admins. Use `--ttl` to control expiry (default: server-controlled).\n- `notifications send` posts a one-way Slack notification through the org integration without exposing raw Slack tokens. It requires `notifications:send` on the project/job token.\n- Supported providers: `google-drive`, `slack`. The BYOA flow is: `setup-info` (get URLs/scopes) -> `configure` (register credentials) -> `connect` (initiate OAuth).\n\nSlack commands (run inside Slack):\n\n```text\n@eve \u003cagent-slug> \u003ccommand>\n@eve agents list\n@eve agents listen \u003cagent-slug>\n@eve agents unlisten \u003cagent-slug>\n@eve agents listening\n```\n\nNostr relay subscriptions provide a non-webhook transport. See `references/gateways.md`.\n\n## GitHub Integration\n\nOne-command webhook setup for connecting a project's GitHub repo to Eve event triggers.\n\n```bash\n# Set up webhook (auto-creates via gh CLI if available, otherwise prints instructions)\neve github setup [--project proj_xxx]\n [--regenerate] # Regenerate webhook secret\n [--json]\n\n# Check webhook status\neve github status [--project proj_xxx] [--json]\n\n# Fire a synthetic github.push test event\neve github test [--project proj_xxx] [--json]\n```\n\nNotes:\n- `setup` ensures `GITHUB_WEBHOOK_SECRET` exists in project secrets and returns the webhook URL + secret.\n- If the `gh` CLI is installed and authenticated, `setup` auto-creates or updates the webhook on the repo. Otherwise it prints the URL, secret, and manual instructions (including a ready-to-paste `gh api` command).\n- `--regenerate` deletes the existing secret and creates a new one (useful for rotation).\n- `test` creates a synthetic `github.push` event on the project's default branch. Use it to verify pipeline triggers fire correctly.\n- Webhook URL format: `${EVE_PUBLIC_API_URL}/integrations/github/events/${project_id}`.\n- Required permissions: `secrets:write` (setup), `secrets:read` (status), `events:write` (test).\n\n## Identity\n\nLink external provider identities (e.g. Slack user) to the current Eve user.\n\n```bash\neve identity link \u003cprovider> --org org_xxx # Generate a link token for a provider\n```\n\nNotes:\n- Supported providers: `slack`.\n- Returns a token with instructions for completing the identity link (e.g. sending a DM to the Eve bot in Slack).\n- Org scope is required; falls back to profile default.\n\n## Events (Triggers)\n\n```bash\neve event list --project proj_xxx\n [--type github.push] [--source github] [--status \u003cs>]\neve event show \u003cevent-id>\neve event emit --type manual.test --source manual\n [--env staging] [--ref-sha \u003csha>] [--ref-branch main]\n [--actor-type user] [--actor-id \u003cid>]\n [--dedupe-key \u003ckey>]\n --payload '{\"k\":\"v\"}'\n```\n\nSee `references/events.md` for the complete event type catalog and trigger syntax.\n\n## Traces (OTEL Trace Query)\n\nAgent-queryable surface over the OTEL trace store. No AWS console access required.\n\n```bash\neve traces query --project proj_xxx --request-id req_01h... [--json]\neve traces query --project proj_xxx --trace-id \u003ctrace-id> [--json]\neve traces query --project proj_xxx --service \u003cname> --since 5m --error\neve traces query --project proj_xxx --service \u003cname> --route \"POST /api/items\" --since 1h --p99\n [--limit \u003cn>] [--no-cache]\n```\n\nNotes:\n- Provide at least one of `--request-id`, `--trace-id`, `--since`, `--route`, or `--error`.\n- Returns structured spans (service, duration, error/fault/throttle flags) plus a\n summary `{ trace_count, span_count, p99_ms }`.\n- Backed by the platform trace store; the response includes `backend` and\n `backend_available` so agents can detect when traces are unavailable.\n- For request-id-based diagnostics that combine logs, events, deploy metadata,\n and traces in one shot, prefer `eve env diagnose \u003cproject> \u003cenv> --request \u003cid> --json`.\n\n## Supervision\n\n```bash\neve supervise [\u003cjob-id>] [--timeout 60] [--since \u003ccursor>] [--json]\n```\n\nMonitors job tree and team coordination. Returns events, children, inbox, and cursor for long-polling.\n\n## Migrate\n\n```bash\neve migrate skills-to-packs # Convert skills.txt to x-eve.packs YAML\n```\n\nReads `skills.txt` and generates the equivalent `x-eve.packs` manifest fragment for migration from legacy skills to the AgentPacks system.\n\n## Providers\n\n```bash\neve providers list [--json] # List registered providers\neve providers discover \u003cprovider> [--json] # Discover models for a provider\n```\n\nNotes:\n- Providers are first-class entities with auth config, harness mapping, and model discovery.\n- `discover` fetches live model lists from the provider's API (cached with TTL).\n\n## Analytics\n\n```bash\neve analytics summary --org org_xxx [--window 7d] # Org-wide summary\neve analytics jobs --org org_xxx [--window 7d] # Job counters (created/completed/failed/active)\neve analytics pipelines --org org_xxx [--window 7d] # Pipeline success rates and durations\neve analytics env-health --org org_xxx # Environment health snapshot (total/healthy/degraded/unknown)\neve analytics cost-by-agent --org org_xxx [--window 7d] # Cost breakdown per agent (attempts, tokens, USD)\n```\n\nNotes:\n- All analytics endpoints return aggregate counters, not per-item listings. Use `--json` for machine-readable output.\n- `--window` accepts relative durations like `7d`, `24h`, `30d`.\n- `cost-by-agent` shows per-agent cost, attempt count, and input/output token totals.\n\n## Webhooks\n\n```bash\neve webhooks create --org org_xxx --url \u003curl> --events \u003cevt1,evt2> --secret \u003csecret>\n [--filter '{\"key\":\"val\"}'] [--project \u003cid>]\neve webhooks list --org org_xxx\neve webhooks show \u003cwebhook_id> --org org_xxx\neve webhooks deliveries \u003cwebhook_id> --org org_xxx [--limit 50]\neve webhooks test \u003cwebhook_id> --org org_xxx\neve webhooks delete \u003cwebhook_id> --org org_xxx\neve webhooks enable \u003cwebhook_id> --org org_xxx\neve webhooks replay \u003cwebhook_id> --org org_xxx\n [--from-event \u003cevent_id>] [--to \u003ciso-time>] [--max-events \u003cn>] [--dry-run]\neve webhooks replay-status \u003cwebhook_id> \u003creplay_id> --org org_xxx\n```\n\nNotes:\n- Webhook subscriptions support HMAC signature verification and retry logic.\n- Filters scope events to specific patterns. Project-scoped webhooks are optional.\n\n## User\n\nInspect user details and memberships.\n\n```bash\neve user show [user_id|me] [--json] # Show user profile, org + project memberships\n```\n\nNotes:\n- Defaults to `me` (the authenticated user) when no user ID is given.\n- Output includes email, display name, admin status, org memberships (slug + role), and project memberships (`org_slug/project_slug` + role).\n\n## Admin\n\nAdministrative commands (require elevated permissions).\n\n```bash\n# List all platform users (shows email, org + project memberships, roles)\neve admin users [--json]\n\n# User invitation\neve admin invite --email [email protected]\n [--github \u003cusername>] [--ssh-key \u003cpath>] # Auth methods (at least one recommended)\n [--role \u003crole>] [--org \u003corg_id>]\n [--web] [--redirect-to \u003curl>] # --web sends Supabase invite email\n\n# Access request management\neve admin access-requests list\neve admin access-requests approve \u003crequest_id>\neve admin access-requests reject \u003crequest_id> --reason \"...\"\n\n# Billing\neve admin balance show \u003corg_id> # Current balance\neve admin balance credit \u003corg_id> --amount 100.00 # Add credit\n [--currency usd] [--description \"reason\"]\neve admin balance transactions \u003corg_id> # Transaction history\n [--limit 50] [--offset 0]\n\neve admin usage list --org org_xxx # Usage records\n [--since 2026-01-01] [--until 2026-02-01] [--limit 100]\neve admin usage summary --org org_xxx # Aggregated usage summary\n [--since 2026-01-01] [--until 2026-02-01]\n\n# Pricing\neve admin pricing seed-defaults # Seed default rate cards\n\n# Receipts\neve admin receipts recompute # Recompute cost receipts\n [--since 2026-01-01] [--project proj_xxx]\n [--dry-run] [--force]\n\n# Ingress aliases (custom domain aliases for services)\neve admin ingress-aliases list # List all aliases\n [--alias \u003calias>] [--project \u003cid>] [--environment \u003cid>]\n [--limit \u003cn>] [--offset \u003cn>]\neve admin ingress-aliases reclaim \u003calias> --reason \"...\" # Reclaim an alias\n\n# Email delivery events (SES bounces, complaints, deliveries, rejects)\neve admin email bounces list # Recent delivery events\n [--recipient \u003caddr>] # Filter by recipient\n [--event-type Bounce|Complaint|Delivery|Reject]\n [--limit \u003cn>] [--json]\n```\n\nNotes:\n- `users` lists every registered user with their email, display name, admin status, memberships, and roles. One row per user-membership pair (org or project). Columns: Email, Name, Admin, Scope (`org`/`project`), Target (org slug or `org_slug/project_slug`), Role, Created. Useful for auditing access.\n- `invite --ssh-key` registers an SSH public key file (e.g. `~/.ssh/id_ed25519.pub`) as an auth identity. If no auth method is given (`--github`, `--ssh-key`, or `--web`), the CLI warns that the user won't be able to log in.\n- Users can self-register via `eve auth request-access --org \"Org Name\" --ssh-key ~/.ssh/id_ed25519.pub --wait`.\n- Access-request approval is retry-safe (`approve` returns the existing approved record on repeat calls).\n- Duplicate identity fingerprints are attached to the existing identity owner.\n- `ingress-aliases reclaim` forcibly releases an alias from its current project/environment. Requires a `--reason`.\n- `email bounces list` reads the `email_delivery_events` table populated by the SES SNS webhook. Use it to investigate magic-link / invite delivery failures — see `references/observability.md` for the table schema and mailer pre-flight behavior.\n\n## System (Internal)\n\n```bash\neve system status # Platform status overview\neve system health [--json] # Health check (reports Degraded on DB failure)\neve system config # Show system configuration\neve system settings # List all settings\neve system settings get \u003ckey> # Get setting value\neve system settings set \u003ckey> \u003cvalue> # Set setting value\neve system env-health [--status \u003chealthy|degraded|critical>] [--limit 100] [--json]\n # Cross-org environment health report (system_admin)\n\neve system orchestrator status # Orchestrator state\neve system orchestrator set-concurrency \u003cn> # Set job concurrency\n\neve system jobs [--org \u003cid>] [--project \u003cid>] # Active jobs overview\n [--phase \u003cp>] [--limit 50]\neve system envs [--org \u003cid>] [--project \u003cid>] # Environment overview\neve system logs \u003cservice> [--tail 50] # Service logs\neve system pods # K8s pod status\neve system events [--limit 50] # Recent platform events\n```\n\nNotes:\n- `eve system env-health` is the platform-wide sentinel view. It returns the latest health check row per environment, including issue details, degraded tick counters, and any circuit-breaker actions.\n- `eve analytics env-health --org \u003corg_id>` remains the org-scoped aggregate view. Use `eve system env-health` when you need cross-org detail as a `system_admin`.\n- Sentinel configuration is stored in system settings:\n - `sentinel.enabled`\n - `sentinel.slack.integration_id`\n - `sentinel.slack.channel_id`\n- The inbound Slack responder also requires the gateway deployment env var `EVE_SENTINEL_CHANNEL_ID` to match `sentinel.slack.channel_id`.\n\n## Local Stack (k3d)\n\nProvision and manage a full Eve platform stack locally using k3d (k3s-in-Docker). Requires Docker Desktop. The CLI auto-installs `k3d` and `kubectl` into `~/.eve/bin/` if not already present.\n\nSupported platforms: macOS and Linux (amd64 and arm64).\n\n```bash\n# Bring up the local stack (creates cluster, pulls images, migrates DB, starts all services)\neve local up\n [--version \u003cx.y.z>] # Platform version (default: latest)\n [--skip-deploy] # Start cluster only, skip image deploy\n [--skip-health] # Don't wait for API health check\n [--timeout \u003cseconds>] # Rollout timeout (default: 300)\n [--verbose] # Show kubectl/docker output\n [--json] # Machine-readable output\n\n# Stop the local cluster (preserves state)\neve local down\n [--destroy] # Delete cluster and all data\n [--force] # Skip confirmation prompt\n\n# Full status dashboard\neve local status\n [--watch] # Auto-refresh every 5s\n [--json]\n\n# Health check (exits non-zero if unhealthy)\neve local health [--json]\n\n# Destroy and recreate from scratch\neve local reset\n [--force] # Skip confirmation prompt\n\n# Stream service logs\neve local logs [\u003cservice>] # Omit service for all logs\n [--follow] [-f] # Tail logs\n [--tail \u003cn>] # Lines to show (default: 50)\n [--since \u003cduration>] # e.g. 5m, 1h\n\n# Multi-project app-link mesh on the local k3d stack\neve local mesh init \u003cname> [--org \u003corg_id>] [--env local] [--profile \u003cprofile>] [--force]\neve local mesh add \u003cproject-slug> --path \u003ccheckout> [--role producer|consumer] [--workspace \u003cname>]\neve local mesh use \u003cname>\neve local mesh list [--json]\neve local mesh show [--workspace \u003cname>] [--json]\neve local mesh up [--workspace \u003cname|path>] [--only \u003cproject>] [--skip-pre-check] [--skip-cli-build] [--json]\neve local mesh redeploy \u003cproject> [--workspace \u003cname>] [--skip-cli-build]\neve local mesh status [--workspace \u003cname>] [--json]\neve local mesh diagnose [--workspace \u003cname>] [--project \u003cslug>] [--probe] [--json]\neve local mesh logs \u003cproject>/\u003ccomponent> [--workspace \u003cname>] [--follow] [--tail \u003cn>] [--since \u003cduration>]\neve local mesh down [--workspace \u003cname>] [--delete-projects]\n\n# Build/import a producer app-link CLI image directly\neve project image build-cli [project-slug|project-id] [--repo-dir \u003cpath>] [--dockerfile \u003cpath>] [--tag \u003cimage>] [--import-to-k3d] [--json]\n```\n\n**Services:** api, orchestrator, worker, gateway, agent-runtime, auth, mailpit, sso, postgres.\n\n**Local URLs** (available after `eve local up`):\n- API: `http://api.eve.lvh.me`\n- Auth: `http://auth.eve.lvh.me`\n- Mail: `http://mail.eve.lvh.me`\n- SSO: `http://sso.eve.lvh.me`\n\n**Typical workflow:**\n```bash\neve local up # First run: ~5min (image pulls)\nexport EVE_API_URL=http://api.eve.lvh.me\neve org ensure \"my-org\" --slug my-org # Bootstrap an org\neve local status --watch # Monitor services\neve local logs api --follow # Debug API issues\neve local down # Stop (state preserved)\neve local up # Restart (fast, no re-pull)\neve local reset --force # Nuclear option: destroy + recreate\n```\n\nNotes:\n- `up` is idempotent: re-running starts a stopped cluster or skips if already running.\n- `down` without `--destroy` preserves cluster state; `up` resumes quickly.\n- `eve local mesh` requires a running local API and refuses non-local API URLs.\n- Mesh workspace project names are Eve project slugs (4-8 alphanumeric chars)\n and must match `x-eve.app_links.*.project` references.\n- Every project in a mesh should declare the same environment name, normally\n `environments.local`.\n- When a producer export uses an image-mode `x-eve.cli`, `mesh up` builds\n `Dockerfile.cli`, imports `local/\u003cproducer>-cli:\u003csha>` into k3d, and syncs\n that image into the grant for local job CLI injection.\n- `reset` is equivalent to `down --destroy` followed by `up`.\n- Version resolution queries the configured registry for the latest common platform tag.\n - Set `ECR_REGISTRY=\u003cregistry>` to override the registry host.\n - Set `ECR_NAMESPACE=\u003cnamespace>` to override the image namespace (defaults to `eve-horizon`).\n- **ECR auto-auth:** `up` automatically authenticates Docker to ECR Public before pulling platform images. If the AWS CLI is installed and configured, it runs `aws ecr-public get-login-password` to obtain a token and logs Docker in. This avoids ECR Public's anonymous rate limits (1 pull/s, 10 pulls/min per IP). If the AWS CLI is not available, pulls proceed without auth but may fail under rate limits -- the error message includes a manual login command.\n- Image pull retries now handle `403 Forbidden` and `429 Too Many Requests` responses, and HTTP fetches (e.g. registry tag listing) retry with exponential backoff on 429.\n- The cluster binds ports 80/443 on localhost via k3d's load balancer.\n- Public raw TCP ingress tests need explicit k3d port mappings. Contributors\n can recreate the local cluster with `./bin/eh k8s start --tcp-ports 33400,33500 --recreate`.\n- Kube context is set to `k3d-eve-local` automatically.\n\n## Job Execution Runtime\n\nPlatform behaviors that affect how jobs and harnesses execute. These are not CLI commands but are important context for understanding job output.\n\n**Type auto-resolve:** If a pipeline step declares `type: run` with a `service` reference but no `command`, the platform auto-resolves it to `type: job` and logs a warning. This catches a common manifest mistake. Update your manifest to use `type: job` directly to silence the warning.\n\n**Secret forwarding:** All resolved project secrets (project, org, and user scope) are now forwarded to the harness process environment. This means secrets like `GITHUB_TOKEN`, custom API keys, and other credentials set via `eve secrets set` are available to the agent without additional configuration. Worker-internal variables (`DATABASE_URL`, etc.) remain excluded by the sanitization allowlist.\n\n**Worker image tooling:** The worker image now includes `gh` (GitHub CLI) and `jq`. When `GITHUB_TOKEN` or `GH_TOKEN` is set in project secrets, `gh` authenticates automatically inside jobs.\n\n**Log normalization:** The platform stream API now includes a pre-computed `text` field on every log event, providing human-readable output without harness-specific parsing. Both `eve job logs` and `eve job follow` use a shared normalization layer (`normalizeLogLine` from `@eve/shared`) that handles Codex, Claude, and other harness event formats uniformly.\n\nBudget-configured jobs emit `budget.summary` rows on completion and\n`budget.exceeded` rows when enforcement terminates an attempt. Use\n`eve job logs \u003cid> --json` or `eve job diagnose \u003cid> --json` to inspect\n`total_tokens`, `weighted_tokens`, `cache_read_tokens`,\n`cache_read_token_weight`, and `cache_read_tokens_excluded`.\n\n**Codex harness improvements:**\n- Streaming logs render cleanly: tool use shows the tool name with a truncated input preview, status messages are prefixed with `>`, and result text is extracted properly.\n- Final result text and token usage are extracted from Codex job logs for display in `eve job show` and `eve job result`.\n\n## Debugging (CLI-first)\n\nSee `references/deploy-debug.md` for the debugging ladder and system health workflows.\n\nQuick reference:\n- `eve job diagnose \u003cid>` -- primary job debugging entry point\n- `eve job follow \u003cid>` -- stream harness logs in real time\n- `eve job runner-logs \u003cid>` -- K8s pod logs for startup failures\n- `eve env diagnose \u003cproject> \u003cenv>` -- environment health, K8s events, and ingress diagnostics\n- `eve env diagnose \u003cproject> \u003cenv> --request \u003cid> --json` -- request-level logs, events, deploy metadata, audit rows, and traces\n- `eve env logs \u003cproject> \u003cenv> \u003cservice> --follow --filter req_id=\u003cid>` -- stream app-service logs with structured filters\n- `eve traces query --project \u003cproject> --request-id \u003cid> --json` -- query trace spans without console access\n- `eve tcp-ingress test \u003cproject> \u003cenv> --listener \u003cname>` -- TCP connect probe for public raw TCP listeners\n- `eve env recover \u003cproject> \u003cenv>` -- analyze state and suggest recovery action\n- `eve system health` -- platform-wide health check\n\n### HTTP Ingress Diagnostics\n\n`eve env diagnose` renders an `HTTP Ingress` table and returns `.http_ingress[]`\nin JSON when services expose public HTTP ingress. Use it to confirm requested\nand effective request timeout/body-size values after changing\n`x-eve.ingress.timeout` or `x-eve.ingress.max_body_size`.\n\n```bash\neve env diagnose \u003cproject> \u003cenv>\neve env diagnose \u003cproject> \u003cenv> --json | jq '.http_ingress'\n```\n\nEach row includes the service, hosts, controller flavor, requested and effective\ntimeout/body-size values, and the source (`manifest`, `platform_default`,\n`unsupported_controller`, or `missing`).\n\n### Public TCP Ingress\n\nFor services declaring `x-eve.tcp_ingress`, `eve env diagnose` renders a\n`TCP Ingress` table and returns `.tcp_ingress[]` in JSON. Listener states are\n`pending`, `provisioning`, or `ready`.\n\n```bash\neve env diagnose \u003cproject> \u003cenv> --json | jq '.tcp_ingress'\neve tcp-ingress test \u003cproject> \u003cenv> --listener a1-gt06\neve tcp-ingress test \u003cproject> \u003cenv> --listener a1-gt06 --timeout 10 --json\n```\n\n`eve tcp-ingress test` resolves the listener through env diagnose, opens a TCP\nsocket from the CLI host, prints `OK` or `FAIL`, and exits non-zero on failure.\nIt does not speak the app's device protocol.\n\n## Resource Cleanup\n\n```bash\neve build delete \u003cbuild_id> # Delete build + runs/artifacts\neve build prune [--project=] [--keep=10] # Delete old builds, keep latest N\neve release delete \u003ctag> [--project=] # Delete release by tag\neve release prune [--project=] [--keep=5] # Delete old releases, keep latest N\neve agents delete \u003cslug> [--project=] # Delete agent config + routes\neve agents delete-team \u003cteam_id> [--project=] # Delete team\neve thread delete \u003cthread_id> # Delete thread + messages\neve pipeline delete \u003cname> [--project=] # Delete pipeline + run history\n```\n\n## All Commands Summary\n\n| Category | Key Commands |\n|----------|-------------|\n| **Profile** | `list`, `show`, `use`, `create`, `set`, `remove` |\n| **Auth** | `login`, `logout`, `status`, `token`, `permissions`, `bootstrap`, `mint`, `creds`, `sync`, `request-access`, `create-service-account`, `list-service-accounts`, `revoke-service-account` |\n| **Access** | `can`, `explain`, `roles create/list/show/update/delete`, `bind`, `unbind`, `bindings list`, `groups create/list/show/update/delete`, `groups members list/add/remove`, `memberships`, `validate`, `plan`, `sync` |\n| **Org** | `list`, `ensure`, `get`, `update`, `delete` (soft/hard), `spend`, `members`, `membership-requests` |\n| **Project** | `list`, `ensure`, `get`, `update`, `delete` (soft/hard), `sync`, `spend`, `members`, `bootstrap`, `status` |\n| **Docs** | `write`/`create`, `read`, `show`, `list`, `search`, `stale`, `review`, `versions`, `query`, `delete` |\n| **Jobs** | `create`, `list`, `ready`, `blocked`, `show`, `current`, `tree`, `diagnose`, `update`, `close`, `cancel`, `dep`, `claim`, `release`, `attempts`, `logs`, `submit`, `approve`, `reject`, `result`, `receipt`, `compare`, `follow`, `wait`, `watch`, `runner-logs`, `attach`, `attachments`, `attachment`, `batch`, `batch-validate` |\n| **Ingest** | `create` (or `\u003cfile>`), `list`, `show` |\n| **Cloud FS** | `list`, `mount`, `unmount`, `show`, `update`, `ls`, `search` |\n| **Endpoints** | `add`, `list`, `show`, `remove`, `health`, `diagnose` |\n| **TCP Ingress** | `test` |\n| **Builds** | `create`, `list`, `show`, `run`, `runs`, `logs`, `artifacts`, `diagnose`, `cancel`, `delete`, `prune` |\n| **Releases** | `resolve`, `delete`, `prune` |\n| **Pipelines** | `list`, `show`, `run`, `runs`, `show-run`, `approve`, `cancel`, `logs`, `delete` |\n| **Workflows** | `list`, `show`, `run`, `invoke`, `retry`, `logs` |\n| **Notifications** | `send` |\n| **Environments** | `create`, `deploy`, `undeploy`, `list`, `show`, `services`, `health`, `diagnose`, `logs`, `rollback`, `reset`, `recover`, `suspend`, `resume`, `delete` |\n| **FS** | `sync` (`init`, `status`, `logs`, `pause`, `resume`, `disconnect`, `mode`, `conflicts`, `resolve`, `doctor`), `share`, `shares`, `revoke`, `publish`, `public-paths` |\n| **Secrets** | `list`, `show`, `set`, `delete`, `import`, `validate`, `ensure`, `export` |\n| **Agents** | `config`, `runtime-status`, `sync` (deprecated), `delete`, `delete-team` |\n| **Teams** | `list` |\n| **Threads** | `create`, `list`, `show`, `messages`, `post`, `follow`, `distill`, `delete` |\n| **KV** | `set`, `get`, `list`, `mget`, `delete` |\n| **Memory** | `set`, `get`, `list`, `delete`, `search` |\n| **Search** | `search` (unified cross-source) |\n| **Packs** | `status`, `resolve` |\n| **Skills** | `install` |\n| **Harnesses** | `list`, `get` |\n| **Database** | `schema`, `rls`, `rls init`, `sql`, `migrate`, `migrations`, `new`, `reset`, `wipe`, `status`, `rotate-credentials`, `scale`, `destroy`, `snapshot`, `snapshots`, `restore`, `backup-status` |\n| **Manifest** | `validate` |\n| **App Links** | `list`, `plan`, `explain` |\n| **Providers** | `list`, `discover` |\n| **Analytics** | `summary`, `jobs`, `pipelines`, `env-health`, `cost-by-agent` |\n| **Webhooks** | `create`, `list`, `show`, `delete`, `replay` |\n| **API** | `list`, `show`, `spec`, `refresh`, `examples`, `call`, `generate`, `diff` |\n| **GitHub** | `setup`, `status`, `test` |\n| **Events** | `list`, `show`, `emit` |\n| **Traces** | `query` |\n| **Chat** | `simulate` |\n| **Identity** | `link` |\n| **Integrations** | `list`, `configure`, `config`, `unconfigure`, `setup-info`, `connect`, `slack connect`, `slack install-url`, `test`, `update` |\n| **Supervision** | `supervise` |\n| **Migrate** | `skills-to-packs` |\n| **Local Stack** | `up`, `down`, `status`, `health`, `reset`, `logs` |\n| **User** | `show` |\n| **Admin** | `users`, `invite`, `access-requests`, `balance`, `usage`, `pricing`, `receipts`, `ingress-aliases`, `email bounces` |\n| **System** | `status`, `health`, `config`, `settings`, `orchestrator`, `jobs`, `envs`, `logs`, `pods`, `events` |\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":56148,"content_sha256":"73b8d4796ac0727fa39bebf42d72be6d4972cda377abff0b1e2c8e008bca4c37"},{"filename":"references/database-ops.md","content":"# Database Operations\n\n## Use When\n- You need to provision, inspect, or manage environment databases.\n- You need to run migrations, SQL queries, or schema inspections.\n- You need managed DB status, credential rotation, or scaling.\n- You need to set up RLS policies with group-aware helpers.\n\n## Load Next\n- `references/manifest.md` for managed DB declaration in `x-eve.role: managed_db`.\n- `references/deploy-debug.md` for environment-level diagnostics.\n- `references/secrets-auth.md` for DB credential secrets and interpolation.\n\n## Ask If Missing\n- Confirm the environment name and project context.\n- Confirm access mode: `--env` (API-proxied) or `--url` (direct connection).\n- Confirm whether this is a managed DB or a self-hosted database.\n\n## Managed DB Provisioning\n\nDeclare a managed database in the manifest:\n\n```yaml\nservices:\n db:\n x-eve:\n role: managed_db\n managed:\n class: db.p1\n engine: postgres\n engine_version: \"16\"\n extensions: [postgis, pgvector, pg_trgm]\n```\n\nProvisioning occurs automatically when an environment is deployed. Managed DB services are not rendered into K8s manifests.\n\n### Managed Extensions\n\nPlain declarable extensions are `postgis`, `pgvector`, `pg_trgm`, `btree_gist`, `hstore`, and `citext`.\n\n```bash\neve db extensions list --env \u003cname> [--project \u003cid>]\n```\n\nNotes:\n- Eve installs declared extensions through the managed-DB reconciler as the backing instance admin, before app migration jobs run.\n- `pgvector` is the manifest name; PostgreSQL reports the installed extension as `vector`.\n- Removing an extension from the manifest does not drop it from an existing DB. Extension removal is sticky in v1.\n- `pg_cron` is provider-gated: it is declarable only when the platform sets `EVE_MANAGED_DB_ENABLED_PRELOAD_EXTENSIONS=pg_cron` and the backing Postgres has `shared_preload_libraries=pg_cron`.\n- `pg_cron` follows the AWS RDS model: Eve installs it in the instance admin database (`postgres`) and tenant DB scheduling is a platform-admin operation.\n- `timescaledb` is still not declarable on AWS RDS; it needs a Timescale-capable provider model first.\n\n### Tiers\n\n| Class | Description |\n|---|---|\n| `db.p1` | Starter (shared resources) |\n| `db.p2` | Standard (dedicated CPU) |\n| `db.p3` | Performance (dedicated CPU + memory) |\n\n### Interpolation\n\nReference managed DB values in environment blocks:\n\n```yaml\nenvironment:\n DATABASE_URL: ${managed.db.url}\n DB_HOST: ${managed.db.host}\n DB_PASSWORD: ${managed.db.password}\n```\n\nAvailable fields: `url`, `host`, `port`, `database`, `username`, `password`.\n\n## TLS Trust (Managed DB)\n\nCloud managed DB tenant URLs default to `sslmode=verify-full`. The platform owns CA distribution: when the worker deploys an environment that uses a cloud managed DB, it creates a namespace `ConfigMap/eve-db-trust`, mounts the provider CA bundle at `/etc/eve/trust/ca-bundle.pem`, and injects `NODE_EXTRA_CA_CERTS` and `PGSSLROOTCERT` into every app pod and job pod.\n\nWhat this means for app code:\n\n- **Do not** set `ssl: { rejectUnauthorized: false }` on your DB client. The pod already trusts the managed DB CA.\n- **Do not** strip `sslmode` from `${managed.db.url}`. Connect with the URL as-is.\n- A plain `new Pool({ connectionString: process.env.DATABASE_URL })` is the supported pattern.\n- Local managed DBs continue to use `sslmode=disable` and need no trust material.\n- Migration jobs (`x-eve.role: job`) get the same trust injection as long-lived Deployments — migrations against a `verify-full` tenant work without app changes.\n\nThe previous URL-rewriting `sslmode` resolver was removed; tenant URLs are preserved as-is and inherit `sslmode` from `DATABASE_URL`. If a managed DB connection fails TLS verification, treat it as a platform issue (`eve job logs` on the deploy job will show the trust ConfigMap step) rather than working around it in app code.\n\n## Status + Credential Rotation\n\n```bash\neve db status --env \u003cname> # Managed DB status and tenant readiness\neve db rotate-credentials --env \u003cname> # Rotate managed DB credentials\neve db extensions list --env \u003cname> # List installed DB extensions\n```\n\nAlways check `eve db status` before relying on managed values. Rotation replaces credentials and updates the stored secret -- redeploy services to pick up new values.\n\n## Migrations\n\n### Create a Migration\n\n```bash\neve db new \u003cdescription> [--path db/migrations]\n```\n\nCreates `YYYYMMDDHHmmss_description.sql` in `db/migrations/` (default).\n\n### Run Migrations\n\n```bash\neve db migrate --env \u003cname> [--path db/migrations] # API-proxied\neve db migrate --url \u003cpostgres-url> [--path db/migrations] # Direct connection\n```\n\n### List Applied\n\n```bash\neve db migrations --env \u003cname>\n```\n\n### Conventions\n\n- Migration files: `YYYYMMDDHHmmss_description.sql` format.\n- Default path: `db/migrations/` relative to project root.\n- Migrations run sequentially by timestamp.\n- Use `--url` mode with `@eve/migrate` library for direct operations.\n- `EVE_DB_URL` env var (or `.env` file) is fallback for `sql` and `migrate`.\n\n### Pipeline Step\n\nThe migrate step must run **after deploy** — the managed DB is provisioned during deploy and does not exist before then. Use `action.type: job` referencing the `migrate` service:\n\n```yaml\npipelines:\n deploy:\n steps:\n - name: build\n action: { type: build }\n - name: release\n depends_on: [build]\n action: { type: release }\n - name: deploy\n depends_on: [release]\n action: { type: deploy }\n - name: migrate\n depends_on: [deploy]\n action: { type: job, service: migrate }\n```\n\n**Common mistake**: Running `migrate` before `deploy`. This will fail because the managed DB doesn't exist until the first deploy provisions it.\n\n### Complete Working Example (based on eden)\n\nA fully working manifest with managed DB, migrations, and pipeline — copy and adapt:\n\n```yaml\nname: my-app\nschema: eve/compose/v2\nregistry: \"eve\"\n\nservices:\n api:\n build:\n context: ./apps/api\n dockerfile: ./apps/api/Dockerfile\n ports: [3000]\n environment:\n NODE_ENV: production\n DATABASE_URL: ${managed.db.url}\n\n migrate:\n image: public.ecr.aws/w7c4v0w3/eve-horizon/migrate:latest\n environment:\n DATABASE_URL: ${managed.db.url}\n MIGRATIONS_DIR: /migrations\n x-eve:\n role: job\n files:\n - source: db/migrations\n target: /migrations\n\n db:\n x-eve:\n role: managed_db\n managed:\n class: db.p1\n engine: postgres\n engine_version: \"16\"\n\nenvironments:\n sandbox:\n pipeline: deploy\n\npipelines:\n deploy:\n steps:\n - name: build\n action: { type: build }\n - name: release\n depends_on: [build]\n action: { type: release }\n - name: deploy\n depends_on: [release]\n action: { type: deploy }\n - name: migrate\n depends_on: [deploy]\n action: { type: job, service: migrate }\n```\n\nLocal docker-compose.yml for parity:\n\n```yaml\nservices:\n db:\n image: postgres:16-alpine\n ports: [\"5432:5432\"]\n environment:\n POSTGRES_USER: app\n POSTGRES_PASSWORD: app\n POSTGRES_DB: myapp\n volumes:\n - pgdata:/var/lib/postgresql/data\n healthcheck:\n test: [\"CMD-SHELL\", \"pg_isready -U app -d myapp\"]\n interval: 5s\n timeout: 5s\n retries: 5\n\n migrate:\n image: ghcr.io/incept5/eve-migrate:latest\n environment:\n DATABASE_URL: postgres://app:app@db:5432/myapp\n volumes:\n - ./db/migrations:/migrations:ro\n depends_on:\n db: { condition: service_healthy }\n\nvolumes:\n pgdata:\n```\n\nFirst migration file (`db/migrations/20260312000000_initial_schema.sql`):\n\n```sql\nCREATE EXTENSION IF NOT EXISTS pgcrypto;\n\nCREATE OR REPLACE FUNCTION update_updated_at_column()\nRETURNS TRIGGER AS $\nBEGIN\n NEW.updated_at = NOW();\n RETURN NEW;\nEND;\n$ LANGUAGE plpgsql;\n\nCREATE TABLE items (\n id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n org_id TEXT NOT NULL,\n name TEXT NOT NULL,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\n);\n\nCREATE TRIGGER trg_items_updated_at\n BEFORE UPDATE ON items\n FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();\n\nALTER TABLE items ENABLE ROW LEVEL SECURITY;\n\nCREATE POLICY items_org_isolation ON items\n USING (org_id = current_setting('app.org_id', true))\n WITH CHECK (org_id = current_setting('app.org_id', true));\n```\n\nCreate new migrations with `eve db new \u003cdescription>`, then apply with `docker compose run --rm migrate` locally or let the pipeline handle it on deploy.\n\n## Schema Inspection + RLS\n\n```bash\neve db schema --env \u003cname> [--project \u003cid>] # Show DB schema\neve db schema --url \u003cpostgres-url> # Direct connection\n\neve db rls --env \u003cname> # RLS policies + group context diagnostics\neve db rls init --with-groups [--out \u003cpath>] [--force] # Scaffold group-aware RLS helpers\n```\n\n`rls` shows resolved principal, org, project, env, group IDs, and permissions for the current session. Useful for debugging why RLS policies are not matching.\n\n`rls init --with-groups` scaffolds these SQL functions to `db/rls/helpers.sql`:\n- `app.current_user_id()`\n- `app.current_group_ids()`\n- `app.has_group()`\n\nApply the output SQL to your target DB, then reference helpers in RLS policies.\n\n## SQL Access\n\n```bash\neve db sql --env \u003cname> --sql \"SELECT count(*) FROM users\" # Read-only (default)\neve db sql --env \u003cname> --sql \"UPDATE users SET active = true\" --write # Mutations\neve db sql --env \u003cname> --file ./query.sql # From file\neve db sql --env \u003cname> --sql \"SELECT * FROM users WHERE id = $1\" \\\n --params '[\"user_abc\"]' # Parameterized\neve db sql --url \u003cpostgres-url> --sql \"SELECT 1\" # Direct connection\n```\n\n| Access Mode | Flag | Default |\n|---|---|---|\n| Read-only | (none) | Yes |\n| Read-write | `--write` | No |\n| Parameterized | `--params '[\"arg\"]'` | No |\n| From file | `--file \u003cpath>` | No |\n| Direct | `--url \u003cpostgres-url>` | No |\n\nBoth `--env` (API-proxied) and `--url` (direct) modes are supported for all `sql` operations.\n\n## Scaling, Reset, Wipe, Destroy\n\n```bash\neve db scale --env \u003cname> --class db.p2 # Scale to higher tier\neve db reset --env \u003cname> --force [--no-migrate] # Drop + recreate schema, re-apply migrations\neve db reset --env \u003cname> --force --danger-reset-production # Required for production envs\neve db wipe --env \u003cname> --force # Reset schema WITHOUT re-applying migrations\neve db destroy --env \u003cname> --force # Destroy managed DB entirely\n```\n\n- `reset` drops all schemas except `pg_catalog` and `information_schema`, then re-applies migrations.\n- `--no-migrate` (or use `wipe`) skips migration re-apply after schema drop.\n- `--danger-reset-production` is required when resetting production environments via the API.\n- `destroy` is irreversible -- removes the managed tenant entirely.\n\n## Admin APIs\n\n| Endpoint | Method | Purpose |\n|---|---|---|\n| `/admin/managed-db/instances` | GET | List all managed DB instances |\n| `/admin/managed-db/instances` | POST | Register a new instance |\n| `/admin/managed-db/instances/:id` | GET | Instance details |\n| `/projects/:id/envs/:env/db/managed` | GET | Tenant status |\n| `/projects/:id/envs/:env/db/managed/rotate` | POST | Rotate credentials |\n| `/projects/:id/envs/:env/db/managed/scale` | POST | Scale tier |\n| `/projects/:id/envs/:env/db/managed` | DELETE | Destroy tenant |\n\n## CLI Quick Reference\n\n| Intent | Command |\n|---|---|\n| Check managed DB readiness | `eve db status --env \u003cname>` |\n| List installed extensions | `eve db extensions list --env \u003cname>` |\n| View schema | `eve db schema --env \u003cname>` |\n| Run read-only query | `eve db sql --env \u003cname> --sql \"...\"` |\n| Run mutation | `eve db sql --env \u003cname> --sql \"...\" --write` |\n| Create migration | `eve db new \u003cdescription>` |\n| Apply migrations | `eve db migrate --env \u003cname>` |\n| List applied migrations | `eve db migrations --env \u003cname>` |\n| Inspect RLS policies | `eve db rls --env \u003cname>` |\n| Scaffold RLS helpers | `eve db rls init --with-groups` |\n| Rotate credentials | `eve db rotate-credentials --env \u003cname>` |\n| Scale tier | `eve db scale --env \u003cname> --class db.p2` |\n| Reset schema | `eve db reset --env \u003cname> --force` |\n| Wipe without remigrate | `eve db wipe --env \u003cname> --force` |\n| Destroy managed DB | `eve db destroy --env \u003cname> --force` |\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":12609,"content_sha256":"87498c4d68c5273a38e972ebf27c29d6bc2772b1665125f6d8c665a51407b726"},{"filename":"references/deploy-debug.md","content":"# Deployment + Debugging (Current)\n\n## Use When\n- You need to troubleshoot deploy, ingress, namespace, or runtime worker issues.\n- You need environment-specific diagnostics or service status during incident response.\n- You need K8s architecture behavior for local or staging deployments.\n\n## Deploy Error Classes (DeployFailure kinds)\n\nThe deployer classifies deploy failures and writes the kind to both the attempt\nlog (`error_context.kind`) and `environments.last_deploy_failure_json`. CLI\ncommands (`eve pipeline logs`, `eve job diagnose`, `eve env show`) render the\nkind plus a \"Next step\" hint. Kinds:\n\n| Kind | Cause | Next step |\n|------|-------|-----------|\n| `k8s_api_error` | Kubernetes API returned an unexpected error | Share attempt_id with support; full body is in the log. |\n| `manifest_invalid` | K8s rejected the manifest (400/422) or manifest drift vs ref | `eve manifest validate`; if drift, `eve project sync --ref \u003csha>`. |\n| `image_pull_error` | `ImagePullBackOff` / `ErrImagePull` | Check `imagePullSecret` and image digest; run `eve env diagnose`. |\n| `app_crash_loop` | Container exits non-zero on start | `eve env logs \u003cproject> \u003cenv> \u003cservice> --previous`. |\n| `readiness_timeout` | Pods up but not passing readiness probes | Check `eve env diagnose`; review probe definitions. |\n| `dependency_timeout` | `depends_on` service never became healthy | `eve env logs \u003cproject> \u003cenv> \u003cdep-service>`. |\n| `ingress_conflict` | Another env owns the hostname (first-bind-wins) | `eve domain list`; `eve domain transfer \u003chost> --to \u003cenv>`. |\n\nObject-store bucket deploy failures:\n- `Eve object storage is not configured`: the worker cannot provision buckets.\n On AWS staging, check the worker overlay for `EVE_STORAGE_BACKEND=s3`,\n `EVE_STORAGE_REGION`, `EVE_STORAGE_INTERNAL_BUCKET`,\n `EVE_STORAGE_ORG_BUCKET_PREFIX`, `EVE_STORAGE_APP_BUCKET_PREFIX`, and\n `EVE_STORAGE_PUBLIC_ENDPOINT`. Do not set worker\n `EVE_STORAGE_ACCESS_KEY_ID` or `EVE_STORAGE_SECRET_ACCESS_KEY` on AWS; the\n worker uses its own IRSA role to provision buckets.\n- `isolation mode 'irsa' is not available`: a manifest explicitly requested\n `x-eve.object_store.isolation: irsa`, or `auto` was expected to choose IRSA,\n but the worker is missing `EVE_OIDC_PROVIDER_ARN`, `EVE_OIDC_PROVIDER_URL`,\n `EVE_APP_BUCKET_ROLE_PREFIX`/app bucket prefix, public storage endpoint, or\n IAM permissions for role/policy management. Local k3d should use `auto` or\n `shared`; explicit `irsa` is expected to fail fast there.\n- Missing `EVE_APP_STORAGE_ACCESS_KEY_ID` / `EVE_APP_STORAGE_SECRET_ACCESS_KEY`:\n static credential isolation (`shared` or local `minio-static-key`) was\n selected, but the worker cannot inject app-facing credentials. On local k3d,\n verify MinIO env vars. On AWS, prefer IRSA; only verify the `eve-app-storage`\n secret if deliberately using shared static app credentials.\n- Unexpected static keys in an AWS app pod: `auto` did not resolve to IRSA.\n Check `eve env diagnose \u003cproject> \u003cenv> --json` for\n `.storage_buckets[].isolation_mode`; expected AWS value is `irsa` with\n `iam_role_arn` and `service_account`.\n\nFor request-specific failures after an app is deployed, use the CLI-first\nrequest ladder:\n\n```bash\neve env diagnose \u003cproject> \u003cenv> --request req_01h... --window 120 --json\neve env logs \u003cproject> \u003cenv> \u003cservice> --follow --filter req_id=req_01h...\neve traces query --project \u003cproject> --request-id req_01h... --json\n```\n\n`env diagnose --request` keeps \"not found\" as structured empty data with\nwarnings, not a command failure. Optional audit-log rows are included when a\nservice declares `x-eve.audit_log_table`.\n\nWhen a deploy applies the manifest but fails to reach readiness, `eve env show`\nreports `Current Release` with `(last ready)` and a separate `Last Applied`\nline flagged `DRIFT — cluster differs from last-ready`. Both are persisted in\n`environments.current_release_id` (last ready / rollback base) and\n`last_applied_release_id` (actually in the cluster).\n\n### Pipeline manifest resolution\n\nPipeline runs resolve `manifest_hash` from the deploy ref's manifest, not from\n\"latest manifest at run-create time\". Pushing a manifest fix and immediately\nrunning `eve env deploy --ref \u003cnew-sha>` picks up the new manifest without a\nseparate `eve project sync` step. CLI auto-sync (when `--repo-dir` contains\n`.eve/manifest.yaml`) and the server-side resolver agree on the hash for the\nref. If the ref's manifest cannot be resolved, the deploy fails fast with a\nclear error rather than silently using a stale hash.\n\n## Custom Domain Ownership\n\nCustom domains are env-scoped with **first-bind-wins** semantics. The first env\nto deploy with a hostname under `x-eve.ingress.domains` owns it; later deploys\nof other envs that reference the same hostname log `owned by environment \"\u003cA>\"`\nand skip rendering the ingress. Move ownership with:\n\n```\neve domain list # hostname → env mapping\neve domain transfer \u003chost> --to \u003cenv> # DB-only ownership move\neve env deploy \u003closing-env> # removes stale ingress\neve env deploy \u003cnew-owner-env> # creates new ingress\n```\n\n`eve domain unbind \u003chost>` clears the binding without picking a new owner.\n\n## Load Next\n- `references/cli.md` for command-based diagnostics.\n- `references/pipelines-workflows.md` if the issue is pipeline-triggered.\n- `references/builds-releases.md` for build/release failure context.\n\n## Ask If Missing\n- Confirm runtime mode (`k8s` vs `docker`) and `EVE_API_URL`.\n- Confirm environment name, namespace, and whether this is staging or local.\n- Confirm which command already ran and what exact output/error was returned.\n\n## Default Environment\n\nDefault to **staging** for user guidance. Use local/docker only when explicitly asked for local development.\n\n## Runtime Modes\n\n| Mode | `EVE_RUNTIME` | Purpose | Runner Execution |\n|------|---------------|---------|------------------|\n| Kubernetes | `k8s` | Integration, staging, production | Ephemeral pods |\n| Docker Compose | `docker` (default) | Local dev iteration | Local process |\n\nAgent runtime hot path is configured separately with `EVE_AGENT_RUNTIME_EXECUTION_MODE`:\n- `inline` (default): execute directly in warm runtime pods\n- `runner`: fallback to per-attempt runner pod execution\n\n## K8s Architecture\n\n- **API, Orchestrator, Worker**: Deployments in the `eve` namespace (worker is not per-env).\n- **Postgres**: StatefulSet with 5Gi PVC.\n- **Runner pods**: Ephemeral pods spawned per job attempt for isolated execution.\n- **Ingress**: Access via `http://api.eve.lvh.me` -- no port-forwarding needed.\n\nWhen web auth is enabled, the stack also runs:\n- **supabase-auth (GoTrue)** at `auth.\u003cdomain>`\n- **sso** at `sso.\u003cdomain>`\n- **mailpit** at `mail.\u003cdomain>` (local only)\n- **auth bootstrap job** for GoTrue DB role provisioning\n\n```bash\n./bin/eh k8s start # Start k3d cluster + apply manifests\n./bin/eh k8s deploy # Build images + deploy stack\n./bin/eh k8s status # Check status\n```\n\n### Runner Pod Reaper\n\nThe worker runs a periodic reaper that cleans up orphaned runner pods and PVCs after job completion. Prevents resource leaks if the worker restarts mid-poll. Settings: `EVE_RUNNER_REAPER_ENABLED`, `EVE_RUNNER_REAPER_INTERVAL_MS`, `EVE_RUNNER_REAPER_GRACE_SECONDS`.\n\n### Secrets Provisioning\n\n1. Define secrets in `system-secrets.env.local` (e.g., `GITHUB_TOKEN`, `CLAUDE_CODE_OAUTH_TOKEN`).\n2. Run `./bin/eh k8s secrets` to sync `system-secrets.env.local` plus auth-derived keys into the K8s secret `eve-app`.\n3. Restart deployments that consume `eve-app` (`eve-api`, `eve-orchestrator`, `eve-worker`) so updated env values are loaded.\n4. API reads system secrets as baseline for all environments.\n\n### Ingress Routing\n\n```\nPattern: {service}.{orgSlug}-{projectSlug}-{env}.{domain}\nExample: api.myorg-myproj-staging.eh1.incept5.dev\nNamespace: eve-{orgSlug}-{projectSlug}-{env}\n```\n\nDomain resolution: 1) manifest `x-eve.ingress.domain`, 2) `EVE_DEFAULT_DOMAIN`, 3) no ingress if neither set. Local dev uses `lvh.me` (resolves to 127.0.0.1). Production: set `EVE_DEFAULT_DOMAIN=apps.yourdomain.com`.\n\n### HTTP Ingress Timeouts and Body Size\n\nServices can tune HTTP ingress behavior without raw controller annotations:\n\n```yaml\nx-eve:\n ingress:\n public: true\n port: 3000\n timeout: 600s\n max_body_size: 100m\n```\n\n- `timeout` sets nginx `proxy-read-timeout` and `proxy-send-timeout` for the\n service's primary, alias, and custom-domain ingresses.\n- `max_body_size` sets nginx `proxy-body-size` for the same ingress set.\n- Platform defaults are `EVE_DEFAULT_INGRESS_TIMEOUT=300s` and\n `EVE_DEFAULT_INGRESS_MAX_BODY_SIZE=10m`; manifest values override them.\n- Valid ranges are `1s`-`30m` and `1k`-`1g`. Use Eve jobs for longer work and\n signed uploads/object storage for larger payloads.\n- Phase 1 emits L7 tuning only when `EVE_DEFAULT_INGRESS_CLASS` is `nginx` or\n `nginx-ingress`. Traefik/unknown classes skip these annotations; explicit\n tenant tuning logs a deploy warning.\n\nDiagnostics:\n\n```bash\neve env diagnose \u003cproject> \u003cenv> # renders HTTP Ingress table\neve env diagnose \u003cproject> \u003cenv> --json | jq '.http_ingress'\n```\n\n### Public TCP Ingress\n\nServices can declare `x-eve.tcp_ingress` for public raw TCP protocols. The\ndeployer renders a separate `Service` of type `LoadBalancer` labelled\n`eve.tcp_ingress=true`; normal ClusterIP service behavior and HTTP ingress are\nunchanged.\n\nProviders:\n- `none`: validate only; no public TCP Service.\n- `klipper`: local k3d/k3s LoadBalancer.\n- `aws-nlb`: internet-facing AWS Network Load Balancer; requires\n `deployment/aws-load-balancer-controller` in `kube-system`.\n\nDiagnostics:\n\n```bash\neve env diagnose \u003cproject> \u003cenv> # renders TCP Ingress table\neve env diagnose \u003cproject> \u003cenv> --json | jq '.tcp_ingress'\neve tcp-ingress test \u003cproject> \u003cenv> --listener a1-gt06\n```\n\n`env diagnose` reports each listener as:\n- `pending`: manifest opts in but the LoadBalancer Service is absent.\n- `provisioning`: Service exists but has no external hostname/IP yet.\n- `ready`: Kubernetes reports an external hostname or IP.\n\n`eve tcp-ingress test` performs a TCP connect from the CLI host and exits\nnon-zero on failure. It does not validate the device protocol payload.\n\nLocal k3d needs raw TCP ports mapped at cluster creation:\n\n```bash\n./bin/eh k8s start --tcp-ports 33400,33500 --recreate\n./bin/eh k8s deploy\n```\n\nCommon checks:\n- `eve env diagnose \u003cproject> \u003cenv> --json | jq '.tcp_ingress'`\n- `./bin/eh kubectl get svc -A -l eve.tcp_ingress=true`\n- Verify each listener port appears in the service's manifest `ports`.\n\n### Custom Domain Ingresses\n\nApps can bring their own domains via `x-eve.ingress.domains` on a base service\nor inside `environments.\u003cenv>.overrides.services.\u003csvc>.x-eve.ingress.domains`\n(see `references/manifest.md` for the schema). Domains are auto-registered\nduring manifest sync and also by the deployer before binding. A hostname\ndeclared in exactly one env override is bound to that env during sync. Use\n`eve domain register \u003chost> --project \u003cid> --service \u003csvc> --env \u003cenv>` for\nmanual reservations. Each custom domain gets a separate K8s Ingress resource\nlabeled `eve.custom_domain=true`. During\ndeploy, the deployer checks DNS (A/CNAME) against `EVE_PLATFORM_INGRESS_IP`\n/ `EVE_PLATFORM_INGRESS_HOSTNAME`. `EVE_PLATFORM_INGRESS_IP` accepts a\ncomma-separated list (e.g. `52.209.1.195,52.17.16.223`) for multi-AZ load\nbalancers — DNS verification matches any of the listed IPs against the A\nrecords. If DNS is verified, the Ingress is applied and cert-manager\nprovisions a per-domain TLS cert via HTTP-01. Unverified domains stay in\n`pending_dns` — use `eve domain verify \u003chostname>` to perform real DNS\nresolution and transition to `dns_verified`, then redeploy to activate.\n\nServices that omit `x-eve.ingress.alias` still get custom-domain ingresses\nwhen `x-eve.ingress.domains` is set; the deployer no longer requires an alias\nto render the per-domain Ingress.\n\nStale custom domain ingresses (removed from manifest) are garbage-collected on re-deploy via label selector. The `custom_domains` database table tracks lifecycle states: `pending_dns`, `dns_verified`, `cert_provisioning`, `active`, `dns_error`, `cert_error`, `removed`.\n\n### Debugging Custom Domains\n\n```bash\neve domain list --project \u003cid> # All domains + owning env\neve domain register \u003chost> --project \u003cid> --service \u003csvc> [--env \u003cenv>]\neve domain status \u003chostname> --project \u003cid> # Per-domain DNS / cert state\neve domain verify \u003chostname> --project \u003cid> # Re-run DNS resolution\neve env diagnose \u003cproject> \u003cenv> # Pulls in per-env custom-domain status\n```\n\n`eve domain status \u003chost> --json` includes stable `owner_env`, `dns_state`,\n`cert_state`, and `last_verified_at` fields for automation.\n\n`eve env diagnose` includes the per-env custom-domain rows alongside pod state,\nso an `ingress_conflict` failure or a `pending_dns` domain shows up in the same\noutput as the failing service.\n\n### Ingress TLS\n\nUse cert-manager for automatic TLS on app ingresses. Set `EVE_DEFAULT_TLS_CLUSTER_ISSUER` (e.g., `letsencrypt-prod`) to enable per-host certs via cert-manager annotations. Optionally set `EVE_DEFAULT_TLS_SECRET` for a wildcard cert or `EVE_DEFAULT_INGRESS_CLASS` for a specific ingress controller.\n\n**Custom domains ignore `EVE_DEFAULT_TLS_SECRET`** — wildcard certs don't cover user-owned FQDNs. They always use `cert-manager.io/cluster-issuer` with HTTP-01 challenges. If the ClusterIssuer only has DNS-01 solvers, add an HTTP-01 solver for non-platform hostnames.\n\n## Private Endpoints (Tailscale)\n\nPlatform networking primitive that makes Tailscale-only services (e.g., LM Studio on a Mac Mini, internal APIs) accessible to all cluster workloads. Uses the Tailscale K8s Operator to create egress proxies via ExternalName Services.\n\n> Tailscale Private Endpoints handle **inbound** access from cluster pods to\n> Tailscale-reachable services. For **outbound** stable source-IP egress (UDP\n> hole-punching, vendor APIs that need a stable client port), see \"Stable\n> Egress\" below — that uses `hostNetwork: true` on a public-egress node group,\n> not Tailscale.\n\n### How It Works\n\n```\nK8s Pod → K8s Service (eve-tunnels ns) → Tailscale Operator Egress Proxy → WireGuard → Tailnet Device\n```\n\nEvery private endpoint gets a stable in-cluster DNS name:\n```\nhttp://\u003corgSlug>-\u003cname>.eve-tunnels.svc.cluster.local:\u003cport>\n```\n\nThis URL works from any pod: app pods, agent runtime, workers, runners.\n\n### CLI Commands\n\n```bash\n# Register a private endpoint\neve endpoint add \\\n --name lmstudio \\\n --provider tailscale \\\n --tailscale-hostname mac-mini.tail12345.ts.net \\\n --port 1234\n\n# List / show / diagnose\neve endpoint list\neve endpoint show lmstudio --verbose\neve endpoint diagnose lmstudio\n\n# Remove\neve endpoint remove lmstudio\n```\n\n### Wiring to Apps and Agents\n\nPrivate endpoints integrate via standard BYOK secrets:\n\n```bash\n# Set the endpoint URL as a secret\neve secrets set LLM_BASE_URL \\\n \"http://myorg-lmstudio.eve-tunnels.svc.cluster.local:1234/v1\" \\\n --scope project\n\neve secrets set LLM_API_KEY \"lm-studio-xxx\" --scope project\n```\n\nReference in manifests or agent profiles via `${secrets.LLM_BASE_URL}`.\n\n### Diagnostics\n\n`eve endpoint diagnose \u003cname>` checks: operator running, K8s Service exists, egress proxy created, DNS resolution, TCP connectivity, HTTP health check. Status transitions: `pending` -> `ready` or `error`.\n\n### Prerequisites\n\nThe Tailscale K8s Operator must be installed once per cluster (k3d, staging, production). The Eve API service account needs RBAC to create/delete Services in the `eve-tunnels` namespace.\n\n**k3d shortcut**: If the host Mac is already on Tailscale, k3d containers can typically route to `100.x.x.x` addresses directly. Use raw `eve secrets set` to point at the Tailscale IP instead of `eve endpoint add`.\n\n## Stable Egress\n\nDifferent problem from Private Endpoints. Stable egress gives an **opted-in\nservice** endpoint-independent UDP NAT mappings on outbound traffic —\ntypically to fix UDP hole-punching, STUN-discovered NAT traversal, or any\nvendor protocol that depends on a stable source-port across destinations.\nThe cluster NAT Gateway breaks all of these because it does symmetric\nport translation.\n\nOpt in from the manifest:\n\n```yaml\nservices:\n api:\n x-eve:\n networking:\n egress: stable\n```\n\nSee `references/manifest.md` for schema. EKS only — k3d treats the field as a\nno-op and logs a warning.\n\n### How It Works\n\nThere is no sidecar, no Secret, no tunnel. On EKS opt-in, the platform\ndeployer renders the pod with `hostNetwork: true` and schedules it onto a\ndedicated managed node group whose nodes have their own associated public\nIPv4. Traffic exits through the node's primary ENI directly to the Internet\nGateway, which performs **1:1 NAT** (port preserved across destinations).\n\n```\napp container (hostNetwork: true)\n ↓ binds in the node netns, pod IP = node primary IP\nnode primary ENI (10.0.x.y)\n ↓ Internet Gateway 1:1 NAT\nnode public IPv4 (54.x.y.z) → vendor / Internet\n```\n\nCluster prerequisites (handled by infra repo):\n- `eks-egress-pool` Terraform module provisions the node group with the\n `eve.io/egress-pool=stable` label and matching `NoSchedule` taint.\n- `egress-snat-bypass` DaemonSet inserts an early-RETURN rule above the AWS\n VPC CNI's MASQUERADE in `AWS-SNAT-CHAIN-0`, so hostNetwork traffic is\n not re-NAT'd with `--random-fully` (which would otherwise restore\n symmetric NAT).\n- Worker deployment has `EVE_COMPUTE_MODEL=eks` set so the deployer's\n hostNetwork render path activates on opt-in.\n\n### Verifying Injection\n\n```bash\n# Pod has hostNetwork + dnsPolicy + egress-pool selector + EVE_NETWORK_EGRESS\nkubectl -n \u003cns> get pod -l eve.component=\u003cservice> -o yaml \\\n | grep -E 'hostNetwork|dnsPolicy|egress-pool|EVE_NETWORK_EGRESS'\n# Expect: hostNetwork: true, dnsPolicy: ClusterFirstWithHostNet,\n# eve.io/egress-pool: stable, EVE_NETWORK_EGRESS env var present.\n\n# Pod IP matches the egress node's primary IP\nkubectl -n \u003cns> get pod -l eve.component=\u003cservice> -o wide\nkubectl get nodes -l eve.io/egress-pool=stable -o wide\n```\n\n### Verifying Egress Behavior\n\nThe diagnostic helper in the infra repo runs three checks from a single\nlocal UDP socket — same-socket STUN is the only valid way to classify\nendpoint-independent vs address-and-port-dependent NAT.\n\n```bash\nINFRA=../incept5-eve-infra\nkubectl -n \u003cns> cp $INFRA/scripts/stable-egress/udp-diag.py \u003cpod>:/tmp/udp-diag.py -c \u003ccontainer>\nkubectl -n \u003cns> exec \u003cpod> -c \u003ccontainer> -- python3 /tmp/udp-diag.py\n```\n\nRead the output:\n\n| Check | Pass | Fail |\n|-------|------|------|\n| `egress public IP` | Equals the egress node's `EXTERNAL-IP` (`kubectl get nodes -l eve.io/egress-pool=stable`). | Equals the cluster NAT Gateway EIP — pod isn't actually running with hostNetwork or isn't on an egress node. Verify scheduling. |\n| STUN classification | `endpoint-independent (good)` — same external port across all three STUN servers, equal to the local socket port. | `address/port-dependent` — `egress-snat-bypass` DaemonSet is missing or its rule got evicted by a CNI restart. |\n| UDP/53 to 1.1.1.1 | OK in \u003c100 ms. | UDP egress broken entirely. Different problem. |\n\n### Common Failure Modes\n\n| Symptom | Likely Cause |\n|---------|--------------|\n| Pod stuck `Pending` with event `0/N nodes are available: ... eve.io/egress-pool selector unsatisfied` | Egress node group is not provisioned. Set `stable_egress_enabled = true` in the infra repo, then `terraform apply`. |\n| Pod renders without `hostNetwork: true` | Worker is missing `EVE_COMPUTE_MODEL=eks`. Render falls through to the no-op path. Re-roll the worker overlay. |\n| Render error: `Phase 1 requires replicas=1` | Multi-replica hostNetwork services are Phase 2 (need anti-affinity + cluster-wide port-collision validation). Set `replicas: 1` for now. |\n| Render error: `port(s) ... in the Kubernetes NodePort range (30000-32767)` | EKS node SG opens NodePort range from `0.0.0.0/0` for NLB traffic. Pick a service port outside that range. |\n| Egress IP equals the egress node's EXTERNAL-IP, but STUN ports differ across servers | `egress-snat-bypass` DaemonSet not running on the egress node. Check `kubectl -n eve get pods -l app.kubernetes.io/name=egress-snat-bypass` and inspect `iptables -t nat -L AWS-SNAT-CHAIN-0` on the node — there should be an early `RETURN` rule for the node's primary IP, before the SNAT. |\n| Vendor relay still times out but STUN passes | NAT semantics are correct; the remaining issue is app/vendor-specific (firewall on vendor side, expired credentials, decommissioned relay host, app-level retry). Reopen the protocol diagnosis at app level. |\n\n## Continuous Environment Monitoring (Sentinel)\n\nThe orchestrator runs an `EnvHealthWatchdogService` cron that periodically\ndiagnoses every active environment, classifies pod health\n(`ImagePullBackOff`, `CrashLoopBackOff`, unresolved env-var interpolation,\nlong `Pending`), upserts state into `environment_health_checks`, and posts\ntransitions to the configured Slack channel via the platform notification\nservice. The orchestrator queries K8s directly (its own `eve-orchestrator`\nServiceAccount has read access to pods/events plus `deployments/scale`); the\nwatchdog needs `environments.namespace` to be populated, which the deployer\nnow persists before readiness checks so freshly-deployed envs are discovered\non the next tick.\n\nWhen an environment stays degraded for `EVE_ENV_HEALTH_STABLE_TICKS` ticks\nand breaches the circuit-breaker thresholds (default: `CrashLoopBackOff` with\n>50 restarts, or any terminal failure for >30 min), the watchdog scales the\nfailing Deployment to zero and marks `environments.deploy_status='failed'`.\nThe Deployment is preserved (not deleted) so `eve env diagnose` keeps full\ncontext; a subsequent `eve env deploy` redeploys normally.\n\nConfiguration (orchestrator env vars):\n\n| Variable | Default | Purpose |\n|---|---|---|\n| `EVE_ENV_HEALTH_ENABLED` | `false` | Master kill switch — opt in |\n| `EVE_ENV_HEALTH_INTERVAL_MS` | `120000` | Tick cadence |\n| `EVE_ENV_HEALTH_STABLE_TICKS` | `2` | Consecutive degraded ticks before action |\n| `EVE_ENV_HEALTH_CIRCUIT_BREAK_ENABLED` | `true` | Gate scale-to-zero |\n| `EVE_ENV_HEALTH_CIRCUIT_BREAK_AFTER_RESTARTS` | `50` | CrashLoop trigger |\n| `EVE_ENV_HEALTH_CIRCUIT_BREAK_AFTER_MS` | `1800000` | Time-in-failure trigger |\n\nSlack channel + workspace are stored as system settings\n(`sentinel.slack.integration_id`, `sentinel.slack.channel_id`,\n`sentinel.enabled`). Notifications are deduplicated within a 4h window per\nenvironment+signature.\n\n## Managed DB TLS Trust\n\nThe worker deployer mounts a namespace-scoped CA bundle into every app\nDeployment and job pod when the environment has at least one cloud managed\nDB tenant (`aws-rds`, `gcp-cloudsql`):\n\n- ConfigMap `eve-db-trust` in the env namespace contains the per-provider PEM bundle.\n- Pods receive volume mount `/etc/eve/trust/ca-bundle.pem` plus env vars\n `NODE_EXTRA_CA_CERTS` and `PGSSLROOTCERT` pointing at it.\n- Cloud tenant URLs default to `sslmode=verify-full`. Local managed instances stay on `sslmode=disable`.\n\nApps no longer need `ssl: { rejectUnauthorized: false }` — the platform owns\ntrust distribution. The shared trust registry lives at\n`packages/shared/src/managed-db/trust/`. See `references/database-ops.md` for\nhow this surfaces in app code and migration jobs.\n\nIf managed DB connections start failing with TLS verification errors after a\ndeploy:\n\n| Symptom | Likely Cause | Fix |\n|---|---|---|\n| `self signed certificate in certificate chain` from Node `pg` | Pod missing the trust bundle (deployed before trust injection landed, or env has no managed DB tenants) | Re-deploy; check `kubectl -n \u003cns> get cm eve-db-trust`. |\n| `no pg_hba.conf entry for host ... SSL off` | URL was rewritten with `sslmode=disable` by the old resolver | Confirm `DATABASE_URL` carries `sslmode=verify-full`; the orchestrator now inherits sslmode from the source URL. |\n| Migration job fails but app pods connect | Job pod missing trust mount | Job render path also injects the bundle; re-deploy and check the Job pod spec. |\n\n## Worker Image Registry\n\nPre-built images on public ECR eliminate local builds and ensure consistent toolchains.\n\n| Image | Public ECR Path | Contents |\n|-------|-----------|----------|\n| base | `public.ecr.aws/w7c4v0w3/eve-horizon/worker-base:\u003cver>` | Node.js, worker harness, base utilities |\n| python | `public.ecr.aws/w7c4v0w3/eve-horizon/worker-python:\u003cver>-py3.11` | Python 3.11, pip, uv |\n| rust | `public.ecr.aws/w7c4v0w3/eve-horizon/worker-rust:\u003cver>-rust1.75` | Rust 1.75, cargo |\n| java | `public.ecr.aws/w7c4v0w3/eve-horizon/worker-java:\u003cver>-jdk21` | OpenJDK 21 |\n| kotlin | `public.ecr.aws/w7c4v0w3/eve-horizon/worker-kotlin:\u003cver>-kotlin2.0-jdk21` | Kotlin 2.0 + JDK 21 |\n| full | `public.ecr.aws/w7c4v0w3/eve-horizon/worker-full:\u003cver>` | All toolchains (default) |\n\n### Versioning + Pinning\n\n- **Version tags**: `0.1.0`, `0.1.0-py3.11` (from git tag `worker-images/vX.Y.Z`).\n- **SHA tags**: `sha-a1b2c3d` (every build, for commit-level pinning).\n- **Multi-arch**: `linux/amd64` + `linux/arm64`.\n\nConfigure via `EVE_RUNNER_IMAGE` on the worker deployment. Pin to semantic versions in production. Use SHA tags or `latest` for dev/CI only.\n\n## Worker Toolchain-on-Demand\n\nThe default worker runs the `base` image (~800MB: Node.js, git, harnesses). Toolchains (Python, Rust, Java, Kotlin, media/ffmpeg) are injected via init containers only when needed.\n\n### How Init Container Injection Works\n\nWhen a job declares toolchains (via agent config or workflow step), the orchestrator adds init containers to the runner pod:\n\n```\nRunner Pod\n Init: tc-python → copies /toolchain/* to /opt/eve/toolchains/python/\n Init: tc-media → copies /toolchain/* to /opt/eve/toolchains/media/\n Container: runner → base image, PATH extended with toolchain bins\n```\n\nEach toolchain image is small (50-300MB). Init containers finish in \u003c1s if the image is cached on the node. First pull adds ~5-10s.\n\n### Toolchain Images\n\n| Toolchain | Contents | Size |\n|-----------|----------|------|\n| `python` | Python 3, pip, venv, uv | ~100MB |\n| `media` | ffmpeg, ffprobe, whisper-cli + ggml-small.en model | ~300MB |\n| `rust` | rustup, stable toolchain, rustfmt, clippy | ~400MB |\n| `java` | Temurin JDK 21 | ~300MB |\n| `kotlin` | kotlinc 2.0 + JDK 21 (self-contained) | ~350MB |\n\nImages published to ECR: `public.ecr.aws/w7c4v0w3/eve-horizon/toolchain-{name}:{version}`\n\n### Environment Setup\n\nThe entrypoint sources per-toolchain `env.sh` files and extends `PATH`:\n\n```bash\n# Automatic: entrypoint.sh handles this\nexport PATH=\"${EVE_TOOLCHAIN_PATHS}:${PATH}\"\n# Per-toolchain env.sh sets JAVA_HOME, RUSTUP_HOME, CARGO_HOME, etc.\n```\n\n### Deployment Impact\n\n- Default worker variant changed from `full` to `base` (CI and local k3d)\n- `EVE_WORKER_VARIANT=full` restores the old fat image if needed\n- Agents without `toolchains` field are unaffected (no init containers)\n- Configure toolchain image prefix/tag: `EVE_TOOLCHAIN_IMAGE_PREFIX`, `EVE_TOOLCHAIN_IMAGE_TAG`\n- Local k3d: build + import toolchain images with `./bin/eh k8s image --toolchains`\n\n### Debugging Toolchain Issues\n\nIf a job fails with \"command not found\" for a toolchain binary:\n1. Check the agent config declares `toolchains: [\u003cname>]`\n2. Check init container logs: `kubectl -n eve logs \u003cpod> -c tc-\u003cname>`\n3. Verify the toolchain image exists: `docker pull \u003cprefix>\u003cname>:\u003ctag>`\n4. Fallback: set `EVE_WORKER_VARIANT=full` on the worker deployment\n\n## Docker Compose Runtime (Dev Only)\n\nOptimized for fast local iteration. **Security note:** exposes services on localhost with simple defaults -- never run in shared or internet-exposed environments.\n\n```bash\n./bin/eh docker auth # Extract auth credentials from host\n./bin/eh start docker # Start the stack\n```\n\n| Aspect | Docker Compose | K8s (k3d) |\n|--------|----------------|-----------|\n| Startup | ~10s | ~60s |\n| Prod parity | Moderate | High |\n| Runner pods | No (local process) | Yes (ephemeral) |\n\n## Local Multi-Project Mesh\n\nUse `eve local mesh` for k3d verification when two or more Eve projects talk\nthrough `x-eve.app_links`.\n\n```bash\nexport EVE_API_URL=http://api.eve.lvh.me\neve local mesh init obs --org org_manualtestorg --env local\neve local mesh add prod --path ../producer\neve local mesh add cons --path ../consumer\neve local mesh up\neve local mesh status\neve local mesh diagnose --probe\neve local mesh redeploy cons\neve local mesh down\n```\n\nThe platform stack must already be running. Mesh project names are Eve project\nslugs and every project should declare `environments.local`. The command syncs\nand deploys producer projects before consumers, and it can build/import\nproducer `Dockerfile.cli` images into k3d for app-link job CLI injection.\n\n## First Deploy Quickstart\n\nThe fastest path from zero to a running deployment. Create a minimal `.eve/manifest.yaml`:\n\n```yaml\nschema: eve/compose/v2\nproject: my-app\n\nregistry: \"eve\"\n\nservices:\n app:\n build:\n context: .\n ports: [\"3000\"]\n x-eve:\n ingress:\n public: true\n\nenvironments:\n sandbox:\n pipeline: deploy\n\npipelines:\n deploy:\n steps:\n - name: build\n action: { type: build }\n - name: release\n depends_on: [build]\n action: { type: release }\n - name: deploy\n depends_on: [release]\n action: { type: deploy, env_name: sandbox }\n```\n\nThen deploy with two commands:\n\n```bash\neve project sync --dir .\neve env deploy sandbox --ref main\n```\n\nKey points:\n- **`registry: \"eve\"`** uses the Eve-native container registry -- no external registry setup needed.\n- **`image` is optional** when `build` and `registry` are configured -- the platform derives image names from service keys.\n- **`eve env deploy` auto-creates** the `sandbox` environment because it is defined in `manifest.environments`. No separate `eve env create` step is required.\n- The pipeline builds, releases, and deploys in sequence. Access the app at `http://app.{orgSlug}-{projectSlug}-sandbox.{domain}`.\n\n## Deploying Environments\n\n```bash\neve env deploy staging --ref main --repo-dir ./my-app\neve env deploy staging --ref \u003c40-char-sha>\n```\n\nIf `environments.\u003cenv>.pipeline` is set, `eve env deploy` triggers that pipeline. Use `--direct` to bypass. `--ref` must be a 40-char SHA or a ref resolved against `--repo-dir`/cwd. When `--repo-dir` is provided and the directory contains `.eve/manifest.yaml`, the CLI automatically syncs the manifest before deploying (see below).\n\n### Manifest Auto-Sync on Deploy\n\nWhen `--repo-dir` points to a repository containing `.eve/manifest.yaml`, the CLI automatically syncs the manifest to the API before deploying. This eliminates the separate `eve project sync` step:\n\n1. CLI reads `.eve/manifest.yaml` from the repo directory.\n2. Expands workflow `$ref` entries and `agent.prompt_file` prompts, matching\n `eve project sync`.\n3. POSTs the expanded manifest YAML with the resolved git SHA and branch to sync.\n4. Uses the returned manifest hash for the deploy request.\n\nIf no `.eve/manifest.yaml` exists in the repo directory, the CLI falls back to fetching the latest manifest hash from the server (previous behavior).\n\n## Deploy Polling\n\n### Starting a Deploy\n\n```\nPOST /projects/{projectId}/envs/{envName}/deploy\n```\n\nBody requires `release_tag` **or** both `git_sha` + `manifest_hash`. Optional: `image_digests`, `image_tag`, `direct` (bypass pipeline), `inputs`.\n\n### Response Discrimination\n\nCheck for `pipeline_run` in the response:\n- **Present**: pipeline deploy -- poll the pipeline run, then health check.\n- **Absent**: direct deploy -- skip to health check immediately.\n\n### Pipeline Polling\n\n```\nGET /projects/{projectId}/pipelines/{pipelineName}/runs/{runId}\n```\n\nTerminal statuses: `succeeded` (proceed to health), `failed` (read errors), `cancelled` (aborted). Non-terminal: `pending`, `running`, `awaiting_approval`. Inspect individual steps on failure for `error_message`, `exit_code`, and `logs_ref`.\n\n### Health Check\n\n```\nGET /projects/{projectId}/envs/{envName}/health\n```\n\n| Status | Meaning |\n|--------|---------|\n| `ready` | All pods healthy, no in-flight pipeline |\n| `deploying` | Pods rolling out or pipeline active |\n| `degraded` | Some pods unhealthy |\n| `unknown` | K8s unavailable |\n\nThe health endpoint is **pipeline-aware**: reports `deploying` while a pipeline run is pending/running, preventing false `ready` from stale pods.\n\nDeploy is complete when all three hold: `ready === true`, `active_pipeline_run === null`, `status === \"ready\"`.\n\n### Polling Pseudocode\n\n```\nresponse = POST /projects/{id}/envs/{env}/deploy { ... }\n\nif response.pipeline_run:\n run_id = response.pipeline_run.run.id\n pipeline = response.pipeline_run.run.pipeline_name\n loop every 3-5s, timeout 300s:\n detail = GET /projects/{id}/pipelines/{pipeline}/runs/{run_id}\n if detail.run.status == \"succeeded\": break\n if detail.run.status in (\"failed\",\"cancelled\"): FAIL\n loop every 3-5s, timeout 120s:\n health = GET /projects/{id}/envs/{env}/health\n if health.ready and not health.active_pipeline_run: SUCCESS\nelse:\n loop every 3-5s, timeout 120s:\n health = GET /projects/{id}/envs/{env}/health\n if health.ready: SUCCESS\n```\n\n## App Undeploy & Delete Lifecycle\n\n### Environment Undeploy\n\nTake an environment offline without losing config. Tears down K8s namespace but preserves the environment record (variables, secrets bindings, release pointer).\n\n```bash\neve env undeploy \u003cenv> --project \u003cid>\neve env show \u003cproject> \u003cenv> --json # Verify deploy_status = 'undeployed'\n```\n\nRedeploy later: `eve env deploy \u003cenv> --ref \u003csha>`. The `deploy_status` field tracks state: `unknown` | `deployed` | `undeployed` | `deploying` | `undeploying` | `failed`. Deploy/rollback/reset flows update this automatically.\n\n### Project Delete\n\n```bash\neve project delete \u003cproject> # Soft-delete (sets deleted_at)\neve project delete \u003cproject> --hard # Hard-delete with cascading cleanup\neve project delete \u003cproject> --hard --force # Continue on partial failures\n```\n\nHard delete: undeploys all environments, cascade-deletes jobs/pipeline-runs/releases/builds/agents/teams/threads, then deletes the project record.\n\n### Org Delete\n\n```bash\neve org delete \u003corg> # Soft-delete\neve org delete \u003corg> --hard --force # Full tenancy teardown\n```\n\n### Resource Cleanup\n\n```bash\neve build delete \u003cid>\neve build prune --project \u003cid> --keep 10 # Keep last N\neve release delete \u003cid>\neve release prune --project \u003cid> --keep 10\neve pipeline delete \u003cname> --project \u003cid>\neve agents delete \u003cname> --project \u003cid>\neve thread delete \u003cid>\n```\n\n### Debugging Delete Failures\n\nIf `--hard` delete fails partway, re-run with `--force` to continue past individual failures. Check for FK constraint errors in API logs -- the migration `00080_fix_project_cascade_deletes.sql` fixes cascades on 11 tables referencing `projects(id)`.\n\n## Environment Variable Interpolation\n\nManifest environment values support these interpolation variables:\n\n| Variable | Description | Example |\n|----------|-------------|---------|\n| `${ENV_NAME}` | Environment name | `staging` |\n| `${PROJECT_ID}` | Project ID | `proj_01abc...` |\n| `${ORG_ID}` | Organization ID | `org_01xyz...` |\n| `${ORG_SLUG}` | Organization slug | `acme` |\n| `${COMPONENT_NAME}` | Current service name | `api`, `web` |\n| `${SSO_URL}` | Platform SSO broker URL | `https://sso.eh1.incept5.dev` |\n| `${secret.KEY}` | Secret value | `${secret.DB_PASSWORD}` |\n| `${managed.\u003cservice>.\u003cfield>}` | Managed DB value (when provisioned) | `${managed.db.url}` |\n\n## Platform Env Vars (Injected into Deployed Apps)\n\n| Variable | Purpose |\n|----------|---------|\n| `EVE_API_URL` | Internal cluster URL for server-to-server calls |\n| `EVE_PUBLIC_API_URL` | Public ingress URL for browser-facing apps |\n| `EVE_SSO_URL` | SSO broker URL for user authentication |\n| `EVE_PROJECT_ID` | Current project ID |\n| `EVE_ORG_ID` | Current org ID |\n| `EVE_ENV_NAME` | Current environment name |\n\nUse `EVE_API_URL` for backend calls from containers. Use `EVE_PUBLIC_API_URL` for browser/client-side code.\n\n## Runtime Environment Variables\n\n| Variable | Component | Purpose |\n|----------|-----------|---------|\n| `ORCH_LOOP_INTERVAL_MS` | Orchestrator | Main claim/dispatch loop cadence |\n| `ORCH_CONCURRENCY` | Orchestrator | Base concurrency |\n| `ORCH_CONCURRENCY_MIN` / `_MAX` | Orchestrator | Tuner bounds |\n| `ORCH_TUNER_ENABLED` | Orchestrator | Enable adaptive tuning |\n| `ORCH_TUNER_INTERVAL_MS` | Orchestrator | Tuning check interval |\n| `ORCH_TUNER_CPU_THRESHOLD` | Orchestrator | CPU threshold for scaling |\n| `ORCH_TUNER_MEMORY_THRESHOLD` | Orchestrator | Memory threshold for scaling |\n| `EVE_WORKER_POLL_INTERVAL_MS` | Orchestrator | Poll cadence for worker completion events |\n| `EVE_AGENT_RUNTIME_POLL_INTERVAL_MS` | Orchestrator | Poll cadence for agent-runtime completion events |\n| `EVE_RUNNER_IMAGE` | Worker | Container image for runner pods |\n| `EVE_RUNNER_REAPER_ENABLED` | Worker | Enable pod reaper |\n| `EVE_RUNNER_REAPER_INTERVAL_MS` | Worker | Reaper sweep interval |\n| `EVE_RUNNER_REAPER_GRACE_SECONDS` | Worker | Grace before cleanup |\n| `EVE_WORKSPACE_MAX_GB` | Worker | Total workspace budget per instance |\n| `EVE_WORKSPACE_MIN_FREE_GB` | Worker | Hard floor; refuse new claims below |\n| `EVE_WORKSPACE_TTL_HOURS` | Worker | Idle TTL for job worktrees |\n| `EVE_SESSION_TTL_HOURS` | Worker | Idle TTL for session workspaces |\n| `EVE_MIRROR_MAX_GB` | Worker | Cap for bare mirrors |\n\n## Workspace Janitor (Disk Safety)\n\nPolicies for production disk management:\n- LRU eviction of worktrees when over budget.\n- TTL cleanup for idle job/session worktrees.\n- Mirror maintenance: `git fetch --prune` + periodic `git gc --prune=now`.\n- Emit system events on low disk; refuse new attempts below minimum free space.\n\nK8s: per-attempt PVCs are deleted after completion. Session-scoped PVCs require TTL cleanup and storage quotas.\n\n## Infrastructure Change Policy\n\nAll AWS infrastructure changes must go through Terraform in the `incept5-eve-infra` repo. No exceptions.\n\n- **Never** mutate AWS resources (security groups, IAM, DNS, EKS, ASGs) via CLI or console -- Terraform will silently revert them on the next apply, which has caused production outages.\n- **Read-only** AWS CLI commands (`describe`, `list`, `get`) are fine for diagnosis.\n- If staging infra is broken, fix it in `incept5-eve-infra/terraform/aws/`, run `terraform plan` then `terraform apply`, and verify the plan shows no changes after apply.\n- If you lack access to the infra repo, escalate to the user -- do not apply ad-hoc fixes.\n\n## CLI-First Debugging Ladder\n\n1. **CLI first** -- `eve system health`, `eve job diagnose \u003cid>`, `eve job follow \u003cid>`.\n2. **Environment status** -- `./bin/eh status` to confirm URLs and running services.\n3. **kubectl** only if CLI lacks data.\n\nCLI only needs `EVE_API_URL`: local/docker = `http://localhost:4801`, K8s = `http://api.eve.lvh.me`.\n\n## Debugging Workflows\n\n### Job Won't Start\n\n```bash\neve job show \u003cid> --verbose # Check phase\neve job dep list \u003cid> # Check blocked deps\neve job ready --project \u003cid> # Check ready queue\n```\n\nCommon causes: phase is not `ready`, blocked by dependencies, orchestrator unhealthy.\n\n### Job Failed\n\n```bash\neve job diagnose \u003cid> # Status, timeline, attempts, errors, recommendations\neve job logs \u003cid> --attempt N # Detailed logs for specific attempt\n```\n\n### Job Stuck Active\n\nRun `eve job diagnose \u003cid>` — the output is heartbeat-aware:\n\n- **Heartbeat recent** (`\u003c120s`): `▶ Harness alive (last heartbeat 15s ago, 291s elapsed)` — the harness is working, just generating output (e.g., long LLM call).\n- **Heartbeat stale** (`>120s`): `⚠ No harness heartbeat for 180s — process may have crashed` — the harness likely crashed. Check `eve job logs \u003cid>`.\n- **No heartbeat data**: Falls back to elapsed-time heuristic (warns after 300s).\n\nThe diagnose `Latest Attempt` section shows:\n- **Pod**: Which agent-runtime pod is running the job (e.g., `eve-agent-runtime-0`)\n- **Heartbeat**: Time since last heartbeat and elapsed execution time\n- **Pod health**: Live status from the agent-runtime heartbeat system (for active jobs)\n\n```bash\neve job diagnose \u003cid> # Heartbeat-aware stuck detection + pod context\neve job follow \u003cid> # Silence warnings at 60s/120s with heartbeat context\neve job logs \u003cid> # Includes pre-harness startup events (clone, creds)\n```\n\n`eve job follow` also has built-in silence detection:\n- After **60s** of no output: prints a contextual warning (e.g., \"harness alive\" if heartbeat is recent, or suggests `eve job diagnose` if no heartbeat data)\n- After **120s**: escalated warning. Heartbeat lifecycle events are silently consumed (not printed as log lines) but tracked for silence detection.\n\n### System Issues\n\n```bash\neve system health # Quick health check\neve system status # Shows all services INCLUDING agent runtime health\neve system logs api # API pod logs\neve system logs orchestrator # Orchestrator logs\neve system logs worker # Worker logs\neve system logs postgres # DB logs\n```\n\n`eve system status` renders all services with health indicators:\n```\nServices:\n API: ✓ healthy (vdev)\n Orchestrator: ✓ healthy\n Agent Runtime: ✓ healthy [3 replicas]\n Worker: ✓ healthy\n```\n\nIf API is unhealthy: check API logs, verify database, confirm `EVE_API_URL`.\n\n### Build Debugging\n\n```bash\neve build list --project \u003cid> # Recent builds\neve build diagnose \u003cbuild_id> # Spec + runs + artifacts + logs\neve build logs \u003cbuild_id> # Raw build output\n```\n\nCommon issues: registry auth (`registry: \"eve\"` for managed apps; `REGISTRY_USERNAME` + `REGISTRY_PASSWORD` only for BYO/custom registry), Dockerfile path (`build.context`), build backend (BuildKit on K8s, Buildx locally).\n\n## Real-time Debugging\n\n### Three-Terminal Approach\n\n```bash\n# Terminal 1: Poll status\nwatch -n 5 'eve job show \u003cid> --verbose 2>&1 | head -30'\n\n# Terminal 2: Stream harness logs\neve job follow \u003cid>\n\n# Terminal 3 (K8s): Watch runner pod\neve job runner-logs \u003cid>\n```\n\n| Command | What It Shows |\n|---------|---------------|\n| `eve job watch \u003cid>` | Combined status + logs streaming |\n| `eve job follow \u003cid>` | Harness JSONL logs (SSE) -- harness output only |\n| `eve job runner-logs \u003cid>` | K8s runner pod stdout/stderr |\n| `eve job wait \u003cid> --verbose` | Status changes while waiting |\n\nStartup lifecycle events (git clone, credential write, app CLI discovery) now appear in `eve job diagnose` as part of the latency breakdown and in `eve job logs`. The latency waterfall shows timing for each phase:\n\n```\nLatency Breakdown:\n secrets 5ms ░░░░░░░░░░░░░░░░ 0%\n workspace 552ms ░░░░░░░░░░░░░░░░ 2%\n hook 4,815ms ██░░░░░░░░░░░░░░ 14%\n secrets 7ms ░░░░░░░░░░░░░░░░ 0%\n harness 28,848ms █████████████░░░ 84%\n```\n\nIf a clone or credential write fails, the lifecycle `end` event captures `success: false` and the error message.\n\n### Auth / Secrets Failures\n\n```bash\neve secrets show GITHUB_TOKEN --project \u003cproj_id>\neve secrets list --project \u003cproj_id>\n```\n\nCheck orchestrator/worker logs for `[resolveSecrets]` warnings. Verify `EVE_INTERNAL_API_KEY` and `EVE_SECRETS_MASTER_KEY` are set.\n\n## Common Error Messages\n\n| Error | Meaning | Fix |\n|-------|---------|-----|\n| \"OAuth token has expired\" | Claude auth stale | `./bin/eh auth extract --save` then redeploy |\n| \"git clone failed\" | Repo inaccessible | Check GITHUB_TOKEN secret |\n| \"Service X ready check failed\" | Service provisioning issue | Check manifest services, container logs |\n| \"Orchestrator restarted while attempt was running\" | Job orphaned on restart | Auto-retries via recovery |\n\n## Environment-Specific Debugging\n\n**Local dev:** `./bin/eh start local` -- logs at `/tmp/eve-{api,orchestrator,worker}.log`.\n\n**Docker Compose:** `./bin/eh start docker` -- use `docker logs eve-api -f`, `docker logs eve-orchestrator -f`, `docker logs eve-worker -f`.\n\n**Kubernetes:** `./bin/eh k8s start` + `export EVE_API_URL=http://api.eve.lvh.me`. Use `kubectl -n eve get pods | grep runner` and `kubectl -n eve logs -f eve-runner-\u003cattempt-id>` only as last resort.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":44740,"content_sha256":"f92510a4b3276de5ec91a1208e38f8e70a744e74629f0660bebf62587c3bae26"},{"filename":"references/document-ingestion.md","content":"# Document Ingestion Pipeline\n\n## Use When\n- You need to wire end-to-end document processing: file upload through agent analysis to structured output.\n- You need to configure the ingest agentpack or customize its processing behavior.\n- You need to understand how media files (audio, video) are transcribed by agents.\n- You need to understand how Slack file uploads reach agent workspaces.\n- You need to connect ingestion to workflows, events, or org docs output.\n\n## Load Next\n- `references/ingest.md` for API endpoints, CLI flags, callback notifications, and CORS details.\n- `references/events.md` for `system.doc.ingest` event payload and trigger syntax.\n- `references/pipelines-workflows.md` for workflow trigger wiring.\n- `references/jobs.md` for resource refs, hydration, and job lifecycle.\n- `references/object-store-filesystem.md` for org bucket storage and presigned URL patterns.\n\n## Ask If Missing\n- Confirm the target project ID and whether a processing workflow is already configured.\n- Confirm whether the input is CLI-driven, API-driven, or chat-driven (Slack file upload).\n- Confirm whether media processing (audio/video transcription) is required.\n- Confirm the desired output format and destination (org docs path, job result, callback).\n\n## Pipeline Overview\n\nDocument ingestion is a composable flow: **file in -> event -> agent processes -> structured output**.\n\n```\nInput Channels Platform Spine Agent Processing\n───────────── ────────────── ────────────────\neve ingest \u003cfile> ─┐\nPOST /ingest API ─┤ Ingest Record (audit)\nSlack file upload ─┘ Object Store (S3/MinIO)\n system.doc.ingest event\n │\n Workflow trigger match\n │\n Job created ──── resource_refs: ingest://{id}\n │\n Worker hydrates ingest:// into workspace\n │\n Agent reads .eve/resources/\n Processes file (text / PDF / audio / video)\n Writes output (org docs, json-result)\n```\n\nThree input channels feed into a single processing spine. The agent receives files in a uniform workspace layout regardless of how the file arrived.\n\n## Ingest Record Lifecycle\n\nEach ingestion creates an immutable audit record (`ingest_xxx`).\n\n**Statuses:** `pending` -> `processing` -> `done` | `failed`\n\n- `pending`: Record created, presigned upload URL issued, waiting for file upload and confirm.\n- `processing`: Upload confirmed, `system.doc.ingest` event emitted, workflow job running.\n- `done`: Processing job completed successfully. `completed_at` set.\n- `failed`: Processing job failed. `error_message` and `completed_at` set.\n\nConfirm is idempotent -- calling it on a `processing` or `done` record returns current state without duplicate events or jobs.\n\n## The `ingest://` URI Scheme\n\nIngest files are referenced via `ingest://` URIs in resource refs, alongside `org_docs://` and `job_attachments://`.\n\n**Format:** `ingest:/{ingest_id}/{encoded_file_name}`\n\n```json\n{\n \"uri\": \"ingest:/ingest_abc123/quarterly-report.pdf\",\n \"label\": \"Ingested document\",\n \"required\": true,\n \"mime_type\": \"application/pdf\",\n \"metadata\": {\n \"title\": \"Q4 Board Deck\",\n \"description\": \"Quarterly board presentation\",\n \"instructions\": \"Extract key financials and action items\"\n }\n}\n```\n\nThe worker resolves `ingest://` URIs by downloading from object store key `ingest/{ingestId}/{fileName}` and writing to `.eve/resources/`. The resource index (`.eve/resources/index.json`) includes `mime_type` and `metadata` so agents know file types and submitter context without guessing from extensions.\n\n## Workflow Trigger Wiring\n\nConfirming an upload emits `system.doc.ingest`. Match it in a workflow trigger:\n\n```yaml\nworkflows:\n process-document:\n trigger:\n system:\n event: doc.ingest\n steps:\n - agent:\n prompt: \"Process the ingested document using your doc-processor skill.\"\n```\n\nThe trigger matcher strips the `system.` prefix, so `system.doc.ingest` matches `event: doc.ingest`.\n\n**Reliability**: doc.ingest workflow triggers fire reliably end-to-end. Pack\nworkflows are merged into the repo manifest before sync (no competing manifest\nrows), `eve event emit --type doc.ingest --source system` auto-prefixes to the\ncanonical `system.doc.ingest` event type, and `eve workflow run --input` wraps\npayloads correctly for the workflow invoke schema. If a doc.ingest event does\nnot match, `eve event show \u003cevent-id>` now shows the per-trigger evaluation\nbreakdown — see `events.md`.\n\nThe orchestrator interpolates event payload fields into workflow resource refs:\n\n```yaml\nresource_refs:\n - uri: \"ingest://${event.payload.ingest_id}/${event.payload.file_name}\"\n label: \"Ingested document\"\n required: true\n```\n\n## Ingest Agentpack\n\nThe `ingest-agentpack` provides a self-contained agent pack for document ingestion. Import it with a single `x-eve.packs` entry -- no boilerplate workflow or prompt authoring required.\n\n### Pack Structure\n\n```\ningest-agentpack/\n├── eve/\n│ ├── pack.yaml # Pack manifest (id: doc-ingest)\n│ ├── agents.yaml # doc_processor agent definition\n│ ├── workflows.yaml # process-document workflow with doc.ingest trigger\n│ └── x-eve.yaml # Harness profiles (ingest, ingest-fast)\n└── skills/\n └── doc-processor/\n └── SKILL.md # Processing instructions for text, PDF, audio, video\n```\n\n### Import in Manifest\n\n```yaml\nx-eve:\n packs:\n - source: github:eve-horizon/ingest-agentpack\n ref: \u003c40-char-sha>\n```\n\nSync the pack into the project:\n\n```bash\neve agents sync --project proj_xxx\n```\n\nThis registers the `doc_processor` agent, the `process-document` workflow (with `doc.ingest` trigger), and harness profiles.\n\n### Harness Profiles\n\n| Profile | Harness | Model | Reasoning | Use Case |\n|---------|---------|-------|-----------|----------|\n| `ingest` | mclaude | opus-4.6 | medium | Quality-first (default) |\n| `ingest-fast` | mclaude | sonnet-4.6 | low | Speed/cost optimization |\n\n### Customize via Manifest Overlay\n\nOverride the agent's profile in the app manifest:\n\n```yaml\nx-eve:\n agents:\n doc_processor:\n harness_profile: ingest-fast\n```\n\n### Agent Behavior\n\nThe `doc_processor` agent:\n1. Reads `.eve/resources/index.json` to find the ingested file, its MIME type, and submitter context.\n2. Selects processing strategy based on MIME type (text, document, audio, video).\n3. Runs tools if needed (whisper-cli for audio, ffmpeg + whisper-cli for video).\n4. Writes structured analysis with summary, key facts, entities, and action items.\n5. Emits a `json-result` block so output is retrievable via `eve job result`.\n\n## Media Processing\n\nAgent containers include `ffmpeg` and `whisper-cli` (whisper.cpp v1.8.1, CPU, `ggml-small.en` model) for audio and video transcription.\n\n### Audio Files\n\nSupported MIME types: `audio/mpeg`, `audio/wav`, `audio/mp4`, `audio/ogg`, `audio/flac`, `audio/opus`, `audio/aac`, `audio/amr`, `audio/x-ms-wma`.\n\nAgent runs:\n```bash\nwhisper-cli -m /opt/whisper/models/ggml-small.en.bin -f \u003cfile> -ovtt\n```\n\nProduces a `.vtt` transcript the agent reads and summarizes.\n\n### Video Files\n\nSupported MIME types: `video/mp4`, `video/x-matroska`, `video/quicktime`, `video/x-msvideo`, `video/webm`, `video/x-ms-wmv`, `video/x-flv`, `video/x-m4v`, `video/mpeg`, `video/3gpp`.\n\nAgent runs:\n```bash\nffmpeg -i \u003cfile> -vn -acodec pcm_s16le -ar 16000 -ac 1 /tmp/audio.wav\nwhisper-cli -m /opt/whisper/models/ggml-small.en.bin -f /tmp/audio.wav -ovtt\n```\n\n### PDF and Text\n\nClaude reads PDFs natively via multimodal input -- no conversion tools needed. The `mime_type` field in the resource index tells the agent the file type. Text formats (markdown, JSON, YAML, CSV, HTML, XML) are read directly.\n\n### Performance\n\n| Input Duration | Approx. Transcription Time (CPU) |\n|---------------|----------------------------------|\n| 10 seconds | ~5 seconds |\n| 1 minute | ~30 seconds |\n| 10 minutes | ~5 minutes |\n| 60 minutes | ~30 minutes |\n\nFiles over ~30 minutes may approach job timeouts. Use async polling (`eve ingest show`) rather than short `eve job wait` timeouts for long media.\n\n### Tool Availability\n\nTools are installed in both `worker` and `agent-runtime` Docker images:\n- **Worker**: `media` stage provides ffmpeg + whisper-cli; `full` and `production` stages inherit it.\n- **Agent-runtime**: Tools installed in the `production` stage.\n- **Model path**: `/opt/whisper/models/ggml-small.en.bin` (~150 MB, English-only).\n\nIf tools are unavailable, the agent reports what it can determine from the raw file and notes the limitation.\n\n## Chat File Materialization\n\nSlack file uploads are automatically downloaded, stored in Eve, and staged for agents. This bridges chat to the ingestion workspace pattern.\n\n### Flow\n\n```\nSlack message + file attachment\n │\n Gateway (async phase)\n 1. Download file via Slack bot token\n 2. Upload to S3 via presigned URL\n 3. Replace Slack URL with eve-storage:// ref\n │\n Routing (chat/route -> job)\n Files flow through in job metadata.files\n │\n Worker (workspace provisioning)\n 4. Detect metadata.files with eve-storage:// URLs\n 5. Download via presigned URL -> .eve/attachments/\n 6. Write .eve/attachments/index.json\n │\n Agent reads .eve/attachments/index.json\n```\n\n### Agent Workspace Layout\n\nFiles land at `.eve/attachments/{file_id}-{sanitized_filename}` with an index at `.eve/attachments/index.json`:\n\n```json\n{\n \"files\": [\n {\n \"id\": \"F019ABC123\",\n \"name\": \"product-spec-v2.pdf\",\n \"path\": \".eve/attachments/F019ABC123-product-spec-v2.pdf\",\n \"mimetype\": \"application/pdf\",\n \"size\": 245760,\n \"source_provider\": \"slack\"\n }\n ]\n}\n```\n\n### Limits\n\n| Limit | Value |\n|-------|-------|\n| Max files per message | 10 |\n| Max file size | 50 MB |\n| Max total per message | 100 MB |\n\nFiles exceeding limits are skipped (warning logged). The message text still routes normally.\n\n### Distinction from Ingest Pipeline\n\nChat file materialization and the ingest pipeline are separate paths:\n- **Chat files** arrive via Slack, land at `.eve/attachments/`, and the chat-routed agent sees them alongside the message.\n- **Ingest files** arrive via `eve ingest` or the API, land at `.eve/resources/` via `ingest://` hydration, and a dedicated processing workflow handles them.\n\nBoth use presigned URLs and avoid proxying binary data through the API.\n\n## CLI Quick Reference\n\n```bash\n# Upload and process a file\neve ingest \u003cfile> --project proj_xxx \\\n [--title \"...\"] [--description \"...\"] \\\n [--instructions \"...\"] [--tags t1,t2] \\\n [--mime-type \u003ctype>] [--json]\n\n# List ingest records\neve ingest list [--status pending|processing|done|failed] [--json]\n\n# Show record with download URL\neve ingest show \u003cingest_id> [--json]\n```\n\nThe CLI auto-infers MIME type from file extension (covers text, documents, images, audio, video formats). Override with `--mime-type` for edge cases. Source channel defaults to `cli`.\n\n## Integration Points\n\n| Primitive | Role in Ingestion |\n|-----------|-------------------|\n| **Object Store** | Presigned upload/download. Files stored at `ingest/{id}/{filename}` in org bucket. |\n| **Events** | `system.doc.ingest` emitted on confirm. Carries full payload (file metadata, submitter context). |\n| **Workflows** | Trigger on `doc.ingest` event. Create processing job with `ingest://` resource ref. |\n| **Resource Hydration** | Worker resolves `ingest://` URIs, downloads from S3, writes to `.eve/resources/`. |\n| **Resource Index** | `.eve/resources/index.json` includes `mime_type` and `metadata` from the ingest event. |\n| **Org Docs** | Agent writes structured output via `eve docs write`. |\n| **Job Result** | Agent emits `json-result` block. Retrieve via `eve job result \u003cjob-id>`. |\n| **Callbacks** | Optional `callback_url` on ingest record. Platform POSTs status on completion. |\n\n## Key Constraints\n\n- **API never proxies binary data.** All file transfer uses presigned S3 URLs.\n- **Ingest records are mostly immutable.** Only lifecycle fields (`status`, `event_id`, `job_id`, `error_message`, `completed_at`) update after creation.\n- **Max file size: 500 MB** for ingest API uploads.\n- **Whisper model: English-only** (`ggml-small.en`). Multilingual support requires swapping the model file.\n- **Single agent per ingest job.** Multi-agent triage routing is not yet implemented.\n- **Confirm is idempotent.** Repeated confirm calls return current state without duplicate events.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":12873,"content_sha256":"cb9c53bb3c5576f0bb905d71218264bb9be976910e6dad9ffa4b999c0c099962"},{"filename":"references/eve-sdk.md","content":"# Eve SDK\n\n## Use When\n- You need to add authentication to an Eve-deployed app (backend, frontend, or fullstack).\n- You need to embed an Eve thread-backed agent conversation pane in an app.\n- You need to know which SDK packages exist and what they export.\n- You need the quick-start install and wiring pattern for a new app.\n- You need to understand the token flow between browser, backend, and Eve platform.\n- You need to ship branded magic-link login, app-org invites, or domain-based signup on top of Eve SSO.\n\n## Load Next\n- `references/auth-sdk.md` for deep auth coverage: middleware behavior, verification strategies, token types, NestJS patterns, session bootstrap sequence, migration guide.\n- `references/agents-teams.md` for embedded conversation endpoints, route predicates, and gateway policies.\n- `references/gateways.md` for choosing `app`, `api`, or `webchat` provider identity.\n- `references/secrets-auth.md` for platform auth model, identity providers, and access control.\n- `references/manifest.md` for environment variable interpolation in manifests.\n\n## Ask If Missing\n- Confirm whether the app is backend-only, frontend-only, or fullstack.\n- Confirm the backend framework (Express or NestJS).\n- Confirm whether the app serves browser users, agent jobs, or both.\n- For embedded conversations, confirm the Eve project id, `app_id`, and product-level `app_key` convention.\n\n## Overview\n\nThe Eve SDK packages eliminate auth and embedded conversation boilerplate in Eve-deployed apps.\n\n| Package | Runtime | Purpose |\n|---------|---------|---------|\n| `@eve-horizon/auth` | Node.js (Express / NestJS) | Token verification, org membership, route protection |\n| `@eve-horizon/auth-react` | Browser (React) | SSO session management, login UI, token cache |\n| `@eve-horizon/chat` | Browser / Node.js | Embedded conversation client, bearer fetch, SSE parser, server proxy helper |\n| `@eve-horizon/chat-react` | Browser (React) | Conversation provider, hooks, and minimal panes |\n\n## Install\n\n```bash\n# Backend\nnpm install @eve-horizon/auth\n\n# Frontend\nnpm install @eve-horizon/auth-react\n\n# Embedded conversations\nnpm install @eve-horizon/chat @eve-horizon/chat-react\n```\n\n## Quick-Start: Backend (Express)\n\n```typescript\nimport { eveAuth, eveIdentityGuard, eveAuthConfig, eveAuthMe } from '@eve-horizon/auth';\n\napp.use(eveAuth()); // Parse any Eve token (non-blocking)\napp.get('/auth/config', eveAuthConfig()); // Serve SSO discovery\napp.get('/auth/me', eveAuthMe()); // Full /auth/me for React SDK\napp.use('/api', eveIdentityGuard()); // Protect all API routes\n```\n\nUse `eveAuth()` for apps serving both users and agents (most Eve apps). It normalizes both token types into `req.eveIdentity`. See `auth-sdk.md` for the full middleware comparison.\n\n## Quick-Start: Frontend (React)\n\n```tsx\nimport { EveAuthProvider, EveLoginGate, useEveAuth } from '@eve-horizon/auth-react';\n\nfunction App() {\n return (\n \u003cEveAuthProvider apiUrl=\"/api\">\n \u003cEveLoginGate>\n \u003cDashboard />\n \u003c/EveLoginGate>\n \u003c/EveAuthProvider>\n );\n}\n\nfunction Dashboard() {\n const { user, logout } = useEveAuth();\n return \u003cdiv>Welcome {user?.email} \u003cbutton onClick={logout}>Sign out\u003c/button>\u003c/div>;\n}\n```\n\n## Token Flow\n\n1. User visits app -- `EveAuthProvider` checks `sessionStorage` for a cached token.\n2. No cached token -- probes SSO broker `/session` using root-domain cookie.\n3. SSO session exists -- receives fresh Eve RS256 token, caches in `sessionStorage`.\n4. No SSO session -- shows login form (SSO redirect or token paste).\n5. All API requests include `Authorization: Bearer \u003ctoken>` header.\n\n## Backend Exports\n\n| Export | Type | Description |\n|--------|------|-------------|\n| `eveAuth(options?)` | Middleware | **Recommended.** Unified auth for user + agent tokens, attach `req.eveIdentity` (non-blocking) |\n| `eveIdentityGuard()` | Middleware | Return 401 if `req.eveIdentity` not set |\n| `eveUserAuth(options?)` | Middleware | User-only token verification, attach `req.eveUser` (non-blocking) |\n| `eveAuthGuard()` | Middleware | Return 401 if `req.eveUser` not set |\n| `eveAuthConfig()` | Handler | Serve `{ sso_url, eve_api_url, ... }` from env vars |\n| `eveAuthMe(options?)` | Handler | Full `/auth/me` — memberships + project role for React SDK |\n| `eveAuthMiddleware(options?)` | Middleware | Agent/job token verification (blocking), attach `req.agent` |\n| `verifyEveToken(token, url?)` | Function | JWKS-based local verification (15-min cache) |\n| `verifyEveTokenRemote(token, url?)` | Function | HTTP verification via `/auth/token/verify` |\n\n## Frontend Exports\n\n| Export | Type | Description |\n|--------|------|-------------|\n| `EveAuthProvider` | Component | Context provider, session bootstrap. Props: `apiUrl?`, `projectId?` |\n| `useEveAuth()` | Hook | `{ user, loading, orgs, activeOrg, switchOrg, loginWithSso, loginWithToken, logout }` |\n| `EveLoginGate` | Component | Render children when authed, login form when not |\n| `EveLoginForm` | Component | SSO + token paste login UI |\n| `createEveClient(baseUrl?)` | Function | Fetch wrapper with automatic Bearer injection |\n| `getStoredToken()` / `storeToken()` / `clearToken()` | Functions | Direct sessionStorage access |\n\n## Chat Exports\n\n| Package | Export | Description |\n|---------|--------|-------------|\n| `@eve-horizon/chat` | `createConversationClient()` | Browser/Node client for ensure, get, send, messages, and stream |\n| `@eve-horizon/chat/server` | `EveConversationsClient` | Server-side helper for backend-proxied turns |\n| `@eve-horizon/chat/server` | `forwardTurn()` | Apply enrichment/rejection hooks before forwarding a turn |\n| `@eve-horizon/chat` | `conversation.events()` / `conversation.streamEvents()` / `conversation.emitEvent()` | Structured event timeline (typed kinds, replayable cursor) |\n| `@eve-horizon/chat` | `conversation.continueByThreadId()` | Continue a routed Eve thread by `thr_*` id, preserving the original dispatch target |\n| `@eve-horizon/chat-react` | `EveConversationProvider` | React state provider for one app conversation |\n| `@eve-horizon/chat-react` | `useEveConversation()` | Hook exposing conversation state, `ensure`, `send`, `reconnect`, and event stream |\n| `@eve-horizon/chat-react` | `EveConversationPane` | Headless render-prop pane |\n| `@eve-horizon/chat-react` | `EveConversationDefaultPane` | Minimal styled conversation pane |\n\n## Environment Variables\n\nAuto-injected by the Eve deployer into every deployed app. No manual configuration needed.\n\n| Variable | Used By | Purpose |\n|----------|---------|---------|\n| `EVE_API_URL` | `@eve-horizon/auth` | JWKS fetch, remote token verification |\n| `EVE_ORG_ID` | `@eve-horizon/auth` | Org membership check |\n| `EVE_SSO_URL` | Both | Auth config discovery, SSO session probe |\n| `EVE_PUBLIC_API_URL` | Both | Public-facing API URL (optional) |\n\n## Common Patterns\n\n### Backend-Only API (Agent Jobs)\n\nFor APIs that only serve agent jobs with no browser users:\n\n```typescript\nimport { eveAuthMiddleware } from '@eve-horizon/auth';\n\napp.use('/api', eveAuthMiddleware({ strategy: 'local' }));\n\napp.get('/api/data', (req, res) => {\n console.log(req.agent.project_id, req.agent.job_id);\n});\n```\n\n### Fullstack React App\n\nCombine both packages for SSO login with protected API routes:\n\n```typescript\n// Backend\napp.use(eveUserAuth());\napp.get('/auth/config', eveAuthConfig());\napp.get('/auth/me', eveAuthMe());\napp.use('/api', eveAuthGuard());\n```\n\n```tsx\n// Frontend\nimport { EveAuthProvider, EveLoginGate, createEveClient } from '@eve-horizon/auth-react';\n\nconst client = createEveClient('/api');\nconst res = await client.fetch('/data');\n```\n\n### Passwordless App Login + Branded Invites\n\nApps opt into branded SSO and passwordless login by adding `x-eve.branding` and `x-eve.auth` to the manifest — no SDK code change required. `EveAuthProvider` already includes `eve_project_id` in the SSO redirect, and `eveAuthConfig()` exposes it on `/auth/config`.\n\n```yaml\nx-eve:\n branding:\n app_name: \"ALL-TRACK\"\n primary_color: \"#1f6feb\"\n email_from_name: \"ALL-TRACK\"\n auth:\n login_method: magic_link # or password_or_magic_link\n self_signup: false\n invite_requires_password: false\n```\n\nBoth magic-link and invite emails ship app-branded subject, body, logo, and `From:` display name, and ride through an SSO confirmation interstitial (`/m/mlw_\u003cid>`) so corporate mail scanners cannot burn single-use OTPs. Apps that need to invite members from in-product admin pages should use `useEveAppAccess()` + `POST /auth/app-invites`. For multi-tenant projects, declare `x-eve.auth.org_access.domain_signup.domains[]` (v2 rule list) to auto-attach allowlisted email domains to per-rule target orgs without per-user invites. Custom-domain apps must also list their origin in `x-eve.auth.allowed_redirect_origins`.\n\nSee `references/auth-sdk.md` and `references/secrets-auth.md` for the full app-scoped magic-link, domain-signup, redirect allowlist, interstitial, and SES suppression coverage.\n\n### Embedded Conversation Pane\n\nUse Eve threads as the durable conversation record for an app-owned object. The SDK calls the project conversations facade and streams snapshot, message, progress, and heartbeat events.\n\n```tsx\nimport { EveConversationProvider, EveConversationDefaultPane } from '@eve-horizon/chat-react';\n\nfunction DesignerChat({ projectId, conversationId, token }: {\n projectId: string;\n conversationId: string;\n token: string;\n}) {\n return (\n \u003cEveConversationProvider\n baseUrl=\"/api/eve\"\n projectId={projectId}\n appKey={`open-design:${projectId}:${conversationId}`}\n appId=\"open-design\"\n getToken={() => token}\n >\n \u003cEveConversationDefaultPane />\n \u003c/EveConversationProvider>\n );\n}\n```\n\nBackend-proxied apps can use `@eve-horizon/chat/server` with a service token to enrich or reject turns before forwarding them to Eve.\n\nConversation streams are fetch-based SSE (not native `EventSource`, because bearer auth requires `Authorization` headers). Each `message` or `progress` event carries `eventId` from `thread_messages.id`; pass the last seen id back as `resumeFrom` to replay without gaps:\n\n```typescript\nfor await (const event of conversation.stream({ resumeFrom: lastEventId })) {\n if (event.eventId) lastEventId = event.eventId;\n}\n```\n\n### Continuing by Eve Thread Id\n\nWhen an app already holds an Eve `thread_id` (`thr_*`) — e.g. it surfaced one to the user from a previous session — it can continue the conversation directly without re-supplying the `app_key`:\n\n```typescript\nawait client.continueByThreadId('thr_ABC', { text: 'follow-up question' });\n// hits POST /threads/thr_ABC/chat\n```\n\nContinuation reuses the original dispatch target (`route` / `agent` / `team`) stored on the thread, so a later `chat.yaml` change cannot silently re-route an existing conversation. Org-scoped, coordination, and legacy threads without continuation metadata are rejected with `409`.\n\n### Structured Conversation Events\n\nBeyond plain user/assistant messages, every conversation has a normalized event timeline that includes job status changes, tool calls/results, file changes, attachments, errors, and app-emitted events. These are durable, ordered, and replayable.\n\n```typescript\n// List events (filter by kind, job, attempt, workflow_step, source)\nconst { events } = await conversation.events({ kind: ['tool.call', 'tool.result'], limit: 100 });\n\n// Stream events (SSE; resume with `after` cursor or Last-Event-ID)\nfor await (const event of conversation.streamEvents({ after: lastCursor })) {\n if (event.kind === 'snapshot') continue;\n lastCursor = event.eventId;\n}\n\n// Emit an app-defined event (server-side or via authenticated client)\nawait conversation.emitEvent({\n kind: 'artifact.update',\n text: 'preview updated',\n payload: { artifact_id: 'a_1', version: 2 },\n});\n```\n\nEvent endpoints are also exposed by thread id:\n\n| Method | Path | Purpose |\n| --- | --- | --- |\n| GET | `/projects/:project_id/conversations/:app_key/events` | List events for an embedded conversation |\n| GET | `/projects/:project_id/conversations/:app_key/events/stream` | SSE stream of events |\n| POST | `/projects/:project_id/conversations/:app_key/events` | Emit an app-defined event |\n| GET | `/threads/:thread_id/events` | List events by thread id |\n| GET | `/threads/:thread_id/events/stream` | SSE stream by thread id |\n| POST | `/threads/:thread_id/events` | Emit an app-defined event by thread id |\n\nStandard event kinds: `user.message`, `assistant.message`, `text.delta`, `tool.call`, `tool.result`, `status.changed`, `progress`, `error`, `attachment.added`, `file.change`, `delivery.status`, `final.result`. App-defined kinds must match `^[a-z][a-z0-9_.-]* eve-read-eve-docs — Skillopedia (max 150 chars). Lists default to 100 events; `limit` clamps to 500.\n\n### SSE Authentication\n\nThe middleware supports `?token=` query parameter for Server-Sent Events:\n\n```\nGET /api/events?token=eyJ...\n```\n\n## Deep Auth Reference\n\nFor middleware behavior details, verification strategies (`local` vs `remote`), token types (`EveUser` / `EveTokenClaims`), NestJS guard patterns, session bootstrap sequence, token lifecycle and TTLs, `orgs` claim mechanics, and migration from custom auth, see `references/auth-sdk.md`.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":13327,"content_sha256":"63ee263dbeb63f0c2634906f7eb9c9003c6c70fe4d8d33ef3c5c4f9e15692c9d"},{"filename":"references/events.md","content":"# Events + Triggers (Current)\n\n## Use When\n- You need to configure, inspect, or reason about event sources and payloads.\n- You need trigger wiring for webhook, webhook replacement, or automation hooks.\n- You need to map events to pipeline/jobs and automation workflows.\n\n## Load Next\n- `references/pipelines-workflows.md` for trigger-to-run mapping and dependencies.\n- `references/gateways.md` for chat-originated events and provider signatures.\n- `references/cli.md` for live event/pipeline inspection commands.\n\n## Ask If Missing\n- Confirm event source (`github`, `slack`, `cron`, etc.) and target project.\n- Confirm required webhook signatures or integration credentials are available.\n- Confirm whether you need one-time replay or persistent subscription behavior.\n\n## Event Model\n\nEvents are stored in Postgres and routed by the orchestrator.\n\nCore fields:\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `id` | string | TypeID (`evt_xxx`) |\n| `project_id` | string | Owning project |\n| `type` | string | Namespaced event type (e.g., `github.push`) |\n| `source` | enum | Event origin (see sources below) |\n| `status` | enum | `pending` → `processing` → `completed` / `failed` |\n| `payload_json` | object | Event-specific data (varies by type) |\n| `env_name` | string? | Target environment |\n| `ref_sha` | string? | Git commit SHA |\n| `ref_branch` | string? | Git branch name |\n| `actor_type` | enum? | `user`, `system`, or `app` |\n| `actor_id` | string? | Actor identifier |\n| `dedupe_key` | string? | Idempotency key (prevents duplicate processing) |\n\n## Event Sources\n\n`github`, `slack`, `cron`, `manual`, `app`, `app_link`, `system`, `runner`, `chat`, `auth`\n\n## Event Type Catalog\n\n### GitHub Events\n\n| Type | Trigger | Payload |\n|------|---------|---------|\n| `github.push` | Push to branch | `{ ref, commits, repository, sender, head_commit }` |\n| `github.pull_request` | PR lifecycle | `{ action, number, pull_request: { head, base, title, ... } }` |\n\nDelivered via webhook: `POST /integrations/github/events/{project_id}`. Signature verified with `X-Hub-Signature-256`.\n\n**Quick setup:** `eve github setup --project proj_xxx` provisions the webhook secret and auto-creates the GitHub webhook via `gh` CLI (or prints manual instructions). Use `eve github test` to fire a synthetic push event and verify triggers.\n\n### Slack Events\n\n| Type | Trigger | Payload |\n|------|---------|---------|\n| `slack.message` | Message in channel | `{ text, channel, user, ts, thread_ts }` |\n| `slack.app_mention` | @eve mention | `{ text, channel, user, ts }` |\n\nDelivered via gateway: `POST /gateway/providers/slack/webhook` (or legacy `POST /integrations/slack/events`). Signature verified with Slack signing secret.\n\n### System Events (Auto-Emitted)\n\n| Type | Trigger | Payload |\n|------|---------|---------|\n| `system.job.failed` | Job execution failure | `{ job_id, attempt_id, error_message, error_code, exit_code }` |\n| `system.job.attempt.completed` | Job attempt succeeded or failed | `{ job_id, attempt_id, assignee, thread_id, execution_type, status, duration_ms }` |\n| `system.pipeline.failed` | Pipeline run failure | `{ run_id, pipeline_name, error_message, error_code, exit_code }` |\n| `system.doc.created` | Org doc created | `{ org_id, project_id, doc_id, doc_version_id, path, version, content_hash, mutation_id, request_id, metadata }` |\n| `system.doc.updated` | Org doc updated | `{ org_id, project_id, doc_id, doc_version_id, path, version, content_hash, mutation_id, request_id, metadata }` |\n| `system.doc.deleted` | Org doc deleted | `{ org_id, project_id, doc_id, path, version, content_hash, mutation_id, request_id, metadata }` |\n| `system.doc.ingest` | Ingest upload confirmed | `{ org_id, project_id, ingest_id, file_name, mime_type, size_bytes, storage_key, title, callback_url }` |\n| `system.resource.hydration.started` | Worker begins resource hydration | `{ job_id, attempt_id, resource_count }` |\n| `system.resource.hydration.completed` | Worker completes hydration | `{ job_id, attempt_id, resolved_count, missing_optional_count, failed_required_count, resources[] }` |\n| `system.resource.hydration.failed` | Worker hydration failed | `{ job_id, attempt_id, resolved_count, missing_optional_count, failed_required_count, resources[] }` |\n\nThese are emitted automatically by the orchestrator. `job.failed` and `pipeline.failed` fire on failures for self-healing automation. `job.attempt.completed` fires on every attempt completion (success or failure) and is the primary trigger for post-session workflows like the learning loop.\n\nDoc events are emitted by the org docs API. Hydration events are emitted by the worker before harness launch.\n\n### Auth Events\n\nAuth-policy actions emit events on the project's event spine. Operators can\nsubscribe webhooks to `auth.*` for real-time visibility into who's joining\nwhich org and through which policy.\n\n| Type | Trigger | Payload |\n|------|---------|---------|\n| `auth.domain_signup.invite_created` | Magic-link send for an email matching a `x-eve.auth.org_access.domain_signup` rule writes a one-shot org_invites row | `{ org_id, email_domain, matched_rule, email_hash }` |\n| `auth.domain_signup.member_attached` | SSO callback consumes a `source: domain_signup` invite and upserts the user as `member` of the matched rule's `target_org` | `{ org_id, user_id, email_domain, email_hash }` |\n| `auth.action_link.wrap_redeemed` | A magic-link/invite wrap is consumed by a human POST on the SSO `/m/:wrap` confirmation interstitial | `{ org_id, email_hash, kind: 'magic_link' \\| 'invite', get_count, latency_ms }` |\n\n`org_id` on both events is the matched rule's `target_org` (v2 schema; one project can route to multiple orgs depending on which rule fires). `matched_rule` on `invite_created` is the rule's domain pattern (e.g. `\"*.acme.com\"` for a wildcard hit on `eu.acme.com`), distinct from `email_domain` (the actual address's domain).\n\n`email_hash` is a truncated SHA-256 of the lowercased email (`sha256:\u003c12 hex chars>`) so the audit payload identifies retries without leaking raw addresses in webhook deliveries or stdout. The full email never appears at INFO log level.\n\n### Webhook Events\n\nOrg and project webhooks can subscribe to event types emitted by the API. The\nwebhook system stores deliveries and supports replay of failed or filtered\ndeliveries.\n\n### LLM Usage Events\n\nHarnesses emit `llm.call` events after each provider call. These events contain\nusage-only metadata (token counts, model identifiers) and are used for receipts\nand live cost tracking. No prompt or response content is included.\n\n### Cron Events\n\n| Type | Trigger | Payload |\n|------|---------|---------|\n| `cron.tick` | Schedule fires | `{ schedule, trigger_name }` |\n\n### Manual / App Events (Custom)\n\n| Type | Trigger | Payload |\n|------|---------|---------|\n| `manual.*` | User-created via CLI/API | Any JSON |\n| `app.*` | Application-emitted | Any JSON |\n\nCustom events use any `type` string. No schema enforcement — payload can be arbitrary JSON.\n\n### App-Link Events\n\nProducer projects can export event feeds through `x-eve.app_links.exports`.\nWhen a producer event matches an active consumer subscription, the orchestrator\ncreates a consumer-side event with:\n\n| Field | Value |\n|-------|-------|\n| `source` | `app_link` |\n| `type` | Same as the producer event type |\n| `dedupe_key` | `app_link:\u003csubscription_id>:\u003csource_event_id>` |\n| `payload_json.producer_event_id` | Original producer event ID |\n| `payload_json.producer_project_id` | Producer project ID |\n| `payload_json.producer_env_name` | Producer event env |\n| `payload_json.producer_export_name` | Exported feed name |\n| `payload_json.link_alias` | Consumer-local alias |\n| `payload_json.original` | Original producer payload |\n\nDeliveries are tracked in `app_link_event_deliveries` and retried by the event\nrouter if consumer event creation fails.\n\n## API + CLI\n\n```bash\n# List events (filterable)\neve event list [project] --type github.push --source github --status completed\n\n# Show event details\neve event show \u003cevent-id>\n\n# Emit a custom event\neve event emit --type manual.test --source manual --payload '{\"k\":\"v\"}'\neve event emit --type app.deploy-check --source app --env staging --branch main\n\n# System events: type is auto-prefixed with source\n# --type doc.ingest --source system → stored as system.doc.ingest\neve event emit --type doc.ingest --source system --payload '{\"ingest_id\":\"ing_xxx\",\"file_name\":\"doc.pdf\"}'\n```\n\n**Event type auto-prefixing**: When `--source` is `system`, `github`, or `slack`, the CLI\nauto-prefixes the type if it doesn't already start with `{source}.`. For example,\n`--type doc.ingest --source system` becomes `system.doc.ingest`. This matches the\ninternal convention where events are stored with fully-qualified types.\n\nAPI endpoints:\n\n```\nPOST /projects/{project_id}/events # Create event\nGET /projects/{project_id}/events # List events (filters: type, source, status)\nGET /projects/{project_id}/events/{id} # Get event details\n```\n\n## Trigger Evaluation Metadata\n\nEvents record trigger evaluation results for observability:\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `trigger_match_count` | int? | Number of triggers that matched (0 = no match, null = not yet processed) |\n| `triggers_evaluated` | json? | Array of `{type, name, matched, reason?}` for each trigger checked |\n\n```bash\n# Show trigger evaluation details\neve event show \u003cevent-id>\n# Output includes:\n# Triggers: matched 1 of 3 evaluated\n# Trigger Evaluations:\n# ✓ workflow:ingestion-pipeline\n# ✗ workflow:alignment-check (type_mismatch)\n# ✗ pipeline:deploy (source_mismatch)\n```\n\nThis makes it immediately clear whether an event triggered anything and exactly why other triggers didn't match. Mismatch reasons include: `source_mismatch`, `type_mismatch`, `branch_mismatch`, `action_mismatch`, `no_trigger`, `manual_trigger`.\n\n## Trigger Routing\n\nThe orchestrator polls pending events, matches them against manifest triggers, and creates pipeline runs or workflow jobs.\n\nClaiming mechanics use `FOR UPDATE SKIP LOCKED`, so multiple orchestrator\ninstances can process the queue without double-claiming.\n\nEvents can include a `dedupe_key`; the API checks for an existing event with\nthe same key before creating a new record.\n\n### How Triggers Work\n\n1. Event arrives in the events table (status: `pending`)\n2. Orchestrator polls every ~5 seconds\n3. Loads project manifest and checks all pipeline/workflow triggers\n4. If a trigger matches, creates a pipeline run or workflow job\n5. Marks event as `completed` (or `failed`)\n\n### Trigger Types\n\nTriggers are defined in the manifest under `pipelines.\u003cname>.trigger` or `workflows.\u003cname>.trigger`.\n\n#### GitHub Trigger\n\n```yaml\ntrigger:\n github:\n event: push # \"push\" or \"pull_request\"\n branch: main # Branch pattern (supports wildcards)\n```\n\n```yaml\ntrigger:\n github:\n event: pull_request\n action: [opened, synchronize, reopened] # PR actions to match\n base_branch: main # Target branch filter\n```\n\nSupported PR actions: `opened`, `synchronize`, `reopened`, `closed`.\nBranch patterns: exact (`main`), prefix wildcards (`release/*`), suffix wildcards (`*-prod`).\n\n#### Slack Trigger\n\n```yaml\ntrigger:\n slack:\n event: message # Slack event type\n channel: C123ABC # Channel ID\n```\n\n#### System Trigger (Self-Healing)\n\n```yaml\ntrigger:\n system:\n event: job.failed # \"job.failed\" or \"pipeline.failed\"\n pipeline: deploy # Optional: scope to specific pipeline\n```\n\nUse system triggers to build automated remediation flows. When a job or pipeline fails, the system event triggers a recovery pipeline or workflow.\n\n#### Cron Trigger\n\n```yaml\ntrigger:\n cron:\n schedule: \"0 */6 * * *\" # Standard cron expression\n```\n\n#### App Trigger (Application Events)\n\n```yaml\ntrigger:\n app:\n event: document.uploaded # Matches event.type from source=app\n```\n\nUse app triggers for event-driven workflows within your application. Emit events via the API (`eve event emit --source app --type document.uploaded`) and the orchestrator auto-dispatches matching workflows.\n\n#### App-Link Trigger (Cross-Project Events)\n\n```yaml\ntrigger:\n app_link:\n alias: observation # Consumer-local x-eve.app_links.consumes alias\n type: app.observation.created\n```\n\nThe `app_link` trigger is a shorthand for consumer-side events with\n`source=app_link`. It can filter by subscription alias and event type.\n\n#### Generic Event Trigger\n\n```yaml\ntrigger:\n event:\n source: app # Any event source (app, runner, chat, etc.)\n type: document.uploaded # Optional type filter\n```\n\nThe generic `event` trigger matches any event source+type combination. Use this when your events come from non-standard sources. The `app` trigger is a shorthand for `event.source: app`.\n\n#### Manual Trigger (No Auto-Trigger)\n\n```yaml\ntrigger:\n manual: true # Only runs when explicitly invoked\n```\n\n### Complete Trigger Example\n\n```yaml\npipelines:\n ci:\n trigger:\n github:\n event: pull_request\n action: [opened, synchronize]\n base_branch: main\n steps:\n - name: test\n script: { run: \"pnpm test\" }\n\n deploy:\n trigger:\n github:\n event: push\n branch: main\n steps:\n - name: build\n action: { type: build }\n - name: deploy\n depends_on: [build]\n action: { type: deploy, env_name: staging }\n\n self-heal:\n trigger:\n system:\n event: job.failed\n pipeline: deploy\n steps:\n - name: diagnose\n agent: { prompt: \"Diagnose the failed deploy and suggest a fix\" }\n\nworkflows:\n process-upload:\n trigger:\n app:\n event: document.uploaded\n steps:\n - name: ingest\n agent: { prompt: \"Process the uploaded document\" }\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":13932,"content_sha256":"921994ddfd8abefbcb8e8ed61c256027a0877b22f48452589c5080fffc18a7bf"},{"filename":"references/gateways.md","content":"# Chat Gateway\n\n## Use When\n- You need to configure Slack/Nostr message ingestion and outbound responses.\n- You need to map external chat identities and events into Eve workflows.\n- You need to validate webhook behavior or provider-specific security checks.\n\n## Load Next\n- `references/events.md` for inbound event types and payload expectations.\n- `references/agents-teams.md` for target agent/team routing after normalization.\n- `references/cli.md` for integration management commands.\n\n## Ask If Missing\n- Confirm provider (`slack` or `nostr`) and deployment environment.\n- Confirm signing secret / signing key inputs are available.\n- Confirm thread/client mappings (`team_id`, integration ids) before editing mappings.\n\n## Overview\n\nThe Gateway service normalizes external chat events into Eve events using a pluggable provider architecture. Providers implement the `GatewayProvider` interface and register via factories at startup. Two transport models exist: webhook (HTTP push) and subscription (persistent connection).\n\n## Transport Models\n\n| Model | Mechanism | Example |\n|-------|-----------|---------|\n| **Webhook** | Platform sends HTTP POST to Eve endpoint | Slack |\n| **Subscription** | Eve connects to external relay and listens | Nostr |\n\n## Provider Interface\n\nEvery provider implements the `GatewayProvider` contract:\n\n| Member | Purpose |\n|--------|---------|\n| `name` | Unique provider identifier (e.g., `slack`, `nostr`) |\n| `transport` | `'webhook'` or `'subscription'` |\n| `capabilities` | Feature flags: threads, reactions, file uploads |\n| `initialize(config)` | Lifecycle hook: setup connections, load state |\n| `shutdown()` | Lifecycle hook: close connections, flush state |\n\n### Webhook-Specific Methods\n\n| Method | Purpose |\n|--------|---------|\n| `validateWebhook(req)` | Verify request authenticity (signatures, challenge handling) |\n| `parseWebhook(req)` | Parse payload into: `message`, `handshake`, or `ignored` |\n\n### Shared Methods\n\n| Method | Purpose |\n|--------|---------|\n| `sendMessage(target, content)` | Deliver outbound message to provider-specific target |\n| `resolveIdentity(externalUserId, accountId)` | Map external user to Eve identity |\n\n## Provider Lifecycle\n\n1. Factories register at startup in `app.module.ts`.\n2. Instances are created per integration (one per org integration).\n3. `initialize(config)` is called to set up the provider (load secrets, open connections).\n4. Provider processes events for the lifetime of the integration.\n5. `shutdown()` is called on teardown (close sockets, flush state).\n\nSubscription providers start persistent connections on `initialize()` and tear them down on `shutdown()`. Webhook providers are stateless between requests.\n\n### Built-in Provider Factories\n\nThe gateway registers four provider factories at startup:\n\n| Provider | Transport | Push model | When to use |\n|----------|-----------|------------|-------------|\n| `slack` | webhook | Slack Web API (`chat.postMessage`) | Slack workspace integration |\n| `nostr` | subscription | Nostr relay publish | Decentralized / Nostr clients |\n| `webchat` | subscription | WebSocket push (gateway port) | Browser apps that want a direct gateway socket |\n| `api` | subscription (poll) | **No-op** — clients poll `GET /threads/{id}/messages` | REST/polling apps, mobile apps, embedded `provider: app` flows whose backend proxies through Eve |\n\nUnknown provider types now produce a `delivery.provider_missing` warning instead of throwing 404, so apps using new provider strings degrade gracefully — the message is still persisted to `thread_messages`.\n\n### Hot-Loading New Integrations\n\nThe gateway polls the API for active integrations every **30 seconds**. When a new integration is detected (e.g., after a Slack OAuth install completes), it is initialized automatically without requiring a gateway restart. Only new integrations are loaded — existing instances are not re-initialized during polling.\n\n## WebhookController Dispatch\n\n```\nPOST /gateway/providers/:provider/webhook\n```\n\n1. Controller extracts `:provider` from path parameter.\n2. Looks up initialized provider instance in the registry.\n3. Calls `validateWebhook(req)` -- reject on failure, respond to handshake challenges.\n4. Calls `parseWebhook(req)` -- returns `message`, `handshake`, or `ignored`.\n5. For `message` results, routes the normalized event through the agent resolution pipeline.\n\n## Slack Provider (Webhook)\n\n### Endpoints\n\n| Endpoint | Purpose |\n|----------|---------|\n| `POST /gateway/providers/slack/webhook` | All Slack Events API payloads (mentions, messages, URL verification) |\n| `POST /gateway/providers/slack/interactive` | Slack interactive payloads (button clicks, e.g., membership approval) |\n\n### Signature Validation\n\nSlack requests are validated using the per-org signing secret from `oauth_app_configs`. Each org configures their own Slack app with `eve integrations configure slack --signing-secret \"...\"`. The gateway reads the signing secret from the integration's enriched `settings_json` (populated by the API from `oauth_app_configs`). The provider computes `HMAC-SHA256(signing_secret, v0:timestamp:body)` and compares against the `X-Slack-Signature` header. Invalid signatures are rejected before parsing. Both the webhook and interactive endpoints use the same per-org signing secret.\n\n### Event Parsing Flow\n\n1. Event arrives at webhook endpoint.\n2. Signature validated with signing secret.\n3. Duplicate check: `event_id` from the Slack envelope checked against in-memory cache (see [Deduplication](#deduplication)).\n4. Integration resolved: `team_id -> org_id`.\n5. Identity interception: Slack user resolved to Eve identity before agent routing (see [Identity Interception](#identity-interception)).\n6. Event type determines dispatch path:\n\n### Message vs app_mention Events\n\n| Event Type | Trigger | Dispatch Path |\n|------------|---------|---------------|\n| `app_mention` | User writes `@eve ...` | Parsed as `@eve \u003cagent-slug> \u003ccommand>`, routed to specific agent |\n| `message` | Any message in subscribed channel (no mention) | Dispatched to channel/thread listeners only |\n\nFor `app_mention`:\n- First word after `@eve` is tested as agent slug.\n- If it matches a known slug, route directly to that agent's project.\n- If no match, route to org `default_agent_slug` with full text as command.\n- Agent slug resolves to `{project_id, agent_id}` (unique per org).\n- Job created for the agent; thread + event recorded.\n\nFor `message` (no mention):\n- Only dispatched if channel/thread has active listeners.\n- Each listener agent receives a separate job in its own project.\n\n### Listener Mechanics\n\n```\n@eve agents listen \u003cagent-slug> # channel-level or thread-level\n@eve agents unlisten \u003cagent-slug> # remove listener\n@eve agents listening # show active listeners\n@eve agents list # directory of slugs\n```\n\n#### Thread-Scoped vs Channel-Scoped\n\n- Command issued in a **channel**: creates a channel-level listener. All `message` events in that channel are dispatched.\n- Command issued inside a **thread**: creates a thread-level listener. Only messages within that specific thread are dispatched.\n- Multiple agents can listen to the same channel or thread.\n- Listener subscriptions stored in `thread_subscriptions` with `subscriber_type: agent`.\n\n### Outbound\n\nResponses delivered via Slack Web API (`chat.postMessage`), threaded to the originating message.\n\n### Platform Sentinel Channel Routing\n\nThe gateway has one special-case Slack routing path for Platform Sentinel.\n\n- If `EVE_SENTINEL_CHANNEL_ID` is set and an inbound Slack message arrives in that channel, the gateway does **not** send the message through normal agent routing.\n- Instead it calls the API responder endpoint:\n\n```text\nPOST /internal/platform-respond\n```\n\n- The API returns markdown text, and the gateway sends that text back to the same Slack channel/thread.\n\nSupported responder keywords:\n\n| Keyword | Result |\n|---------|--------|\n| `health`, `status` | Full platform health report |\n| `degraded`, `issues` | Only degraded/critical environments |\n| `resources`, `report` | Current alias of the health report |\n| `help`, `cmds`, `commands` | Command help text |\n\nOperational notes:\n- `EVE_SENTINEL_CHANNEL_ID` is a deployment-time env var on the gateway, not a dynamic DB lookup.\n- Keep it aligned with the system setting `sentinel.slack.channel_id`.\n- Outbound sentinel alerts still use the normal gateway delivery path (`/internal/deliver`); the special routing above is only for inbound messages in the sentinel channel.\n\n### Interactive Endpoint\n\n`POST /gateway/providers/slack/interactive` handles Slack interactive components (buttons, modals, menus).\n\n- Content-Type: `application/x-www-form-urlencoded` with a `payload` field containing a JSON string.\n- Signature validated using the same per-org signing secret as the webhook endpoint.\n- Routes by `action_id` to the appropriate handler.\n\nSupported actions:\n- `membership_approve` -- approve a pending org membership request.\n- `membership_deny` -- deny a pending org membership request.\n\nReturns `200 OK` with an optional message update to replace the interactive message in Slack.\n\n## Nostr Provider (Subscription)\n\n### Connection Model\n\nConnect to configured relay(s) via WebSocket. Subscribe to events targeting the platform pubkey.\n\n### Inbound Event Types\n\n| Kind | Type | Description |\n|------|------|-------------|\n| Kind 4 | NIP-04 Encrypted DM | Private message to platform pubkey |\n| Kind 1 | Public Mention | Public note tagging platform pubkey |\n\n### Inbound Flow\n\n1. Relay broadcasts event matching subscription filters.\n2. Provider verifies **Schnorr signature** on the event.\n3. Kind 4 events are decrypted using **NIP-04** (shared secret derived from sender pubkey + platform private key).\n4. Message normalized to standard inbound format.\n5. Agent slug extracted from content.\n6. Routed through the same chat dispatch pipeline as Slack.\n\n### Agent Slug Extraction (Nostr)\n\n| Pattern | Example |\n|---------|---------|\n| `/agent-slug \u003ctext>` | `/mission-control review PR` |\n| `agent-slug: \u003ctext>` | `mission-control: review PR` |\n| First word of public mention | `mission-control review PR` |\n\nIf no slug matches, the org default agent is used.\n\n### Outbound Mechanics\n\n| Context | Event Kind | Details |\n|---------|-----------|---------|\n| DM reply | Kind 4 | NIP-04 encrypted, published to relays |\n| Public reply | Kind 1 | NIP-10 reply threading tags (`e` and `p` tags) |\n\n### Cross-Relay Deduplication\n\nEvent IDs are tracked in a bounded set (10k entries). Duplicate events from multiple relays are dropped.\n\n## Deduplication\n\nSlack may deliver the same event more than once (retries on slow response, network glitches). The gateway tracks `event_id` values in a short-lived in-memory cache:\n\n- On receipt, the `event_id` from the Slack event envelope is checked against the cache.\n- If already seen, return `200 OK` immediately with no further processing.\n- If new, store the `event_id` and continue normal processing.\n- Cache entries expire after a short TTL (order of minutes) -- long enough to cover Slack's retry window.\n\n## Timeout Handling\n\nSlack retries webhook deliveries if no response within **3 seconds**. To avoid duplicate deliveries:\n\n- The webhook handler acknowledges (`200`) as quickly as possible.\n- Slow work (identity resolution, agent routing, job creation) runs asynchronously after the HTTP response.\n- This ensures the 3-second deadline is met even under load.\n\n## Identity Interception\n\nBefore agent routing, the gateway resolves the Slack user to an Eve identity. This happens after integration lookup (`team_id -> org_id`) but before agent slug parsing:\n\n1. Sender's Slack user ID checked against known identity links for the org.\n2. If resolved, processing continues to agent routing.\n3. If unresolved, the user receives a helpful error explaining how to link their Slack account.\n\n### Reserved Commands\n\nThe `link` command is reserved and checked before agent slug resolution. `@eve link` initiates the identity linking flow regardless of whether a `link` agent exists. This ensures new users can always link their accounts, even before any agents are configured.\n\n## Multi-Tenant Mapping\n\n- `team_id -> org_id` stored at integration connect time.\n- Agent slugs in `agents.yaml` are unique per org, selecting the target project/agent.\n\n## Identity Resolution\n\n`resolveIdentity(externalUserId, accountId)` maps an external user (Slack user ID, Nostr pubkey) to an Eve identity. External identities are stored in `external_identities` and linked to Eve users/orgs for permission checks and audit trails.\n\n## Gateway Discovery Policy\n\nAgents must opt in to be visible and routable from gateways. See `agents-teams.md` for full details.\n\n| Policy | Directory | Direct chat | Internal dispatch |\n|--------|-----------|-------------|-------------------|\n| `none` | Hidden | Rejected | Works |\n| `discoverable` | Visible | Rejected (hint) | Works |\n| `routable` | Visible | Works | Works |\n\nDirectory endpoint: `GET /internal/orgs/{org_id}/agents` -- filters out `none` agents, supports `?client=slack`.\n\nSlug routing: `POST /internal/orgs/{org_id}/chat/route` -- enforces `routable` policy and `gateway_clients` whitelist.\n\n## Agent Slug Extraction (Summary)\n\n| Provider | Pattern | Fallback |\n|----------|---------|----------|\n| Slack | `@eve \u003cslug> \u003ccommand>` | Org default agent |\n| Nostr | `/slug \u003ctext>` or `slug: \u003ctext>` or first word | Org default agent |\n\n## Thread Key Format\n\nThread continuity uses a canonical key scoped to the integration account:\n\n```\naccount_id:channel[:thread_id]\n```\n\n- Slack: `T123ABC:C456DEF:1234567890.123456`\n- Nostr: `\u003cplatform-pubkey>:\u003csender-pubkey>`\n\n## WebChat Provider\n\nBrowser-native agent chat via WebSocket. Follows the subscription transport model (like Nostr).\n\n**Connection:**\n```\nws://gateway:4820/?token=\u003cjwt>\n```\n\n**Send message:**\n```json\n{\"type\": \"message\", \"text\": \"Hello\", \"agent_slug\": \"coder\", \"thread_id\": \"optional\"}\n```\n\n**Receive reply:**\n```json\n{\"type\": \"message\", \"text\": \"Queued 1 job(s)...\", \"thread_id\": \"...\", \"timestamp\": \"...\"}\n```\n\nFeatures:\n- JWT auth in WebSocket handshake using Eve JWKS verification (`RS256`, `exp`, `nbf`, `kid`)\n- Heartbeat ping/pong (30s interval)\n- Thread continuity across reconnections\n- Multi-tab support (same user, multiple connections)\n\nRegistration: configured as an integration with `provider: webchat`.\n\nUse provider `app` and the conversations facade for same-origin embedded app panes. Use `webchat` only when a direct browser-to-gateway WebSocket is required. Use provider `api` for generic REST-originated chat clients without browser push.\n\n## API Provider (No-Op for Polling Clients)\n\nThe `api` provider exists so that chat routing succeeds for REST clients that do not maintain a push connection. It implements the `GatewayProvider` interface with a no-op `sendMessage()`:\n\n- `name: 'api'`, `transport: 'subscription'`, capabilities: `['inbound', 'outbound']`.\n- `initialize()` and `shutdown()` are no-ops — there is no socket, no listener, no connection state.\n- `sendMessage()` returns immediately. The agent's reply is already stored in `thread_messages` before the gateway is called; clients retrieve it via polling (`GET /threads/{id}/messages?since=`) or SSE (`GET /threads/{id}/stream`).\n\nWhy this exists: before the `api` factory was registered, chat from REST web apps (e.g. `provider: \"api\"`, `account_id: \"eden-web\"`) failed with `No active provider instance for api:eden-web`, which marked every reply `delivery_status = failed` even though the message had been persisted. With the no-op provider registered, those deliveries are now `delivered` and the polling/SSE catch-up paths are the source of truth for the client.\n\nConfigure via `provider: api` on `chat/route` requests, or as the implicit transport for `provider: app` embedded conversations — no integration row is required.\n\n## Chat Simulation\n\nTest the full routing pipeline without a live provider connection.\n\n```bash\neve chat simulate --project \u003cid> --team-id T123 --channel-id C123 --user-id U123 --text \"hello\" --json\n```\n\nReturns `thread_id` and `job_ids` showing the dispatch result.\n\n## API Endpoints\n\n```\nPOST /gateway/providers/:provider/webhook # generic webhook ingress\nPOST /gateway/providers/slack/interactive # Slack interactive components\n\nGET /internal/orgs/{org_id}/agents # agent directory (filtered by policy)\nPOST /internal/orgs/{org_id}/chat/route # slug-based routing\n\nPOST /chat/simulate # simulate chat message\nPOST /chat/listen # subscribe agent to channel/thread\nPOST /chat/unlisten # unsubscribe agent\nGET /chat/listeners # list active listeners\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":16879,"content_sha256":"b2b5e8e1a3763e9ecd1f3daca60cacf5eb91cf0b5891ed21c53280c7d1e0f6ef"},{"filename":"references/harnesses.md","content":"# Harness Execution Reference\n\n## Use When\n- You need to choose or compare available harnesses and profiles.\n- You need workspace, permission, or sandbox guidance for job execution.\n- You need to trace how Eve invokes harness binaries during execution.\n\n## Load Next\n- `references/jobs.md` for execution lifecycle and attempt scheduling.\n- `references/cli.md` for harness-specific profile commands.\n- `references/secrets-auth.md` for credentials and secret injection.\n\n## Ask If Missing\n- Confirm target harness and repo path for execution.\n- Confirm permission policy and sandbox mode requirements (`inline` vs `runner`).\n- Confirm available secrets/toolchain constraints for command execution.\n\nEve executes AI work through **harnesses** -- thin adapters that wrap AI coding CLIs\n(Claude Code, Gemini CLI, Codex, etc.) behind a uniform invocation contract. All model\naccess is BYOK: harnesses and apps bring their own API keys via secrets and call\nproviders directly. Eve never proxies inference traffic. This reference covers the full\nlifecycle: invocation, workspace setup, authentication, per-harness configuration,\npolicy controls, and introspection.\n\n---\n\n## Invocation Flow\n\nBoth the worker and agent-runtime execute jobs through a shared invoke module\n(`packages/shared/src/invoke/`). Agent jobs route to the agent-runtime; builds,\ndeploys, pipelines, and scripts route to the worker. The shared module provides\nfull feature parity across both runtimes.\n\nEvery job attempt follows a two-stage pipeline:\n\n```\nHarnessInvocation\n { attemptId, jobId, projectId, text, workspacePath, repoUrl, harness, toolchains }\n |\n v\nInvokeService.execute() (worker or agent-runtime, both using shared invoke module)\n 1. prepareWorkspace() -- mkdir + git clone/copy\n 2. writeCarryoverContext() -- memory, docs, parent attachments\n 3. stageAttachments() -- download chat files to workspace\n 4. writeSecurityClaudeMd() -- security policy to CLAUDE_CONFIG_DIR\n 5. resolveWorkerAdapter() -- look up WorkerHarnessAdapter by name\n 6. adapter.buildOptions(ctx) -- resolve auth, config dirs, env\n 7. BudgetEnforcer.start() -- enforce max_tokens/max_cost via llm.call tracking\n 8. executeEveAgentCli() -- spawn eve-agent-cli, stream JSON to execution_logs\n |\n v\neve-agent-cli\n 1. resolveCliAdapter(harness) -- look up CliHarnessAdapter by name\n 2. adapter.buildCommand(ctx) -- binary + args + env; map permission policy to flags\n 3. spawn(binary, args, {cwd: repoPath, env})\n -- run the harness CLI, normalize output to JSON events\n```\n\nThe worker never calls harness binaries directly. It always goes through `eve-agent-cli`,\nwhich owns argument construction, permission mapping, and output normalization.\n\n### Shared Invoke Module\n\nAgent-execution features live in `packages/shared/src/invoke/` and are imported by\nboth worker and agent-runtime. Key modules:\n\n| Module | Purpose |\n|--------------------------|------------------------------------------------------|\n| `budget-enforcement.ts` | BudgetEnforcer: max_tokens/max_cost, llm.call tracking, kill |\n| `carryover-context.ts` | Write memory, docs, parent attachments to workspace |\n| `security-policy.ts` | Build and write security CLAUDE.md |\n| `result-extraction.ts` | Extract result text, JSON, token usage, error messages |\n| `attachment-staging.ts` | Stage chat file attachments into workspace |\n| `coordination.ts` | Write coordination inbox and thread context |\n| `workspace-hooks.ts` | Run acquire/release hooks |\n| `workspace-secrets.ts` | Resolve, materialize, and clean up secrets |\n| `eve-message-relay.ts` | EveMessageRelay: deliver to chat + coordination thread |\n| `resource-hydration.ts` | Hydrate resources with lifecycle events |\n| `harness-lifecycle.ts` | Log harness start/end events |\n| `codex-auth.ts` | Write-back refreshed Codex auth tokens |\n\nNew agent-execution features go in the shared module, never in a single runtime.\n\n---\n\n## Harness Naming and Aliases\n\n| Harness | Binary | Aliases | Notes |\n|------------|-----------|----------|-----------------------------------------------------|\n| `mclaude` | `mclaude` | -- | cc-mirror Claude variant |\n| `claude` | `claude` | -- | Official @anthropic-ai/claude-code |\n| `zai` | `zai` | -- | cc-mirror Z.ai variant |\n| `gemini` | `gemini` | -- | @google/gemini-cli |\n| `code` | `code` | `coder` | @just-every/code. Use `coder` on host to avoid VS Code clash |\n| `codex` | `codex` | -- | @openai/codex |\n\nDo not parse `harness:variant` syntax. Use `harness_options.variant` instead.\n\n---\n\n## Workspace Directory Structure\n\n```\n$WORKSPACE_ROOT/ # e.g. /opt/eve/workspaces\n {attemptId}/ # unique per attempt\n repo/ # cloned/copied repository\n AGENTS.md # project memory for agents\n CLAUDE.md # Claude-specific instructions\n .agents/skills/ # installed skills (gitignored)\n .agents/harnesses/\u003charness>/ # per-harness config (optional)\n .claude/skills/ # symlink or overrides (gitignored)\n```\n\n| Variable | Value | Description |\n|-----------------|------------------------------------|------------------------------------|\n| `workspacePath` | `$WORKSPACE_ROOT/{attemptId}` | Root workspace for this attempt |\n| `repoPath` | `$workspacePath/repo` | Cloned repository |\n| `cwd` (harness) | `$repoPath` | Working directory for execution |\n\nThe harness runs with `cwd = repoPath` so it sees AGENTS.md, CLAUDE.md, and project files.\n\n### Environment Contract\n\n- `EVE_WORKSPACE_ROOT` -- root for all workspaces (e.g. `/opt/eve/workspaces`)\n- `EVE_CACHE_ROOT` -- shared cache for package managers and build artifacts\n- Processes run as UID 1000 (non-root); workspace dirs are writable\n- Cache directories are shared across attempts for efficiency\n- Credentials and config injected via secrets/config maps\n\n---\n\n## Repository Preparation\n\nThe worker requires `repoUrl` for every job. Preparation depends on URL type:\n\n**Remote URL** (https://, git://):\n- If `job.git` is set, use **GitWorkspace**: shallow clone of the resolved ref,\n fetch-based checkout, branch creation if requested.\n- Otherwise, legacy shallow clone: `git clone --depth 1 --branch \u003cbranch> \u003curl>`.\n\n**Local URL** (file://):\n- Copy directory with `fs.cp`. Branch is ignored. Dev/test only.\n- Not supported in k8s runtime or push-required workflows.\n\n### Git Controls\n\nWhen a job has `git` configuration, the worker:\n\n1. Resolve the ref per `git.ref_policy` (env release -> manifest defaults -> project branch).\n2. Create or check out a branch per `git.branch` and `git.create_branch`.\n3. Apply commit and push policies **after** execution:\n - `commit=auto` -- `git add -A` and commit any changes, even on failed attempts.\n - `commit=required` -- fail on success if the working tree is clean.\n - `push=on_success` or `push=required` -- push only when the worker created commits.\n4. Store resolved metadata on the attempt (`job_attempts.git_json`).\n\n---\n\n## Worker Image and Toolchains\n\nThe default worker image is `base` (~800MB), which includes Node.js, git, gh, kubectl,\nkaniko, buildctl, and all harness binaries (claude-code, codex, gemini-cli, cc-mirror,\ncode, bd, skills). Most agent jobs need nothing beyond this.\n\n### Toolchain-on-Demand\n\nToolchains (Python, Rust, Java, Kotlin, ffmpeg/whisper) are delivered as init containers\nrather than baked into the worker image. This replaces the previous `full` image (2.6GB)\napproach.\n\nAvailable toolchains:\n\n| Toolchain | Contents | Approx Size |\n|-----------|----------------------------------------------|-------------|\n| `python` | Python 3, pip, venv, uv | ~100MB |\n| `media` | ffmpeg, whisper-cli, ggml-small.en model | ~300MB |\n| `rust` | rustup, stable toolchain, rustfmt, clippy | ~400MB |\n| `java` | Temurin JDK 21 | ~300MB |\n| `kotlin` | kotlinc 2.0.21 + bundled JDK 21 | ~350MB |\n\nInit containers copy toolchain payloads from small single-purpose images into\n`/opt/eve/toolchains/{name}/`. The entrypoint extends `PATH` from `EVE_TOOLCHAIN_PATHS`\nand sources per-toolchain `env.sh` files for variables like `JAVA_HOME` and `CARGO_HOME`.\n\n### Declaring Toolchains\n\nAgents declare required toolchains in their config:\n\n```yaml\n# eve/agents.yaml\nagents:\n data-analyst:\n name: Data Analyst\n skill: analyze-data\n harness_profile: claude-sonnet\n toolchains: [python]\n\n doc-processor:\n name: Document Processor\n skill: process-documents\n harness_profile: claude-sonnet\n toolchains: [media]\n```\n\nWorkflows can override the agent default:\n\n```yaml\n# eve/workflows.yaml\nworkflows:\n process-document:\n trigger:\n system.event: doc.ingest\n steps:\n - name: process\n agent: doc-processor\n toolchains: [media, python] # override agent default\n```\n\nToolchain precedence: workflow step `toolchains` overrides agent `toolchains` overrides\nempty (base-only). The `full` image remains available via `EVE_WORKER_VARIANT=full` for\nbackwards compatibility.\n\n### Runner Pod Injection\n\nFor K8s runner pods, the orchestrator generates init containers from the `toolchains`\narray. Each init container copies its payload into a shared `emptyDir` volume mounted\nat `/opt/eve/toolchains/`. On nodes with a persistent toolchain cache (node-local PVC),\ninit containers skip the copy if the cached version matches.\n\n---\n\n## Disk Management\n\nOperator knobs (env vars):\n\n| Variable | Purpose |\n|-----------------------------|--------------------------------------------|\n| `EVE_WORKSPACE_MAX_GB` | Total workspace budget per instance |\n| `EVE_WORKSPACE_MIN_FREE_GB` | Hard floor; refuse new claims if below |\n| `EVE_WORKSPACE_TTL_HOURS` | Idle TTL for job worktrees |\n| `EVE_SESSION_TTL_HOURS` | Idle TTL for session workspaces |\n| `EVE_MIRROR_MAX_GB` | Cap for bare mirrors |\n\nPolicies: LRU eviction when over budget. TTL cleanup for idle worktrees. Mirror\nmaintenance via `git fetch --prune` and periodic `git gc --prune=now`. Fail-fast on\nlow disk (emit system event; do not start new attempts).\n\nK8s: per-attempt PVCs are deleted after completion. Session-scoped PVCs use TTL cleanup.\n\n---\n\n## Authentication\n\n### Claude-Based Harnesses (mclaude, claude, zai)\n\n**Priority:** `ANTHROPIC_API_KEY` (highest, skips OAuth) -> OAuth tokens\n(`CLAUDE_CODE_OAUTH_TOKEN` + `CLAUDE_OAUTH_REFRESH_TOKEN`).\n\n**OAuth refresh:** Automatic with 5-minute buffer before expiry. Cached in-memory for\nthe worker process lifetime. Refresh endpoint:\n```\nPOST https://console.anthropic.com/v1/oauth/token\n{ \"grant_type\": \"refresh_token\", \"refresh_token\": \"\u003ctoken>\",\n \"client_id\": \"9d1c250a-e61b-44d9-88ed-5944d1962f5e\" }\n```\n\n**Credentials file search order:**\n1. `~/.claude/.credentials.json`\n2. `~/.claude/credentials.json` (legacy)\n3. `$XDG_CONFIG_HOME/claude/.credentials.json`\n4. `$XDG_CONFIG_HOME/claude/credentials.json` (legacy)\n5. `$CLAUDE_CONFIG_DIR/.credentials.json` (cc-mirror)\n\n**File format:** `{ \"claudeAiOauth\": { \"accessToken\": \"...\", \"refreshToken\": \"...\", \"expiresAt\": \u003cms> } }`\n\nThe Docker entrypoint writes credentials to `~/.claude/.credentials.json` and the\ncc-mirror config dir at container startup.\n\n### Zai Harness\n\nRequires `Z_AI_API_KEY`. The worker maps this to `ANTHROPIC_API_KEY` at spawn time.\nThe Docker image strips `ANTHROPIC_API_KEY` from zai's settings.json so the runtime\nvalue takes precedence.\n\n`ANTHROPIC_BASE_URL` precedence for zai adapter:\n1. `ANTHROPIC_BASE_URL`\n2. `Z_AI_BASE_URL` (fallback)\n\n### Gemini Harness\n\nUses `GEMINI_API_KEY` or `GOOGLE_API_KEY`. No special credential setup.\n\n### Code / Codex Harnesses\n\nDocker entrypoint writes OpenAI OAuth to `~/.code/auth.json` and `~/.codex/auth.json`.\nFormat: `{ \"tokens\": { \"access_token\": \"...\", \"refresh_token\": \"...\", \"id_token\": \"...\", \"account_id\": \"...\" } }`\n\nEve automatically writes back refreshed Code/Codex tokens after each invocation. If the\n`auth.json` changed during the session, the new value is patched to the originating secret\nscope (user/org/project). Write-back failures are non-fatal (logged as warning).\n\nTo initially register tokens, re-auth with `codex auth` / `code auth`, then run `eve auth sync`.\n\n---\n\n## Token Lifecycle Management\n\n### Claude OAuth Tokens\n\nClaude OAuth tokens (`sk-ant-oat01-*`) are short-lived (~15h) and cannot be refreshed by the\nworker -- the Claude harness handles refresh internally during a session. For jobs that may\nexceed the token's remaining lifetime, prefer `ANTHROPIC_API_KEY` (a long-lived setup-token).\n\nToken types detected by `eve auth creds` and `eve auth sync`:\n\n| Token prefix | Type | Lifetime |\n|---|---|---|\n| `sk-ant-oat01-` | `setup-token` | Long-lived (preferred) |\n| Other `sk-ant-*` | `oauth` | ~15h (short-lived) |\n\n`eve auth sync` warns when syncing a short-lived OAuth token (any Claude token not starting\nwith `sk-ant-oat01-`), recommending a setup-token for long-running jobs.\n\n### Codex/Code OAuth Tokens\n\nCodex and Code CLI store OAuth tokens in `auth.json` under `~/.codex/` or `~/.code/`. The\nCLI may refresh these tokens automatically during a session.\n\n**Write-back flow:**\n\n1. Before invocation: worker captures the base64-encoded `auth.json` and its originating secret scope.\n2. After invocation: worker reads `auth.json` from disk (picks freshest across `~/.code` and `~/.codex`).\n3. If the base64 differs (token was refreshed), the new value is written back to the originating secret via `PATCH /internal/secrets/:scope_type/:scope_id/CODEX_AUTH_JSON_B64`.\n4. Write-back failures are non-fatal (logged as `warn`).\n\nThis keeps tokens fresh across jobs without manual re-sync.\n\n---\n\n## Harness Config Root\n\nPer-harness configuration lives in a single root with subfolders:\n\n```\n.agents/harnesses/\n \u003charness>/\n config.toml|json|yaml\n variants/\n \u003cvariant>/\n config.toml|json|yaml\n```\n\nResolution: `EVE_HARNESS_CONFIG_ROOT` (if set) -> `\u003crepo>/.agents/harnesses/\u003charness>`.\nIf a `variants/\u003cvariant>` directory exists, it overlays the base config.\n\n---\n\n## Adding a New BYOK Model\n\n1. **Rate card** — `packages/shared/src/pricing/default-rate-card.ts`:\n add entry under `llm.byok.\u003cprovider>.\u003cmodel-id>`, update effective date.\n2. **Model examples** — `packages/shared/src/harnesses/capabilities.ts`:\n update `model_examples` for the relevant harness (recommended default first).\n3. **Env example** — `.env.example`: update the suggested model if it's the new default.\n4. **Model normalization** — `packages/shared/src/pricing/model-normalization.ts`:\n add rules if provider uses non-standard suffixes.\n5. **Harness CLI normalization** — `packages/shared/src/harnesses/model-aliases.ts`:\n add aliases when a model has multiple user-facing forms (e.g. `opus4.7`,\n `opus-4-7`, `claude-opus-4-7`) that should all resolve to the harness's\n own short alias. Per-job + chat-hint model overrides flow through the\n normalizer before reaching the harness CLI.\n\n### Currently Registered Models\n\nDefault rate card (`DEFAULT_RATE_CARD_EFFECTIVE_AT = 2026-04-29`):\n\n| Provider | Models |\n|-----------|-----------------------------------------------------------------------------------------------------|\n| anthropic | `claude-opus-4-7`, `claude-opus-4-6`, `claude-sonnet-4-6`, `claude-opus-4-5`, `claude-sonnet-4-5`, `claude-haiku-4-5`, `claude-sonnet-4` |\n| openai | `gpt-5.5`, `o3` |\n| zai | `glm-5`, `glm-5-code` |\n\n`claude-opus-4-7` priced at $5/$25 in/out per million tokens, $0.50 cache_read,\n$6.25 cache_write (matches 4.6). `gpt-5.5` priced at $5/$30 in/out, $0.50\ncache_read, $30 reasoning.\n\nPinned harness binary versions (worker + agent-runtime Dockerfiles): cc-mirror\n2.1.0, claude-code 2.1.123, codex 0.125.0, gemini-cli 0.40.0,\njust-every/code 0.6.96, pi 0.70.6, skills 1.5.3.\n\n---\n\n## Per-Harness CLI Arguments\n\n### mclaude / claude\n\n```\nmclaude --print --verbose --output-format stream-json \\\n --model sonnet --permission-mode default \"\u003cprompt>\"\n```\n\n- Config dir: `\u003cconfig root>/mclaude` or `$CLAUDE_CONFIG_DIR`\n- Model: `$CLAUDE_MODEL` or `sonnet` (default fallback changed from `opus` to `sonnet`)\n- Skills: mclaude installs from `skills.txt` into `.agents/skills/` at runtime\n- Model aliases: `opus4.7`, `opus-4-7`, `opus-4.7`, `claude-opus-4-7` (and the\n `anthropic/` provider-prefixed variants) all normalize to Claude Code's `opus`\n alias via `normalizeClaudeCodeModelAlias`. Per-job and chat-hint model\n overrides feed through this normalizer before reaching the harness CLI.\n\n### zai\n\n```\nzai --print --verbose --output-format stream-json \\\n --model \u003cmodel> --permission-mode default \"\u003cprompt>\"\n```\n\n- Config dir: `\u003cconfig root>/zai` or `$CLAUDE_CONFIG_DIR`\n- Model: `$ZAI_MODEL` or `$CLAUDE_MODEL`\n\n### gemini\n\n```\ngemini --output-format stream-json \\\n --model \u003cmodel> --approval-mode default \"\u003cprompt>\"\n```\n\nUses `--approval-mode` instead of `--permission-mode`.\n\n### code / coder / codex\n\n```\ncode --ask-for-approval on-request --model \u003cmodel> \\\n --profile \u003cvariant> exec --json --skip-git-repo-check \"\u003cprompt>\"\n```\n\n- Config dir: `\u003cconfig root>/code` (or `/codex`) or `$CODEX_HOME`\n- Auth: `auth.json` in config dir (from `CODEX_AUTH_JSON_B64` or `CODEX_OAUTH_*` vars)\n- Reasoning: `code` uses `--reasoning \u003ceffort>`. `codex` uses\n `-c model_reasoning_effort=\"\u003ceffort>\"` (config override) instead, because the\n codex CLI no longer accepts `--reasoning` as a top-level flag. The\n `eve-agent-cli` adapter switches automatically based on `ctx.harness`.\n\n---\n\n## Permission Policies\n\n| Policy | mclaude/claude/zai | gemini | code/codex |\n|-------------|---------------------------------------|----------------------------|---------------------------------|\n| `default` | `--permission-mode default` | `--approval-mode default` | `--ask-for-approval on-request` |\n| `auto_edit` | `--permission-mode acceptEdits` | `--approval-mode auto_edit`| `--ask-for-approval on-failure` |\n| `never` | `--permission-mode dontAsk` | (fallback to default) | `--ask-for-approval never` |\n| `yolo` | `--dangerously-skip-permissions` | `--yolo` | `--ask-for-approval never` |\n\nSandbox flags applied automatically by `eve-agent-cli`:\n- Claude/mclaude/zai: `--add-dir \u003cworkspace>`\n- Code/Codex: `--sandbox workspace-write -C \u003cworkspace>`\n- Gemini: `--sandbox`\n\n---\n\n## Reasoning Effort\n\nJobs pass `harness_options.reasoning_effort` (`low`, `medium`, `high`, `x-high`).\n\n| Harness family | Mechanism | Notes |\n|--------------------|-------------------|----------------------------------------|\n| mclaude/claude/zai | thinking tokens | Maps effort level to token budget |\n| code/codex | `--reasoning` | Passes effort level as CLI flag |\n| gemini | passthrough | Effort level passed directly |\n\n---\n\n## Project Harness Profiles\n\nDefine named profiles in the manifest under `x-eve.agents`:\n\n```yaml\nx-eve:\n agents:\n version: 1\n availability:\n drop_unavailable: true # drop entries for unavailable harnesses\n profiles:\n primary-coder:\n - harness: codex\n model: gpt-5.2-codex\n reasoning_effort: high\n primary-reviewer:\n - harness: mclaude\n model: opus-4.5\n reasoning_effort: high\n - harness: codex\n model: gpt-5.2-codex\n reasoning_effort: x-high\n planning-council:\n - profile: primary-planner # reference another profile\n```\n\nReference profiles in jobs via `harness_profile` to avoid hardcoding harness choices.\nSkills should always reference profiles, not specific harnesses.\n\n### Profile Resolution for Chat-Routed Jobs\n\nHarness profiles resolve correctly for chat-routed jobs. The ChatService resolves\n`harness_profile` names to concrete harness + harness_options using the project's\n`x_eve_yaml` (from agent config, synced via `eve agents sync`). This applies to all\nchat job creation paths: direct agent routing, team lead/coordinator jobs, team relay\nand fanout member jobs, and direct slug routing.\n\n### Profile Resolution Source\n\nProfiles are read from `agent_config.x_eve_yaml` (set during agents sync), not from\n`manifest.manifest_yaml`. The manifest contains `x-eve.packs` but not profiles. For\nbackwards compatibility, resolution falls back to the manifest if agent config is absent.\n\n---\n\n## Harness Auth Status (Introspection)\n\n**API:**\n- `GET /harnesses` -- list all harnesses with auth status\n- `GET /harnesses/{name}` -- single harness details\n- `POST /projects/{project_id}/harness-profile/validate` -- dry-run an inline harness profile override and/or env overrides against project auth and secret availability without creating a job\n\n**Response shape:**\n```json\n{ \"name\": \"mclaude\", \"aliases\": [], \"description\": \"...\",\n \"variants\": [{ \"name\": \"default\", \"description\": \"...\", \"source\": \"config\" }],\n \"auth\": { \"available\": true, \"reason\": \"...\", \"instructions\": [] },\n \"capabilities\": {\n \"supports_model\": true, \"model_examples\": [\"opus\", \"opus4.7\", \"opus-4-7\", \"sonnet\", \"haiku\"],\n \"reasoning\": { \"supported\": true, \"levels\": [\"low\",\"medium\",\"high\",\"x-high\"],\n \"mode\": \"thinking_tokens\" }\n }\n}\n```\n\n### Validate Endpoint\n\n`POST /projects/{project_id}/harness-profile/validate` — verifies that an\ninline `harness_profile_override` and/or `env_overrides` would dispatch\nsuccessfully against the project. **Performs no inference**; no harness binary\nis invoked.\n\n**Request:**\n```json\n{\n \"harness_profile_override\": { \"harness\": \"zai\", \"model\": \"glm-5\", \"reasoning_effort\": \"medium\" },\n \"env_overrides\": {\n \"ANTHROPIC_BASE_URL\": \"${secret.EDEN_TEST_BASE_URL}\",\n \"OPENAI_BASE_URL\": \"${secret.EDEN_OPENAI_URL}\"\n }\n}\n```\n\n**Response shape:**\n```json\n{\n \"ok\": true,\n \"harness\": {\n \"requested\": \"Zai\",\n \"canonical\": \"zai\",\n \"auth\": { \"available\": true, \"reason\": null, \"instructions\": [] }\n },\n \"env_overrides\": [\n { \"key\": \"ANTHROPIC_BASE_URL\", \"secret_ref\": \"EDEN_TEST_BASE_URL\",\n \"status\": \"resolved\", \"scope\": \"project\", \"hint\": null },\n { \"key\": \"OPENAI_BASE_URL\", \"secret_ref\": \"EDEN_OPENAI_URL\",\n \"status\": \"missing\", \"scope\": null,\n \"hint\": \"Run: eve secrets set EDEN_OPENAI_URL \u003cvalue> --project \u003cid>\" }\n ],\n \"warnings\": []\n}\n```\n\nThe endpoint does NOT return a would-be argv. Argv preview was deliberately\nremoved from the API surface — the API's answer is `{harness_known, auth_ok,\nsecrets_resolve}`. Wizards that need argv preview should call\n`eve-agent-cli --build-command-only` on the caller's machine.\n\nPer-secret reports include the resolved scope (`system | org | user | project`)\nso callers can tell whether a value comes from a higher precedence tier than\nexpected. Permission gate matches direct job creation:\n`jobs:harness_override` (always) + `secrets:read` (when placeholders are\npresent).\n\n**CLI:**\n- `eve harness list` -- auth availability\n- `eve harness list --capabilities` -- auth + model + reasoning support\n- `eve harness validate --project \u003cid> --profile-file profile.json` -- validate an inline profile override\n- `eve harness validate --project \u003cid> --env-override KEY=${secret.NAME}` -- validate secret-backed env overrides\n- `eve harness validate --project \u003cid> --workflow \u003cname> [--env-override KEY=VALUE]` -- validate each workflow step's merged env overrides without creating jobs\n- `eve agents config --json` -- project policy + profile resolution\n\nThe `--workflow` mode merges workflow-level + step-level + invocation-level\n`env_overrides` in that precedence and validates each step independently. Exit\ncode is 2 on any failure so CI can gate deploys on it.\n\n### Per-Job Harness Override Flow\n\nEnd-to-end resolution lives in\n`packages/shared/src/harnesses/profile-resolver.ts` and replaces the previous\nduplicated logic in `chat.service.ts` and `workflows.service.ts`:\n\n```\nsources ──► resolveHarnessProfile() ──► ResolvedProfile\n (single shared module) ↓\nagent_default harness, harness_options,\nstring_ref env_overrides,\ninline_override profile_name,\nworkflow_template profile_hash, source,\n warnings[]\n```\n\nPrecedence: `workflow_template > inline_override > string_ref > agent_default`.\nConflicts emit a single `harness.profile.conflict` warning log; inline wins.\n\n**End-to-end path** (Phases 1–4):\n\n1. **API** validates DTO (`InlineProfileBundleSchema` + `EnvOverridesSchema`),\n permission-checks `jobs:harness_override` (+ `secrets:read` for `${secret.*}`\n refs), and stores the raw bundle on `jobs.harness_profile_override` /\n `jobs.env_overrides`. Direct job creation also projects the effective\n profile into the legacy `jobs.harness` + `jobs.harness_options` columns\n before insert.\n2. **Orchestrator** forwards `env_overrides` and override fields unchanged on\n the invocation payload, plus writes attribution to the routing execution log\n and `job_attempts.harness_profile_source` / `harness_profile_hash`.\n3. **Shared invoke** (worker + agent-runtime, single module) resolves\n `${secret.KEY}` placeholders against already-resolved project secrets via\n `interpolateEnvOverrides()`. Missing keys throw\n `error_code = missing_secret_override` before harness launch; resolved\n values merge into `adapterEnv` after the reserved-key strip in env-builder.\n4. **Chat hints** (Phase 3): `ChatHintsSchema` accepts the same\n `harness_profile_override` + `env_overrides` on `/chat/route`,\n `/chat/dispatch`, and `/chat/simulate`. A legacy `metadata.hints` alias is\n bridged. All 8 chat dispatch sites propagate overrides to lead + child\n jobs, and listener jobs. Override snapshot is written to\n `threads.metadata.harness_overrides` (chat + coordination threads),\n placeholders intact. On `missing_secret_override` failure,\n `EveMessageRelay.deliverProvisioningError` posts a structured error back to\n the originating thread.\n5. **Workflow templates** (Phase 4): step-level `harness_profile` and\n `harness_profile_override` accept `${inputs.\u003ckey>}` and\n `${event.payload.\u003cdotted.path>}` expressions. Manifest sync rejects\n malformed templates and undeclared `${inputs.*}` references. Missing event\n payload fields at runtime fall back to the agent default with a warning.\n\nReceipts (`packages/shared/src/pricing/receipt/`) carry `harness_profile_*`\nmetadata so cost analytics can group by profile (e.g. `cost-by-harness-profile`)\nin addition to the existing `cost-by-agent`.\n\n---\n\n## eve-agent-cli Arguments\n\n```\neve-agent-cli \\\n --harness \u003charness> # mclaude, claude, zai, gemini, code, codex\n --permission \u003cpolicy> # default, auto_edit, never, yolo\n --output-format stream-json\n --workspace \u003cworkspacePath>\n --prompt \"\u003ctext>\"\n [--variant \u003cvariant>] # optional harness variant\n [--model \u003cmodel>] # optional model override\n [--build-command-only] # print the would-be exec argv and exit; no spawn\n```\n\n`--build-command-only` is the client-side companion to the API validate\nendpoint: it prints exactly what would be executed (binary + args + env-bound\nflags) without actually spawning the harness. Useful for wizards or local\ndebugging — the API itself does not return argv.\n\n---\n\n## Execution Logging\n\nAll harness output is logged to the `execution_logs` table:\n\n| Type | Description |\n|----------------|-------------------------------------------------------|\n| `event` | Normalized harness event (assistant, tool_use, tool_result, etc.) |\n| `system` | System events (init, completed) |\n| `system_error` | Stderr output |\n| `parse_error` | Failed to parse JSON line from harness |\n| `spawn_error` | Failed to spawn harness process |\n\n---\n\n## Environment Variables Reference\n\n### Auth Variables\n\n| Variable | Description |\n|--------------------------------|------------------------------------------------|\n| `ANTHROPIC_API_KEY` | API key for Claude harnesses (overrides OAuth) |\n| `CLAUDE_CODE_OAUTH_TOKEN` | OAuth access token for Claude harnesses |\n| `CLAUDE_OAUTH_REFRESH_TOKEN` | OAuth refresh token |\n| `CLAUDE_OAUTH_EXPIRES_AT` | Token expiry (ms since epoch) |\n| `Z_AI_API_KEY` | API key for zai harness |\n| `GEMINI_API_KEY` / `GOOGLE_API_KEY` | API key for Gemini |\n| `CODEX_AUTH_JSON_B64` | Base64-encoded auth.json for Code/Codex |\n| `CODEX_OAUTH_ACCESS_TOKEN` | OAuth access token for Code/Codex |\n| `CODEX_OAUTH_REFRESH_TOKEN` | OAuth refresh token for Code/Codex |\n| `CODEX_OAUTH_ID_TOKEN` | OAuth ID token for Code/Codex |\n| `CODEX_OAUTH_ACCOUNT_ID` | Account ID for Code/Codex |\n\n### Config + Worker Variables\n\n| Variable | Description |\n|--------------------------------|------------------------------------------------|\n| `EVE_HARNESS_CONFIG_ROOT` | Override repo `.agent/harnesses` root |\n| `CLAUDE_CONFIG_DIR` | Config directory for Claude-based harnesses |\n| `CLAUDE_MODEL` | Default model for Claude harnesses |\n| `ZAI_MODEL` | Model override for zai |\n| `CODEX_HOME` | Config directory for Code/Codex |\n| `CLAUDE_CODE_TEAM_NAME` | Set to attemptId for tracking |\n| `WORKSPACE_ROOT` / `EVE_WORKSPACE_ROOT` | Root directory for workspaces |\n| `EVE_CACHE_ROOT` | Shared cache directory |\n| `EVE_AGENT_CLI_PATH` | Override path to eve-agent-cli binary |\n| `EVE_TOOLCHAIN_PATHS` | Colon-separated paths to mounted toolchain bins |\n| `EVE_TOOLCHAIN_IMAGE_PREFIX` | Image prefix for toolchain init containers |\n| `EVE_TOOLCHAIN_IMAGE_TAG` | Image tag for toolchain init containers |\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":31709,"content_sha256":"c795957ce279fc8c94d46ddf37b80434a8bde7f8e3c04bd7051d5863da0c3b0a"},{"filename":"references/ingest.md","content":"# Ingest API Reference\n\n## Use When\n- You need to upload documents or files to Eve for processing (extraction, indexing, analysis).\n- You need to download or render ingested files in-browser.\n- You need to track processing status or receive callbacks on completion.\n- You need to understand the ingest lifecycle (upload → confirm → process → done/failed).\n\n## Load Next\n- `references/events.md` for the `system.doc.ingest` event that triggers processing workflows.\n- `references/pipelines-workflows.md` for wiring ingest events to workflow steps.\n- `references/object-store-filesystem.md` for the underlying object store and org buckets.\n- `references/eve-sdk.md` for authenticating app requests to the ingest API.\n\n## Ask If Missing\n- Confirm the target project ID before running any ingest command.\n- Confirm whether the app needs callback notifications or will poll for status.\n- Confirm whether the app needs to render files in-browser (JS fetch requires CORS).\n\n## Overview\n\nThe Ingest API provides a three-step lifecycle for uploading, processing, and accessing files:\n\n1. **Create** -- register the file and get a presigned upload URL\n2. **Upload** -- PUT the file directly to S3/MinIO (no bytes through the API)\n3. **Confirm** -- trigger processing (emits `system.doc.ingest` event)\n\nProcessing runs asynchronously via a workflow triggered by the event. When processing completes, the ingest record status updates to `done` or `failed`, and the optional callback URL is invoked.\n\n## CLI Commands\n\n```bash\n# Upload a file (create + upload + confirm in one step)\neve ingest \u003cfile> --project proj_xxx\n [--title \u003ctitle>] [--description \u003cdesc>]\n [--instructions \u003ctext>] [--tags \u003ca,b>]\n [--mime-type \u003ctype>] [--source-channel \u003cchannel>]\n [--json]\n\n# Explicit subcommands\neve ingest create \u003cfile> [same flags]\neve ingest list [--status \u003cstatus>] [--limit \u003cn>] [--offset \u003cn>] [--json]\neve ingest show \u003cingest_id> [--json]\n```\n\nNotes:\n- `eve ingest \u003cfile>` is shorthand for create -- it handles the full upload lifecycle.\n- The CLI auto-infers MIME type from file extension.\n- `source_channel` defaults to `cli`. Allowed values: `upload`, `cli`, `slack`, `api`.\n- `show` returns `download_url` and `download_url_expires_at` (presigned, 5-min TTL).\n\n## API Endpoints\n\nAll endpoints are scoped under `projects/:project_id/ingest`.\n\n| Method | Path | Permission | Description |\n|--------|------|-----------|-------------|\n| `POST` | `/projects/{id}/ingest` | `projects:write` | Create ingest record, returns presigned upload URL |\n| `POST` | `/projects/{id}/ingest/{ingest_id}/confirm` | `projects:write` | Confirm upload and trigger processing |\n| `GET` | `/projects/{id}/ingest` | `projects:read` | List ingest records (filterable by status) |\n| `GET` | `/projects/{id}/ingest/{ingest_id}` | `projects:read` | Show record details with `download_url` |\n| `GET` | `/projects/{id}/ingest/{ingest_id}/download` | `projects:read` | 302 redirect to presigned S3 download URL |\n\n### Create Request Body\n\n```json\n{\n \"file_name\": \"report.pdf\",\n \"mime_type\": \"application/pdf\",\n \"size_bytes\": 1048576,\n \"title\": \"Q1 Report\",\n \"description\": \"Quarterly financial summary\",\n \"instructions\": \"Extract key metrics and trends\",\n \"tags\": [\"finance\", \"quarterly\"],\n \"source_channel\": \"api\",\n \"callback_url\": \"https://myapp.example.com/webhooks/ingest\"\n}\n```\n\nRequired: `file_name`, `mime_type`, `size_bytes`. Max file size: 500 MB.\n\n### Create Response\n\n```json\n{\n \"ingest_id\": \"ingest_abc123\",\n \"upload_url\": \"https://s3.../ingest/ingest_abc123/report.pdf?X-Amz-...\",\n \"upload_method\": \"PUT\",\n \"upload_expires_at\": \"2026-03-13T10:05:00.000Z\",\n \"max_bytes\": 524288000,\n \"storage_key\": \"ingest/ingest_abc123/report.pdf\"\n}\n```\n\n### Upload\n\nPUT the file content directly to `upload_url` with the matching `Content-Type`. No Eve auth needed -- the URL is self-authenticating.\n\n```bash\ncurl -X PUT \"$UPLOAD_URL\" \\\n -H \"Content-Type: application/pdf\" \\\n --data-binary @report.pdf\n```\n\n### Confirm Response\n\n```json\n{\n \"ingest_id\": \"ingest_abc123\",\n \"status\": \"processing\",\n \"event_id\": \"evt_xyz\",\n \"job_id\": null\n}\n```\n\nConfirm is idempotent -- calling it again on a `processing` or `done` record returns the current state.\n\n### Show Response\n\n```json\n{\n \"id\": \"ingest_abc123\",\n \"org_id\": \"org_xxx\",\n \"project_id\": \"proj_xxx\",\n \"file_name\": \"report.pdf\",\n \"mime_type\": \"application/pdf\",\n \"size_bytes\": 1048576,\n \"storage_key\": \"ingest/ingest_abc123/report.pdf\",\n \"status\": \"done\",\n \"download_url\": \"https://s3.../ingest/ingest_abc123/report.pdf?X-Amz-...\",\n \"download_url_expires_at\": \"2026-03-13T10:10:00.000Z\",\n \"callback_url\": \"https://myapp.example.com/webhooks/ingest\",\n \"event_id\": \"evt_xyz\",\n \"job_id\": \"proj-abc12345\",\n \"created_at\": \"2026-03-13T10:00:00.000Z\",\n \"completed_at\": \"2026-03-13T10:02:30.000Z\"\n}\n```\n\nNotes:\n- `download_url` is a presigned S3 URL (5-min TTL). Refresh by calling show again.\n- List responses do NOT include `download_url` (too expensive per-item). Call show for individual records.\n\n### Download Redirect\n\n`GET /projects/{id}/ingest/{ingest_id}/download` returns a 302 redirect to a presigned S3 URL. Use this for:\n\n- `\u003cimg src=\"...\">` -- browser follows redirect, renders image\n- `\u003cembed src=\"...\">` -- PDF viewer in-browser\n- `\u003ca href=\"...\">` -- download link\n- `window.open(url)` -- open in new tab\n\nNo file bytes flow through the API. The S3 response includes the correct `Content-Type` from the original upload.\n\n## Callback Notifications\n\nWhen an ingest record has a `callback_url` set and processing completes (success or failure), the platform POSTs a notification:\n\n```\nPOST \u003ccallback_url>\nContent-Type: application/json\nX-Eve-Event: ingest.completed\n```\n\n```json\n{\n \"ingest_id\": \"ingest_abc123\",\n \"status\": \"done\",\n \"job_id\": \"proj-abc12345\",\n \"file_name\": \"report.pdf\",\n \"mime_type\": \"application/pdf\",\n \"size_bytes\": 1048576,\n \"storage_key\": \"ingest/ingest_abc123/report.pdf\",\n \"completed_at\": \"2026-03-13T10:02:30.000Z\",\n \"error_message\": null\n}\n```\n\nBehavior:\n- Fire-and-forget (does not block status sync).\n- 3 retries with exponential backoff (5s, 15s, 45s).\n- 10s timeout per attempt.\n- Never fails the ingest status update.\n- `download_url` is NOT included in the callback (call `GET /ingest/{id}` to get it).\n\n## CORS on Org Buckets\n\nOrg buckets are automatically created with CORS headers that allow browser-based JS access:\n\n- **Allowed origins**: `*` (safe because presigned URLs are self-authenticating)\n- **Allowed methods**: `GET`, `PUT`, `HEAD`\n- **Allowed headers**: `*`\n- **Max age**: 3600s\n\nThis means `fetch()`, `XMLHttpRequest`, PDF.js, and other JS-based file loaders work against presigned download URLs without CORS errors.\n\nSimple HTML elements (`\u003cimg>`, `\u003cembed>`, `\u003ca>`) do not need CORS -- browsers load them directly. CORS only matters for JS-initiated requests.\n\n## Ingest Lifecycle Diagram\n\n```\nApp Eve API S3/MinIO\n | | |\n |-- POST /ingest --------->| |\n |\u003c-- upload_url, ingest_id-| |\n | | |\n |-- PUT upload_url --------|------------------------->|\n |\u003c-- 200 OK --------------|--------------------------|\n | | |\n |-- POST /confirm -------->| |\n |\u003c-- status: processing ---| |\n | | |\n | [workflow runs, processing completes] |\n | | |\n |\u003c-- POST callback_url ----| (if callback_url set) |\n | | |\n |-- GET /ingest/{id} ----->| |\n |\u003c-- download_url ---------| |\n | | |\n |-- GET download_url ------|------------------------->|\n |\u003c-- file bytes -----------|--------------------------|\n```\n\n## Event Integration\n\nConfirming an upload emits a `system.doc.ingest` event:\n\n```json\n{\n \"type\": \"system.doc.ingest\",\n \"source\": \"system\",\n \"payload\": {\n \"org_id\": \"org_xxx\",\n \"project_id\": \"proj_xxx\",\n \"ingest_id\": \"ingest_abc123\",\n \"file_name\": \"report.pdf\",\n \"mime_type\": \"application/pdf\",\n \"size_bytes\": 1048576,\n \"storage_key\": \"ingest/ingest_abc123/report.pdf\",\n \"title\": \"Q1 Report\",\n \"callback_url\": \"https://myapp.example.com/webhooks/ingest\"\n }\n}\n```\n\nWire this to a workflow trigger to run processing jobs:\n\n```yaml\nworkflows:\n document-processing:\n trigger:\n system:\n event: doc.ingest # matches system.doc.ingest events\n steps:\n - name: process\n agent:\n name: doc-processor\n```\n\nTo manually test with input (e.g., to pass ingest context):\n\n```bash\neve workflow run document-processing --input '{\"payload\":{\"ingest_id\":\"ing_xxx\",\"file_name\":\"doc.pdf\"}}'\n```\n\n## Common Patterns\n\n### App Backend: Upload with Callback\n\n```typescript\n// Create ingest record with callback\nconst resp = await fetch(`${EVE_API_URL}/projects/${projectId}/ingest`, {\n method: 'POST',\n headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },\n body: JSON.stringify({\n file_name: file.name,\n mime_type: file.type,\n size_bytes: file.size,\n callback_url: `${APP_URL}/api/webhooks/ingest`,\n }),\n});\nconst { upload_url, ingest_id } = await resp.json();\n\n// Upload directly to S3\nawait fetch(upload_url, { method: 'PUT', body: fileBuffer, headers: { 'Content-Type': file.type } });\n\n// Confirm\nawait fetch(`${EVE_API_URL}/projects/${projectId}/ingest/${ingest_id}/confirm`, {\n method: 'POST',\n headers: { 'Authorization': `Bearer ${token}` },\n});\n```\n\n### App Frontend: Render Ingested File\n\n```tsx\n// Use the download redirect endpoint as src (no CORS needed for \u003cimg>/\u003cembed>)\n\u003cembed src={`${API_URL}/projects/${projectId}/ingest/${ingestId}/download`} type=\"application/pdf\" />\n\n// For JS-based rendering (e.g. PDF.js), use download_url from show response\nconst record = await fetch(`${API_URL}/projects/${projectId}/ingest/${ingestId}`).then(r => r.json());\nconst pdfData = await fetch(record.download_url).then(r => r.arrayBuffer());\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":10298,"content_sha256":"6fb6bb9bf413b360c7aabb7a262d603c8aa9d2e73040427db3cca11f473b0dbb"},{"filename":"references/integrations.md","content":"# Integrations Reference\n\n## Use When\n- You need to register OAuth app credentials for a provider (Google Drive, Slack).\n- You need to connect a Google Drive account, Slack workspace, or GitHub repo to an Eve org.\n- You need to resolve external provider identities to Eve users.\n- You need to manage membership requests from unresolved external users.\n- You need to browse or search files in connected cloud storage (Google Drive).\n- You need to understand how chat file uploads (e.g., Slack) are materialized into agent workspaces.\n- You need to let an Eve-deployed app declare which customer orgs can use it, expose in-app admin invites, or land invite/magic-link redirects on a custom domain.\n\n## Load Next\n- `references/gateways.md` for chat gateway routing and thread key mechanics.\n- `references/agents-teams.md` for agent slug resolution and chat dispatch modes.\n- `references/secrets-auth.md` for webhook secrets, app branding, magic-link wraps, and redirect allowlist derivation.\n- `references/manifest.md` for `x-eve.branding`, `x-eve.auth`, `org_access`, and `allowed_redirect_origins` manifest fields.\n- `references/auth-sdk.md` for `eveAppUserAuth`, `useEveAppAccess`, and the `inviteMember` SDK surface.\n\n## Ask If Missing\n- Confirm the target org ID and whether the integration already exists.\n- Confirm the provider type (google-drive, slack, or github) and available OAuth app credentials.\n- Confirm whether OAuth app credentials have been registered (`eve integrations config \u003cprovider>`).\n- Confirm whether identity resolution is needed or if users are already bound.\n\nExternal provider integrations (Slack, Google Drive, GitHub), OAuth app configuration, cloud storage mounts, and identity resolution for Eve orgs.\n\n## Overview\n\nIntegrations connect external providers to Eve events and chat routing. Each integration is **org-scoped** — one row per provider account (e.g., one Slack workspace per org).\n\nKey tables:\n\n| Table | Purpose |\n|-------|---------|\n| `oauth_app_configs` | Per-org OAuth application credentials (client_id, client_secret) |\n| `integrations` | Maps provider accounts to orgs (holds OAuth tokens) |\n| `cloud_fs_mounts` | Cloud storage mounts linking a provider folder to an org/project |\n| `external_identities` | Maps provider user IDs to Eve users |\n| `membership_requests` | Tracks pending/approved/denied access requests |\n\n## OAuth App Configuration (BYOA)\n\nEach org registers its own OAuth application credentials — the **Bring Your Own App** (BYOA) pattern. No cluster-level OAuth secrets exist; every org configures its own GCP project or Slack app.\n\n### Why BYOA\n\n- **Isolation**: One org's leaked credentials cannot compromise another.\n- **Branding**: Org employees see their own company name on consent screens.\n- **Enterprise**: Google Workspace and Slack Enterprise Grid admins can trust and manage the app internally.\n- **Rate limits**: Per-org API quotas.\n\n### Setup Flow\n\n1. **Get setup instructions** — provider-specific callback URLs and required scopes:\n\n```bash\neve integrations setup-info google-drive --org \u003corg_id>\neve integrations setup-info slack --org \u003corg_id>\n```\n\n2. **Create the OAuth app** in the provider's console (GCP, api.slack.com), using the callback URL from step 1.\n\n3. **Register credentials** in Eve:\n\n```bash\n# Google Drive\neve integrations configure google-drive \\\n --org \u003corg_id> \\\n --client-id \"xxx.apps.googleusercontent.com\" \\\n --client-secret \"GOCSPX-xxx\" \\\n --label \"Acme Corp Google Drive\"\n\n# Slack (includes signing secret)\neve integrations configure slack \\\n --org \u003corg_id> \\\n --client-id \"12345.67890\" \\\n --client-secret \"abc123\" \\\n --signing-secret \"def456\" \\\n --label \"Acme Corp Slack Bot\"\n```\n\n4. **Verify config**:\n\n```bash\neve integrations config google-drive --org \u003corg_id> # secrets redacted\n```\n\n5. **Initiate OAuth connection**:\n\n```bash\neve integrations connect google-drive --org \u003corg_id>\n```\n\n### Provider Name Normalization\n\nCLI accepts both `google-drive` and `google_drive`. Internally the provider name is normalized to underscore form (`google_drive`, `slack`).\n\n### Manage Config\n\n```bash\neve integrations config \u003cprovider> --org \u003corg_id> # View (secrets redacted)\neve integrations unconfigure \u003cprovider> --org \u003corg_id> # Remove config\n```\n\nRemoving a config prevents new OAuth flows. Existing integration tokens continue to work until they expire or refresh fails.\n\n### Relationship Between Tables\n\n```\noauth_app_configs integrations\n(one per org per provider) (one per connected account)\n client_id, client_secret → tokens_json (access_token, refresh_token)\n config_json (signing_secret) settings_json, status\n```\n\nOne OAuth app config can back multiple integrations (e.g., connecting multiple Google accounts to the same org).\n\n## Google Drive Integration\n\n### Prerequisites\n\n1. Configure Google OAuth app credentials via `eve integrations configure google-drive` (see BYOA section above).\n2. The Google Drive API must be enabled in the org's GCP project.\n\n### OAuth Authorization\n\nAfter configuring credentials, initiate the Google Drive OAuth flow:\n\n```bash\neve integrations connect google-drive --org \u003corg_id>\n```\n\nThis prints an authorization URL. Open it in a browser to grant access. The callback creates an integration with a refresh token. Token refresh is automatic — the platform reads the org's `client_id` and `client_secret` from `oauth_app_configs` when refreshing.\n\nAPI endpoint: `GET /orgs/:org_id/integrations/google-drive/authorize`\n\n### Cloud FS Mounts\n\nCloud FS mounts link a Google Drive folder to an org (or project). Files in the mounted folder are browsable and searchable via the CLI.\n\n```bash\n# Create a mount\neve cloud-fs mount \\\n --provider google-drive \\\n --folder-id 0ABxxx \\\n --label \"Shared Drive\" \\\n --org \u003corg_id> \\\n [--project \u003cproject_id>] \\\n [--mode read_only|write_only|read_write] \\\n [--auto-index true|false]\n\n# List mounts\neve cloud-fs list --org \u003corg_id>\n\n# Show mount details\neve cloud-fs show \u003cmount_id> --org \u003corg_id>\n\n# Update mount settings\neve cloud-fs update \u003cmount_id> --label \"New Name\" --org \u003corg_id>\n\n# Remove a mount\neve cloud-fs unmount \u003cmount_id> --org \u003corg_id>\n```\n\n### Browsing and Searching\n\nBrowse files at a path within a mount, or search across all mounts:\n\n```bash\n# Browse files at a path\neve cloud-fs ls / --mount \u003cmount_id> --org \u003corg_id>\neve cloud-fs ls /reports --mount \u003cmount_id> --org \u003corg_id>\n\n# Search files\neve cloud-fs search \"Q4 report\" --org \u003corg_id>\neve cloud-fs search \"*.pdf\" --mount \u003cmount_id> --mime-type application/pdf --org \u003corg_id>\n```\n\n### RBAC Permissions\n\n| Permission | Grants |\n|------------|--------|\n| `cloud_fs:read` | List mounts, browse files, search |\n| `cloud_fs:write` | Reserved for write-capable Cloud FS operations |\n| `cloud_fs:admin` | Create/update/delete mounts, upload/create folders, and integration management |\n\n### Token Refresh\n\nToken refresh is transparent. When an access token expires, the platform reads the org's OAuth app credentials from `oauth_app_configs` and exchanges the refresh token for a new access token. If refresh fails (e.g., credentials revoked), the integration status is updated accordingly.\n\n## Slack Integration\n\n### Prerequisites\n\nConfigure Slack app credentials via BYOA before connecting:\n\n```bash\neve integrations setup-info slack --org \u003corg_id> # Get callback/webhook URLs\neve integrations configure slack \\\n --org \u003corg_id> \\\n --client-id \"...\" \\\n --client-secret \"...\" \\\n --signing-secret \"...\" # Required for webhook verification\n```\n\n### Connect (OAuth Install Link — Recommended)\n\nGenerate a shareable install link. The recipient needs only Slack workspace admin access — no Eve credentials.\n\n```bash\n# Generate install link (24h TTL by default)\neve integrations slack install-url --org \u003corg_id>\n\n# Custom TTL\neve integrations slack install-url --org \u003corg_id> --ttl 7d\n```\n\nThe link redirects to Slack OAuth. On approval, Eve exchanges the code for a bot token and creates the integration automatically. Gateway hot-loads the new integration within ~30 seconds (no restart needed).\n\n**Install token mechanics**: The CLI calls `POST /orgs/:org_id/integrations/slack/install-token` (requires `integrations:write`). The API returns a signed URL containing an HMAC token (`eve-slack-install-\u003cbase64url(payload)>.\u003cbase64url(hmac)>`). The token is single-use (JTI tracked), time-bounded (default 24h, max 7d), and org-scoped. The public endpoint `GET /integrations/slack/install?token=...` validates the token and redirects to Slack OAuth — no Eve auth session required.\n\n### Gateway Hot-Loading\n\nThe gateway polls `GET /internal/integrations/active` every 30 seconds. Any integration not yet in the provider registry is initialized automatically. This means a Slack workspace connected via the install link (or manual connect) becomes operational within ~30 seconds without a gateway restart. On shutdown, the sync timer is cleared and all provider instances are gracefully torn down.\n\n### Connect (Manual — Fallback)\n\n```bash\n# Connect Slack workspace to org\neve integrations slack connect \\\n --org \u003corg_id> \\\n --team-id \u003cT-ID> \\\n --token xoxb-...\n\n# Full bootstrap with all tokens\neve integrations slack connect \\\n --org \u003corg_id> \\\n --team-id \u003cT-ID> \\\n --tokens-json '{\"access_token\":\"xoxb-...\",\"bot_user_id\":\"U...\",\"team_id\":\"T...\",\"app_id\":\"A...\"}'\n\n# Verify\neve integrations list --org \u003corg_id>\neve integrations test \u003cintegration_id> --org \u003corg_id>\n```\n\n### Routing\n\n- `@eve \u003cagent-slug> \u003ccommand>` — resolves slug to project/agent (unique per org)\n- If first word after `@eve` is not a known slug → routes to org `default_agent_slug`\n- Channel messages without mention → dispatched to channel/thread listeners\n\n### Auth\n\n- **Signing secret**: Verifies inbound webhook signatures. Each org's signing secret is stored in `oauth_app_configs.config_json` and set during `eve integrations configure slack --signing-secret \"...\"`. The gateway reads it from the integration's enriched `settings_json`.\n- **Bot token**: `xoxb-...` for outbound messages, stored in integration `tokens_json`\n\n### Non-Chat Slack Notifications\n\nJobs and workflows that did not originate in Slack can send one-way channel\nnotifications through Eve:\n\n```bash\neve notifications send \\\n --project \u003cproject_id_or_slug> \\\n --channel eve-horizon-notifications \\\n --message \"Workflow complete\"\n```\n\nThis calls `POST /projects/:project_id/notifications/send`, requires\n`notifications:send`, resolves the org Slack integration server-side, and posts\nwith the stored bot token. The token is never exposed to the job. Pass\n`--integration-id \u003cid>` when an org has multiple active Slack workspaces.\n\nChannel may be a Slack channel ID (`C...`, `G...`, `D...`) or a channel name\nwith or without `#`. Name lookup uses `channels:read` and `groups:read`; posting\nuses `chat:write` and optionally `chat:write.public` for public channels where\nthe app has not been invited.\n\n### Required Bot Events\n\n| Event | Purpose |\n|-------|---------|\n| `app_mention` | `@eve` commands |\n| `message.channels` | Public channel listeners |\n| `message.groups` | Private channel listeners |\n| `message.im` | Direct messages |\n\n### Integration Settings\n\n```bash\n# Set admin notification channel for membership requests\neve integrations update \u003cintegration_id> --org \u003corg_id> \\\n --setting admin_channel_id=C-ADMIN-CHANNEL\n```\n\n### Chat File Materialization\n\nWhen a user uploads files in a Slack message (or any chat provider), the gateway automatically downloads and stages them for agents. The pipeline is provider-agnostic downstream — each provider handles its own auth for file downloads.\n\n**Flow**:\n1. **Gateway** (async, after webhook ack): Provider calls `resolveFiles()` — downloads files using the bot token, uploads to S3 via API presigned URLs, replaces provider URLs with `eve-storage://` references.\n2. **API**: Presigned URL endpoint (`POST /internal/storage/chat-attachments/presign`) keeps S3 credentials centralized. Both gateway (upload) and worker (download) use it.\n3. **Worker** (workspace provisioning): Detects `eve-storage://` URLs in `job.metadata.files`, downloads via presigned URLs, stages to `.eve/attachments/`, writes `.eve/attachments/index.json`.\n4. **Agent**: Reads `.eve/attachments/index.json` for the file manifest. Files are at `.eve/attachments/{filename}`.\n\n**S3 key format**: `chat-attachments/{org_id}/{provider}:{account_id}/{channel_id}/{message_ts}/{file_id}-{filename}`\n\nThread replies group under the thread root timestamp so all files in a conversation are co-located.\n\n**Limits**: 50 MB per file, 100 MB total per message, 10 files per message. Files exceeding limits are skipped (original provider URL preserved in metadata as fallback).\n\n**Provider interface**: Each provider implements an optional `resolveFiles(files, context)` method. The `FileResolveContext` provides `getUploadUrl` callback so providers never need direct S3 access. Currently implemented for Slack; other providers (WebChat, GitHub) follow the same pattern.\n\n**Attachment index schema** (`.eve/attachments/index.json`):\n```json\n{\n \"files\": [{\n \"id\": \"F019ABC123\",\n \"name\": \"spec.pdf\",\n \"path\": \".eve/attachments/F019ABC123-spec.pdf\",\n \"mimetype\": \"application/pdf\",\n \"size\": 245760,\n \"source_provider\": \"slack\"\n }]\n}\n```\n\n## GitHub Integration\n\n```bash\n# Set up GitHub integration for a project\neve github setup\n```\n\n- Webhook endpoint: `/integrations/github/events/:projectId`\n- Auth: `EVE_GITHUB_WEBHOOK_SECRET` + project-scoped secret override\n- Events: Push, pull request, and configured GitHub webhook events trigger Eve pipelines and workflows\n\n## App Integrations (org access, in-app admin invites, branded email, redirect allowlist)\n\nEve-deployed apps integrate with customer orgs through three project-level controls authored in `.eve/manifest.yaml` and synced onto `projects.branding` and `projects.auth_config`. The platform-side mechanics live in `manifest.md`, `auth-sdk.md`, and `secrets-auth.md`; this section is the integration map.\n\n### App org access (multi-tenant SSO)\n\n```yaml\nx-eve:\n auth:\n login_method: magic_link\n self_signup: false\n invite_requires_password: false\n org_access:\n mode: allowlist # default project_org limits app to project owner org\n allowed_orgs: [org_customer123, customer-slug]\n invite:\n enabled: true\n admin_roles: [admin, owner]\n invited_role: member # fixed to member; app invites cannot mint admins\n```\n\nManifest sync resolves slugs to canonical org IDs. Public `GET /auth/app-context?project_id=...` exposes only summary booleans (`multi_org`, `invite_enabled`, `domain_signup_enabled`); the full allowlist and domain-signup rules require `GET /auth/app-context/admin` or `eve project auth-context \u003cproject_id>`.\n\n### In-app admin invites\n\nOrg admins/owners in any `allowed_orgs` org can invite regular members directly from the app:\n\n```http\nPOST /auth/app-invites\nAuthorization: Bearer \u003ceve-user-token>\n{ \"project_id\": \"proj_xxx\", \"org_id\": \"org_customer123\", \"email\": \"[email protected]\", \"redirect_to\": \"https://app.example.com/\" }\n```\n\nReact: `useEveAppAccess().inviteMember({ orgId, email })`. CLI fallback: `eve org invite \u003cemail> --org \u003corg_id> --project \u003cproject_id> [--redirect-to \u003curl>]`. The invite carries `app_context.{project_id, org_id}`; accepting it provisions an `org_memberships(target_org, user, member)` row, never a project membership. System-created invites from `domain_signup` rules have `created_by = NULL` and `app_context.source = 'domain_signup'` (migration `00097_org_invite_system_created.sql`) so audits can distinguish them from admin-issued invites.\n\n### App-branded invite + magic-link emails\n\n```yaml\nx-eve:\n branding:\n app_name: \"ALL-TRACK\"\n app_logo_url: \"https://sandbox.all-track.co.uk/assets/logo.svg\"\n primary_color: \"#1f6feb\"\n email_from_name: \"ALL-TRACK\"\n reply_to_email: \"[email protected]\"\n support_email: \"[email protected]\"\n```\n\n`projects.branding` (migration `00093_project_branding.sql`) drives the email subject, body, and `From:` display name for invite and magic-link mail when the invite is created with `--project \u003cproject_id>`. The sender address remains the platform default in Phase 1; missing branding falls back to \"Eve Horizon\" defaults. Header-injection guards reject CR/LF in `app_name`, `email_from_name`, `reply_to_email`; non-HTTPS logos are silently omitted.\n\n### Redirect allowlist (custom-domain apps)\n\nFor apps on custom domains, declare which off-cluster origins SSO may redirect to and which origins may call `/session`:\n\n```yaml\nx-eve:\n auth:\n allowed_redirect_origins:\n - https://sandbox.all-track.co.uk\n - https://app.example.com\n```\n\nThe resolved list returned by `/auth/app-context` as `auth.allowed_redirect_origins` is the union of: explicit manifest entries; the project's own eligible custom domains (status `dns_verified | cert_provisioning | active`); and — when `org_access.mode: allowlist` — eligible custom domains owned by any project in `allowed_orgs`. This replaces the legacy `EVE_DEFAULT_DOMAIN`-only allowlist, so invite redemption and magic-link callback land on the app's declared path (e.g. `/cameras`) instead of the SSO landing page. Inspect with `eve project auth-context \u003cproject_id>`.\n\n## Identity Resolution\n\nWhen an external user (e.g., Slack) messages `@eve`, the platform resolves their identity through three tiers. The first match short-circuits the rest.\n\n### Tier 1: Email Auto-Match\n\nThe gateway fetches the provider user's email and checks if an Eve user with that email exists and is an org member. If so, the identity is automatically bound. No user action required.\n\n### Tier 2: Self-Service CLI Link\n\nAn existing Eve user can link their external identity:\n\n```bash\neve identity link slack --org \u003corg_id>\n```\n\nGenerates a one-time token. User sends `@eve link \u003ctoken>` in Slack. The gateway validates and binds.\n\n### Tier 3: Admin Approval\n\nWhen neither Tier 1 nor 2 resolves, a **membership request** is created.\n\nAdmins handle requests via:\n- **CLI**: `eve org membership-requests list --org \u003corg_id>`\n- **Slack**: Block Kit Approve/Deny buttons (if `admin_channel_id` configured)\n\nOn approval: Eve user created (if needed), org membership added, identity bound, user notified.\n\n### Resolution Decision Table\n\n| Has Eve email? | Is org member? | Result |\n|----------------|---------------|--------|\n| Yes | Yes | Tier 1: auto-bind |\n| Yes | No | Tier 3: membership request |\n| No / unknown | -- | Tier 2 (self-link) or Tier 3 |\n\n### Membership Request CLI\n\n```bash\neve org membership-requests list --org \u003corg_id>\neve org membership-requests approve \u003crequest_id> --org \u003corg_id>\neve org membership-requests deny \u003crequest_id> --org \u003corg_id>\n```\n\n## External Identities\n\nExternal identities map provider user IDs to Eve users. Once bound, subsequent messages skip resolution entirely.\n\nLifecycle:\n1. **Created** when provider user first seen (`eve_user_id` = null)\n2. **Bound** when resolution succeeds (`eve_user_id` set)\n3. **Unbound** if Eve user deleted (returns to unresolved)\n\n## CLI Quick Reference\n\n| Command | Purpose |\n|---------|---------|\n| **OAuth App Configuration (BYOA)** | |\n| `eve integrations setup-info \u003cprovider> --org \u003corg>` | Get callback URLs and setup instructions |\n| `eve integrations configure \u003cprovider> --org \u003corg> --client-id \"...\" --client-secret \"...\"` | Register OAuth app credentials |\n| `eve integrations config \u003cprovider> --org \u003corg>` | View OAuth app config (secrets redacted) |\n| `eve integrations unconfigure \u003cprovider> --org \u003corg>` | Remove OAuth app config |\n| `eve integrations connect \u003cprovider> --org \u003corg>` | Initiate OAuth connection flow |\n| **Cloud FS (Google Drive)** | |\n| `eve cloud-fs list --org \u003corg>` | List cloud FS mounts |\n| `eve cloud-fs mount --provider google-drive --folder-id \u003cid> --org \u003corg>` | Create a mount |\n| `eve cloud-fs show \u003cmount_id> --org \u003corg>` | Show mount details |\n| `eve cloud-fs update \u003cmount_id> --org \u003corg>` | Update mount settings |\n| `eve cloud-fs unmount \u003cmount_id> --org \u003corg>` | Remove a mount |\n| `eve cloud-fs ls [path] --mount \u003cmount_id> --org \u003corg>` | Browse files |\n| `eve cloud-fs search \u003cquery> --org \u003corg>` | Search files across mounts |\n| **Slack** | |\n| `eve integrations list --org \u003corg>` | List integrations |\n| `eve integrations test \u003cid> --org \u003corg>` | Test integration health |\n| `eve integrations slack install-url --org \u003corg> [--ttl 7d]` | Generate shareable Slack install link |\n| `eve integrations slack connect --org \u003corg> --team-id \u003cid> --token \u003ctoken>` | Connect Slack (manual fallback) |\n| `eve integrations update \u003cid> --org \u003corg> --setting key=value` | Update settings |\n| `eve notifications send --project \u003cproject> --channel \u003cname-or-id> --message \u003ctext>` | Send a project-scoped Slack channel notification |\n| **Identity** | |\n| `eve identity link slack --org \u003corg>` | Self-service identity link |\n| `eve org membership-requests list --org \u003corg>` | List pending requests |\n| `eve org membership-requests approve \u003cid> --org \u003corg>` | Approve request |\n| `eve org membership-requests deny \u003cid> --org \u003corg>` | Deny request |\n| **GitHub** | |\n| `eve github setup` | GitHub webhook setup |\n| **App integrations (org access, invites, branding)** | |\n| `eve org invite \u003cemail> --org \u003corg> --project \u003cproject> [--role member] [--redirect-to \u003curl>]` | Send app-branded invite scoped to the project's org policy |\n| `eve project auth-context \u003cproject>` | Show resolved `org_access`, `allowed_redirect_origins`, and admin-only `domain_signup` rules |\n\n## Related Skills\n\n- **Chat gateway details**: `references/gateways.md`\n- **Auth, branding, magic-link wraps, redirect allowlist**: `references/secrets-auth.md`\n- **Manifest fields (`x-eve.branding`, `x-eve.auth`, `org_access`, `allowed_redirect_origins`)**: `references/manifest.md`\n- **App SSO + SDK (`eveAppUserAuth`, `useEveAppAccess`, `inviteMember`)**: `references/auth-sdk.md`\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":22080,"content_sha256":"b180699a8aee6e2f5d42e121ff45997df91241b7e1b881a989208e559e23380f"},{"filename":"references/jobs.md","content":"# Jobs Reference\n\n## Use When\n- You need to inspect job lifecycle phases, attempts, and dependencies.\n- You need to tune scheduling, review, or retry behavior.\n- You need to follow, stream, or debug running/failed jobs.\n\n## Load Next\n- `references/cli.md` for job list/show/follow/result commands.\n- `references/pipelines-workflows.md` when jobs are part of pipeline execution.\n- `references/deploy-debug.md` for infrastructure-side runtime symptoms.\n\n## Ask If Missing\n- Confirm whether you are working with a root job or child attempt.\n- Confirm target job ID, org/project scope, and desired action (`list`/`show`/`follow`).\n- Confirm whether you need to review dependency graph or submission state.\n\n## Entity Model\n\n```\nJob → JobAttempt → Session → ExecutionProcess\n```\n\n- **Job**: Logical unit of work. ID: `{slug}-{hash8}` (e.g., `myproj-a3f2dd12`).\n- **JobAttempt**: Isolated execution run. Has UUID `id` + job-scoped `attempt_number` (1, 2, 3...).\n- **Session**: Tracks executor within an attempt. May change on reconstruction; attempt_id stays stable.\n- **ExecutionProcess**: Single harness invocation within a session.\n\nChild jobs use `{parent}.{n}` format (e.g., `myproj-a3f2dd12.1`). Max depth: 3 levels.\n\n## Lifecycle\n\n**Phases:** `idea` → `backlog` → `ready` → `active` → `review` → `done` | `cancelled`\n\nJobs default to `ready` (immediately schedulable). Priority: 0-4 (P0 highest, default 2).\n\n**Scheduling order:** Filter phase=ready + all deps done → sort by priority (ascending) → sort by created_at (FIFO).\n\n## API Endpoints\n\n### Project-Scoped\n\n```\nPOST /projects/{project_id}/jobs Create job\nGET /projects/{project_id}/jobs List jobs\nGET /projects/{project_id}/jobs/ready Ready/schedulable jobs\nGET /projects/{project_id}/jobs/blocked Blocked jobs\nGET /jobs List jobs (admin, cross-project)\n```\n\n### Job-Scoped\n\n```\nGET /jobs/{job_id} Get job\nPATCH /jobs/{job_id} Update job\nGET /jobs/{job_id}/tree Job hierarchy\nGET /jobs/{job_id}/context Context + derived status\nGET /jobs/{job_id}/dependencies List dependencies\nPOST /jobs/{job_id}/dependencies Add dependency\nDELETE /jobs/{job_id}/dependencies/{related} Remove dependency\n```\n\n### Claim, Release, Attempts\n\n```\nPOST /jobs/{job_id}/claim Claim (creates attempt, moves to active)\nPOST /jobs/{job_id}/release Release attempt\nGET /jobs/{job_id}/attempts List attempts\nGET /jobs/{job_id}/attempts/{n}/logs Attempt logs\nGET /jobs/{job_id}/attempts/{n}/stream SSE log stream for attempt\n```\n\n### Monitoring\n\n```\nGET /jobs/{job_id}/result Latest or attempt-specific result\nGET /jobs/{job_id}/wait Block until completion (SSE, default 300s)\nGET /jobs/{job_id}/stream SSE log stream for job\n```\n\n### Review Workflow\n\n```\nPOST /jobs/{job_id}/submit Submit for review (requires summary)\nPOST /jobs/{job_id}/approve Approve (optional comment)\nPOST /jobs/{job_id}/reject Reject (requires reason)\n```\n\n### Thread Endpoints (Coordination)\n\n```\nGET /threads/{id}/messages?since=\u003ciso>&limit=\u003cn> List messages\nPOST /threads/{id}/messages Post message\n```\n\n## Resource Refs\n\n`resource_refs` attach org documents or job attachments to a job. The worker\nhydrates them into `.eve/resources/` before harness launch.\n\n```json\n[\n {\n \"uri\": \"org_docs:/pm/features/FEAT-123.md@v4\",\n \"label\": \"Approved Plan\",\n \"required\": true,\n \"mount_path\": \"pm/approved-plan.md\"\n }\n]\n```\n\nFields:\n- `uri` (required): `org_docs:/path[@vN]` or `job_attachments:/job_id/name`\n- `label` (optional): human readable\n- `required` (optional, default true): fail provisioning when missing\n- `mount_path` (optional): relative path under `.eve/resources/`\n\n## CLI Quick Reference\n\n### Create\n\n```bash\neve job create --description \"Fix the login bug\"\neve job create --parent myproj-a3f2dd12 --description \"Sub-task\"\neve job create --description \"Review\" --harness mclaude --model opus-4.5 --reasoning high\neve job create --description \"Fix checkout\" \\\n --git-ref main --git-branch job/fix-checkout \\\n --git-create-branch if_missing --git-commit auto --git-push on_success\n\n# Resource refs (org docs + attachments)\neve job create --project proj_xxx --description \"Review brief\" \\\n --resource-refs='[{\"uri\":\"org_docs:/pm/features/FEAT-123.md@v4\",\"required\":true,\"mount_path\":\"pm/brief.md\",\"label\":\"Approved Plan\"}]'\n```\n\nResource refs mount into `.eve/resources/` before harness start. The worker writes\n`.eve/resources/index.json` and injects `EVE_RESOURCE_INDEX` for agents.\n\n### App API Awareness\n\n```bash\neve job create --description \"Analyze data\" --with-apis coordinator,analytics\neve job create --description \"Analyze producer observations\" --with-links observation\n```\n\n`--with-apis` is now **server-side**: the CLI passes `app_apis` in job hints\ninstead of generating instructions client-side. The server validates that the\nnamed APIs exist for the project, generates the instruction block (with a\nruntime-safe Node `fetch` helper using `EVE_JOB_TOKEN`), and appends it to the\njob description. This ensures consistent behavior across CLI, API, workflow, and\nSDK job creation paths.\n\n`--with-links` requests named `x-eve.app_links.consumes` subscriptions for the\njob. Subscriptions with `inject_into.jobs: true` are also auto-discovered. The\nruntime mints short-lived app-link tokens and injects\n`EVE_APP_LINK_\u003cALIAS>_API_URL`, `EVE_APP_LINK_\u003cALIAS>_TOKEN`,\n`EVE_APP_LINK_\u003cALIAS>_SCOPES`, `EVE_APP_LINK_\u003cALIAS>_PROJECT`,\n`EVE_APP_LINK_\u003cALIAS>_ENV`, and `EVE_APP_LINK_\u003cALIAS>_CLI` when the producer\nexported an image-mode CLI.\n\n### Attachments\n\n```bash\neve job attach \u003cjob-id> --file ./report.pdf --name report.pdf\neve job attach \u003cjob-id> --stdin --name output.json --mime application/json\neve job attachments \u003cjob-id> # List attachments\neve job attachment \u003cjob-id> \u003cname> # Fetch attachment content\n```\n\n### Batch Operations\n\n```bash\neve job batch --project proj_xxx --file batch.json # Submit batch job graph\neve job batch-validate --file batch.json # Validate without submitting\n```\n\n### List and View\n\n```bash\neve job list --phase active\neve job list --since 1h --stuck\neve job ready # Schedulable jobs\neve job blocked # Waiting on deps\neve job show \u003cjob-id>\neve job current # From EVE_JOB_ID\neve job tree \u003cjob-id>\neve job diagnose \u003cjob-id>\n```\n\n### Update and Complete\n\n```bash\neve job update \u003cjob-id> --phase active --priority 0\neve job close \u003cjob-id> --reason \"Done\"\neve job cancel \u003cjob-id> --reason \"No longer needed\"\n```\n\n### Monitor Execution\n\n```bash\neve job follow \u003cjob-id> # Stream logs (SSE)\neve job wait \u003cjob-id> --timeout 120 --json # Block until done\neve job watch \u003cjob-id> # Status polling + log streaming\neve job result \u003cjob-id> --format text # Get result\neve job result \u003cjob-id> --attempt 2 --format json\neve job runner-logs \u003cjob-id> # kubectl pod logs\n```\n\n`wait` exit codes: 0=success, 1=failed, 124=timeout, 125=cancelled.\n\n### Script and Action-Run Execution\n\nPipeline/workflow script jobs and pipeline `action: { type: run }` jobs are\nworker-executed bash commands. The orchestrator submits these jobs to the worker\nwith a short request and then waits for runner events, so long-running commands\nare not tied to a single open HTTP request.\n\nOutput behavior:\n- stdout/stderr are appended to attempt logs while the command is still running.\n- `eve job follow \u003cjob-id>` and `eve job logs \u003cjob-id> --attempt \u003cn>` show the\n streamed entries.\n- Final job results keep a bounded stdout/stderr tail for quick inspection.\n- Per-stream output is capped by `EVE_SCRIPT_OUTPUT_CAP_BYTES` (default 10 MiB).\n When the cap is reached, Eve drains the process output and writes one\n `output_truncated` warning per stream.\n\nTimeout behavior:\n- Script jobs use `jobs.script_timeout_seconds` first, then\n `hints.timeout_seconds`, then the 30-minute default.\n- Pipeline `action: { type: run }` jobs use `action_input.timeout_seconds`,\n then `action_input.timeout`, then `hints.timeout_seconds`, then the 30-minute\n default.\n- Script timeout logs use code `script_timeout`; action-run timeout logs use\n `action_run_timeout`.\n- If the worker never publishes a terminal runner event, the orchestrator marks\n the attempt failed with `poll_timeout`.\n\n### Claim/Release (Agent Use)\n\n```bash\neve job claim \u003cjob-id> --agent my-agent --harness mclaude\neve job release \u003cjob-id> --reason \"Need info\"\neve job attempts \u003cjob-id>\neve job logs \u003cjob-id> --attempt 2\n```\n\n### Review\n\n```bash\neve job submit \u003cjob-id> --summary \"Implemented fix, added tests\"\neve job approve \u003cjob-id> --comment \"LGTM\"\neve job reject \u003cjob-id> --reason \"Missing tests\"\n```\n\n### Dependencies\n\n```bash\neve job dep add \u003cjob-id> \u003cdepends-on-id>\neve job dep remove \u003cjob-id> \u003cdepends-on-id>\neve job dep list \u003cjob-id>\n```\n\n### Supervision and Thread Coordination\n\n```bash\neve supervise # Long-poll child events (current job)\neve supervise \u003cjob-id> --timeout 60\neve thread messages \u003cthread-id> --since 5m\neve thread post \u003cthread-id> --body '{\"kind\":\"directive\",\"body\":\"focus on auth\"}'\neve thread follow \u003cthread-id>\n```\n\n## Dependency Model\n\nRelations between jobs: `blocked_by`, `blocks`, `waits_for`, `conditional_blocks`.\n\n- `blocked_by[]`: Job IDs that must complete before this job starts.\n- `blocks[]`: Sets the reverse relationship on blocking jobs.\n- Scheduler filters out blocked jobs from the ready queue.\n\n```bash\neve job create --description \"Deploy to staging\" # then:\neve job dep add \u003cdeploy-job> \u003cbuild-job>\n```\n\n## Job Context\n\n**Endpoint:** `GET /jobs/{job_id}/context` | **CLI:** `eve job current [--json|--tree]`\n\nResponse shape:\n\n```\n{ job, parent, children, relations: { dependencies, dependents, blocking },\n latest_attempt, latest_rejection_reason, blocked, waiting, effective_phase }\n```\n\n**Derived fields:**\n- `blocked`: true when unresolved blocking relations exist.\n- `waiting`: true when latest attempt returned `result_json.eve.status == \"waiting\"`.\n- `effective_phase`: priority order `blocked` → `waiting` → `job.phase`.\n\nUse `effective_phase` for display and orchestration decisions, not raw `phase`.\n\n## Control Signals\n\nHarnesses emit a `json-result` block. The worker extracts the **last** one and stores it as `job_attempts.result_json`.\n\n```json-result\n{\n \"eve\": {\n \"status\": \"waiting\",\n \"summary\": \"Spawned 3 child jobs, added waits_for relations\",\n \"reason\": \"Waiting on child jobs to complete\"\n }\n}\n```\n\n**`eve.status` values:**\n- `success`: Normal success path (review or done based on job settings).\n- `waiting`: Attempt succeeds, job requeued to `ready`, assignee cleared. No review submission. If no blockers exist, orchestrator applies `defer_until` backoff to prevent tight loops.\n- `failed`: Normal failure path.\n\n**`eve.summary`**: Persisted to `job_attempts.result_summary` for quick visibility.\n\n## Git Controls\n\nJob-level git configuration governs ref resolution, branch creation, commit, and push behavior.\n\n### Configuration Object\n\n```json\n{\n \"git\": {\n \"ref\": \"main\",\n \"ref_policy\": \"auto\",\n \"branch\": \"job/${job_id}\",\n \"create_branch\": \"if_missing\",\n \"commit\": \"auto\",\n \"commit_message\": \"job/${job_id}: ${summary}\",\n \"push\": \"on_success\",\n \"remote\": \"origin\"\n },\n \"workspace\": {\n \"mode\": \"job\",\n \"key\": \"session:${session_id}\"\n }\n}\n```\n\n**Precedence:** explicit job fields → `x-eve.defaults.git` (manifest) → project defaults.\n\n### Ref Resolution (`ref_policy`)\n\n| Policy | Behavior |\n|--------|----------|\n| `auto` | env release SHA → manifest defaults → project default branch |\n| `env` | Requires `env_name` + current release SHA |\n| `project_default` | Always uses `project.branch` |\n| `explicit` | Requires `git.ref` to be set |\n\n### Repo Auth\n\n- HTTPS: uses `github_token` secret (e.g., `GITHUB_TOKEN`).\n- SSH: uses `ssh_key` secret via `GIT_SSH_COMMAND`.\n- Missing auth fails fast with remediation hints (`eve secrets set`).\n\n### Branch Creation (`create_branch`)\n\n| Value | Behavior |\n|-------|----------|\n| `never` | Branch must already exist |\n| `if_missing` | Create only when missing (default when `branch` is set) |\n| `always` | Reset branch to `ref` |\n\n### Commit Policy (`commit`)\n\n| Value | Behavior |\n|-------|----------|\n| `never` | No commits |\n| `manual` | Agent decides when to commit (default) |\n| `auto` | Worker runs `git add -A` + commit after execution, even on failure |\n| `required` | On success, fail attempt if working tree is clean |\n\n### Push Policy (`push`)\n\n| Value | Behavior |\n|-------|----------|\n| `never` | No push (default) |\n| `on_success` | Push only when worker created commits in this attempt |\n| `required` | Attempt push; no-op if no commits. Fail if push fails. |\n\nPush without git credentials fails fast.\n\n### Attempt Git Metadata (Audit)\n\nResolved values stored on attempt for debugging:\n\n```json\n{\n \"resolved_ref\": \"refs/heads/main\",\n \"resolved_sha\": \"abc123\",\n \"resolved_branch\": \"job/myproj-a3f2dd12\",\n \"ref_source\": \"env_release|manifest|project_default|explicit\",\n \"pushed\": true,\n \"commits\": [\"def456\"]\n}\n```\n\nAlso promoted to `JobResponse.resolved_git` from the latest successful attempt.\n\n## Harness Selection\n\nTarget a harness directly or via a project profile (`x-eve.agents`):\n\n| Flag | Purpose |\n|------|---------|\n| `--harness` | Harness name (mclaude, codex, gemini, zai) |\n| `--profile` | Profile from `x-eve.agents` |\n| `--variant` | Config overlay preset |\n| `--model` | Model override |\n| `--reasoning` | Effort: low, medium, high, x-high |\n\n### Per-Job Harness & Env Overrides\n\nPer-invocation overrides let consumer apps pick a different brain (model, endpoint,\nBYOK credentials, reasoning) for one job without mutating shared `x-eve.yaml`.\n\nJob columns (migration `00090_per_job_harness_overrides.sql`):\n\n- `harness_profile_override` (JSONB) — inline bundle `{harness, model, reasoning_effort, variant?, temperature?}`\n- `env_overrides` (JSONB) — env values, may contain `${secret.KEY}` placeholders (kept verbatim until spawn)\n- `harness_profile_source` — `agent_default | string_ref | inline_override | workflow_template`\n- `harness_profile_hash` — stable hash of the normalized override (no plaintext secrets)\n\nSame `_source` and `_hash` columns are mirrored on `job_attempts` for routing logs and\nanalytics. Direct job creation projects the effective profile into the legacy\n`jobs.harness` + `jobs.harness_options` columns before insert so the orchestrator\nactually runs the requested profile.\n\n**Precedence:** `workflow_template > inline_override > string_ref > agent_default`.\nA job containing both `harness_profile` (string ref) and `harness_profile_override`\nemits a single `harness.profile.conflict` warning log; inline wins.\n\n**Validation rules** (enforced in shared resolver + DTO):\n\n- `env_overrides` keys must match `^[A-Z_][A-Z0-9_]* eve-read-eve-docs — Skillopedia ; reject reserved keys/prefixes\n (`EVE_*`, `PATH`, `HOME`, `SHELL`, `USER`, `TMPDIR`, `NODE_OPTIONS`,\n `CLAUDE_CONFIG_DIR`, `CODEX_HOME`).\n- Values may contain literals plus `${secret.KEY}` placeholders only — other `${...}`\n expressions are rejected.\n- Total `env_overrides` JSON ≤ 4 KB.\n- Overrides are **create-only**; once an attempt exists they cannot be patched.\n\n**Permissions:** `jobs:harness_override` is required on any create that includes\noverride fields. `secrets:read` is additionally required when `env_overrides`\ncontains `${secret.KEY}` references. Both are enforced on direct job creation,\nchat dispatch (against the resolved Eve principal), and the validate endpoint.\n\n**Secret interpolation flow:** API stores placeholders intact → orchestrator forwards\nunchanged → shared invoke module resolves at spawn time against already-resolved\nproject secrets via `interpolateEnvOverrides()` → resolved values merged into\n`adapterEnv` after reserved-key strip. Missing references fail fast with\n`error_code = missing_secret_override` before harness launch. Resolved plaintext\nnever appears in job rows, attempts, receipts, or execution logs. Chat-triggered\njobs that fail with `missing_secret_override` post a structured error back to the\noriginating chat thread (and coordination thread for team dispatch) via\n`EveMessageRelay.deliverProvisioningError`.\n\n**Routing log attribution:** the orchestrator's `routing` execution log records\n`harness_profile_name`, `harness_profile_source`, `harness_profile_hash`,\n`effective_harness`, `effective_model`, and `effective_effort` so receipts and\nanalytics can group cost by harness profile.\n\n**Chat hint propagation:** chat requests (`/chat/route`, `/chat/dispatch`,\n`/chat/simulate`) accept a typed `hints` object carrying\n`harness_profile_override` + `env_overrides`. A legacy `metadata.hints` alias\nis bridged for gateway payloads. All 8 chat dispatch sites — direct agent,\ndirect team lead + relay/fanout/council members, route → agent, route → team\nlead variants — propagate overrides into every lead and child job, listener jobs\nincluded. The override snapshot is also written to thread metadata\n(`threads.metadata.harness_overrides`) on the chat and coordination threads,\nwith placeholders intact.\n\n**Workflow templating:** workflow steps may set `harness_profile` or\n`harness_profile_override` with `${inputs.\u003ckey>}` and\n`${event.payload.\u003cdotted.path>}` template expressions. `workflow.inputs.\u003cname>`\ndeclarations bind those inputs (with optional `from: event.payload.\u003cpath>` and\n`default:`). The expression engine is intentionally tiny — no operators, no\nfunction calls. Manifest sync rejects malformed templates and undeclared\n`${inputs.*}` references; missing event payload fields at runtime fall back to\nthe agent default with a warning, not an error. See pipelines-workflows.md for\nthe workflow-side coverage.\n\nCLI flags on `eve job create` (and via API as `harness_profile_override` /\n`env_overrides`):\n\n```bash\neve job create --description \"...\" \\\n --harness-override-file ./overrides.json \\\n --env-override ANTHROPIC_BASE_URL='${secret.EDEN_TEST_BASE_URL}' \\\n --env-override OPENAI_BASE_URL='${secret.EDEN_OPENAI_URL}'\n```\n\n`--env-override` is repeatable. `eve job show \u003cid> --json` returns the\noverride and env placeholders verbatim.\n\n### Per-Job Token Scope\n\nJob tokens carry an explicit resource scope, not just permission names. The\nscope is persisted on `jobs.token_scope` (migration `00096_jobs_token_scope.sql`)\nand signed into the JWT, so the on-disk `.org` mount and the API authority\nmatch. `NULL` token scope means no narrowing (legacy permission-name-only\nbehavior).\n\nSupported axes:\n\n```json\n{\n \"orgfs\": { \"allow_prefixes\": [\"/groups/projects/proj-a/**\"] },\n \"orgdocs\": { \"read_only_prefixes\": [\"/briefs/**\"] },\n \"envdb\": { \"schemas\": [\"public\"], \"tables\": [\"public.jobs\"] },\n \"cloud_fs\":{ \"allow_mount_ids\": [\"mount_a\"] }\n}\n```\n\nEnforcement happens at every layer that handles the job token — API\n(`ScopedAccessService` for orgfs/orgdocs/envdb/cloud_fs), orchestrator (mount\nmaterialization), worker and agent-runtime (workspace `.org` symlinks), and\ncloud-fs controller (per-mount checks).\n\n**Propagation chain** (parallels `env_overrides`): workflow-level `scope` →\nstep-level `scope` → workflow invoke request `scope` are **intersected** for\neach executable step job and persisted as `jobs.token_scope`. Invocation\nscope may narrow but never widen the manifest. Request-supplied `scope`\nrequires `jobs:harness_override`. There is no `eve job create --scope-*` or\n`eve workflow run --scope-*` flag yet — declare scope in the workflow/step\nmanifest or the API body. See `references/pipelines-workflows.md` for the\nworkflow-side coverage and `references/secrets-auth.md` for the permission\nmodel.\n\n`eve job show \u003cid> --json` surfaces the resolved scope under `token_scope`.\n\n### Per-Job Token Permissions\n\nSibling field to `token_scope`. Pipeline `script:` and `action: { type: run }`\nsteps (and workflow agent steps that opt in) can declare a per-step\n`permissions: [...]` list in the manifest. The expander resolves it\n(step-level wins over pipeline/workflow-level) and persists it on\n`jobs.token_permissions` (migration `00099_jobs_token_permissions.sql`).\n`NULL` means \"use the executor's default\":\n\n| Execution type | Default | Source |\n| --- | --- | --- |\n| `agent` | `DEFAULT_AGENT_PERMISSIONS` | `packages/shared/src/permissions.ts` |\n| `script` | `DEFAULT_SCRIPT_JOB_PERMISSIONS` | `packages/shared/src/permissions.ts` |\n| `action: { type: run }` | `DEFAULT_ACTION_RUN_JOB_PERMISSIONS` (least-privilege) | `packages/shared/src/permissions.ts` |\n\nThe script and action-run executors mint `EVE_JOB_TOKEN` with the resolved\nlist, write `~/.eve/credentials.json` into a workspace-local HOME, and\nsanitise the env so `EVE_INTERNAL_API_KEY` does not leak into user shell.\n\nThe expander also rejects any permission the invoking actor does not\nthemselves hold (`assertActorCanGrantPermissions`), so workflow authors\ncan narrow but not escalate.\n\n`eve job show \u003cid> --verbose` and `eve job diagnose \u003cid>` render a\n`Token:` block with both `Permissions:` and `Scope:`. `diagnose` adds\n`⚠ scope.* set but permissions[] missing X` warnings for obvious\nmisalignments.\n\n## Scheduling Hints\n\nPreferences (not requirements) that influence scheduling:\n\n| Hint | Description |\n|------|-------------|\n| `worker_type` | e.g., `default`, `gpu` |\n| `permission_policy` | `yolo` (default), `auto_edit`, `never` |\n| `timeout_seconds` | Execution timeout hint. Script jobs prefer `script_timeout_seconds`; `action: { type: run }` jobs prefer `action_input.timeout_seconds` / `action_input.timeout`; this hint is the fallback before the 30-minute default |\n| `max_cost` | Authoritative per-attempt budget cap; prefer this over token caps |\n| `max_tokens` | Coarse guardrail; cache-read tokens are discounted by rate-card weight when cheaper than input tokens |\n| `toolchains` | Resolved toolchains for agent, workflow/pipeline script, shorthand `run`, or pipeline `action: { type: run }` jobs. Inspect with `eve job show \u003cid> --json` or `eve job diagnose \u003cid>` |\n\nBudget-configured attempts emit `budget.summary` on completion and\n`budget.exceeded` when enforcement fires. Inspect them with\n`eve job logs \u003cid> --json` or `eve job diagnose \u003cid> --json`. Both rows retain\n`total_tokens` and add `weighted_tokens`, `cache_read_tokens`,\n`cache_read_token_weight`, and `cache_read_tokens_excluded`.\n\n## Coordination Threads\n\nTeam dispatches create coordination threads with key `coord:job:{parent_job_id}`. Thread ID stored in `hints.coordination.thread_id`.\n\nChild agents receive `EVE_PARENT_JOB_ID` to derive the coordination key. On attempt completion, the orchestrator auto-posts a status summary to the thread.\n\n**Inbox file:** `.eve/coordination-inbox.md` is regenerated from recent thread messages at job start.\n\n**Message kinds:** `status` (auto summary), `directive` (lead→member), `question` (member→lead), `update` (progress).\n\n## Agent Environment Variables\n\nInjected by the worker during execution:\n\n- `EVE_PROJECT_ID` — current project\n- `EVE_JOB_ID` — current job\n- `EVE_ATTEMPT_ID` — current attempt UUID\n- `EVE_AGENT_ID` — agent identifier\n- `EVE_PARENT_JOB_ID` — parent job (for coordination)\n\n## Agent-Native Job Monitoring\n\n### Event→Job Linkage\n\nWhen the orchestrator processes an event and creates a workflow job, it writes the `job_id` back to the event record. Use `eve event show \u003cevent-id>` to see which job an event triggered. This enables tracing from event source through to job execution.\n\n### Workflow-Aware List Filtering\n\n```bash\neve job list --label workflow:ingestion-pipeline --root # Root workflow jobs only\neve job list --type agent --since 1h # Filter by job type\neve job list --dead-letters # Failed (not cancelled) jobs\neve job list --disposition failed # Explicit disposition filter\n```\n\nFlags: `--label`, `--type`, `--root` (root jobs only, excludes children), `--dead-letters` (shorthand for `--phase cancelled --disposition failed`), `--disposition` (`failed` | `cancelled` | `upstream_failed`).\n\n### Summary Follow Mode\n\n```bash\neve job follow \u003cjob-id> --summary\neve job logs \u003cjob-id> --summary\n```\n\n`--summary` emits only actionable lines: phase transitions, permission rejections, periodic LLM cost/token aggregates, tool names (no I/O), eve-message blocks, errors, and a final summary footer with totals (LLM calls, tokens in/out, cost, tool uses). Cuts hundreds of raw JSONL lines to ~20 lines.\n\n### Active-Job Observability\n\nFor active jobs, the CLI now exposes the same signals operators previously had to cross-check in kubectl:\n\n- `eve job diagnose \u003cjob-id>` includes the latest attempt pod name from `runtime_meta`, best-effort live pod health from agent-runtime status, and the most recent harness heartbeat age.\n- `eve job follow \u003cjob-id>` warns after 60s and 120s of silence. If heartbeat lifecycle events are still arriving, it reports the harness as alive but quiet; otherwise it warns that the run may have stalled.\n- `eve agents runtime-status --org \u003corg-id>` now shows stale pods and active-job counts in the tabular output.\n- `eve system status` now renders agent-runtime health alongside API, orchestrator, worker, and queue state when the backend returns it.\n\n## Production Hardening\n\n### Content-Hash Deduplication (Ingest)\n\n`eve ingest confirm` checks the S3 ETag as a content fingerprint. If an identical file was already confirmed in the same project, it returns the existing record (`deduplicated: true`) instead of firing a new processing event. Use `--force` to skip the dedup check.\n\n### Failure Disposition (Dead Letters)\n\nJobs have a `failure_disposition` field distinguishing intentional cancellation from exhausted-retry failure:\n\n| Value | Meaning |\n|-------|---------|\n| `cancelled` | Explicitly cancelled by user/API |\n| `failed` | Failed after exhausting retries |\n| `upstream_failed` | Cascaded failure from upstream dependency |\n\nQuery dead letters: `GET /projects/{id}/jobs?phase=cancelled&failure_disposition=failed`\n\n### Auto-Retry with Backoff\n\nConfigure retry policy via hints or CLI:\n\n```bash\neve job create --description \"Process data\" --retry-max 3 --retry-backoff 30\n```\n\nPolicy fields (in `hints.retry`):\n- `max_attempts` — max attempts before permanent failure (default: 1 = no retry)\n- `backoff_seconds` — base delay (default: 60)\n- `backoff_multiplier` — exponential multiplier (default: 2)\n- `retryable_errors` — error codes eligible for retry (default: `['attempt_timeout', 'attempt_stale']`)\n\nOn failure, the orchestrator checks the retry policy. If retries remain and the error is retryable, it creates a new attempt with `trigger_type = 'auto_retry'` and sets `defer_until` for backoff. When retries are exhausted, the job gets `failure_disposition = 'failed'`.\n\nManual workflow-step retry is separate from per-job auto-retry. Use\n`eve workflow retry \u003croot-job-id> --failed` after a workflow root reaches a\nterminal state and a failed/upstream-failed tail should be retried without\nrerunning successful predecessor steps. Use `--from \u003cstep-name>` to rerun a\nnamed step and its downstream dependents. Eve creates replacement child jobs,\nmarks the replaced step jobs superseded in `hints`, and rewires dependencies to\nthe current replacement jobs.\n\nWorkflow steps support retry in the manifest:\n\n```yaml\nsteps:\n - name: ingest\n agent: doc-processor\n retry:\n max_attempts: 3\n backoff_seconds: 30\n retryable_errors: [attempt_timeout, attempt_stale]\n```\n\n### Cost Tracking\n\n```bash\neve analytics cost-by-agent --window 7d\n```\n\nGroups cost by agent across all projects in the org. Shows attempts, total cost, and token counts per agent.\n\n### Per-Phase Latency in Diagnostics\n\n`eve job diagnose` now shows a latency waterfall from existing lifecycle execution logs:\n\n```\nLatency Breakdown:\n provision/clone 12,340ms ████████░░░░░░░░ 14%\n provision/setup 2,100ms █░░░░░░░░░░░░░░░ 2%\n invoke/harness 71,200ms ████████████████ 82%\n cleanup/workspace 1,400ms █░░░░░░░░░░░░░░░ 2%\n ────────────────────────────\n Total 87,040ms\n```\n\nThe same diagnostic view now adds:\n- pod health correlation for active jobs when the latest attempt includes `runtime_meta.pod_name`\n- heartbeat-aware stuck detection (`Harness alive` vs `No harness heartbeat`)\n- pre-harness startup timing visibility, including clone/setup work that previously required pod logs\n\n### Routing Decision Logging\n\nA structured `routing` execution log is written at claim time, capturing harness selection, target (agent-runtime vs worker), budget config, and selection source. Visible in `eve job diagnose` and `eve job logs`.\n\nThe routing log payload includes harness-profile attribution: `harness_profile_name`,\n`harness_profile_source` (`agent_default | string_ref | inline_override |\nworkflow_template`), `harness_profile_hash` (no plaintext secrets), plus\n`effective_harness`, `effective_model`, and `effective_effort`. The same\nattribution is mirrored on `job_attempts.harness_profile_source` /\n`harness_profile_hash` so analytics can group cost by profile without scanning\nlog JSON.\n\n### Recovery & Resilience\n\nEvery job assignee — not just `orchestrator` — is in scope for the watchdog:\n\n- `recoverActiveJobsWithTerminatedAttempts` periodic sweep catches jobs left\n `active` after their attempts were finalized externally (pod drain, recovery,\n agent-runtime restart). Sweep grace period via `EVE_ORCH_TERMINATED_GRACE_SECONDS`\n (default 30s); recovery completes in ~35s instead of 30 minutes.\n- `processJob` and its error-handler path now transition job phase even when the\n attempt was finalized externally, so `attemptSucceeded` is set for the\n ingest-sync `finally` block.\n- Agent-runtime has a graceful-shutdown handler: on `SIGTERM`, all running\n attempts are marked failed with `error_code = pod_terminated` and the pod\n status is set to `draining` so no new work routes there. K8s manifest sets\n `preStop: sleep 5` and `terminationGracePeriodSeconds: 120s`.\n- Agent-runtime auto-discovers orgs from the API (DB fallback) on startup and\n re-discovers every 5 minutes. The placeholder `org_default` is gone; the\n heartbeat endpoint returns 404 (not 500) when an org is missing, so silent FK\n failures no longer mask unregistered pods.\n\n### Action vs Ad-Hoc Env Gates\n\n`defaults.env` no longer forces every ad-hoc agent job to acquire an exclusive\nenvironment mutex. The env gate now requires `action_type` (`deploy`, `build`,\n`migrate`) before serializing on environment. Ad-hoc agent jobs keep `env_name`\nfor API resolution but run in parallel. Workflow / pipeline action jobs are\nunchanged.\n\n### Job List Defaults\n\n`eve job list` returns newest-first by default — recent jobs are no longer\nhidden behind page boundaries. The build step auto-syncs the manifest from the\ncloned repo when the manifest hash changes, so workflow trigger definitions\nstay current after each deploy without a separate `eve project sync`.\n\n### Job Completion Event\n\nThe orchestrator emits `system.job.attempt.completed` on every attempt finish\npath — success, failure, and orchestrator error. Payload:\n\n```json\n{\n \"job_id\": \"myproj-a3f2dd12\",\n \"attempt_id\": \"att_...\",\n \"assignee\": \"my-agent\",\n \"thread_id\": \"thr_...\",\n \"execution_type\": \"agent\",\n \"status\": \"succeeded|failed\",\n \"duration_ms\": 12345\n}\n```\n\nUse this event to drive post-session learning workflows. The event is registered\nin `KNOWN_SYSTEM_EVENTS`, so `eve agents sync` / manifest validation accepts\n`trigger.system.event: job.attempt.completed` without warnings. Carryover\ncontext now also materializes the `user` memory category alongside `learnings`,\n`decisions`, `runbooks`, `context`, and `conventions` — for per-user preferences\nthat should ride forward to the next session.\n\n### Auto-Expiry for Stale Documents\n\nOrg documents with `expires_at` are automatically transitioned to `expired` status by a background loop (every 15 minutes). After a grace period (default 7 days, configurable via `EVE_DOC_EXPIRY_GRACE_DAYS`), expired documents are archived (content cleared, metadata preserved).\n\n## Per-Job HOME Isolation\n\nEach job attempt gets an isolated HOME directory at `/tmp/eve/agent-homes/\u003cattemptId>/home/`. The worker overrides `HOME` and sets `EVE_JOB_USER_HOME` in the harness environment. Pre-created directories:\n\n- `.config/eve/` — Eve CLI credentials\n- `.config/gh/` — GitHub CLI auth\n- `.claude/` — Claude config\n- `.eve/harnesses/` — Harness config\n\nAll directories are mode 0700. Cleaned up after the attempt completes. This prevents agents from reading credentials written for other jobs or the host system.\n\n## Not Yet Implemented\n\n- Workspace reuse (`workspace.mode=job|session|isolated`). Today every attempt gets a fresh workspace.\n- Disk LRU/TTL cleanup policies.\n- Review semantics that compute diffs for branch-based jobs.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":33608,"content_sha256":"b8d35007059ef5ca0e4df8386ee2bfed904cdb70aab0ba30ce9edd6b012b19a6"},{"filename":"references/llm-wiki.md","content":"# LLM Wiki Pattern\n\n## Use When\n- You need to build or maintain a persistent, structured knowledge base using agents.\n- You are implementing the Company as Intelligence world model or OrgPack coordination wiki.\n- You want to understand how agents read, write, search, and maintain wiki pages on Eve.\n\n## Load Next\n- `references/cli-org-project.md` for full docs CLI syntax (patch, diff, tree, search, sync, watch).\n- `references/object-store-filesystem.md` for org filesystem mount and auto-indexing pipeline.\n- `references/overview.md` for platform architecture and identifiers.\n\n## Ask If Missing\n- Confirm the org ID and whether the wiki prefix is established (e.g. `/wiki/`, `/world-model/`).\n- Confirm whether the agent writes via file tools (org-fs mount) or CLI (`eve docs write`).\n- Confirm whether near-instant indexing is required (LISTEN/NOTIFY is active by default).\n\n## What Is an LLM Wiki\n\nAn LLM Wiki is a persistent, structured knowledge base built and maintained by agents — not by humans. Instead of retrieving raw documents at query time (RAG), the agent **incrementally compiles knowledge into interlinked wiki pages**. When new information arrives, the agent reads it, extracts key claims, and integrates them into the existing wiki — updating entity pages, revising summaries, flagging contradictions, strengthening the evolving synthesis.\n\nThe key difference from RAG: **the wiki is a compounding artifact.** Cross-references are already there. Contradictions are already flagged. The synthesis already reflects everything ingested. The wiki gets richer with every source added and every question asked.\n\nThe pattern was described by Andrej Karpathy and applies broadly: research wikis, internal company knowledge bases, competitive analysis, personal learning, and more. Eve Horizon provides first-class infrastructure for this pattern.\n\n## The Wiki Substrate on Eve\n\nEve's wiki substrate is a two-layer system:\n\n```\nAgent writes/reads files at /org/wiki/... \u003c- normal file tools (Read, Write, Edit)\n | auto-index (near-instant via LISTEN/NOTIFY)\nOrg docs at /wiki/... \u003c- versioned, searchable, queryable\n |\nOther agents and humans search/query via CLI \u003c- eve docs search, eve docs query\n```\n\n**Write path**: Agents use normal file tools on the org-fs mount. Zero friction.\n**Read path**: Agents use normal file tools on the org-fs mount. Zero friction.\n**Search path**: Agents use `eve docs search` with `--path` prefix and `--context` lines.\n**Patch path**: Agents use `eve docs patch` for surgical edits without full rewrites.\n**Navigation**: Agents use `eve docs list --tree` to see wiki structure.\n\nThe org filesystem is the working copy. Org docs is the indexed, versioned layer. Changes flow automatically from org-fs to org docs via the async indexing pipeline (sub-second latency with LISTEN/NOTIFY).\n\n## Three Layers\n\n| Layer | What | Who owns it | Eve primitive |\n|-------|------|-------------|---------------|\n| Raw sources | Curated collection of source documents | Human or ingest pipeline | Org filesystem, attachments |\n| The wiki | Structured, interlinked markdown pages | Agent (exclusively) | Org docs (versioned, searchable) |\n| The schema | Conventions, structure, workflows | Human + agent (co-evolved) | CLAUDE.md, agent config |\n\n## Agent Workflow\n\n```bash\n# Read a wiki page (normal file tool)\nRead /org/world-model/state.yaml\n\n# Edit a wiki page (normal file tool)\nEdit /org/world-model/state.yaml\n old: \"health: green\"\n new: \"health: amber\"\n\n# Search the wiki\neve docs search --org $ORG_ID --query \"deploy failure\" --context 3 --path /world-model\n\n# Navigate wiki structure\neve docs list --org $ORG_ID --path /operating-model --tree\n\n# Surgical patch without fetching full doc\neve docs patch --org $ORG_ID --path /world-model/state.yaml \\\n --replace \"health: green\" \"health: amber\"\n\n# Check what changed\neve docs diff --org $ORG_ID --path /world-model/state.yaml\n\n# Watch for downstream updates\neve docs watch --org $ORG_ID --path /world-model --since now\n```\n\n**Key principle**: normal file tools for read/write. CLI for search, navigation, diff, watch, and surgical operations.\n\n## Operations\n\n**Ingest**: Drop a source into the raw collection. The agent reads it, extracts key information, writes summary pages, updates the index, updates entity/concept pages across the wiki. A single source might touch 10-15 wiki pages.\n\n**Query**: Ask questions against the wiki. The agent searches with `eve docs search --context N`, reads relevant pages, synthesizes an answer. Good answers can be filed back into the wiki as new pages — explorations compound.\n\n**Lint**: Periodically health-check the wiki. Look for contradictions, stale claims, orphan pages, missing cross-references. The agent is good at suggesting new questions and sources.\n\n**Bulk operations**: Use `eve docs write-dir` for initial wiki population. Use `eve docs sync --dry-run --delete` for safe directory-to-wiki synchronization.\n\n## Near-Instant Indexing\n\nThe org-fs to org-docs indexing pipeline uses PostgreSQL LISTEN/NOTIFY for sub-second wake on file changes. The processor:\n\n1. Receives NOTIFY from `org_fs_events` trigger on `file.created`/`file.updated`\n2. Wakes immediately and drains the index queue\n3. Falls back to 2-second polling if the LISTEN connection drops\n\nTarget: p95 under 500ms for single-write indexing on local k3d.\n\n## Relationship to Company as Intelligence\n\nThe LLM Wiki pattern is the foundation for OrgPack coordination:\n\n| OrgPack Agent | Wiki Usage |\n|---|---|\n| World model agent | Maintains `/world-model/` wiki pages via file read/write + bulk sync |\n| Signal watcher | Writes signal pages, triggers near-instant indexing for downstream agents |\n| Policy engine | Searches wiki with context, navigates via tree view, watches for changes |\n| Operating review | Searches across wiki, diffs versions to track evolution |\n| All agents | Uses `eve docs patch` for cross-reference updates |\n\n## Recent enhancements (shipped 2026-04-05)\n\nAll seven LLM Wiki platform enhancements are live and verified on local k3d. They are the prerequisite surface for OrgPack / Company as Intelligence and round out fluent agent use of the wiki substrate from normal file tools plus the Eve CLI.\n\n### 1. Near-instant indexing (org-fs to org docs)\n\nThe org-fs index processor now LISTENs on the existing `org_fs_events` Postgres NOTIFY channel and wakes immediately on `file.created`/`file.updated`. The 2-second poller remains as fallback if the LISTEN connection drops. The queue stays the source of truth — NOTIFY is only a wake signal. Target latency: p95 under 500ms on local k3d for single writes. No new flag; agents just see writes appear in `eve docs search` sub-second instead of after up to 2s.\n\n### 2. `eve docs patch` — surgical edits\n\nCLI parity for the existing `PATCH /docs/by-path` API. Three operations, usable as flags or as a JSON batch:\n\n```bash\neve docs patch --org $ORG_ID --path /wiki/page --replace \"old\" \"new\"\neve docs patch --org $ORG_ID --path /wiki/page --append \"## New Section\\n...\"\neve docs patch --org $ORG_ID --path /wiki/page --insert-after \"## Header\" \"para\"\neve docs patch --org $ORG_ID --path /wiki/page \\\n --operations '[{\"op\":\"replace\",\"search\":\"a\",\"replace\":\"b\"},{\"op\":\"append\",\"content\":\"x\"}]'\n```\n\nReturns the updated doc with `current_version` and `content_hash`. Missing anchor / search text exits non-zero — agents can detect failed patches without re-reading.\n\n### 3. `eve docs list --tree` — wiki navigation\n\nThe flat list output now has a hierarchical mode for navigation:\n\n```bash\neve docs list --org $ORG_ID --path /operating-model --tree # human-readable\neve docs list --org $ORG_ID --path /operating-model --tree --json # nested nodes for agents\n```\n\nDefault flat list is unchanged for back-compat.\n\n### 4. `eve docs search --path` and `--context`\n\nTwo additions to full-text search:\n\n- `--path \u003cprefix>` constrains search to a wiki subtree, reusing the existing `searchWithFilters()` path-prefix support via a new `path_prefix` query param on `GET /orgs/{org_id}/docs/search`.\n- `--context N` fetches each match and returns N lines of surrounding context grep-style, so the agent can judge relevance before fetching the full doc.\n\n```bash\neve docs search --org $ORG_ID --query \"error rate\" --path /world-model --context 3\n```\n\nHeadline width was widened and `ts_headline()` now allows multiple useful fragments. `--mode hybrid|semantic` still currently degrades to text — the API shape is stable.\n\n### 5. `eve docs diff` — version diff\n\nPure-CLI diff between two versions of a doc, defaulting to latest vs latest-1:\n\n```bash\neve docs diff --org $ORG_ID --path /world-model/state.yaml\neve docs diff --org $ORG_ID --path /world-model/state.yaml --from 3 --to 5\neve docs diff --org $ORG_ID --path /world-model/state.yaml --unified\n```\n\nBuilt on the existing version endpoints; diff is computed client-side.\n\n### 6. Bulk write and safe sync\n\nThree commands for multi-doc ingest, with safety rules around deletion:\n\n```bash\n# Map a local directory to doc paths (create/update only)\neve docs write-dir --org $ORG_ID --source ./wiki-pages --path-prefix /operating-model\n\n# NDJSON bulk write\nprintf '%s\\n' '{\"path\":\"/wiki/a\",\"content\":\"...\"}' '{\"path\":\"/wiki/b\",\"content\":\"...\"}' \\\n | eve docs bulk-write --org $ORG_ID\n\n# Directory \u003c-> wiki sync; --delete is required and never default\neve docs sync --org $ORG_ID --source ./.eve-org --path-prefix /operating-model --dry-run\neve docs sync --org $ORG_ID --source ./.eve-org --path-prefix /operating-model --delete\n```\n\n`--dry-run` prints create/update/delete counts before mutating. Hidden/junk files are skipped unless explicitly included.\n\n### 7. `eve docs watch` — change stream\n\nReuses existing `system.doc.created|updated|deleted` events instead of inventing a docs-specific channel. The CLI polls org events, filters by path prefix, and emits NDJSON:\n\n```bash\neve docs watch --org $ORG_ID --path /world-model --since now\neve docs watch --org $ORG_ID --path /world-model --since 5m\n# {\"type\":\"system.doc.updated\",\"path\":\"/world-model/state\",\"version\":42,\"updated_at\":\"...\",\"content_hash\":\"...\"}\n```\n\nAgents block on the stream instead of polling. Generic org-events SSE remains a future option; until then, the polling watch is the supported path.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":10362,"content_sha256":"2bf39d13257e5e2deb96b373d621e3b8772760086e8e31e423388150240b80be"},{"filename":"references/manifest.md","content":"# Manifest (Current)\n\n## Use When\n- You need to author, validate, or review `.eve/manifest.yaml`.\n- You need to configure services, environments, pipelines, or harness defaults.\n- You need to prepare manifest changes for deployable, reproducible builds.\n\n## Load Next\n- `references/pipelines-workflows.md` for pipeline/job wiring in manifests.\n- `references/secrets-auth.md` for secret declaration and resolution order.\n- `references/overview.md` for core platform concepts before editing complex files.\n\n## Ask If Missing\n- Confirm target manifest path and environment names.\n- Confirm whether managed DBs, external services, or custom ingress are required.\n- Confirm any required repository path, branch, or org/project identifiers.\n\nThe manifest (`.eve/manifest.yaml`) is the single source of truth for builds, deploys, pipelines, and workflows.\nSchema is Compose-like with Eve extensions under `x-eve`.\n\n## Minimal Example\n\nThe simplest possible deployable project. Uses the Eve-native registry so `image` fields are auto-derived from service keys:\n\n```yaml\nschema: eve/compose/v2\nproject: my-app\n\nregistry: \"eve\"\n\nservices:\n app:\n build:\n context: .\n ports: [\"3000\"]\n x-eve:\n ingress:\n public: true\n\nenvironments:\n sandbox:\n pipeline: deploy\n\npipelines:\n deploy:\n steps:\n - name: build\n action: { type: build }\n - name: release\n depends_on: [build]\n action: { type: release }\n - name: deploy\n depends_on: [release]\n action: { type: deploy, env_name: sandbox }\n```\n\nDeploy with two commands:\n\n```bash\neve project sync --dir .\neve env deploy sandbox --ref main\n```\n\n## Full Example\n\nA complete manifest showing the standard SPA + API + managed DB pattern with eve-migrate, nginx reverse proxy, and the canonical pipeline:\n\n```yaml\nschema: eve/compose/v2\nproject: my-project\n\nregistry: \"eve\"\n\nservices:\n api:\n build:\n context: ./apps/api\n dockerfile: ./apps/api/Dockerfile\n ports: [3000]\n environment:\n NODE_ENV: production\n DATABASE_URL: ${managed.db.url}\n CORS_ORIGIN: \"https://my-project.eh1.incept5.dev\"\n # No x-eve.ingress — API is internal, reached via web's /api/ proxy\n\n web:\n build:\n context: ./apps/web\n dockerfile: ./apps/web/Dockerfile\n ports: [80]\n environment:\n API_SERVICE_HOST: ${ENV_NAME}-api\n depends_on:\n api:\n condition: service_healthy\n x-eve:\n ingress:\n public: true\n port: 80\n alias: my-project\n\n migrate:\n image: public.ecr.aws/w7c4v0w3/eve-horizon/migrate:latest\n environment:\n DATABASE_URL: ${managed.db.url}\n MIGRATIONS_DIR: /migrations\n x-eve:\n role: job\n files:\n - source: db/migrations\n target: /migrations\n\n db:\n x-eve:\n role: managed_db\n managed:\n class: db.p1\n engine: postgres\n engine_version: \"16\"\n extensions: [postgis, pgvector, pg_trgm]\n\nenvironments:\n sandbox:\n pipeline: deploy\n\npipelines:\n deploy:\n steps:\n - name: build\n action: { type: build }\n - name: release\n depends_on: [build]\n action: { type: release }\n - name: deploy\n depends_on: [release]\n action: { type: deploy }\n - name: migrate\n depends_on: [deploy]\n action:\n type: job\n service: migrate\n - name: smoke-test\n depends_on: [migrate]\n script:\n run: ./scripts/smoke-test.sh\n timeout: 300\n```\n\nKey patterns:\n- **`eve-migrate`** for database migrations — plain SQL files mounted via `x-eve.files`. Runs after deploy because the managed DB must be provisioned first.\n- **nginx reverse proxy** on the web service proxies `/api/` to the internal API via `API_SERVICE_HOST: ${ENV_NAME}-api` (k8s service DNS). No CORS, no hard-coded hostnames.\n- **`${managed.db.url}`** — connection string injected by Eve for managed databases.\n- **`managed.extensions`** — optional managed Postgres extensions. Plain extensions are `postgis`, `pgvector`, `pg_trgm`, `btree_gist`, `hstore`, and `citext`. `pgvector` installs as PostgreSQL extension `vector`; extension removal is sticky and does not run `DROP EXTENSION`. `pg_cron` is provider-gated and only validates when the platform enables `EVE_MANAGED_DB_ENABLED_PRELOAD_EXTENSIONS=pg_cron` and preloads it on the backing Postgres; Eve installs it in the instance admin database (`postgres`) per the AWS RDS model. `timescaledb` is not declarable on AWS RDS.\n- **Smoke test** validates the deployed services end-to-end before pipeline success.\n\n## Top-Level Fields\n\n```yaml\nschema: eve/compose/v2 # optional schema identifier\nname: my-project # project name (preferred)\ndescription: \"Short description\" # optional project description\nproject: my-project # legacy alias for name (either works)\nregistry: # optional container registry\nservices: # required\nenvironments: # optional\npipelines: # optional\nworkflows: # optional\nversioning: # optional\nx-eve: # optional Eve extensions\n```\n\nUse `name` as the top-level project identifier. `project` is accepted as a legacy alias but `name` is preferred for new manifests.\n\nUnknown fields are allowed for forward compatibility.\n\n### Project Branding And Auth (`x-eve.branding`, `x-eve.auth`)\n\nProject-level branding is used by app invite emails, app-scoped magic-link emails, and branded SSO login pages. It is synced onto the project record by `eve project sync`:\n\n```yaml\nx-eve:\n branding:\n app_name: \"ALL-TRACK\"\n app_logo_url: \"https://sandbox.all-track.co.uk/assets/logo.svg\"\n primary_color: \"#1f6feb\"\n email_from_name: \"ALL-TRACK\"\n reply_to_email: \"[email protected]\"\n support_email: \"[email protected]\"\n support_url: \"https://all-track.co.uk/help\"\n```\n\n`app_name` is required when `branding` is present (≤60 chars, no newlines). Logo/support URLs must be valid URLs; only HTTPS logo URLs are emitted into Phase 1 emails. `primary_color` must be a six-digit hex like `#1f6feb`. `email_from_name`, `reply_to_email`, and `support_email` set the `From:`/`Reply-To:` headers on app-scoped mail while keeping the platform sender address.\n\nApps can opt into passwordless login with:\n\n```yaml\nx-eve:\n auth:\n login_method: magic_link # password_or_magic_link | password | magic_link\n self_signup: false\n invite_requires_password: false\n```\n\nWhen `login_method: magic_link`, app SSO hides password login and signup and sends branded magic-link email through Eve API. When `login_method: password_or_magic_link`, password login remains visible and the secondary magic-link request still goes through Eve API for branding and app self-signup enforcement. With `self_signup: false`, unknown emails get generic success but no GoTrue call/email. With `invite_requires_password: false`, invite acceptance establishes the SSO session and redirects to the app without `/set-password`.\n\nApps can also opt into app-scoped org access and in-app admin invites:\n\n```yaml\nx-eve:\n auth:\n login_method: magic_link\n self_signup: false\n invite_requires_password: false\n org_access:\n mode: allowlist # project_org | allowlist\n allowed_orgs:\n - org_customer123 # org IDs or slugs; sync stores canonical IDs\n - customer-slug\n invite:\n enabled: true\n admin_roles: [admin, owner]\n invited_role: member # fixed to member\n```\n\nDefault `org_access.mode` is `project_org`, which limits SSO/app SDK access to the project owner org. `allowlist` lets a project-owned app serve specific customer orgs. `invite.enabled` allows org admins/owners in those app-allowed orgs to call `POST /auth/app-invites`; app-facing invites always create regular members and use the same project branding as invite/magic-link emails.\n\n#### `x-eve.auth.org_access.domain_signup`\n\nPre-approve email domains so anyone with a matching address can sign in via magic link without a per-user invite. Each rule maps one domain pattern to one target org, so a single project can serve multiple customer orgs from one manifest.\n\n```yaml\nx-eve:\n auth:\n login_method: magic_link\n invite_requires_password: false\n org_access:\n mode: allowlist\n allowed_orgs: [org_Alltrack, org_Tesco, org_Morrisons, org_ALLTAG]\n domain_signup:\n enabled: true\n domains:\n - domain: incept5.com\n target_org: org_Alltrack\n role: member # optional; defaults to 'member'\n - domain: tesco.com\n target_org: org_Tesco\n - domain: morrisons.com\n target_org: org_Morrisons\n - domain: all-tag.com\n target_org: org_ALLTAG\n - domain: \"*.all-tag.com\" # wildcard matches subdomains, not the apex\n target_org: org_ALLTAG\n```\n\n**v2 schema (2026-05-12, breaking change).** `domains` is now a list of rule objects. The v1 list-of-strings shape and the block-level `target_org`/`role` fields are gone; manifest sync rejects them.\n\nRules:\n\n- `enabled: true` requires at least one rule in `domains`.\n- Each rule must declare both `domain` and `target_org`. `role` is optional and defaults to `member` (the only allowed value in v2; reserved for future expansion).\n- `target_org` must be in the project's effective `allowed_orgs`. Manifest sync resolves slugs to canonical org IDs and rejects rules whose target isn't allowed.\n- Duplicate `domain:` keys within one block are rejected.\n- Domain patterns are lowercased and IDN-normalized (punycode) at parse time.\n- `*.acme.com` matches `eu.acme.com` and `sub.eu.acme.com` but **not** bare `acme.com`. Declare both as separate rules if both should match.\n- Invalid with `login_method: password`. `magic_link` and `password_or_magic_link` are both valid.\n- Declaring `gmail.com` (or any free-email provider) emits a manifest coherence warning per rule — the effect is unbounded for that target org.\n- No DNS proof. The operator declares; the platform trusts.\n\nEligibility precedence on `POST /auth/magic-link`:\n\n1. Known user with allowed-org or project membership → branded send.\n2. Pending *explicit* (admin-issued) invite → generic success, no email; the invite remains the entry point.\n3. Email's domain matches one rule (first-match in declaration order, no implicit longest-match) → write a one-shot `org_invites` row tagged `app_context.source: domain_signup`, `app_context.org_id: \u003cmatched rule.target_org>`, `app_context.matched_rule: \u003cdomain pattern>` and send branded magic link. The SSO callback consumes the invite and upserts membership into that org.\n4. Otherwise, fall through to legacy `self_signup`, then to generic success.\n\nTwo audit events flow through the event spine: `auth.domain_signup.invite_created` (creation) and `auth.domain_signup.member_attached` (callback consumption). The creation event payload includes `org_id`, `email_domain`, `matched_rule` (the rule pattern that fired), and a truncated `email_hash`.\n\nPublic `/auth/app-context` exposes only `auth.org_access.domain_signup_enabled` (bool). Use `GET /auth/app-context/admin?project_id=...` with a project-admin token to see each rule (`domain`, `target_org`, `role`), or run `eve project auth-context \u003cproject_id>` (the CLI tries the admin endpoint first and renders one line per rule as `\u003cdomain> -> \u003ctarget_org> (\u003crole>)`).\n\n#### `x-eve.auth.allowed_redirect_origins`\n\nApps deployed on custom domains (off-cluster) must declare which origins SSO is\nallowed to redirect back to after invite redemption or magic-link callback.\nWithout this, the SSO broker drops the redirect target and lands the user on\nthe SSO root.\n\n```yaml\nx-eve:\n auth:\n login_method: magic_link\n allowed_redirect_origins:\n - https://sandbox.all-track.co.uk\n - https://app.example.com\n```\n\nRules:\n\n- Entries are **origins only**: `scheme://host[:port]`. Paths, query strings,\n fragments, and userinfo are rejected at manifest-validate time.\n- `https://` is required, except for local development (`localhost`, loopback\n IPs, `*.lvh.me`) which may use `http://`.\n- The SSO broker accepts `redirect_to` and CORS origins matching any entry by\n exact origin (scheme + host + port).\n\nYou usually do not need to declare this for apps whose own custom domains are\nalready registered through `services.\u003cname>.x-eve.ingress.domains`. Eligible\ncustom domains owned by the project are auto-included in the resolved\nallowlist. If your manifest is in a branding-only project that redirects into a\nsibling org's deployed app, set `org_access.mode: allowlist` and list the\nsibling org under `allowed_orgs` — the sibling's eligible custom domains are\nauto-included too.\n\nInspect the resolved list (manifest ∪ own custom domains ∪ cross-org domains)\nwith `eve project auth-context \u003cproject_id>`.\n\nSee `references/secrets-auth.md` for SSO endpoints, app context resolution,\nand the `eve org invite` / `POST /auth/app-invites` flows that consume these\nfields. `references/integrations.md` covers the per-org OAuth configs that\nback app-branded mail delivery, and `references/deploy-debug.md` covers the\ncustom-domain lifecycle (`pending_dns` → `active`) that feeds the\nauto-included redirect origins.\n\n## Workflow References\n\nFor large workflows, keep `.eve/manifest.yaml` small and reference repo-local\nworkflow files:\n\n```yaml\nworkflows:\n alltrack-make-plan:\n $ref: .eve/workflows/alltrack-make-plan\n```\n\nRecommended layout:\n\n```text\n.eve/workflows/alltrack-make-plan/\n workflow.yaml\n prompts/\n plan.md\n review.md\n```\n\nWhen `$ref` points to a directory, `eve project sync` and\n`eve manifest validate` load `workflow.yaml` or `workflow.yml` from that\ndirectory. `$ref` can also point directly to a `.yaml` or `.yml` workflow\nfile. References are resolved locally by the CLI before sync; direct API sync\nrejects unresolved `$ref` values.\n\nWorkflow files can keep long prompts in Markdown files:\n\n```yaml\nsteps:\n - name: plan\n agent:\n name: alltrack-planner\n prompt_file: prompts/plan.md\n```\n\n`prompt_file` paths are resolved relative to the workflow file directory, read\nverbatim, and expanded into `agent.prompt` in the stored manifest.\n\n## Registry\n\n```yaml\nregistry: \"eve\" # Default Eve-managed registry\n```\n\nUse `registry: \"eve\"` unless your app must publish to a BYO registry.\n\nFor a private custom registry, switch to full object form:\n\n```yaml\nregistry:\n host: public.ecr.aws/w7c4v0w3\n namespace: myorg\n auth:\n username_secret: REGISTRY_USERNAME\n token_secret: REGISTRY_PASSWORD\n```\n\nThe deployer uses these secrets to create Kubernetes `imagePullSecrets` for private BYO registries. See container registry reference for setup details.\n\nString modes:\n```yaml\nregistry: \"eve\" # Use Eve-managed registry (default)\nregistry: \"none\" # Disable registry handling\nregistry: # BYO registry (full object; see above)\n```\n\n## Services (Compose-Style)\n\n```yaml\nservices:\n api:\n build:\n context: ./apps/api\n # image omitted (auto-derived as \"api\" when build is present)\n ports: [3000]\n environment:\n NODE_ENV: production\n depends_on:\n db:\n condition: service_healthy\n x-eve:\n ingress:\n public: true\n port: 3000\n timeout: 300s\n max_body_size: 10m\n api_spec:\n type: openapi\n spec_url: /openapi.json\n```\n\nSupported Compose fields: `image`, `build`, `environment`, `ports`, `depends_on`, `healthcheck`, `volumes`.\n\n**Image auto-derivation**: When a service has `build` config and a `registry` is configured, the `image` field is optional. With Eve-managed default (`registry: \"eve\"`), platform derives the image name from the service key (for example, service `app` becomes `image: app`) and prefixes it at build time with the managed registry host.\n\n### Eve Service Extensions (`x-eve`)\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `role` | string | `component` (default), `worker`, `job`, or `managed_db` |\n| `ingress` | object | `{ public: true\\|false, port: number, alias?: string, domains?: string[], timeout?: string, max_body_size?: string }` |\n| `api_spec` | object | Single API spec registration |\n| `api_specs` | array | Multiple API spec registrations |\n| `cli` | object | App CLI declaration (see CLI Declaration below) |\n| `external` | boolean | External dependency (not deployed) |\n| `connection_url` | string | Connection string for external services |\n| `worker_type` | string | Worker pool type for this service |\n| `files` | array | Mount source files into container |\n| `storage` | object | Persistent volume configuration |\n| `managed` | object | Managed DB config (requires `role: managed_db`) |\n| `object_store` | object | App object store bucket declarations |\n| `networking` | object | `{ egress: 'nat' \\| 'stable' }` (default `nat`). See Stable Egress below. |\n| `permissions` | array | Additional permissions for the service's `EVE_SERVICE_TOKEN` |\n| `audit_log_table` | string | Optional table queried by `eve env diagnose --request` |\n| `request_id_column` | string | Optional request ID column for audit logs (default `request_id`) |\n\nNotes:\n- `x-eve.role: job` makes a service runnable as a one-off job (migrations, seeds).\n- `x-eve.role: managed_db` marks a service as a platform-provisioned database.\n- `spec_url` can be relative (resolved against service URL) or absolute.\n- `spec_path` is supported only for local `file://` repos.\n- If a service exposes ports and the cluster domain is configured, Eve creates ingress by default. Set `x-eve.ingress.public: false` to disable.\n- `ingress.alias` creates a vanity hostname: `{alias}.{domain}` instead of the default `{service}.{orgSlug}-{projectSlug}-{env}.{domain}`. Useful for user-facing apps that need a clean URL.\n- `ingress.domains` brings your own domain names (e.g., `[\"limelee.com\", \"www.limelee.com\"]`). Declare it either on the base service or in `environments.\u003cenv>.overrides.services.\u003csvc>.x-eve.ingress.domains`. `eve project sync` registers both forms; a hostname declared in exactly one env override is bound to that env during sync. Each domain gets a separate K8s Ingress with per-domain TLS via cert-manager HTTP-01. Max 10 per service. Domains under the platform domain are rejected — use `alias` instead. All three ingress types (primary, alias, custom domain) coexist and route to the same backend.\n- `ingress.timeout` sets nginx-ingress request/response timeout for the service's primary, alias, and custom-domain ingresses. Use lowercase durations such as `30s`, `5m`, or `30m`. Default: `EVE_DEFAULT_INGRESS_TIMEOUT=300s`; range: `1s`-`30m`. Use Eve jobs for longer batch work.\n- `ingress.max_body_size` sets nginx-ingress request body size for the same ingress set. Use lowercase sizes such as `512k`, `10m`, or `1g`. Default: `EVE_DEFAULT_INGRESS_MAX_BODY_SIZE=10m`; range: `1k`-`1g`. Use signed uploads/object storage for larger payloads.\n- Timeout/body-size annotations are emitted only for `EVE_DEFAULT_INGRESS_CLASS=nginx` or `nginx-ingress`. Traefik/unknown classes keep routing behavior and skip L7 tuning; explicit tuning logs a deploy warning. Confirm live values with `eve env diagnose \u003cproject> \u003cenv>` or `.http_ingress[]`.\n- **Domain ownership is env-scoped with first-bind-wins**: a hostname declared in the base manifest is claimed by the **first environment to deploy with it**. A hostname declared in exactly one env override is sync-bound to that env. Hostnames declared in multiple env overrides keep first-bind-wins and sync warns instead of guessing. Use `eve domain transfer \u003chost> --to \u003cenv>` + redeploys to move ownership, or `eve domain register \u003chost> --project \u003cid> --service \u003csvc> --env \u003cenv>` for imperative reservations.\n- `audit_log_table` is optional. When set, `eve env diagnose --request \u003cid>` runs a read-only query against that table using `request_id_column` and returns matching rows verbatim. Query failures become warnings in the diagnose response.\n\n### Managed DB Services\n\n```yaml\nservices:\n db:\n x-eve:\n role: managed_db\n managed:\n class: db.p1\n engine: postgres\n engine_version: \"16\"\n extensions: [postgis, pgvector]\n```\n\nSupported plain extensions are `postgis`, `pgvector`, `pg_trgm`, `btree_gist`, `hstore`, and `citext`. `pg_cron` is provider-gated; `timescaledb` is still not declarable on AWS RDS.\nPreload-required candidates such as `pg_cron` and `timescaledb` are not declarable yet.\n\n### App Object Store Buckets (`x-eve.object_store`)\n\nDeclare S3-compatible buckets for a service. Eve provisions each bucket at\ndeploy time and injects env vars for the resolved env-wide credential binding.\n\n```yaml\nservices:\n api:\n x-eve:\n object_store:\n isolation: auto # auto (default) | irsa | shared\n buckets:\n - name: uploads # logical name → env var suffix\n visibility: private # private (default) | public\n cors:\n origins: [\"https://app.example.com\"]\n methods: [GET, PUT, HEAD, DELETE]\n max_age_seconds: 3600\n lifecycle:\n abort_incomplete_uploads_days: 7\n - name: assets\n visibility: public\n```\n\nBuckets are provisioned during deploy, tracked in environment diagnostics with\n`isolation_mode`, and fail deploy if the platform cannot create the bucket,\napply public-read policy, or resolve the requested credential binding. Eve\nreconciles one binding per env across app services and job services. Local k3d\nMinIO uses server-wide CORS (`MINIO_API_CORS_ALLOW_ORIGIN=*`) instead of the S3\nper-bucket CORS API. Wildcard CORS works for browser presigned URL flows;\nrestrictive origins are recorded in diagnostics but are not enforced per bucket\nby local MinIO.\n\nStatic-key env vars for local MinIO or explicit `shared` mode:\n- `STORAGE_ENDPOINT` — MinIO/S3 endpoint\n- `STORAGE_REGION`\n- `STORAGE_ACCESS_KEY_ID` / `STORAGE_SECRET_ACCESS_KEY` — storage credentials injected for the app\n- `STORAGE_BUCKET_\u003cNAME>` — physical bucket name (e.g. `eve-org-myorg-myapp-test-uploads` locally or `eh1-eve-app-myorg-myapp-test-uploads` on staging)\n- `STORAGE_FORCE_PATH_STYLE` — `true` for MinIO, omitted for AWS S3\n\nIRSA env vars for AWS:\n- `STORAGE_ENDPOINT`\n- `STORAGE_REGION`\n- `STORAGE_AUTH_MODE=irsa`\n- `AWS_REGION`\n- `STORAGE_BUCKET_\u003cNAME>`\n\nIRSA pods do not receive `STORAGE_ACCESS_KEY_ID` or\n`STORAGE_SECRET_ACCESS_KEY`; AWS SDKs use the annotated Kubernetes service\naccount.\n\nVisibility `public` sets the bucket ACL for anonymous GET access (suitable for static assets).\n\nIsolation resolution:\n- `auto` resolves to IRSA when the worker has AWS OIDC/IAM configuration,\n otherwise to `minio-static-key` on local MinIO or `shared` when only static\n app keys are available.\n- `irsa` fails fast on non-IRSA clusters.\n- `shared` uses static app credentials, resolving to `minio-static-key` on\n local MinIO.\n\nAWS IRSA creates one IAM role per org/project/env and fully replaces its\n`app-bucket-access` inline policy with the declared physical bucket names.\n\n### Stable Egress (`x-eve.networking.egress`)\n\nOpt a service into platform-managed stable egress. Use when an app needs\n**endpoint-independent UDP source-port mappings** — typically camera relays,\npeer-to-peer / hole-punched UDP protocols, STUN-discovered NAT traversal, or\nvendors that won't tolerate the cluster NAT Gateway's symmetric port\nrewriting.\n\n```yaml\nservices:\n api:\n x-eve:\n networking:\n egress: stable # 'nat' (default) | 'stable'\n```\n\n| Value | Behavior |\n|-------|----------|\n| `nat` (default) | Existing path: AWS NAT Gateway on EKS, normal local networking on k3d. |\n| `stable` | EKS only: the deployer schedules the pod onto the dedicated `eve.io/egress-pool=stable` node group with `hostNetwork: true` and `dnsPolicy: ClusterFirstWithHostNet`. Traffic exits via the node's own public IPv4 / Internet Gateway, with 1:1 NAT that preserves the kernel-chosen source port across destinations. On k3d, accepted and logged as a no-op. |\n\n**Pod shape on EKS when opted in (no sidecar, no Secret):**\n- `hostNetwork: true`, `dnsPolicy: ClusterFirstWithHostNet`.\n- `nodeSelector: { eve.io/egress-pool: stable }` and a matching toleration.\n- App container gets `EVE_NETWORK_EGRESS=stable`.\n- Single container, no extra volumes, no privileged caps.\n\n**Phase 1 limits (deployer fail-fasts at render time):**\n- `replicas` must be `1`. Multi-replica hostNetwork services need pod\n anti-affinity + cluster-wide port-collision validation, which is Phase 2.\n- Service ports must be **outside** the Kubernetes NodePort range\n (30000–32767). The EKS node SG opens that range from `0.0.0.0/0` for NLB\n traffic, so a hostNetwork pod listening there would be unintentionally\n public.\n\n**Operational notes:**\n- Each opt-in pod inherits the IP of the egress node it's scheduled on.\n Phase 1 ships a single-AZ, single-instance pool — node replacement /\n upgrade gives a new public IP. Apps that rely on STUN auto-reconnect\n (pvscam-class) tolerate this; vendors that allow-list source IPs do not.\n Phase 2 introduces a pre-allocated EIP pool.\n- `NetworkPolicy` does not apply to hostNetwork pods — closed-egress\n policies on the namespace are silently bypassed.\n- Verify with `eve env diagnose \u003cproject> \u003cenv>` (the rendered pod has\n `hostNetwork: true` + the `eve.io/egress-pool` selector) and the\n `udp-diag.py` helper in `incept5-eve-infra/scripts/stable-egress/`\n (run inside the pod; same-socket STUN should report\n `endpoint-independent (good)`).\n\n### Service Token Permissions\n\nEvery deployed service receives an auto-injected `EVE_SERVICE_TOKEN` with **read-only defaults**. Services that need write access must declare additional permissions:\n\n```yaml\nservices:\n api:\n x-eve:\n permissions: [jobs:write, events:write, threads:write]\n```\n\n**Default permissions** (always included, no declaration needed):\n`projects:read`, `jobs:read`, `threads:read`, `envs:read`, `secrets:read`, `builds:read`, `pipelines:read`, `agents:read`, `events:read`\n\n**Common write permissions to opt in to:**\n\n| Permission | Use Case |\n|-----------|----------|\n| `jobs:write` | Create or update jobs (e.g., trigger workflows) |\n| `events:write` | Emit app events (e.g., `question.answered`) |\n| `threads:write` | Create or reply to threads |\n| `envdb:write` | Write to managed databases via API |\n| `notifications:send` | Send Slack channel notifications via `eve notifications send` (see `references/integrations.md`) |\n\nDeclared permissions are **merged** with defaults — you only need to list the additional write scopes your service needs.\n\n### API Spec Schema\n\n```yaml\napi_spec:\n type: openapi # openapi | postgrest | graphql\n spec_url: /openapi.json # relative to service URL, or absolute\n spec_path: ./openapi.yaml # local file path (file:// repos only)\n name: my-api # optional display name\n auth: eve # eve (default) | none\n on_deploy: true # refresh on deploy (default: true)\n```\n\nMultiple specs:\n\n```yaml\napi_specs:\n - type: openapi\n spec_url: /openapi.json\n - type: graphql\n spec_url: /graphql\n```\n\n### CLI Declaration\n\nDeclare a domain-specific CLI that agents use instead of raw REST calls:\n\n```yaml\nx-eve:\n api_spec:\n type: openapi\n cli:\n name: eden # binary name on $PATH (lowercase alphanumeric + hyphens)\n bin: cli/bin/eden # path relative to repo root (repo-bundled mode)\n```\n\n| Field | Type | Required | Description |\n|-------|------|----------|-------------|\n| `name` | string | Yes | CLI binary name. Lowercase alphanumeric + hyphens (`^[a-z][a-z0-9-]* eve-read-eve-docs — Skillopedia ). Must be unique per project. |\n| `bin` | string | Yes (repo mode) | Path to executable, relative to repo root. |\n| `image` | string | Yes (image mode) | Docker image containing CLI binary. |\n| `description` | string | No | Brief description shown in agent instruction block. |\n\n**Distribution modes:**\n\n- **Repo-bundled** (primary): CLI is a pre-built single-file executable in the repo (e.g., esbuild bundle). Platform runs `chmod +x` and adds to PATH after clone. Zero additional latency.\n- **Image-based**: CLI is distributed via Docker init container (same pattern as toolchains). Platform pulls the image and copies the binary to a shared volume. Adds 2-5s startup latency.\n\n**Auto-discovery**: The platform automatically scans the manifest for services with `x-eve.cli` or `x-eve.api_spec` and injects them into every agent job. No explicit `with_apis` needed — just declare the CLI here and all agents get it on PATH.\n\nWhen app APIs are resolved (via auto-discovery or explicit `with_apis`), the agent receives:\n- The CLI on `$PATH` (ready to run)\n- `EVE_APP_API_URL_{SERVICE}` env var (for CLI internal use)\n- `EVE_JOB_TOKEN` for auth (CLI reads this automatically)\n- A CLI-first instruction block: \"Use `eden --help` to see all commands\"\n\nSee `references/app-cli.md` for the full implementation guide including bundling, env var contract, and testing patterns.\n\n### Cross-Project App Links (`x-eve.app_links`)\n\nApp links let one project consume another project's API, image-mode CLI, and\napplication events without shared long-lived secrets. Producers declare exports;\nconsumers declare subscriptions.\n\nProducer example:\n\n```yaml\nx-eve:\n app_links:\n exports:\n apis:\n observation:\n service: api\n cli: obs\n scopes: [observations:read, deployments:read]\n consumers:\n - project: consumer\n scopes: [observations:read]\n envs: [staging]\n events:\n observation-feed:\n types: [app.observation.created]\n consumers:\n - project: consumer\n types: [app.observation.created]\n```\n\nConsumer example:\n\n```yaml\nx-eve:\n app_links:\n consumes:\n observation:\n project: producer\n api: observation\n environment: same\n scopes: [observations:read]\n events:\n feed: observation-feed\n types: [app.observation.created]\n inject_into:\n services: [worker]\n jobs: true\n```\n\nRules:\n- Project refs can be project IDs (`proj_...`) or same-org project slugs.\n- Producer API exports must reference an existing service with `x-eve.api_spec` or `x-eve.api_specs`.\n- If an export names `cli`, that service's `x-eve.cli.name` must match and `x-eve.cli.image` is required. Cross-project CLIs use image mode because consumers do not have the producer repo checked out.\n- Consumer requested `scopes` and event `types` must be subsets of the active producer grant.\n- `environment: same` maps consumer env names to the same producer env names. A concrete value pins `producer_env_name`.\n- `inject_into.services` injects app-link env vars into deployed consumer services. `inject_into.jobs: true` injects them into agent jobs; `eve job create --with-links alias1,alias2` can request explicit links.\n- Local k3d meshes should use one shared env name across all projects, normally\n `environments.local`. `eve local mesh up` fails fast if a project in the\n workspace does not declare the workspace env.\n- For local mesh work, workspace project names are Eve project slugs and should\n match `project` refs in producer/consumer app-link blocks.\n\nInjected surfaces receive:\n- `EVE_APP_LINK_\u003cALIAS>_API_URL`\n- `EVE_APP_LINK_\u003cALIAS>_TOKEN`\n- `EVE_APP_LINK_\u003cALIAS>_SCOPES`\n- `EVE_APP_LINK_\u003cALIAS>_PROJECT`\n- `EVE_APP_LINK_\u003cALIAS>_ENV`\n- `EVE_APP_LINK_\u003cALIAS>_CLI` when an exported CLI image is mounted\n\nDiagnostics:\n\n```bash\neve app-links list --project \u003cproject>\neve app-links plan --project \u003cconsumer> --file .eve/manifest.yaml\neve app-links explain --consumer \u003cconsumer> --alias observation\n```\n\n### Toolchain Declarations\n\nAgents can declare toolchains they need. The platform injects them as init containers at pod creation time, keeping the base worker image small (~800MB) while making language runtimes available on demand.\n\n```yaml\n# In eve/agents.yaml\nversion: 1\nagents:\n data-analyst:\n name: Data Analyst\n skill: analyze-data\n harness_profile: claude-sonnet\n toolchains: [python] # python + uv available at runtime\n\n doc-processor:\n name: Document Processor\n skill: process-documents\n harness_profile: claude-sonnet\n toolchains: [media] # ffmpeg + whisper available at runtime\n\n full-stack:\n name: Full Stack Dev\n skill: full-stack-dev\n harness_profile: claude-opus\n toolchains: [python, rust, java] # multi-toolchain\n```\n\nAvailable toolchains: `python`, `media`, `rust`, `java`, `kotlin`.\n\nWorkflow steps can override agent toolchain defaults:\n\n```yaml\nworkflows:\n process-document:\n steps:\n - name: process\n agent: doc-processor\n toolchains: [media, python] # override agent default\n```\n\nToolchain precedence: workflow step `toolchains` > agent `toolchains` > none.\n\nEach toolchain is a small container image (~50-300MB) copied to `/opt/eve/toolchains/{name}/` via init containers. The entrypoint extends `PATH` from `EVE_TOOLCHAIN_PATHS`. Per-toolchain `env.sh` files set additional variables (e.g., `JAVA_HOME`, `RUSTUP_HOME`).\n\nAgents without `toolchains` run on the `base` image (Node.js + harnesses only). The `full` image (~2.6GB, all toolchains baked in) remains available via `EVE_WORKER_VARIANT=full`.\n\n### Cloud FS Mounts\n\nManifest can reference cloud filesystem mounts (Google Drive) connected at the org level. Cloud FS mounts are managed via the `eve cloud-fs` CLI, not declared in the manifest directly.\n\n```bash\n# Mount a Google Drive folder\neve cloud-fs mount \\\n --provider google-drive \\\n --folder-id \u003cdrive-folder-id> \\\n --mode read_write \\\n --label \"Engineering Shared Drive\"\n\n# List mounts\neve cloud-fs list\n\n# Browse files in a mount\neve cloud-fs ls / --mount \u003cmount-id>\neve cloud-fs ls /subfolder --mount \u003cmount-id> # alias: browse\n\n# Search across mounts\neve cloud-fs search \u003cquery> [--mount \u003cmount-id>]\n\n# Show mount details\neve cloud-fs show \u003cmount-id>\n\n# Update mount settings\neve cloud-fs update \u003cmount-id> --mode read_only\n\n# Remove a mount\neve cloud-fs unmount \u003cmount-id> # aliases: remove, delete\n```\n\nMounts are stored in the `cloud_fs_mounts` table, scoped to org (or optionally project). Each mount links an integration's OAuth credentials to a provider folder with configurable mode (`read_only`, `write_only`, `read_write`) and optional auto-indexing into org docs.\n\nRequires a Google Drive integration connection first (`eve integrations connect google-drive`). See Per-Org OAuth Configs below.\n\n### Per-Org OAuth Configs\n\nEach org brings its own OAuth app credentials for external providers (Google Drive, Slack). No cluster-level OAuth secrets required.\n\n```bash\n# View setup instructions (shows redirect URI to register)\neve integrations setup-info google-drive\neve integrations setup-info slack\n\n# Register OAuth app credentials for your org\neve integrations configure google-drive \\\n --client-id \"xxx.apps.googleusercontent.com\" \\\n --client-secret \"GOCSPX-xxx\"\n\neve integrations configure slack \\\n --client-id \"12345.67890\" \\\n --client-secret \"abc123\" \\\n --signing-secret \"def456\" \\\n --app-id \"A0123ABC\"\n\n# View current config (secrets redacted)\neve integrations config google-drive\n\n# Remove config\neve integrations unconfigure google-drive\n\n# Then connect as before (uses per-org credentials)\neve integrations connect google-drive\n```\n\nOne OAuth app config per provider per org. Multiple connections (integrations) share the same app config. The platform stores credentials in `oauth_app_configs` and uses them for all OAuth authorize, callback, and token refresh flows. Cluster-level `EVE_GOOGLE_CLIENT_*` / `EVE_SLACK_CLIENT_*` env vars are deprecated.\n\n### Files Mount\n\nMount source files from the repo into the container:\n\n```yaml\nx-eve:\n files:\n - source: ./config/app.conf # relative path in repo\n target: /etc/app/app.conf # absolute path in container\n```\n\n### Persistent Storage\n\n```yaml\nx-eve:\n storage:\n mount_path: /data\n size: 10Gi\n access_mode: ReadWriteOnce # ReadWriteOnce | ReadWriteMany | ReadOnlyMany\n storage_class: standard # optional\n name: my-data # optional PVC name\n```\n\n### Healthcheck\n\n```yaml\nhealthcheck:\n test: [\"CMD\", \"curl\", \"-f\", \"http://localhost:3000/health\"]\n interval: 5s\n timeout: 3s\n retries: 3\n start_period: 10s\n```\n\n### Dependency Conditions\n\n```yaml\ndepends_on:\n db:\n condition: service_healthy # service_started | service_healthy | started | healthy\n```\n\n## Platform Environment Variables\n\nEve automatically injects these variables into all deployed services:\n\n| Variable | Description |\n|----------|-------------|\n| `EVE_API_URL` | Internal cluster URL for server-to-server calls (e.g., `http://eve-api:4701`) |\n| `EVE_PUBLIC_API_URL` | Public ingress URL for browser-facing apps (e.g., `https://api.eh1.incept5.dev`) |\n| `EVE_SSO_URL` | SSO broker URL for user authentication (e.g., `https://sso.eh1.incept5.dev`) |\n| `EVE_PROJECT_ID` | The project ID (e.g., `proj_01abc123...`) |\n| `EVE_ORG_ID` | The organization ID (e.g., `org_01xyz789...`) |\n| `EVE_ENV_NAME` | The environment name (e.g., `staging`, `production`) |\n\nJob runners also receive `EVE_ENV_NAMESPACE`, but service containers do not.\nServices can override these values by defining them explicitly in their `environment` section.\n\n**Which API URL to use:**\n\n- Use `EVE_API_URL` for backend/server-side calls from your container to the Eve API (internal cluster networking).\n- Use `EVE_PUBLIC_API_URL` for browser/client-side calls or any code running outside the cluster.\n\n```javascript\n// Server-side: call Eve API from your backend\nconst eveApiUrl = process.env.EVE_API_URL;\n\n// Client-side: expose to browser for frontend API calls\nconst publicApiUrl = process.env.EVE_PUBLIC_API_URL;\n```\n\n## Environments\n\n```yaml\nenvironments:\n staging:\n pipeline: deploy\n pipeline_inputs:\n smoke_test: true\n approval: required\n overrides:\n services:\n api:\n environment:\n NODE_ENV: staging\n workers:\n - type: default\n service: worker\n replicas: 2\n```\n\n### Environment Fields\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `type` | string | `persistent` (default) or `temporary` |\n| `kind` | string | `standard` (default) or `preview` (PR envs) |\n| `pipeline` | string | Pipeline name to trigger on deploy |\n| `pipeline_inputs` | object | Inputs passed to pipeline (CLI `--inputs` wins on conflict) |\n| `approval` | string | `required` to gate deploys |\n| `overrides` | object | Compose-style service overrides |\n| `workers` | array | Worker pool configuration |\n| `labels` | object | Metadata (PR info for preview envs) |\n\n### Environment Pipeline Behavior\n\nWhen `pipeline` is configured for an environment, `eve env deploy \u003cenv> --ref \u003csha>` triggers a pipeline run instead of performing a direct deployment. This enables:\n\n- Consistent build/test/deploy workflows across environments\n- Promotion patterns where staging/production reuse releases from test\n- Environment-specific pipeline inputs and approval gates\n\nTo bypass the pipeline and perform a direct deployment, use `--direct`:\n\n```bash\neve env deploy staging --ref 0123456789abcdef0123456789abcdef01234567 --direct\n```\n\n### Promotion Example\n\nDefine environments that share a pipeline but vary in inputs and approval gates:\n\n```yaml\nenvironments:\n test:\n pipeline: deploy-test\n staging:\n pipeline: deploy\n pipeline_inputs:\n smoke_test: true\n production:\n pipeline: deploy\n approval: required\n```\n\nDeploy flow:\n\n```bash\n# Build + test + release in test\neve env deploy test --ref 0123456789abcdef0123456789abcdef01234567\n\n# Promote to staging (reuse release, no rebuild)\neve release resolve v1.2.3 # Get release_id from test\neve env deploy staging --ref 0123456789abcdef0123456789abcdef01234567 --inputs '{\"release_id\":\"rel_xxx\"}'\n\n# Promote to production (approval required)\neve env deploy production --ref 0123456789abcdef0123456789abcdef01234567 --inputs '{\"release_id\":\"rel_xxx\"}'\n```\n\nThis pattern enables build-once, deploy-many promotion workflows without rebuilding images.\n\n## Pipelines (Steps)\n\n```yaml\npipelines:\n deploy-test:\n steps:\n - name: migrate\n action: { type: job, service: migrate }\n - name: deploy\n depends_on: [migrate]\n action: { type: deploy }\n```\n\nStep types: `action`, `script`, `agent`, or shorthand `run`.\n\nPipeline `toolchains` can be declared at pipeline root or step level. Valid\nvalues are `python`, `media`, `rust`, `java`, and `kotlin`. Script, shorthand\n`run`, agent, and `action: { type: run }` steps resolve `step.toolchains >\npipeline.toolchains > []`. Non-run actions cannot declare step-level\ntoolchains, and `action.toolchains` is rejected.\n\nSee `references/pipelines-workflows.md` for step types, triggers, and the canonical build-release-deploy pattern.\n\n## Workflows\n\n```yaml\nworkflows:\n nightly-audit:\n db_access: read_only\n hints:\n gates: [\"remediate:proj_xxx:staging\"]\n steps:\n - name: prepare\n script:\n run: \"eve job list --json\"\n timeout_seconds: 60\n - agent:\n prompt: \"Audit error logs\"\n```\n\nWorkflow invocation creates a root container job with the workflow hints merged,\nthen creates one child job per step. Each step must define exactly one execution\nkind: `agent`, `script`, or shorthand `run`. `script` and `run` workflow steps\nmaterialize as worker-executed script jobs. Workflow `action` steps are reserved\nfor future support and are rejected at invoke time.\n\nWorkflow `toolchains` can be declared at workflow root or step level. Script and\nshorthand `run` steps resolve `step.toolchains > workflow.toolchains > []`.\nAgent steps resolve `step.toolchains > agent config toolchains >\nworkflow.toolchains > []`. The resolved value is stored on\n`jobs.hints.toolchains` and appears in `eve job show` / `eve job diagnose`.\n\n### Multi-Step Workflow Syntax\n\nWorkflows support multi-step DAGs that expand into child jobs at invocation time:\n\n```yaml\nworkflows:\n ingestion-pipeline:\n with_apis:\n - service: coordinator\n description: Coordinator API for orchestration\n steps:\n - name: ingest\n agent:\n name: ingestion\n - name: extract\n depends_on: [ingest]\n agent:\n name: extraction\n - name: review\n depends_on: [extract]\n agent:\n name: reviewer\n```\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `steps[].name` | string | Unique step identifier (required when using `depends_on`) |\n| `steps[].depends_on` | string[] | Step names this step blocks on |\n| `steps[].condition` | string | Conditional execution: `step_name.status == 'value'` or `!= 'value'` (skip step if false) |\n| `steps[].agent` | object | Agent ref: `name`, optional `prompt`/`prompt_file`, `harness`, `harness_profile`, `toolchains` |\n| `steps[].script` | object | Worker-executed script step; accepts `run` or `command` plus optional `timeout`/`timeout_seconds` |\n| `steps[].run` | string | Shorthand for `steps[].script.run`; creates a script child job |\n| `steps[].toolchains` | string[] | Step-level toolchains (`python`, `media`, `rust`, `java`, `kotlin`) |\n| `steps[].harness` | string | Step-level harness override (takes precedence over agent-resolved value) |\n| `steps[].harness_options` | object | Step-level harness options: `model`, `reasoning_effort`, `temperature` (passthrough) |\n| `steps[].harness_profile` | string | Named profile reference; supports `${inputs.\u003ckey>}` template expressions |\n| `steps[].harness_profile_override` | object | Inline profile override: `{ harness, model?, reasoning_effort?, variant?, temperature? }` |\n| `steps[].git` | object | Step git controls (`ref`, `ref_policy`, `branch`, `create_branch`, `commit`, `push`, `remote`); overrides workflow-level `git` |\n| `steps[].resource_refs` | string \\| string[] \\| object | Step resource policy; overrides workflow-level policy |\n| `steps[].env_overrides` | object | Step env overrides; merged over workflow-level defaults |\n| `steps[].scope` | object | Step job token scope; intersected with workflow/invocation scope |\n| `inputs` | object | Workflow-level named inputs. Each entry: `{ from?: 'event.payload.\u003cpath>', default?: any }` |\n| `git` | object | Default job git controls inherited by steps unless overridden |\n| `resource_refs` | string \\| string[] \\| object | Default invocation resource policy for all workflow steps |\n| `env_overrides` | object | Default env overrides applied to every workflow step |\n| `scope` | object | Default job token scope applied to every workflow step |\n| `toolchains` | string[] | Default toolchains for workflow steps |\n| `with_apis` | object[] | API specs attached to the workflow — `{ service, description }` (workflow-level or per-step) |\n| `db_access` | string | `read_only` or `read_write` |\n| `hints` | object | Merged into the root job at invocation time (gates, timeouts, harness prefs) |\n| `trigger` | object | Event trigger for automatic invocation (see `references/pipelines-workflows.md`) |\n\n`resource_refs` controls which invocation resources are hydrated into each\nstep workspace:\n\n```yaml\nworkflows:\n create-design:\n resource_refs: inherit # default; \"all\" also works\n steps:\n - name: read-sources\n agent: { name: designer }\n - name: publish\n depends_on: [read-sources]\n resource_refs: none\n agent: { name: publisher }\n\n scoped-review:\n resource_refs:\n mode: selected\n include: [brief, design-system]\n steps:\n - name: review\n agent: { name: reviewer }\n```\n\nAllowed values:\n- `inherit` / `all`: pass all invocation refs to each step.\n- `none`: pass no invocation refs.\n- string array: select refs by `name`, `label`, `mount_path`, `uri`, or `metadata.name`.\n- object form: `{ mode: selected, include: [...] }`.\n\nStep-level policy overrides workflow-level policy. If omitted everywhere, every\nstep inherits all invocation refs, including dependent steps.\n\n`env_overrides` can be declared at workflow level and step level. Invocation\nflags from `eve workflow run|invoke --env-override KEY=VALUE` are merged in at\nrun time. Precedence is invocation > step YAML > workflow YAML. Values may be\nliterals or `${secret.KEY}` placeholders; unsupported `${env.X}` style\nexpressions and reserved Eve runtime variables are rejected. The merged object is\npersisted on each executable step job, not on the root workflow container job.\nSecret placeholders are resolved only inside worker or agent-runtime before the\nharness process starts.\n\nWorkflow and step `scope` blocks narrow the job token and org filesystem mount.\nSupported axes are `orgfs`, `orgdocs`, `envdb`, and `cloud_fs`:\n\n```yaml\nworkflows:\n scoped-review:\n scope:\n orgfs:\n allow_prefixes: [/groups/projects/proj-a/**]\n steps:\n - name: review\n agent: { name: reviewer }\n scope:\n cloud_fs:\n allow_mount_ids: [mount_a]\n```\n\nWorkflow, step, and invocation scopes are intersected for each executable step\njob and persisted as `jobs.token_scope`. Request-supplied scope requires\n`jobs:harness_override`. There is no CLI `--scope-*` flag yet.\n\n```yaml\nworkflows:\n research:\n env_overrides:\n WEB_SEARCH_API_KEY: ${secret.WEB_SEARCH_API_KEY}\n steps:\n - name: search\n agent: { name: researcher }\n - name: publish\n depends_on: [search]\n env_overrides:\n PUBLISH_API_KEY: ${secret.PUBLISH_API_KEY}\n agent: { name: publisher }\n```\n\n**Conditional steps and step-level harness**:\n\n```yaml\nworkflows:\n triage-and-deepen:\n inputs:\n model:\n from: event.payload.meta.brand\n default: claude\n steps:\n - name: triage\n agent: { name: fast-triage }\n harness: claude\n harness_options:\n model: sonnet\n reasoning_effort: medium\n - name: deep-analysis\n depends_on: [triage]\n condition: \"triage.status == 'complex'\"\n harness_profile: ${inputs.model}\n agent: { name: deep-analyzer }\n```\n\n`condition` format is `step_name.status == 'value'` or `!= 'value'`, evaluated\nagainst `result_json.eve.status` of the referenced step (which must be in\n`depends_on`). Skipped steps count as `done` for downstream resolution. Step\n`harness`/`harness_options`/`harness_profile` override agent-resolved values\nand may carry `${inputs.\u003ckey>}` template expressions that resolve at\ninvocation time.\n\n**Validation** (`eve manifest validate` and `eve project sync` check workflows):\n- Duplicate step names → error.\n- Cyclic dependencies → error (reports cycle path).\n- Invalid `depends_on` references → error.\n- Trigger with no recognized type → warning.\n- Invalid GitHub event type → warning.\n- Unknown system event type → warning.\n- Cron trigger with missing schedule → warning.\n- Invalid condition format → error.\n- Condition references non-existent step → error.\n- Condition step not in `depends_on` → error.\n\n**Pack workflow merging**: When packs define workflows, they are merged into the\nrepo manifest before sync (single POST). Pack workflows overlay repo-manifest\nworkflows — pack definitions take precedence on name collision.\n\nSee `references/pipelines-workflows.md` for expansion behavior, response format, and job tree view.\n\n## Secret Requirements and Validation\n\nDeclare required secrets at the top level or per pipeline step:\n\n```yaml\nx-eve:\n requires:\n secrets: [GITHUB_TOKEN, REGISTRY_TOKEN]\n\npipelines:\n ci-cd-main:\n steps:\n - name: integration-tests\n script:\n run: \"pnpm test\"\n requires:\n secrets: [DATABASE_URL]\n```\n\nValidate secrets before syncing:\n\n```bash\neve project sync --validate-secrets # Warn on missing secrets\neve project sync --strict # Fail on missing secrets\neve manifest validate # Schema + secret validation without syncing\n```\n\nUse `eve manifest validate` for pre-flight checks against a local manifest or the latest synced version. Required keys follow standard scope resolution rules.\n`${secret.KEY}` references in workflow-level or step-level `env_overrides` are included in the same validation.\n\n### Platform-Injected Environment Variables\n\nThe deployer automatically injects these env vars into every deployed service.\n**Do NOT redeclare them as `${secret.*}` in the manifest** — that overrides the\nplatform value with an empty string when the secret isn't configured.\n\n| Variable | Value | Notes |\n|----------|-------|-------|\n| `EVE_API_URL` | Platform API URL (K8s in-cluster) | Auto-resolved |\n| `EVE_PROJECT_ID` | Project TypeID | Auto-resolved |\n| `EVE_ORG_ID` | Org TypeID | Auto-resolved |\n| `EVE_ENV_NAME` | Environment name (e.g., `sandbox`) | Auto-resolved |\n| `EVE_SERVICE_TOKEN` | 90-day JWT (type: `service`) | Minted per-deploy, refreshed automatically |\n| `EVE_PUBLIC_API_URL` | Public API URL (if configured) | Optional |\n| `EVE_SSO_URL` | SSO URL (if configured) | Optional |\n\nThe service token carries **read-only default permissions** (`projects:read`,\n`jobs:read`, `threads:read`, etc.). Apps that need write access must declare\nadditional permissions via `x-eve.permissions` — see [Service Token Permissions](#service-token-permissions).\n\nUser-defined env vars in the manifest override platform vars, so only declare\n`EVE_*` vars if you need a different value than the platform default.\n\n### Secret Interpolation\n\nInterpolate secrets in environment variables:\n\n```yaml\nenvironment:\n DATABASE_URL: postgres://user:${secret.DB_PASSWORD}@db:5432/app\n```\n\nAlso supported (runtime interpolation): `${ENV_NAME}`, `${PROJECT_ID}`, `${ORG_ID}`, `${ORG_SLUG}`, `${COMPONENT_NAME}`, `${SSO_URL}`, `${secret.KEY}`, `${managed.\u003cservice>.\u003cfield>}`.\n\n### Internal Service URLs\n\nApps with multiple services often need to call their own API internally (e.g., to emit events back to themselves). Use `${ENV_NAME}` with K8s service DNS naming:\n\n```yaml\nenvironment:\n MY_API_URL: \"http://${ENV_NAME}-api:3000\"\n```\n\nThe pattern is `${ENV_NAME}-{service_key}:{port}`. This resolves to the in-cluster service address — no ingress, no CORS, no public exposure. Essential for apps that trigger workflows via their own API.\n\n## Manifest Defaults (`x-eve.defaults`)\n\nDefault job settings applied on creation (job fields override defaults). Default environment should be **staging** unless explicitly overridden:\n\n```yaml\nx-eve:\n defaults:\n env: staging\n harness: mclaude\n harness_profile: primary-orchestrator\n harness_options:\n model: opus-4.5\n reasoning_effort: high\n hints:\n permission_policy: auto_edit\n resource_class: job.c1\n max_cost:\n currency: usd\n amount: 5\n max_tokens: 200000\n git:\n ref_policy: auto\n branch: job/${job_id}\n create_branch: if_missing\n commit: manual\n push: never\n workspace:\n mode: job\n```\n\n`hints` can include budgeting and accounting fields such as `resource_class`,\n`max_cost`, and `max_tokens`. These map to scheduling hints and per-attempt\nbudget enforcement.\n\n## Project Agent Profiles (`x-eve.agents`)\n\nDefine harness profiles used by orchestration skills:\n\n```yaml\nx-eve:\n agents:\n version: 1\n availability:\n drop_unavailable: true\n profiles:\n primary-reviewer:\n - harness: mclaude\n model: opus-4.5\n reasoning_effort: high\n - harness: codex\n model: gpt-5.2-codex\n reasoning_effort: x-high\n```\n\nFor pack-distributed profiles, define them in `eve/x-eve.yaml` and import via `pack.yaml`:\n\n```yaml\n# eve/x-eve.yaml\nversion: 1\nagents:\n profiles:\n coordinator:\n - harness: claude\n model: sonnet\n reasoning_effort: medium\n expert:\n - harness: claude\n model: sonnet\n reasoning_effort: high\n```\n\nProfiles in `eve/x-eve.yaml` are distributed with the pack. Profiles in the manifest are project-local only.\n\n## AgentPacks (`x-eve.packs` + `x-eve.install_agents`)\n\nAgentPacks import agent/team/chat config and skills from pack repos. Packs are\nresolved by `eve agents sync` and locked in `.eve/packs.lock.yaml`.\n\n```yaml\nx-eve:\n install_agents: [claude-code, codex, gemini-cli] # defaults to [claude-code]\n packs:\n - source: ./skillpacks/my-pack\n - source: incept5/eve-skillpacks\n ref: 0123456789abcdef0123456789abcdef01234567\n - source: ./skillpacks/claude-only\n install_agents: [claude-code]\n```\n\nNotes:\n- Remote pack sources require a 40-char git SHA `ref`.\n- Packs can be full AgentPacks (`eve/pack.yaml`) or skills-only packs.\n- Local packs use relative paths (resolved from repo root).\n\n### Full AgentPack Descriptor (`eve/pack.yaml`)\n\nA full AgentPack declares its resources via an `imports` map and optional gateway defaults:\n\n```yaml\n# eve/pack.yaml\nversion: 1\nid: my-pack\nimports:\n agents: eve/agents.yaml\n teams: eve/teams.yaml\n workflows: eve/workflows.yaml\n chat: eve/chat.yaml\n x_eve: eve/x-eve.yaml\ngateway:\n default_policy: none\n```\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `version` | number | Schema version (currently `1`) |\n| `id` | string | Unique pack identifier |\n| `imports` | object | Map of resource type to relative YAML file path |\n| `imports.agents` | string | Agent definitions file |\n| `imports.teams` | string | Team definitions file |\n| `imports.workflows` | string | Workflow definitions (merged with manifest workflows at sync) |\n| `imports.chat` | string | Chat route definitions |\n| `imports.x_eve` | string | Harness profiles and agent config |\n| `gateway` | object | Gateway defaults for the pack |\n| `gateway.default_policy` | string | Default gateway policy for pack agents (`none`, `discoverable`, `routable`) |\n\nAll `imports` paths are relative to the pack root. Only declare the imports your pack needs — all fields are optional.\n\n### Pack Lock File\n\n`.eve/packs.lock.yaml` tracks resolved state:\n\n```yaml\nresolved_at: \"2026-02-09T...\"\nproject_slug: myproject\npacks:\n - id: pack-id\n source: incept5/eve-skillpacks\n ref: 0123456789abcdef0123456789abcdef01234567\n pack_version: 1\neffective:\n agents_count: 5\n teams_count: 2\n routes_count: 3\n profiles_count: 4\n```\n\n### Pack Overlay Customization\n\nLocal YAML overlays pack defaults using deep merge + `_remove`:\n\n```yaml\n# In local agents.yaml\nversion: 1\nagents:\n pack-agent:\n harness_profile: my-override # override pack default\n unwanted-agent:\n _remove: true # remove from pack\n```\n\n### Pack CLI\n\n```bash\neve packs status [--repo-dir \u003cpath>] # Show lockfile + drift\neve packs resolve [--dry-run] [--repo-dir \u003cpath>] # Preview resolution\n```\n\n## Project Bootstrap\n\nBootstrap creates a project + environments in a single API call:\n\n```bash\neve project bootstrap --name my-app --repo-url https://github.com/org/repo \\\n --environments staging,production\n```\n\nAPI: `POST /projects/bootstrap` with body:\n- `org_id`, `name`, `repo_url`, `branch` (required)\n- `slug`, `description`, `template`, `packs`, `environments` (optional)\n\nIdempotent — re-calling with the same name returns the existing project.\n\n## Ingress Defaults\n\nIf a service exposes ports and the cluster domain is configured, Eve creates ingress by default.\nSet `x-eve.ingress.public: false` to disable.\n\nURL pattern: `{service}.{orgSlug}-{projectSlug}-{env}.{domain}`\n\n### Custom Domains\n\nBring your own domain by adding `domains` to the ingress config:\n\n```yaml\nservices:\n web:\n x-eve:\n ingress:\n public: true\n alias: myapp # myapp.eh1.incept5.dev (platform subdomain)\n domains:\n - myapp.com # custom domain (A record)\n - www.myapp.com # custom domain (CNAME)\n```\n\nEnv-specific domains belong in environment service overrides:\n\n```yaml\nenvironments:\n sandbox:\n overrides:\n services:\n web:\n x-eve:\n ingress:\n domains:\n - sandbox.myapp.com\n```\n\n**Lifecycle**: `pending_dns` → `dns_verified` → `cert_provisioning` → `active`. Domains are auto-registered during `eve project sync`; env-override domains declared in exactly one env are also bound to that env during sync. After setting up DNS, run `eve domain verify \u003chostname>` — it performs real DNS resolution server-side and transitions the status to `dns_verified`. Redeploy to create the Ingress and provision the TLS cert. Use `eve domain register \u003chost> --project \u003cid> --service \u003csvc> --env \u003cenv>` for manual reservations that are not in the manifest.\n\n**DNS**: Apex domains (`myapp.com`) require an A record pointing to the platform ingress IP. Subdomains (`www.myapp.com`) can use a CNAME to the platform ingress hostname.\n\n**TLS**: Custom domains use `cert-manager.io/cluster-issuer` (HTTP-01 challenge), NOT the platform wildcard cert (`EVE_DEFAULT_TLS_SECRET`). The ClusterIssuer must support HTTP-01 challenges.\n\n## Public TCP Ingress\n\nUse service `x-eve.tcp_ingress` for raw L4 TCP protocols such as device\ntrackers. This is separate from HTTP ingress and renders a Kubernetes\n`Service` of type `LoadBalancer` labelled `eve.tcp_ingress=true`.\n\n```yaml\nservices:\n device-edge:\n image: ghcr.io/acme/device-edge:latest\n ports: [33400, 33500]\n x-eve:\n tcp_ingress:\n hostname: trackers\n allow_cidrs:\n - 0.0.0.0/0\n listeners:\n - name: a1-gt06\n port: 33400\n - name: mictrack-mt700\n port: 33500\n```\n\nRules:\n- `listeners` is required and supports 1-20 entries.\n- Listener names must be lowercase alphanumeric with hyphens.\n- Each listener `port` must also appear in top-level service `ports`.\n- Listener ports cannot use the Kubernetes NodePort range `30000-32767`.\n- `allow_cidrs` is optional and becomes `loadBalancerSourceRanges`.\n- `hostname` is an optional platform alias under `EVE_TCP_INGRESS_HOSTED_ZONE`\n or `EVE_DEFAULT_DOMAIN`; without it Eve advertises a generated service host.\n\nTCP aliases share the same global claim and reserved-name rules as HTTP ingress\naliases. A manifest cannot declare the same alias for both HTTP and TCP.\n\nThe app container receives `EVE_TCP_PUBLIC_HOST` plus listener-specific env vars:\n`EVE_TCP_LISTENER_\u003cNAME>_PORT` and `EVE_TCP_LISTENER_\u003cNAME>_HOST`, where hyphens\nin `\u003cNAME>` are converted to underscores and the value is uppercased.\n\nProviders:\n- `EVE_TCP_INGRESS_PROVIDER=none`: validate only; render no public TCP service.\n- `EVE_TCP_INGRESS_PROVIDER=klipper`: local k3d/k3s LoadBalancer.\n- `EVE_TCP_INGRESS_PROVIDER=aws-nlb`: internet-facing AWS NLB; requires the AWS\n Load Balancer Controller.\n\nDiagnostics:\n\n```bash\neve env diagnose \u003cproject> \u003cenv> # TCP Ingress table\neve env diagnose \u003cproject> \u003cenv> --json | jq '.tcp_ingress'\neve tcp-ingress test \u003cproject> \u003cenv> --listener a1-gt06\n```\n\nFor local k3d, create the cluster with the raw TCP ports mapped:\n\n```bash\n./bin/eh k8s start --tcp-ports 33400,33500 --recreate\n```\n\n## App Undeploy & Delete\n\nFull lifecycle operations for environments, projects, and orgs.\n\n### Environment Undeploy\n\nTake an environment offline without losing config. Tears down K8s resources but preserves the environment record for redeployment.\n\n```bash\neve env undeploy \u003cenv> --project \u003cid>\neve env show \u003cproject> \u003cenv> --json # Verify deploy_status = 'undeployed'\n```\n\nRedeploy later with `eve env deploy \u003cenv> --ref \u003csha>`. The `deploy_status` field tracks state: `unknown`, `deployed`, `undeployed`, `deploying`, `undeploying`, `failed`.\n\n### Project Delete\n\n```bash\neve project delete \u003cproject> # Soft-delete (sets deleted_at)\neve project delete \u003cproject> --hard # Hard-delete: cascades through all resources\neve project delete \u003cproject> --hard --force # Continue on partial failures\n```\n\nHard delete sequence: undeploy all environments, delete environments (triggers managed DB cleanup), cascade-delete jobs/pipeline-runs/releases/builds/agents/teams/threads, delete project record.\n\n### Org Delete\n\n```bash\neve org delete \u003corg> # Soft-delete\neve org delete \u003corg> --hard --force # Full tenancy teardown\n```\n\nCascades through all projects and environments in the org.\n\n### Resource Cleanup\n\nIndividual resource delete and prune commands:\n\n```bash\neve build delete \u003cid> # Delete a single build\neve build prune --project \u003cid> --keep 10 # Keep last N, delete rest\neve release delete \u003cid>\neve release prune --project \u003cid> --keep 10\neve pipeline delete \u003cname> --project \u003cid>\neve agents delete \u003cname> --project \u003cid>\neve thread delete \u003cid>\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":63043,"content_sha256":"26375fd3ccf4c65e4b9601c4ac61d7475921e103be875781085bf4ec4b0bab32"},{"filename":"references/object-store-filesystem.md","content":"# Object Store & Org Filesystem Reference\n\nUnified storage layer for Eve Horizon: S3-compatible object storage backing org filesystem sync and app object buckets.\n\n## Use When\n\n- You need to set up or troubleshoot org filesystem sync between local machines and Eve.\n- You need to share files via share tokens or public paths.\n- You need to understand how agents access the org filesystem at runtime.\n- You need to declare object store buckets for an app service.\n- You need to configure native GCS storage via Workload Identity.\n- You need to mount Google Drive (or other cloud storage) into an org's filesystem.\n\n## Load Next\n\n- `references/cli-org-project.md` for org/project setup and docs CLI commands.\n- `references/secrets-auth.md` for access groups and scoped bindings (orgfs permissions).\n- `references/events.md` for event-driven automation triggered by file changes.\n\n## Ask If Missing\n\n- Confirm target org ID before running any `eve fs` command.\n- Confirm sync mode needed (two-way, push-only, pull-only) before initializing.\n- Confirm include/exclude patterns if syncing a subset of files.\n\n## Overview\n\nFour storage primitives share a common object store backend:\n\n| Primitive | Scope | Use Case |\n|-----------|-------|----------|\n| **Object Store** | Platform | Binary file storage via presigned URL transfers |\n| **Org Filesystem** | Org | Multi-device file sync with real-time events |\n| **Org Docs** | Org | Versioned document store with full-text search |\n| **Cloud FS** | Org/Project | Google Drive (and future providers) as mounted cloud filesystems |\n\nThe object store is MinIO locally (k3d dev) and S3/GCS/R2/Tigris in cloud. All speak the S3 protocol.\n\n### Storage Backends\n\nThe platform supports two native storage backends:\n\n| Backend | Config | Auth | Use Case |\n|---------|--------|------|----------|\n| **S3-compatible** | `EVE_STORAGE_BACKEND=s3` (default) | Access key + secret key | AWS S3, GCS+HMAC, MinIO, R2, Tigris |\n| **Native GCS** | `EVE_STORAGE_BACKEND=gcs` | Workload Identity (ADC) | Google Cloud Storage without API keys |\n\nWhen `EVE_STORAGE_BACKEND=gcs` and no HMAC keys are set, the platform uses the native GCS client with Application Default Credentials / Workload Identity. No API keys or HMAC configuration needed -- the GKE service account's Workload Identity binding provides auth automatically.\n\nThe `ObjectStorageClient` abstraction (`packages/shared/src/storage/`) provides a unified interface:\n- `S3StorageClient`: existing path for all S3-compatible backends\n- `GcsStorageClient`: native GCS via ADC (dynamic import, only loaded when `backend=gcs`)\n\nAll consumers (StorageService, BucketProvisioner, InvokeService, snapshot system) use this abstraction. AWS deployments are completely unaffected.\n\n**CORS**: Org buckets are automatically created with CORS headers (`GET`, `PUT`, `HEAD` from all origins). This means browser JS (`fetch`, `XMLHttpRequest`, PDF.js) can access presigned URLs directly. Simple HTML elements (`\u003cimg>`, `\u003cembed>`, `\u003ca>`) work without CORS.\n\n## Org Filesystem\n\n### Sync Protocol\n\nFiles transfer via **presigned URLs** -- content never flows through the Eve API:\n\n- **Upload**: CLI detects change -> computes SHA-256 -> gets presigned PUT URL -> uploads direct to S3\n- **Download**: SSE event stream delivers presigned GET URL -> CLI downloads direct from S3\n\n### Sync Modes\n\n| Mode | Behavior |\n|------|----------|\n| `two-way` | Bidirectional sync (default) |\n| `push-only` | Local -> remote only |\n| `pull-only` | Remote -> local only |\n\n### CLI Commands\n\n```bash\n# Initialize sync\neve fs sync init --org \u003corg> --local \u003cpath> [--mode two-way|push-only|pull-only] \\\n [--remote-path /] [--include \"**/*.md\"] [--exclude \"**/.git/**\"]\n\n# Status and monitoring\neve fs sync status --org \u003corg>\neve fs sync logs --org \u003corg> [--follow]\neve fs sync doctor --org \u003corg>\n\n# Link management\neve fs sync pause --org \u003corg>\neve fs sync resume --org \u003corg>\neve fs sync disconnect --org \u003corg>\neve fs sync mode --org \u003corg> --set \u003cmode>\n\n# Conflict resolution\neve fs sync conflicts --org \u003corg>\neve fs sync resolve --org \u003corg> --conflict \u003cid> --strategy \u003cpick-remote|pick-local|manual>\n```\n\n### Share Tokens\n\nTime-limited, revocable access to individual files:\n\n```bash\neve fs share \u003cpath> --org \u003corg> [--expires 7d] [--label \"description\"]\neve fs shares --org \u003corg>\neve fs revoke \u003ctoken> --org \u003corg>\n```\n\n### Public Paths\n\nPermanent unauthenticated access to path prefixes:\n\n```bash\neve fs publish \u003cpath-prefix> --org \u003corg> [--label \"description\"]\neve fs public-paths --org \u003corg>\n```\n\nPublic file resolver (no auth): `GET /orgs/{orgId}/fs/public/{path}`\n\n### Text Indexing\n\nText files (markdown, YAML, JSON; under 512 KB) synced to the org filesystem are automatically indexed into org documents for full-text search. Indexing is async (poll interval: 2s, batch: 10).\n\n### Events\n\n| Event | Trigger |\n|-------|---------|\n| `file.created` | New file uploaded |\n| `file.updated` | Existing file modified |\n| `file.deleted` | File removed |\n| `conflict.detected` | Both sides modified same file |\n\nSSE stream: `GET /orgs/{orgId}/fs/events/stream?after_seq=\u003cn>`\n\n### Agent Runtime\n\nWarm pods mount org filesystem as PVC at `/org` (`EVE_ORG_FS_ROOT=/org`). Agents read/write directly -- changes sync to S3 and index into org docs automatically.\n\n## App Object Stores\n\nDeclare S3-compatible buckets per service in the manifest. Eve provisions each\nbucket at deploy time and injects a credential binding as env vars. See\n`references/manifest.md` (App Object Store Buckets) for the full schema.\n\n```yaml\nservices:\n api:\n x-eve:\n object_store:\n # Optional: auto (default), irsa, or shared\n isolation: auto\n buckets:\n - name: uploads\n visibility: private\n - name: avatars\n visibility: public\n cors:\n origins: [\"*\"]\n```\n\nEach bucket is provisioned per environment during deploy and appears in\n`eve env diagnose` under storage buckets with its resolved `isolation_mode`.\nEve resolves one credential binding for the whole env, including app services\nand job services that declare buckets. If Eve object storage is not configured,\nif explicit `isolation: irsa` is requested on a non-IRSA cluster, or\nbucket/policy setup fails, deploy fails before the app pod starts. Changes to\n`x-eve.object_store.buckets` flow through the deploy manifest hash, so editing\nbuckets triggers a redeploy that re-applies bucket config (creation, CORS,\npublic policy) and prunes stale `storage_buckets` rows.\n\nStatic-key env vars for local MinIO or explicit shared mode:\n\n| Variable | Description |\n|----------|-------------|\n| `STORAGE_ENDPOINT` | S3-compatible endpoint |\n| `STORAGE_REGION` | Storage region |\n| `STORAGE_ACCESS_KEY_ID` / `STORAGE_SECRET_ACCESS_KEY` | App-scoped storage credentials injected for the app |\n| `STORAGE_BUCKET_\u003cNAME>` | Physical bucket name (e.g. `eve-org-myorg-myapp-test-uploads` locally or `eh1-eve-app-myorg-myapp-test-uploads` on staging) |\n| `STORAGE_FORCE_PATH_STYLE` | `true` for MinIO, omitted for AWS S3 |\n\nIRSA env vars for AWS:\n\n| Variable | Description |\n|----------|-------------|\n| `STORAGE_ENDPOINT` | S3 endpoint |\n| `STORAGE_REGION` | Storage region |\n| `STORAGE_AUTH_MODE` | `irsa` |\n| `AWS_REGION` | Region used by AWS SDK credential providers |\n| `STORAGE_BUCKET_\u003cNAME>` | Physical bucket name |\n\nIRSA app pods do not receive static storage access keys. Eve renders\n`ServiceAccount/eve-app` with `eks.amazonaws.com/role-arn` and sets\n`serviceAccountName: eve-app` on app/job pods that declare buckets.\n\n### Credential Separation\n\nApp pods receive credentials distinct from the worker's platform-internal\nstorage credentials. In static mode, the deployer resolves app-injected env in\nthis order, so the worker can keep its provisioner credentials (used for\n`CreateBucket`, CORS, and public policy) separate from what app pods see:\n\n1. `EVE_APP_STORAGE_PUBLIC_ENDPOINT` / `EVE_APP_STORAGE_ENDPOINT` / `EVE_APP_STORAGE_REGION` / `EVE_APP_STORAGE_ACCESS_KEY_ID` / `EVE_APP_STORAGE_SECRET_ACCESS_KEY` (preferred)\n2. `EVE_STORAGE_*` fallback (local MinIO compatibility only)\n\nThe app bucket physical-name prefix is controlled by\n`EVE_STORAGE_APP_BUCKET_PREFIX` (e.g. `eh1-eve-app`), defaulting to\n`EVE_STORAGE_ORG_BUCKET_PREFIX` for local backwards compatibility. App\ncredentials are scoped to that app-bucket prefix and cannot reach the\nplatform internal bucket, org filesystem buckets (`eh1-eve-org-*`), or non-Eve\nbuckets. On AWS, `auto` resolves to IRSA when the worker has OIDC provider\nconfiguration and IAM permissions. Eve creates one role per org/project/env and\nfully replaces its `app-bucket-access` inline policy with the env's declared\nphysical bucket names. Local k3d resolves `auto` to `minio-static-key`; explicit\n`shared` remains available for non-IRSA clusters. Setting\n`EVE_APP_BUCKET_AUTH_MODE=shared` on the worker forces the shared fallback even\nwhen IRSA env vars are present.\n\n### Operational Notes\n\n- **Local MinIO CORS** is configured server-wide (`MINIO_API_CORS_ALLOW_ORIGIN=*`)\n because MinIO does not reliably implement the S3 per-bucket CORS API.\n Restrictive per-bucket origins are recorded in diagnostics but not enforced\n by local MinIO; wildcard CORS works for browser presigned-URL flows.\n- **Public-read buckets**: AWS auto-applies `BlockPublicAccess` to new buckets,\n which would silently drop a public-read bucket policy. The deployer relaxes\n `BlockPublicPolicy` and `RestrictPublicBuckets` at the bucket level *before*\n applying the public-read policy. If you see a \"public\" bucket that rejects\n anonymous reads, check that this relax step ran (it logs at warn level on\n `NotImplemented` from non-AWS backends).\n\n## Cloud FS (Google Drive)\n\nCloud FS mounts external cloud storage (Google Drive today) into the org's storage topology. Agents and apps interact with cloud files through Eve's Cloud FS APIs and tools instead of provider-specific APIs.\n\n### Architecture\n\nMounts link an org's OAuth integration to a specific provider folder. The platform handles:\n- **Mount registry**: DB table mapping `(org, project?) -> provider folder`\n- **Change tracking**: Hybrid push (webhooks) + poll (changes cursor) for detecting external changes\n- **Auto-indexing**: Filed documents update org docs for full-text search\n- **Agent tools**: File operations exposed to agents at runtime\n\n### CLI Commands\n\n```bash\n# Mount a Google Drive folder\neve cloud-fs mount \\\n --provider google-drive \\\n --folder-id \u003cdrive-folder-id> \\\n --mode read_write \\\n --label \"Engineering Shared Drive\"\n\n# List / browse / search\neve cloud-fs list\neve cloud-fs ls / --mount \u003cmount-id>\neve cloud-fs ls /subfolder --mount \u003cmount-id> # alias: browse\neve cloud-fs search \u003cquery> [--mount \u003cmount-id>]\n\n# Manage\neve cloud-fs show \u003cmount-id>\neve cloud-fs update \u003cmount-id> --mode read_only\neve cloud-fs unmount \u003cmount-id> # aliases: remove, delete\n```\n\n### Mount Configuration\n\n| Field | Description |\n|-------|-------------|\n| `provider` | `google_drive` internally. CLI accepts `google-drive` and normalizes it. |\n| `root_folder_id` | Provider-specific folder ID |\n| `mode` | `read_only`, `write_only`, `read_write` (default) |\n| `auto_index` | Update org docs on file changes (default: `true`) |\n| `label` | Human-friendly display name |\n\nMounts are org-scoped, optionally project-scoped. Each mount requires an active integration connection (`eve integrations connect google-drive`). Per-org OAuth app credentials are required (see `references/manifest.md`, Per-Org OAuth Configs).\n\nWorkflow job tokens may carry `scope.cloud_fs.allow_mount_ids`. When present,\n`eve cloud-fs list` only returns allowed mounts, implicit `ls`/`search` choose\nfrom allowed mounts, and explicit per-mount routes return\n`403 resource_access_denied` for mounts outside the token scope.\n\n### Per-Mount File Operations API\n\nThe March 18, 2026 Cloud FS update added direct per-mount file operations in the API for Drive-backed mounts. These routes are useful for app UIs and service-to-service flows that need to browse a mounted folder, fetch metadata, download files, upload content, or create folders without going through provider SDKs.\n\n| Method | Route | Purpose |\n|--------|-------|---------|\n| `GET` | `/orgs/:org_id/cloud-fs/mounts/:mount_id/browse?path=/subdir` | Browse a specific mount by path or `folder_id` |\n| `GET` | `/orgs/:org_id/cloud-fs/mounts/:mount_id/files/:file_id` | Fetch file metadata |\n| `GET` | `/orgs/:org_id/cloud-fs/mounts/:mount_id/files/:file_id/download` | Stream file contents |\n| `POST` | `/orgs/:org_id/cloud-fs/mounts/:mount_id/upload` | Upload a file to a target path |\n| `POST` | `/orgs/:org_id/cloud-fs/mounts/:mount_id/folders` | Create a folder |\n\nUpload details:\n- Requires `cloud_fs:admin`.\n- Send raw file bytes as the request body.\n- Set `X-Cloud-FS-Path: /folder/file.ext` to choose the destination path.\n- Set `Content-Type` to the file MIME type.\n- Read-only mounts reject uploads and folder creation.\n\n### Events\n\nCloud FS changes emit system events: `system.cloud_fs.file.created`, `system.cloud_fs.file.modified`, `system.cloud_fs.file.deleted`. These can trigger workflows (e.g., a filing agent that auto-organizes uploaded documents).\n\n## Access Control\n\n| Permission | Allows |\n|------------|--------|\n| `orgfs:read` | List, download, view shares and public paths |\n| `orgfs:write` | Upload, create links, resolve conflicts |\n| `orgfs:admin` | Manage share tokens, publish/unpublish public paths |\n\nLinks support path-scoped ACLs via `scope_json.allow_prefixes`.\n\n## Org Docs (Versioned Document Store)\n\nCovered in detail in the `storage-primitives.md` reference (Section 5). Key points relevant to the storage layer:\n\n- Org docs is Postgres-native -- content stored in rows, not the object store\n- Full-text search via `tsvector` with weighted path (A) and content (B)\n- Async indexer bridges org filesystem -> org docs for text files\n- Search modes: `text` (default), `semantic`, `hybrid` (semantic/hybrid degrade to text when embeddings absent)\n- Lifecycle: `--review-in`, `--expires-in`, `eve docs stale`, `eve docs review`\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":14174,"content_sha256":"57ab8b2c2b93dc8531c262174b5d08ba009a15844519fb8f1b3a1ee58f7c4fcf"},{"filename":"references/observability.md","content":"# Observability + Cost Tracking\n\n## Use When\n- You need to trace a request or job through the system using correlation IDs.\n- You need to inspect execution receipts, token usage, or cost breakdowns.\n- You need analytics dashboards for org health, job stats, or pipeline metrics.\n- You need to configure OpenTelemetry or inspect provider/model availability.\n\n## Load Next\n- `references/cli.md` for full analytics and admin CLI syntax.\n- `references/jobs.md` for job lifecycle phases and attempt details.\n- `references/deploy-debug.md` for real-time log streaming and debugging.\n\n## Ask If Missing\n- Confirm the org ID for analytics queries (all analytics are org-scoped).\n- Confirm the time window for usage and cost reports (default varies by endpoint).\n- Confirm whether OTEL is enabled and where the collector endpoint is.\n\n## Correlation IDs\n\nEvery request carries `x-eve-correlation-id`. If missing on inbound, the API generates a UUID and echoes it back. The ID propagates: API -> Orchestrator -> Worker -> Runner.\n\nStandard structured log fields:\n\n| Field | Always | When Available |\n|---|---|---|\n| `timestamp`, `level`, `service`, `message` | Yes | -- |\n| `correlation_id`, `trace_id` | Yes | -- |\n| `job_id`, `attempt_id`, `event_id` | -- | Yes |\n\nJob execution lifecycle events are also written to `execution_logs` with correlation fields in the lifecycle `meta` object.\n\nEmbedded conversations (project-scoped chat threads) carry a normalized event\ntimeline (`conversation_events`, ids `cevt_*`) alongside the message stream.\nStandard kinds include `user.message`, `assistant.message`, `tool.call`,\n`tool.result`, `status.changed`, `progress`, `error`, and `final.result`; apps\nmay emit custom kinds. Each event records `job_id`, `attempt_id`,\n`workflow_step`, and `source` for cross-correlation. Stream via the SSE\nendpoint or tail with `eve thread events \u003cid> --follow`. See\n`agents-teams.md` and `eve-sdk.md` for the full surface.\n\n### Event -> Trigger Visibility\n\nEvents now record why they did or did not fan out to workflows:\n\n| Field | Meaning |\n|---|---|\n| `trigger_match_count` | Number of triggers that matched (0 = no match) |\n| `triggers_evaluated` | Array of `{type, name, matched, reason?}` for each trigger checked |\n\n```bash\neve event show \u003cevent-id>\n# Triggers: matched 1 of 3 evaluated\n# Trigger Evaluations:\n# workflow:process-document - MATCHED\n# workflow:reindex - no match (event_type mismatch)\n```\n\n`eve event list` shows a compact `MATCH` column (`1/3`). Manifest sync\n(`eve project sync`) now validates trigger definitions and surfaces warnings\nfor unrecognized event types, invalid GitHub events, unknown system events,\nand missing cron schedules — catching trigger wiring mistakes before the\nfirst event arrives. See `events.md` for the full event + trigger surface.\n\nFor deployed app services, use environment logs and request diagnostics:\n\n```bash\neve env logs \u003cproject> \u003cenv> \u003cservice> --follow --since 30\neve env logs \u003cproject> \u003cenv> \u003cservice> --grep req_01h...\neve env logs \u003cproject> \u003cenv> \u003cservice> --filter req_id=req_01h... --filter level=error\neve env diagnose \u003cproject> \u003cenv> --request req_01h... --window 120 --json\n```\n\n`eve env logs --filter k=v` is repeatable, ANDs filters together, and matches\nJSON log fields exactly. Dotted paths such as `req.path=/api/items` read nested\nJSON fields. Numeric and boolean values are coerced (`--filter status=500`\nmatches `{\"status\":500}`). Non-JSON lines fall back to `--grep` substring\nsemantics. `--follow` streams through the API using SSE, emits pod-change\nnotifications when a matching pod rolls, and combines with `--filter` for live\nstructured queries. `eve env diagnose --request \u003creq_id>` integrates app-service\nlogs, K8s events, deploy metadata at request time, optional audit rows, and\ntrace pointers into one JSON payload — no more stitching four commands\ntogether by hand.\n\n## Execution Receipts\n\nReceipts capture timing, token usage, and cost breakdowns per attempt. Assembled from lifecycle events plus `llm.call` usage events.\n\n- `llm.call` events contain usage only (no content). Emitted by harnesses after each provider call.\n- `eve job follow` displays live cost totals when `llm.call` events stream.\n\n```bash\neve job receipt \u003cjob-id> # View cost receipt for a job\neve job compare \u003cjob-id-1> \u003cjob-id-2> # Compare receipts across jobs\n```\n\nAPI: `GET /jobs/{job_id}/receipt`\n\n### Receipt Fields\n\n| Field | Description |\n|---|---|\n| `duration_ms` | Wall-clock execution time |\n| `input_tokens` | Total input tokens across all LLM calls |\n| `output_tokens` | Total output tokens across all LLM calls |\n| `total_cost` | Computed cost from rate card |\n| `model` | Model used (may differ from requested if bridged) |\n| `provider` | Resolved provider |\n\n### Admin Receipt Recompute\n\n```bash\neve admin receipts recompute # Recompute all\neve admin receipts recompute --since 2026-01-01 # From date\neve admin receipts recompute --project proj_xxx # Single project\neve admin receipts recompute --dry-run # Preview only\n```\n\n## Email Delivery Events\n\nOutbound mail through the platform mailer is durably tracked in\n`email_delivery_events`. AWS SES posts bounce/complaint/delivery notifications\nvia SNS to `POST /webhooks/ses-feedback`; the controller parses both\n`application/json` and SNS's `text/plain` body and inserts one row per\n(SNS `MessageId`, SES `eventType`, recipient) so retries do not duplicate.\n\nKey columns: `recipient`, `event_type` (`Bounce` | `Complaint` | `Delivery` |\n`Reject`), `bounce_type`, `bounce_subtype`, `diagnostic`, `ses_message_id`,\n`rfc_message_id`, `raw_payload`, `received_at`. Indexed by `(recipient,\nreceived_at DESC)` and `ses_message_id`.\n\nBefore send, the mailer pre-flights the SES account-level suppression list and\nshort-circuits with a loud structured error if the recipient is suppressed.\nMailer logs are JSON with `recipient`, `message-id`, and outcome.\n\n```bash\neve admin email bounces list # Recent events\neve admin email bounces list --recipient [email protected] # Lookup by address\neve admin email bounces list --event-type Bounce --limit 50 --json\n```\n\n## Analytics Dashboard\n\nAll analytics are org-scoped. Return aggregate counters, not per-item listings. Use `--json` for machine-readable output.\n\n```bash\neve analytics summary --org org_xxx [--window 7d]\neve analytics jobs --org org_xxx [--window 7d]\neve analytics pipelines --org org_xxx [--window 7d]\neve analytics env-health --org org_xxx\n```\n\n| Endpoint | Returns |\n|---|---|\n| `summary` | Org-wide aggregate (jobs, pipelines, envs) |\n| `jobs` | Job counters: created, completed, failed, active |\n| `pipelines` | Pipeline success rates and durations |\n| `env-health` | Environment snapshot: total, healthy, degraded, unknown |\n\n`--window` accepts relative durations: `7d`, `24h`, `30d`.\n\n## Platform Sentinel\n\nPlatform Sentinel is the platform-side environment health monitor. It is separate from org analytics:\n\n- `eve analytics env-health --org \u003corg_id>`: org-scoped aggregate counts only\n- `eve system env-health`: cross-org detailed snapshot for `system_admin`\n\n```bash\neve system env-health [--status critical] [--limit 100] [--json]\neve system settings sentinel.enabled --json\neve system settings sentinel.slack.integration_id --json\neve system settings sentinel.slack.channel_id --json\n```\n\n`eve system env-health` returns:\n\n| Field | Meaning |\n|---|---|\n| `summary.total` | Number of tracked environments |\n| `summary.healthy/degraded/critical` | Current health counts |\n| `environments[].issues_json` | Active issue list (`image_pull_backoff`, `crash_loop_backoff`, `high_restarts`, `pending_too_long`) |\n| `environments[].consecutive_degraded_ticks` | How many watchdog ticks the environment has stayed non-healthy |\n| `environments[].actions_taken_json` | Circuit-breaker actions such as `scale_to_zero` |\n\nSentinel delivery behaviour:\n- Outbound alerts are suppressed unless `sentinel.enabled=true`\n- Slack delivery uses `sentinel.slack.integration_id` + `sentinel.slack.channel_id`\n- Recovery and circuit-break alerts bypass the normal dedup window\n- The watchdog can keep populating environment health rows even when Slack delivery is not configured\n\n## Cost Tracking\n\nEve tracks costs through execution receipts, resource classes, per-job budgets, and an org balance ledger.\n\n### Rate Cards\n\n```bash\neve admin pricing seed-defaults # Seed default rate cards (admin)\n```\n\nRate cards map model + provider to per-token costs. The platform uses these to compute receipt costs automatically.\n\n### Balance + Usage\n\n```bash\neve admin balance show \u003corg_id> # Current balance\neve admin balance credit \u003corg_id> --amount 100.00 # Add credit\neve admin balance transactions \u003corg_id> # Transaction history\n\neve admin usage list --org org_xxx # Usage records\n [--since 2026-01-01] [--until 2026-02-01]\neve admin usage summary --org org_xxx # Aggregated summary\n\neve org spend --org org_xxx # Org spend overview\neve project spend --project proj_xxx # Project spend\n```\n\n## Provider + Harness Discovery\n\n```bash\neve providers list [--json] # Registered providers\neve providers discover \u003cprovider> [--json] # Live model list (cached with TTL)\neve harness list [--capabilities] # Harness model support matrix\n```\n\n## OpenTelemetry Configuration\n\nOTEL is enabled when `OTEL_ENABLED=true` or `OTEL_EXPORTER_OTLP_ENDPOINT` is set. Uses OTLP HTTP exporter with automatic Node.js instrumentation.\n\n| Variable | Purpose |\n|---|---|\n| `OTEL_ENABLED` | `true`/`false` -- enable OTEL |\n| `OTEL_DISABLED` | `true` -- hard disable (overrides OTEL_ENABLED) |\n| `OTEL_EXPORTER_OTLP_ENDPOINT` | Collector endpoint (e.g. `http://otel-collector:4318`) |\n\nRequest trace lookup requires apps to stamp `request_id` on the active span.\nUse the shared helper from `@eve/shared`:\n\n```ts\nimport { stampCurrentRequestId } from '@eve/shared';\n\nstampCurrentRequestId(requestId);\n```\n\nAgents can query traces without AWS console access:\n\n```bash\neve traces query --project proj_xxx --request-id req_01h... --json\neve traces query --project proj_xxx --trace-id 1-abcdef...\neve traces query --project proj_xxx --service api --since 5m --error\neve traces query --project proj_xxx --service api --route \"POST /api/items\" --since 1h --p99\n```\n\n## Real-Time Monitoring\n\n```bash\neve job follow \u003cid> # Stream harness logs + live cost totals + silence detection\neve env logs \u003cproject> \u003cenv> \u003cservice> --follow --since 30 # Stream app service logs\neve job watch \u003cid> # Combined status + logs streaming\neve job runner-logs \u003cid> # K8s runner pod stdout/stderr\neve system status # Service health including agent runtime + replicas\neve system logs \u003cservice> [--tail 50] # Service logs (api, orchestrator, worker, agent-runtime, postgres)\neve system events [--limit 50] # Recent platform events\neve agents runtime-status --org \u003cid> # Pod health with stale markers and active job counts\n```\n\n### Harness Heartbeat + Silence Detection\n\nDuring job execution, the harness emits heartbeat lifecycle events every 30 seconds. These are used for:\n\n- **`eve job diagnose`**: Shows `Heartbeat: 15s ago (120s into execution)` in the Latest Attempt section. Stuck detection uses heartbeat age instead of raw elapsed time — a heartbeat within 120s means \"harness alive\" even if there's no log output.\n- **`eve job follow`**: Built-in silence timer warns at 60s and 120s of no output. If heartbeat data is available, the warning differentiates between \"harness alive but quiet\" and \"harness may have stalled\".\n- **`eve job logs`**: Heartbeat events appear in the log stream (type `lifecycle_runner`) with `kind: heartbeat`, `elapsed_ms`, `harness`, and `pid`.\n\n### Pre-Harness Startup Lifecycle\n\nThe agent-runtime emits lifecycle events for pre-harness phases:\n\n| Phase | Event Type | What It Captures |\n|-------|-----------|------------------|\n| Git clone | `workspace/start` + `workspace/end` | Duration, ref, success/failure with error |\n| Credential write | `secrets/start` + `secrets/end` | Duration, success (token produced?) |\n| App CLI discovery | `workspace/log` | CLI name, availability |\n\nThese appear in `eve job diagnose` as part of the latency waterfall. If clone or credential provisioning fails, the error is captured in the lifecycle event.\n\n## CLI Quick Reference\n\n| Intent | Command |\n|---|---|\n| Trace a request | Check `x-eve-correlation-id` in response headers |\n| Diagnose app request | `eve env diagnose \u003cproject> \u003cenv> --request \u003cid> --json` |\n| Stream app logs | `eve env logs \u003cproject> \u003cenv> \u003cservice> --follow --filter req_id=\u003cid>` |\n| Query traces | `eve traces query --project \u003cid> --request-id \u003cid> --json` |\n| Job cost receipt | `eve job receipt \u003cid>` |\n| Compare job costs | `eve job compare \u003cid1> \u003cid2>` |\n| Org analytics | `eve analytics summary --org \u003cid> --window 7d` |\n| Job metrics | `eve analytics jobs --org \u003cid>` |\n| Pipeline metrics | `eve analytics pipelines --org \u003cid>` |\n| Env health | `eve analytics env-health --org \u003cid>` |\n| Org balance | `eve admin balance show \u003corg_id>` |\n| Usage report | `eve admin usage summary --org \u003cid>` |\n| Recompute receipts | `eve admin receipts recompute [--dry-run]` |\n| Provider models | `eve providers discover \u003cprovider>` |\n| Stream job logs | `eve job follow \u003cid>` |\n| Platform events | `eve system events` |\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":13600,"content_sha256":"847abfa6e7c9eefd69e9d6961e0bdece2835f1bb7a47cce9dce70c03af726bb5"},{"filename":"references/overview.md","content":"# Eve Horizon Overview\n\n## Use When\n- You need first-time orientation for Eve Horizon concepts and IDs.\n- You need to confirm which entry doc and environment context to load first.\n- You need an overall roadmap of commands and reference selection before deep dives.\n\n## Load Next\n- `references/cli.md` for interface and command-level tasks.\n- `references/manifest.md` for architecture-to-config mapping.\n- `references/jobs.md` or `references/pipelines-workflows.md` for execution and automation tasks.\n\n## Ask If Missing\n- Confirm whether the user is on staging, local docker, or k3s/k3d stack.\n- Confirm whether local repos are synced and whether `./bin/eh status` has been run.\n- Confirm the target org/project context before giving prescriptive commands.\n\n## Load Eve Docs First\n\nBefore any work on or with Eve Horizon, load this skill:\n\n```\n/eve-read-eve-docs\n```\n\nThen review the references that match your task. Start here for architecture and conventions; open `references/cli.md`, `references/manifest.md`, or `references/jobs.md` for specifics.\n\n## Check Environment Status\n\nBefore ANY build, test, or development activity, run:\n\n```bash\n./bin/eh status\n```\n\nThis shows what environments are running, the correct `EVE_API_URL` for each, port configuration, and multi-instance isolation status. Never assume URLs or ports.\n\n### Environment Access\n\n| Environment | API URL | When to Use |\n|---|---|---|\n| K8s (k3d) | `http://api.eve.lvh.me` | Manual tests, deployment testing |\n| Docker Compose | `http://localhost:4801` | Integration tests, quick dev loop |\n| Local pnpm dev | `http://localhost:4801` | Hot-reload development |\n| Staging | `https://api.eh1.incept5.dev` | Production-like testing |\n\nNo port-forwarding required for K8s -- all services are accessible via Ingress.\n\n### No Direct AWS Infrastructure Changes\n\nAll AWS infrastructure changes must go through Terraform in the `incept5-eve-infra` repo. Never run AWS CLI commands that mutate infrastructure (security groups, IAM, DNS, EKS, ASGs, etc.). Terraform is authoritative -- out-of-band changes are silently reverted on the next `terraform apply`.\n\nIf staging infra is broken (API unreachable, SG rules wrong, DNS misconfigured):\n\n1. Diagnose with read-only commands (curl, dig, AWS CLI reads are fine).\n2. Fix in `../incept5-eve-infra/terraform/aws/`.\n3. Run `terraform plan` then `terraform apply` from that repo.\n4. Verify the plan shows \"No changes\" after apply.\n\nIf you lack access to the infra repo, escalate to the user -- do not apply a quick fix via AWS CLI.\n\n### Staging Kubeconfig Safety\n\nWhen operating the Incept5 staging EKS cluster:\n\n- **Only** use `../incept5-eve-infra/config/kubeconfig.yaml` as kubeconfig.\n- Prefer running operations from `../incept5-eve-infra` via `./bin/eve-infra ...`.\n- **Never** use `~/.kube/eve-staging.yaml` or the implicit default kube context for staging.\n- If direct `kubectl` is unavoidable, always pass both `--kubeconfig ../incept5-eve-infra/config/kubeconfig.yaml` and `--context arn:aws:eks:eu-west-1:767828750268:cluster/eh1-cluster`.\n\n## Developer Quick Start\n\n```bash\n./bin/eh status # 0. Check what's running\n./bin/eh k8s start && ./bin/eh k8s deploy # 1. Start k3d cluster + deploy\nexport EVE_API_URL=http://api.eve.lvh.me # 2. Set API URL\neve org ensure test-org --slug test-org # 3. Use the CLI\n./bin/eh k8s stop # 4. Clean up\n```\n\n## Current State (Implemented)\n\n**Phase**: Pre-MVP (K8s runtime + agent runtime + chat gateway + builds/deploy pipeline + auth/RBAC complete).\n\n**What exists**: monorepo with 6 services (API, orchestrator, worker, agent runtime, gateway, SSO). Database with orgs, projects, environments, jobs, attempts, agents, teams, threads, integrations, schedules. Full auth stack (SSH login, web auth via GoTrue + SSO broker, service principals, custom roles, access groups, project role resolution). RBAC with policy-as-code and default-deny data plane. Persistent environment deployment to K8s with manifest variable interpolation, HTTP ingress routing, and public TCP ingress for raw protocols. First-class builds (BuildKit), releases, and pipelines (build -> release -> deploy). Job execution with mclaude/claude/zai/gemini/code/codex harnesses via `eve-agent-cli`. BYOK inference model (apps and agents bring their own LLM API keys via secrets; no platform-managed models or inference proxy). Provider registry + model discovery. Agent/team/thread primitives with repo-first sync and AgentPacks (`x-eve.packs`). Chat gateway with Slack + Nostr integration. Agent runtime (primary harness executor for all agent jobs; org-scoped warm pods). Org filesystem sync + org docs. Document ingestion (file upload -> event -> agent processing -> structured output in org docs; content deduplication via fingerprint). Cloud FS integration (Google Drive per-org OAuth, agent file tools). Per-org OAuth credentials (orgs bring their own Google/Slack app credentials via `oauth_app_configs`). Private endpoints (Tailscale networking for reaching private services from cluster pods). App CLI framework (typed CLI wrappers for agent-to-app API interactions, reducing LLM call waste). Cost tracking (execution receipts, resource classes, budgets, balance ledger). Production hardening (content dedup, dead letter handling, per-phase latency diagnostics, routing decision logging, cost breakdown by agent/team, automatic retry policies, document expiration). Analytics, webhooks, and supervision primitives. Agent app API access (server-side `app_apis` in job hints, `@eve-horizon/auth` SDK). Workflow expansion into job DAGs with `depends_on` step dependencies. Org-aware auth SDK (memberships, org switching, project role resolution via `@eve-horizon/auth-react`). CLI as npm package (`@eve-horizon/cli`) and local `./bin/eh` helpers. K8s local stack via k3d.\n\n### Pre-Deployment Phase\n\nNo deployments to production. No real users. No backwards compatibility concerns. This means:\n\n1. **Simplify aggressively** -- if something can be simpler, make it simpler.\n2. **Refactor without fear** -- no migrations to maintain, no users to break.\n3. **Delete ruthlessly** -- dead code, unused abstractions, speculative features.\n4. **Question everything** -- every abstraction must earn its place.\n\n## What Eve Horizon Is\n\nEve Horizon is a **job-first platform** that runs AI-powered skills against Git repos:\n\n- **CLI-first**: humans and agents use the same CLI against the API.\n- **Job-centric**: all work is tracked as jobs with phases, priorities, and dependencies.\n- **Event-driven**: automation routes through an event spine in Postgres.\n- **Skills-based**: reusable capabilities live as `SKILL.md` files in repos; preferred flow via AgentPacks.\n- **Isolated execution**: each job attempt runs in a fresh workspace with a cloned repo.\n- **BYOK inference**: no managed models or inference proxy -- harnesses call providers directly via secrets.\n- **Auth-complete**: SSH login, web auth (GoTrue + SSO), service principals, custom roles, access groups, policy-as-code.\n- **Document ingestion**: file upload -> event -> agent processing -> structured output in org docs.\n- **Cloud FS**: Google Drive integration with per-org OAuth, agent file tools.\n- **Private endpoints**: Tailscale networking makes private services reachable from cluster pods.\n- **Staging-first**: default guidance targets staging; local dev is opt-in.\n\n## Architecture Summary\n\n```\nUser -> CLI -> API -> Orchestrator --+--> Agent Runtime --> Harness -> Agent\n | | | | (all agent jobs)\nChat -> Gateway + Postgres |\n +--> Worker\n (builds, deploys, pipelines, scripts)\n\nONLY URL needed: EVE_API_URL\nCLI is a thin wrapper; all control flows through the API.\n```\n\n**Services** (6 in the monorepo):\n\n- **API**: single gateway for org/project/job CRUD, secrets, events, runs, auth.\n- **Orchestrator**: polls ready jobs, routes to agent-runtime or worker based on job type.\n- **Agent Runtime**: executes ALL agent/harness jobs (chat, manual, scheduled) in org-scoped warm pods.\n- **Worker**: executes pipeline actions, scripts, and builds -- does NOT run agent jobs.\n- **Gateway**: routes inbound messages from Slack/Nostr to agents.\n- **SSO**: GoTrue + SSO broker for web authentication.\n\n**Key flows**:\n\n1. Create job -> API validates -> stored in DB.\n2. Orchestrator claims ready jobs -> routes by execution type.\n3. Agent jobs -> Agent Runtime invokes harness (mclaude/claude/zai/gemini/code/codex) -> streams JSONL logs.\n4. Chat flow: Slack -> Gateway -> API routes -> jobs + threads -> Agent Runtime executes.\n5. Build flow: pipeline step -> Worker -> BuildKit -> push images -> release -> deploy to K8s namespace.\n6. HITL review -> continue or complete.\n\n## Key Decisions\n\n| Decision | Rationale |\n|---|---|\n| 6 harnesses via eve-agent-cli | Uniform invocation for mclaude/claude/zai/gemini/code/codex |\n| Job (not Task) terminology | Avoids collision with cc-mirror's Task tools |\n| NestJS for backend | Clean architecture, TypeScript native |\n| Hierarchical job IDs | `{slug}-{hash8}` root, `{parent}.{n}` children |\n| Phase-based job lifecycle | idea -> backlog -> ready -> active -> review -> done |\n| Single repo per project | Simplifies execution and config |\n| CLI as thin REST wrapper | Single source of truth, no DB bypass |\n| API as single gateway | CLI needs only `EVE_API_URL` |\n| K8s runtime via k3d | Production-like local testing, runner pods for isolation |\n| Agent runtime runs ALL agent jobs | Primary harness executor; worker is for builds/pipelines only |\n| Chat gateway + Slack mapping | Multi-tenant `team_id -> org_id` routing |\n| Repo-first agents sync | Deterministic config via `--ref` |\n| Two-repo deploy model | Source builds images, infra repo applies K8s manifests |\n| BuildKit-first builds | Replaced kaniko; reliable in-cluster container builds |\n| BYOK inference (no managed models) | Harnesses call providers directly via secrets; no proxy layer |\n| Per-org OAuth credentials | Orgs bring own Google/Slack app credentials; no cluster-level secrets |\n| Private endpoints (Tailscale) | K8s pods reach Tailscale-only services via egress proxy |\n| GoTrue + SSO broker | Web auth via Supabase-compatible auth, dual-mode API auth |\n| Default-deny data plane | Members/agents/users need explicit group-scoped grants |\n\n## Conventions\n\n- **Org/Project IDs**: `org_xxx`, `proj_xxx` (TypeID format).\n- **Job IDs**: `{slug}-{hash8}` for root jobs (e.g., `myproj-a3f2dd12`).\n- **Job phases**: `idea` -> `backlog` -> `ready` -> `active` -> `review` -> `done`/`cancelled`.\n- **Priority**: 0-4 (P0=critical, P4=backlog, default=2).\n- **K8s namespaces**: `eve-{orgSlug}-{projectSlug}-{envName}` for deployments.\n\n### IDs and Formats\n\n| Entity | Format | Example |\n|---|---|---|\n| Org ID | `org_...` | `org_abc123` |\n| Project ID | `proj_...` | `proj_def456` |\n| Job ID (root) | `{slug}-{hash8}` | `myproj-a3f2dd12` |\n| Job ID (child) | `{parent}.{n}` | `myproj-a3f2dd12.1` |\n| Build ID | `bld_...` | `bld_xyz789` |\n| Release ID | `rel_...` | `rel_uvw012` |\n| Managed DB Instance | `mdbi_...` | `mdbi_01abc` |\n| Managed DB Tenant | `mdbt_...` | `mdbt_01def` |\n| Event ID | `evt_...` | `evt_ghi345` |\n| Pipeline Run ID | `prun_...` | `prun_jkl678` |\n| Service Principal | `sp_...` | `sp_abc123` |\n| SP Token | `spt_...` | `spt_def456` |\n| Access Role | `role_...` | `role_ghi789` |\n| Access Binding | `bind_...` | `bind_jkl012` |\n| Attempt | UUID + `attempt_number` | (1, 2, 3...) |\n\n## Development Workflow\n\n### Environment Status First\n\nAlways start here:\n\n```bash\n./bin/eh status\nexport EVE_API_URL=http://api.eve.lvh.me # for k8s\nexport EVE_API_URL=http://localhost:4801 # for docker/dev\n```\n\n### Build and Test\n\n```bash\npnpm install # Refresh workspace links\npnpm build # Full build (all packages and apps)\npnpm test # Unit tests (fast, no external deps)\n./bin/eh test integration # Integration tests (docker DB + local processes)\n```\n\n### Hot-Reload Development\n\n```bash\n./bin/eh start local # DB container + local node processes (hot-reload)\n./bin/eh start docker # All services in containers\n./bin/eh k8s start && ./bin/eh k8s deploy # K8s stack (manual testing)\n./bin/eh stop # Stop current mode\n```\n\nModes are exclusive -- starting a different mode stops the current one.\n\n### Manual Testing (Observable Tests)\n\nFor k8s stack testing, use manual test scenarios with parallel job watching. Read `tests/manual/README.md` before running.\n\n```bash\n./bin/eh status # Must show k8s cluster \"running\"\neve system health --json # Must return {\"status\":\"ok\"}\neve org ensure \"manual-test-org\" --slug manual-test-org --json\neve secrets import --org org_manualtestorg --file manual-tests.secrets\n# Run scenarios from tests/manual/scenarios/\neve job follow \u003cjob-id> # Stream logs in real-time\n```\n\n### CLI-First Debugging\n\nDebug via the Eve CLI first. Always. Our clients do not have kubectl access -- replicate their experience. Every debugging gap is a product improvement opportunity.\n\n**Debugging ladder**:\n\n| Priority | Tool | When to Use |\n|---|---|---|\n| 1st | `eve` CLI | Always start here -- job status, logs, diagnose |\n| 2nd | `./bin/eh status` | Environment health, connectivity |\n| 3rd | `kubectl` | Only when CLI is insufficient -- then file an issue |\n\n**CLI debugging commands**:\n\n```bash\neve system health --json\neve job list --all --phase active\neve job show \u003cid> --verbose\neve job follow \u003cid> # Stream harness logs\neve job logs \u003cid> # Historical logs\neve job result \u003cid> # Exit status + outputs\neve job diagnose \u003cid> # Full diagnostic dump\neve env show \u003cproject> \u003cenv> # Deployment health\n```\n\n### Build/Deploy Debugging Ladder\n\n| Priority | Command | What It Shows |\n|---|---|---|\n| 1st | `eve pipeline logs \u003cpipeline> \u003crun-id> --follow` | Real-time streaming of all steps |\n| 2nd | `eve pipeline logs \u003cpipeline> \u003crun-id>` | Snapshot with inline errors + hints |\n| 3rd | `eve build diagnose \u003cbuild_id>` | Full build state (last 30 lines of buildkit output) |\n| 4th | `eve env diagnose \u003cproject> \u003cenv>` | K8s deployment diagnostics |\n| 5th | `eve job diagnose \u003cjob_id>` | Full job execution details |\n\nPipeline build failures include the build ID in error output. Use `eve build diagnose \u003cbuild_id>` to see the failed Dockerfile stage.\n\n### Multi-Instance Support\n\nEve Horizon supports multiple repo checkouts running integration tests in parallel:\n\n| Resource | Isolation | Shared |\n|---|---|---|\n| Docker Compose | Per-instance (via `EVE_INSTANCE` prefix) | -- |\n| Postgres | Per-instance volume | -- |\n| Ports | Per-instance (via `base_port`) | -- |\n| k3d Cluster | -- | Shared (one `eve-local` cluster) |\n\nDefault ports: API=4801, Orchestrator=4802, DB=4803, Worker=4811. Config stored in `.eve-horizon.yaml` (gitignored). Check `./bin/eh status` for k8s ownership status before touching the shared cluster.\n\n## Testing Architecture\n\nThree-tier test pyramid:\n\n| Tier | Type | What It Tests | Environment | Command |\n|---|---|---|---|---|\n| 1 | Unit | Pure logic, validators | None | `pnpm test` |\n| 2 | Integration | API, job flows, secrets | Docker DB + local pnpm | `./bin/eh test integration` |\n| 3 | Manual | Happy paths, real repos | K8s stack | See `tests/manual/` |\n\nIntegration tests use API endpoints, not direct DB queries. Test and dev use separate databases (`eve` for dev, `eve_test` for integration).\n\n## Sister Repositories\n\n| Repo | Expected Path | Purpose |\n|---|---|---|\n| incept5-eve-infra | `../incept5-eve-infra` | K8s manifests, kustomize overlays, deploy automation |\n| eve-horizon-starter | `../eve-horizon-starter` | Starter template for new Eve projects |\n| eve-horizon-fullstack-example | `../eve-horizon-fullstack-example` | Example fullstack app for deployment testing |\n| eve-skillpacks | `../eve-skillpacks` | Published skill packs referenced by `skills.txt` |\n\nAgents are free to make changes, commit, and push to `main` in these repos without explicit approval -- this is part of the normal development flow.\n\n### Skillpacks Sync Obligation\n\nWhen platform behavior changes in eve-horizon, the corresponding reference file in `eve-skillpacks/eve-work/eve-read-eve-docs/references/` must be updated. If `eve-read-eve-docs` is outdated, agents will make incorrect assumptions. See the `eve-docs-upkeep` skill for the full audit checklist.\n\n## Eve Dashboard (System App)\n\nEve ships a first-party web dashboard (`apps/dashboard`) — a single-shell SSO-authenticated UI with real-time job/phase views, environment health, spending, and admin surfaces. It is built and deployed through the same build/release/deploy pipeline as customer apps.\n\nThis proves the **system app pattern**: platform-shipped apps go through the standard publish-images CI matrix and infra-repo overlays, with a single platform version tag from `config/platform.yaml`. New system apps follow the checklist in `eve-horizon/docs/system-apps.md`. See `references/deploy-debug.md` for the deployment flow and the `eve-fullstack-app-design` skill for design guidance.\n\n## Build -> Release -> Deploy\n\nThe standard deployment flow:\n\n1. **Build**: create container images from source code (BuildKit-based).\n2. **Release**: capture a deployable snapshot (SHA + manifest + digests).\n3. **Deploy**: apply release to an environment.\n\nStaging deploys use a **two-repo model**: source repo (`eve-horizon`) builds and pushes images on `release-v*` tags, then dispatches to the infra repo (`incept5-eve-infra`) which applies K8s manifests.\n\nPipelines orchestrate these steps as a job graph. See `references/builds-releases.md`.\n\n## Event Spine\n\nEvents are stored in Postgres and routed by the orchestrator:\n\n| Source | Description |\n|---|---|\n| `github` | Webhook events (push, pull_request) |\n| `slack` | Chat messages and app mentions |\n| `cron` | Scheduled triggers |\n| `system` | Auto-emitted failure events (job.failed, pipeline.failed) |\n| `runner` | Worker execution events (started, progress, completed, failed) |\n| `manual` | User-created via CLI/API |\n| `app` | Application-emitted |\n| `chat` | Chat system events |\n\nTriggers in the manifest map events to pipeline runs or workflow jobs. See `references/events.md`.\n\n## Agents, Teams, and Chat\n\n- **Agents**: defined in `agents.yaml`, synced via `eve agents sync`.\n- **Teams**: defined in `teams.yaml`, coordinate multiple agents (fanout/council/relay).\n- **Chat routing**: defined in `chat.yaml`, maps messages to agents via regex patterns.\n- **Slack**: `@eve \u003cagent-slug> \u003ccommand>` routing.\n- **Nostr**: relay-based subscription transport.\n\nSee `references/agents-teams.md`.\n\n## Skills System\n\nReusable capabilities installed via `skills.txt` manifest:\n\n- `SKILL.md` files with frontmatter metadata.\n- Installed to `.agents/skills/` at clone time (legacy: `.agent/skills/`).\n- Workers run `.eve/hooks/on-clone.sh` to install skills.\n- Preferred flow: AgentPacks via `x-eve.packs` + `.eve/packs.lock.yaml`.\n\nSee `references/skills-system.md`.\n\n## Key Rules\n\n- **CLI only needs `EVE_API_URL`**; everything routes through the API.\n- **Default to staging** for user guidance unless explicitly asked for local dev.\n- **Pipelines and workflows are manifest-defined** and materialize into jobs.\n- **Git controls live on jobs** (ref, branch, commit/push policies).\n- **Agent slugs are org-unique** (enforced at sync time).\n- **Secrets scopes stack**: project > user > org > system.\n- **Service principals** provide machine identity for app backends (scoped JWT tokens).\n- **Custom roles** are additive overlays on member/admin/owner (no deny rules).\n- **Policy-as-code** via `.eve/access.yaml` enables reviewable, CI-friendly access config.\n- **Fail fast on provisioning errors** -- swallow no errors, fix root causes.\n\n## Reference Index\n\n| Topic | Reference File |\n|---|---|\n| CLI commands | `references/cli.md` |\n| Manifest schema | `references/manifest.md` |\n| Events + triggers | `references/events.md` |\n| Jobs | `references/jobs.md` |\n| Builds + releases | `references/builds-releases.md` |\n| Agents, teams, chat | `references/agents-teams.md` |\n| Pipelines + workflows | `references/pipelines-workflows.md` |\n| Secrets + auth | `references/secrets-auth.md` |\n| Skills system | `references/skills-system.md` |\n| Deploy + debug | `references/deploy-debug.md` |\n| Harnesses | `references/harnesses.md` |\n| Gateway plugins | `references/gateways.md` |\n| Observability + cost | `references/observability.md` |\n| Database operations | `references/database-ops.md` |\n| Troubleshooting | `references/troubleshooting.md` |\n| App CLIs | `references/app-cli.md` |\n| Eve SDK | `references/eve-sdk.md` |\n| Auth SDK | `references/auth-sdk.md` |\n| Integrations | `references/integrations.md` |\n| Object store + filesystem | `references/object-store-filesystem.md` |\n| Document ingest | `references/ingest.md` |\n\n**Note:** These references are distilled from the Eve Horizon system docs. For narrative walkthroughs and tutorials, see the [eve-horizon](https://github.com/incept5/eve-horizon) repository docs.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":21260,"content_sha256":"f879077033c6b8eb1be37db1f5a93d6b6abb9c9b5b265adc8db47dba5e462130"},{"filename":"references/pipelines-workflows.md","content":"# Pipelines + Workflows (Current)\n\n## Use When\n- You need to define, run, inspect, or debug pipeline and workflow automation.\n- You need trigger wiring for environment deploy and event-based job orchestration.\n- You need guidance on build-release-deploy and promotion patterns.\n\n## Load Next\n- `references/events.md` if the trigger source is webhook or scheduled.\n- `references/builds-releases.md` for image/release semantics and diagnostics.\n- `references/cli.md` for pipeline/workflow execution commands.\n\n## Ask If Missing\n- Confirm pipeline/workflow name, target env, and repo ref/hash.\n- Confirm whether you want standard pipeline execution or direct deploy mode.\n- Confirm which inputs/outputs are required before creating or re-running steps.\n\n## Pipelines (Manifest)\n\nPipelines are ordered steps that expand into a job graph. Define them in `.eve/manifest.yaml`.\n\n```yaml\npipelines:\n deploy-test:\n trigger:\n github:\n event: push\n branch: main\n steps:\n - name: build\n action: { type: build }\n - name: unit-tests\n script: { run: \"pnpm test\", timeout_seconds: 1800 }\n - name: deploy\n depends_on: [build, unit-tests]\n action: { type: deploy }\n```\n\n### Canonical Pipeline Pattern\n\nThe standard build-release-deploy pipeline:\n\n```yaml\nsteps:\n - name: build\n action: { type: build }\n # Creates BuildSpec + BuildRun, outputs build_id + image_digests\n - name: release\n depends_on: [build]\n action: { type: release }\n # References build_id, uses digest-based image refs from BuildArtifacts\n - name: deploy\n depends_on: [release]\n action: { type: deploy, env_name: staging }\n```\n\nWhen a project includes persistent DB state, the deploy pipeline must run migrations before deploy:\n\n```yaml\nsteps:\n - name: build\n action: { type: build }\n - name: release\n depends_on: [build]\n action: { type: release }\n - name: migrate\n depends_on: [release]\n action:\n type: job\n service: migrate\n - name: deploy\n depends_on: [migrate]\n action: { type: deploy, env_name: sandbox }\n```\n\nPlace a `migrate` service in `services` with `x-eve.role: job`, and make `deploy` depend on it.\nThat ensures `presence/projects/other-schema` tables are created before pods start serving traffic.\n\n### Step Output Linking\n\nUnderstand how data flows between pipeline steps:\n\n- The `build` action creates BuildSpec and BuildRun records. On success, it emits `build_id` and `image_digests` as step outputs.\n- BuildRuns produce BuildArtifacts containing per-service image digests (`sha256:...`).\n- The `release` action automatically receives `build_id` from the upstream build step. It derives `image_digests_json` from BuildArtifacts, ensuring immutable digest-based image references.\n- The `deploy` action references images by digest for deterministic, reproducible deployments.\n\nThis chain ensures that what was built is exactly what gets released and deployed -- no tag mutation, no ambiguity.\n\n### Step Types\n\n- **action**: built-in actions (`build`, `release`, `deploy`, `run`, `job`, `create-pr`, `notify`, `env-ensure`, `env-delete`)\n- **script**: shell command executed by worker (`run` or `command` + `timeout_seconds`)\n- **agent**: AI agent job (prompt-driven)\n- **run**: shorthand for `script.run`\n\nScript steps and `action: { type: run }` commands run durably: the\norchestrator submits the job to the worker with a short request, the worker\nexecutes bash in the background, and completion is reported with runner events.\nLong-running commands do not depend on an open HTTP request between\norchestrator and worker.\n\nTimeout source order:\n- pipeline/workflow `script:` or shorthand `run`: `script.timeout_seconds`\n persisted as `jobs.script_timeout_seconds`, then `jobs.hints.timeout_seconds`,\n then the 30-minute default.\n- pipeline `action: { type: run }`: `action.timeout_seconds`, then\n `action.timeout`, then `jobs.hints.timeout_seconds`, then the 30-minute\n default.\n\nWorker stdout/stderr are streamed into attempt logs while the process runs.\nThe worker stores a bounded tail for the final step output and drains excess\noutput after the per-stream cap (`EVE_SCRIPT_OUTPUT_CAP_BYTES`, default 10 MiB)\nwith an `output_truncated` warning.\n\nPipeline root and step definitions can declare `toolchains` with valid values\n`python`, `media`, `rust`, `java`, and `kotlin`. Script, shorthand `run`,\nagent, and `action: { type: run }` steps resolve `step.toolchains >\npipeline.toolchains > []`; the resolved value is stored on\n`jobs.hints.toolchains`. Other action types cannot declare step-level\ntoolchains, and `action.toolchains` is rejected.\n\nPipeline root and step definitions can also declare `env_overrides` for\n`action: { type: run }` steps. The persisted action-run job receives the merged\nmap with step keys overriding pipeline keys; other action types ignore\n`env_overrides` because they are platform operations rather than user shell.\n`${secret.KEY}` values are resolved in memory before bash starts, and the job\nrow keeps the raw placeholder text for audit.\n\n### Pipeline Runs\n\n- A run creates one job per step with dependencies wired from `depends_on`.\n- Run IDs: `prun_xxx`.\n- Pipeline runs use the job-graph expander by default.\n- `eve pipeline run --only \u003cstep>` runs a subset of steps.\n- A failed job marks the run as failed and cascades cancellation to dependents.\n- Cancelled jobs are terminal and unblock downstream jobs.\n- Effective `env_name` resolution: `request.env_name` > `pipeline.env` > null. If\n the pipeline definition declares `env: staging` and the caller omits\n `--env`, the run and its `deploy`/`job` steps inherit `staging`. Without this\n fallback, env-bound steps fail with `Job action requires env_name`.\n\n### CLI\n\n```bash\neve pipeline list [project]\neve pipeline show \u003cproject> \u003cname>\neve pipeline run \u003cname> --ref \u003csha> --env \u003cenv> --inputs '{\"k\":\"v\"}' --repo-dir ./my-app\neve pipeline runs [project] --status \u003cstatus>\neve pipeline show-run \u003cpipeline> \u003crun-id>\neve pipeline approve \u003crun-id>\neve pipeline cancel \u003crun-id> [--reason \u003ctext>]\neve pipeline logs \u003cpipeline> \u003crun-id> [--step \u003cname>]\n```\n\nNotes:\n- `--ref` must be a 40-character SHA, or a ref resolved against `--repo-dir`/cwd.\n\n### Auto-Trigger Environment-Linked Pipelines\n\nWhen an environment references a pipeline (`environments.\u003cenv>.pipeline: deploy`) and that pipeline has no explicit `trigger` block, the platform creates an implicit trigger: the pipeline fires automatically on `github.push` to the project's default branch.\n\nEnvironments can override the branch with `environments.\u003cenv>.branch`. Set `auto_deploy: false` to disable implicit triggering for a specific environment.\n\nIf the pipeline already has an explicit `trigger` block, the implicit trigger is skipped (user controls triggering).\n\n### Env Deploy as Pipeline Alias\n\nIf `environments.\u003cenv>.pipeline` is set, `eve env deploy \u003cenv> --ref \u003csha>` triggers the pipeline.\nUse `--direct` to bypass. `--ref` must be a 40-character SHA, or a ref resolved\nagainst `--repo-dir`/cwd.\n\n### Promotion Pattern\n\n1. Deploy to test (creates release):\n `eve env deploy test --ref \u003csha>`\n2. Resolve release:\n `eve release resolve vX.Y.Z`\n3. Deploy to staging/production with:\n `eve env deploy staging --ref \u003csha> --inputs '{\"release_id\":\"rel_xxx\"}'`\n\nThis enables build-once, deploy-many promotion workflows without rebuilding images.\n\n## Pipeline Logs and Streaming\n\n### Snapshot Logs\n\nView build and execution logs (not just metadata) with timestamps and step name prefixes:\n\n```bash\neve pipeline logs \u003cpipeline> \u003crun-id> # All step logs\neve pipeline logs \u003cpipeline> \u003crun-id> --step \u003cname> # Single step\n```\n\n### Live Streaming\n\nStream logs in real time via SSE:\n\n```bash\neve pipeline logs \u003cpipeline> \u003crun-id> --follow # All steps\neve pipeline logs \u003cpipeline> \u003crun-id> --follow --step \u003cname> # Single step\n```\n\nOutput format:\n\n```\n[14:23:07] [build] Cloning repository...\n[14:23:09] [build] buildkit addr: tcp://buildkitd.eve.svc:1234\n[14:23:15] [build] [api] #5 [dependencies 1/4] COPY pnpm-lock.yaml ...\n[14:24:01] [deploy] Deployment started; waiting up to 180s\n[14:24:12] [deploy] Deployment status: 1/1 ready\n```\n\nFor script and `action: { type: run }` steps, stdout/stderr lines appear as\nattempt log entries before the command exits. Use `--follow` when supervising a\nlong-running shell command; a quiet but still-running command remains active\nuntil its configured timeout.\n\n### Failure Hints\n\nWhen a build step fails, the CLI automatically shows:\n- The error type and classification\n- An actionable hint (e.g., `Run 'eve build diagnose bld_xxx'`)\n- The build ID for cross-referencing\n\n### Pipeline-to-Build Linkage\n\nPipeline steps of type `build` create build specs and runs. On failure:\n1. The pipeline step error includes the build ID.\n2. The CLI prints a hint to run `eve build diagnose \u003cbuild_id>`.\n3. Build diagnosis shows the full buildkit output and the failed Dockerfile stage.\n\n## Workflow Definitions\n\nWorkflows are defined in the manifest and invoked as jobs. For pack distribution, also define workflows in `eve/workflows.yaml` and reference it from `eve/pack.yaml` via `imports.workflows`. Pack workflows are merged with manifest workflows at sync time — pack definitions take precedence on name collision.\n\n```yaml\nworkflows:\n nightly-audit:\n db_access: read_only\n hints:\n gates: [\"remediate:proj_xxx:staging\"]\n steps:\n - agent:\n prompt: \"Audit error logs and summarize anomalies\"\n```\n\n### Workflow Files and Prompt Files\n\nLarge workflows can be split into repo-local files. Keep one directory per\nworkflow and reference it from the manifest:\n\n```text\n.eve/workflows/\n alltrack-make-plan/\n workflow.yaml\n prompts/\n plan.md\n review.md\n```\n\n```yaml\nworkflows:\n alltrack-make-plan:\n $ref: .eve/workflows/alltrack-make-plan\n```\n\nIf `$ref` points to a directory, `eve project sync` and\n`eve manifest validate` load `workflow.yaml` or `workflow.yml` from that\ndirectory. `$ref` may also point directly to a YAML workflow file. References\nare expanded before sync; the API stores the expanded workflow and rejects\nunresolved `$ref` values.\n\nIn `workflow.yaml`, use `agent.prompt_file` for long Markdown prompts:\n\n```yaml\nsteps:\n - name: plan\n agent:\n name: alltrack-planner\n prompt_file: prompts/plan.md\n```\n\nPrompt files are resolved relative to the workflow file directory, read\nverbatim, and expanded into `agent.prompt`.\n\n### Multi-Step Workflow Expansion\n\nWorkflows compile to a full job DAG at invocation time. A multi-step workflow creates 1 root container job + N child step jobs with dependency ordering.\n\n```yaml\nworkflows:\n ingestion-pipeline:\n with_apis:\n - service: coordinator\n description: Coordinator API for orchestration\n steps:\n - name: prepare\n script:\n run: \"eve job list --json\"\n timeout_seconds: 60\n - name: ingest\n depends_on: [prepare]\n agent:\n name: ingestion\n - name: extract\n depends_on: [ingest]\n agent:\n name: extraction\n - name: review\n depends_on: [extract]\n agent:\n name: reviewer\n```\n\n**How it works:**\n- Each step becomes a child job under the root workflow job.\n- `depends_on: [step_names]` wires dependency as `blocks` relations -- the scheduler respects them.\n- Each step must define exactly one execution kind: `agent`, `script`, or\n shorthand `run`. `script` and `run` steps create worker-executed script jobs\n with `script_command` and optional `script_timeout_seconds`. Execution is\n durable and streams stdout/stderr to attempt logs like pipeline script steps.\n- Per-step agent, harness, and toolchain resolution is supported. Script and\n shorthand `run` steps resolve `step.toolchains > workflow.toolchains > []`;\n agent steps resolve `step.toolchains > agent config toolchains >\n workflow.toolchains > []`. Workflow `action` steps remain unsupported and\n cannot declare toolchains.\n- `with_apis` can be set at the workflow level (applies to all steps) or per step.\n- `env_overrides` can be set at the workflow level and per step, then overridden\n at invocation time with `--env-override`.\n- When a service declares `x-eve.cli`, agents also get the CLI binary on `$PATH`. See `references/app-cli.md`.\n\n### Resource Propagation Between Steps\n\nAll workflow steps receive the invocation `resource_refs` by default, including\nsteps with `depends_on`. Resources are hydrated into `.eve/resources/` in each\nstep's workspace automatically.\n\nControl access with `resource_refs` at workflow level or step level:\n\n```yaml\nworkflows:\n create-design:\n resource_refs: inherit # optional default; \"all\" is an alias\n steps:\n - name: read-sources\n agent: { name: designer }\n - name: publish\n depends_on: [read-sources]\n resource_refs: none\n agent: { name: publisher }\n\n scoped-review:\n resource_refs: [brief, design-system]\n steps:\n - name: review\n agent: { name: reviewer }\n```\n\nAccepted values:\n- `inherit` / `all`: pass all invocation refs.\n- `none`: pass no invocation refs.\n- string array: pass refs whose `name`, `label`, `mount_path`, `uri`, or `metadata.name` matches a selector.\n- object form: `{ mode: selected, include: [...] }`.\n\nStep-level `resource_refs` overrides workflow-level `resource_refs`. The root\nworkflow job still records the full invocation refs for audit. The invoke\nresponse includes `step_jobs[].resource_refs` with the effective mode, source\n(`default`, `workflow`, or `step`), inherited count, selected count, selectors,\nand missing selectors.\n\n### Step Git Controls\n\nWorkflow steps can declare per-step `git` controls (workspace ref, branch,\ncommit, push behavior). Workflow-level `git` is the default; step-level `git`\noverrides individual fields. String fields (`ref`, `branch`, `commit_message`,\n`remote`) accept `${inputs.\u003cname>}` and `${event.payload.\u003cdotted.path>}`\ntemplate expressions.\n\n```yaml\nworkflows:\n branch-per-pr:\n inputs:\n pr_number:\n from: event.payload.pull_request.number\n git:\n ref_policy: explicit\n remote: origin\n steps:\n - name: prepare\n agent: { name: preparer }\n git:\n branch: \"review/pr-${inputs.pr_number}\"\n create_branch: if_missing\n commit: manual\n - name: publish\n depends_on: [prepare]\n agent: { name: publisher }\n git:\n push: on_success\n commit_message: \"chore: review notes for PR #${inputs.pr_number}\"\n```\n\nAvailable fields (all optional): `ref`, `ref_policy` (`auto` | `env` |\n`project_default` | `explicit`), `branch`, `create_branch` (`never` |\n`if_missing` | `always`), `commit` (`never` | `manual` | `auto` | `required`),\n`commit_message`, `push` (`never` | `on_success` | `required`), `remote`.\n\nWorkflow retry replays already-materialized step git controls verbatim, so\nretried steps land on the same branch and ref the original step used.\n\n### Workflow Env Overrides\n\nWorkflow agent, script, and shorthand `run` steps can receive secret-backed\nenvironment overrides without hand-building a job DAG. The effective step-job\n`env_overrides` object is merged by key with this precedence: invocation request\n> step YAML > workflow YAML.\n\n```yaml\nworkflows:\n research:\n env_overrides:\n WEB_SEARCH_API_KEY: ${secret.WEB_SEARCH_API_KEY}\n steps:\n - name: search\n agent: { name: researcher }\n - name: publish\n depends_on: [search]\n env_overrides:\n PUBLISH_API_KEY: ${secret.PUBLISH_API_KEY}\n agent: { name: publisher }\n```\n\n```bash\neve workflow run research --env-override WEB_SEARCH_API_KEY='${secret.WEB_SEARCH_API_KEY}'\neve workflow invoke research --env-override MODE=diagnostic\neve harness validate --project \u003cproject> --workflow research --env-override MODE=diagnostic\n```\n\nThe schema is the same as direct job `env_overrides`: keys must be\n`UPPER_SNAKE_CASE`, reserved Eve runtime variables cannot be overridden, values\nmay contain `${secret.KEY}` placeholders only, and unsupported `${env.X}` style\nexpressions are rejected. `eve manifest validate --validate-secrets` and\n`eve project sync --validate-secrets` include workflow env override secret refs\nin missing-secret reports.\n\nAt execution time, resolved values are injected into the agent harness or bash\nenvironment. Missing secret placeholders fail the step before bash/harness\nexecution with `missing_secret_override`; logs preserve the missing key names,\nwhile `eve job show \u003cjob> --json` still returns the unresolved placeholders.\n\n### Workflow Token Scope (Scoped Job Tokens)\n\nWorkflow and step `scope` blocks narrow a step job's API authority and org\nfilesystem mount, so a step granted `cloud_fs:read` can only read the specific\nmounts/prefixes the scope allows. Supported axes match access binding scope\nJSON: `orgfs`, `orgdocs`, `envdb`, and `cloud_fs`.\n\n```yaml\nworkflows:\n scoped-review:\n scope:\n orgfs:\n allow_prefixes: [/groups/projects/proj-a/**]\n steps:\n - name: review\n agent: { name: reviewer }\n scope:\n cloud_fs:\n allow_mount_ids: [mount_a]\n```\n\n**Propagation chain** (parallels `env_overrides`): workflow `scope` → step\n`scope` → invocation `scope` (API body or `WorkflowInvokeRequest.scope`). All\nthree are intersected per executable step job and persisted as\n`jobs.token_scope`; empty intersections fail closed. The orchestrator uses the\nsame scope to mint the job token and to materialize the step's `.org` mount.\n\nRequest-supplied scope requires `jobs:harness_override`. There is no\n`eve workflow run --scope-*` or `eve job create --scope-*` flag yet — use the\nmanifest `scope` block or the workflow invoke API body. `eve manifest validate`\nrejects malformed `scope` payloads at sync time.\n\n#### Scope narrows; permission grants\n\n`scope` is a *narrowing* mechanism, not a granting one. The step's job token\nneeds the underlying resource permission for the scope to be exercisable:\n\n| Axis declared in `scope` | Permission the step's agent must declare |\n|---|---|\n| `orgfs.allow_prefixes` | `orgfs:read` (and `orgfs:write` if the step writes) |\n| `orgfs.read_only_prefixes` | `orgfs:read` |\n| `orgdocs.allow_prefixes` | `orgdocs:read` (and `orgdocs:write` if the step writes) |\n| `envdb.{schemas,tables}` | `envdb:read` / `envdb:write` |\n| `cloud_fs.allow_mount_ids` | `cloud_fs:read` (and `cloud_fs:write` if the step writes) |\n\nThese permissions are **not** in `DEFAULT_AGENT_PERMISSIONS` — declare them on\nthe step's agent in `agents.yaml` under `access.permissions` (see\n`references/agents-teams.md` § Agent Permissions). A workflow that declares\n`scope.orgfs.allow_prefixes` without a matching `orgfs:read` on the agent\nproduces a correctly-scoped token that has no permission to act, and orgfs API\ncalls fail with `Missing required permission: orgfs:read`.\n\nExample pairing:\n\n```yaml\n# agents.yaml\nagents:\n reviewer:\n skill: review\n access:\n permissions: [orgfs:read] # required to exercise the workflow scope\n\n# workflows section\nworkflows:\n scoped-review:\n scope:\n orgfs:\n allow_prefixes: [/groups/projects/proj-a/**]\n steps:\n - name: review\n agent: { name: reviewer }\n```\n\nSee `references/jobs.md` for the per-job `token_scope` view and\n`references/manifest.md` for the manifest field shape.\n\n#### Per-step `permissions:` for non-agent steps\n\nPipeline `script:` and `action: { type: run }` steps, plus workflow `script:`,\n`run:`, and `agent:` steps, accept a `permissions: [...]` list with the same ergonomics as\n`scope:`. The expander resolves it (step-level wins over\npipeline/workflow-level), persists it on `jobs.token_permissions`, and\nthe script/action-run executors mint `EVE_JOB_TOKEN` with that exact list\nplus `~/.eve/credentials.json` so the Eve CLI authenticates inside the\nstep. When the step does not declare `permissions:`, the executor falls\nback to its default (broad for `script:`, narrow read-only for\n`action: { type: run }` — see `references/secrets-auth.md`).\n\n```yaml\npipelines:\n scoped-jobs-only:\n permissions: [jobs:read] # pipeline-level default\n steps:\n - name: list-jobs\n script:\n run: eve job list --project $EVE_PROJECT_ID --json\n\n - name: post-thread\n permissions: [jobs:read, threads:write] # narrow override\n action:\n type: run\n command: |\n eve thread post coord:job:$EVE_PARENT_JOB_ID --message \"step done\"\n```\n\nThe expander rejects any permission the invoking actor does not hold\n(`assertActorCanGrantPermissions`) — workflow authors can narrow but\nnever escalate.\n\n### Prior Step Result Injection\n\nWhen a workflow step has `depends_on`, the orchestrator injects the completed dependency's `result_text` into the step's job description at dispatch time. This means downstream agents receive upstream outputs without making API calls.\n\n- Injected as a `## Prior Step Results` section in the job description\n- Each prior step's result appears under a `### Step: \u003cname> (\u003cjob_id>)` heading\n- Capped at 50KB per step to avoid prompt bloat\n- If a step has multiple dependencies, all completed results are included\n\n### Per-Step `with_apis` Overrides\n\nIndividual workflow steps can override the workflow-level `with_apis` declaration:\n\n```yaml\nworkflows:\n pipeline:\n with_apis:\n - service: coordinator\n description: Coordinator API for orchestration\n steps:\n - name: ingest\n agent: { name: ingestion }\n # Inherits with_apis from workflow level\n - name: transform\n with_apis:\n - service: coordinator\n description: Coordinator API for orchestration\n - service: analytics\n description: Analytics API for data processing\n agent: { name: transformer }\n # Uses its own with_apis, overriding workflow level\n```\n\nSteps without their own `with_apis` inherit from the workflow level.\n\n### Non-Chat Workflow Notifications\n\nWorkflow steps that need to announce completion to Slack should use\n`eve notifications send`; do not read integrations or handle raw Slack bot\ntokens in the job:\n\n```bash\neve notifications send \\\n --project \u003cproject_id_or_slug> \\\n --channel eve-horizon-notifications \\\n --message \"Published PR: https://github.com/org/repo/pull/123\"\n```\n\nThe command calls `POST /projects/:project_id/notifications/send` and requires\nthe step's job token to include `notifications:send`. Grant that permission to\nthe publishing agent in `agents.yaml`:\n\n```yaml\nagents:\n publisher:\n access:\n permissions:\n - notifications:send\n```\n\n**Validation rules** (enforced by `eve manifest validate` and `eve project sync`):\n- Duplicate step names → error.\n- Cyclic dependencies → error (reports the cycle path).\n- Invalid `depends_on` references (non-existent step name) → error.\n- Trigger with no recognized type key → warning.\n- Invalid GitHub event type (not `push` or `pull_request`) → warning.\n- Unknown system event type → warning (advisory, custom events allowed).\n- Cron trigger with missing schedule → warning.\n\nTrigger validation runs during `eve project sync` (not just `eve manifest validate`), so malformed triggers are surfaced immediately.\n\n### Conditional Steps\n\nSteps can declare a `condition` that's evaluated when dependencies complete. If false, the step is skipped without running an agent.\n\n```yaml\nworkflows:\n smart-edit:\n steps:\n - name: triage\n agent: { name: fast-triage }\n - name: deep-analysis\n depends_on: [triage]\n condition: \"triage.status == 'complex'\"\n agent: { name: deep-analyzer }\n```\n\n**Condition format:** `step_name.status == 'value'` or `step_name.status != 'value'`.\nEvaluates against `result_json.eve.status` of the referenced step.\n\n**Rules:**\n- Referenced step must exist in the workflow and be in `depends_on`\n- Skipped steps are marked `done` with `close_reason: 'condition_not_met'`\n- Downstream steps that depend on a skipped step still become eligible\n- Condition validation runs at sync time (format, reference, dependency checks)\n\n**Use case — triage escalation:** A fast agent (low reasoning) classifies task complexity. An expert agent (high reasoning) only runs for complex tasks. Simple tasks are handled entirely by the triage step.\n\n### Step-Level Harness and harness_options\n\nWorkflow steps can override harness selection and pass per-step harness tuning\nwithout referencing a named profile. Useful for inline prompts that need a\nspecific model or `reasoning_effort` for one step only.\n\n```yaml\nworkflows:\n triage-then-fix:\n steps:\n - name: triage\n harness: claude\n harness_options:\n model: claude-haiku-4\n reasoning_effort: minimal\n agent:\n name: triager\n - name: fix\n depends_on: [triage]\n harness: claude\n harness_options:\n model: claude-opus-4\n reasoning_effort: high\n agent:\n name: fixer\n```\n\nStep-level `harness` and `harness_options` take precedence over agent-resolved\nvalues, matching the override pattern used by `toolchains`. `harness_options`\naccepts arbitrary keys (`model`, `reasoning_effort`, `temperature`, …) that the\nselected harness understands. For dynamic per-invocation selection (templated\nfrom `${inputs.*}` or event payload), see the next section.\n\n### Per-Step Harness Overrides (Template Expressions)\n\nWorkflow steps can choose their brain per-invocation using `${inputs.\u003ckey>}` and\n`${event.payload.\u003cpath>}` template expressions. Useful when the same workflow\nmust run with a different model/harness depending on who triggered it.\n\n```yaml\nworkflows:\n classify:\n # Declare workflow inputs. `from:` pulls from the triggering event's payload;\n # `default:` is used when the payload field is absent.\n inputs:\n brain:\n from: event.payload.meta.brain\n default: planner\n steps:\n - name: classify\n agent:\n name: classifier\n # Templated reference to a named profile in x-eve.agents.profiles.\n harness_profile: \"${inputs.brain}\"\n\n per-brand:\n inputs:\n brand:\n from: event.payload.meta.brand\n steps:\n - name: run\n agent:\n name: worker\n # Inline bundle — per-field templates. Unknown-ref fields fall back to\n # the agent's default profile at dispatch with a warning log.\n harness_profile_override:\n harness: zai\n model: \"glm-4.6-${inputs.brand}\"\n```\n\n**Grammar (intentionally tiny):**\n- `${inputs.\u003ckey>}` — a single declared input or a caller-supplied ad-hoc input.\n- `${event.payload.\u003cdotted.path>}` — walks the event's raw payload.\n- No operators, no function calls, no array indexing.\n\n**Precedence at dispatch:** `harness_profile_override` (workflow_template) beats\n`harness_profile` (string_ref) beats the agent's declared `harness_profile`\n(agent_default). The `harness_profile_source` enum on `jobs` and `job_attempts`\nrecords which branch applied.\n\n**Validation:**\n- `eve manifest validate` / `eve project sync` reject malformed templates and\n `${inputs.\u003cundeclared>}` references at sync time.\n- Event-payload refs are accepted structurally — the payload shape is only known\n at runtime, so missing fields cause a fallback (with a warning log), not an\n error.\n\n**Response format** includes `step_jobs`:\n\n```json\n{\n \"job_id\": \"proj-abc12345\",\n \"status\": \"active\",\n \"step_jobs\": [\n {\n \"job_id\": \"proj-abc12345.1\",\n \"step_name\": \"ingest\",\n \"resource_refs\": {\"mode\": \"inherit\", \"source\": \"default\", \"count\": 2, \"inherited_count\": 2}\n },\n {\n \"job_id\": \"proj-abc12345.2\",\n \"step_name\": \"extract\",\n \"depends_on\": [\"ingest\"],\n \"resource_refs\": {\"mode\": \"inherit\", \"source\": \"default\", \"count\": 2, \"inherited_count\": 2}\n },\n {\n \"job_id\": \"proj-abc12345.3\",\n \"step_name\": \"review\",\n \"depends_on\": [\"extract\"],\n \"resource_refs\": {\"mode\": \"none\", \"source\": \"step\", \"count\": 0, \"inherited_count\": 2}\n }\n ]\n}\n```\n\n**Job tree view** (`eve job tree`):\n\n```\n[*] proj-abc12345 [Workflow] ingestion-pipeline\n|- [-] proj-abc12345.1 [ingestion-pipeline] ingest\n|- [-] proj-abc12345.2 [ingestion-pipeline] extract\n|- [-] proj-abc12345.3 [ingestion-pipeline] review\n```\n\n### Workflow Hints\n\nWorkflow definitions may include a `hints` block. These hints are merged into the job at invocation time (API, CLI, or event triggers). Use hints for:\n\n- **Remediation gates**: control which environments a workflow can remediate. Pattern: one gate per environment.\n ```yaml\n hints:\n gates: [\"remediate:proj_abc123:staging\"]\n ```\n- **Timeouts**: set execution time limits for the workflow job.\n- **Harness preferences**: specify model/harness settings that override project defaults for this workflow.\n\n### Invocation\n\n- Invoking a workflow creates a **job** with workflow metadata in `hints`.\n- `wait=true` returns `result_json` with a 60s timeout.\n\n### Workflow CLI\n\n```bash\neve workflow list [project]\neve workflow show \u003cproject> \u003cname>\neve workflow run \u003cproject> \u003cname> --input '{\"k\":\"v\"}' --env-override KEY=VALUE\neve workflow invoke \u003cproject> \u003cname> --input '{\"k\":\"v\"}' --env-override KEY=VALUE\neve workflow retry \u003croot-job-id> --failed\neve workflow retry \u003croot-job-id> --from \u003cstep-name>\neve workflow logs \u003cjob-id>\neve notifications send --project \u003cproject> --channel \u003cname-or-id> --message \u003ctext>\n```\n\nWorkflow retry is a recovery path for terminal multi-step workflow roots. It\nclones already-materialized current step jobs, so original input values, git\ncontrols, resource refs, harness settings, and API hints are preserved.\n`--failed` retries failed/upstream-failed current steps; `--from \u003cstep-name>`\nretries that step and downstream dependents. Superseded jobs remain in the tree\nwith retry metadata, while replacement jobs receive rewired dependency edges so\nprior-step result injection reads from the correct predecessor.\n\n## Triggers\n\nBoth pipelines and workflows can include a `trigger` block. The orchestrator matches incoming events and creates pipeline runs or workflow jobs.\n\n### Generic Event Triggers\n\nWorkflows and pipelines can trigger on any event source and type:\n\n```yaml\ntrigger:\n event:\n source: app\n type: document.uploaded\n```\n\n- `source` (required): matches `event.source` (e.g., `app`, `app_link`, `runner`, `chat`, `github`)\n- `type` (optional): matches `event.type` exactly; omit to match all events from that source\n\nWhen a workflow is triggered by an event, the event payload is forwarded as workflow input. The input JSON is included in child job descriptions so step agents can see what triggered them.\n\n### App Trigger (Shorthand)\n\nFor app-sourced events, use the `app` trigger as a shorthand:\n\n```yaml\ntrigger:\n app:\n event: question.answered\n```\n\nThis is equivalent to `event: { source: app, type: question.answered }`. Both formats work; use whichever reads better in context.\n\n### App-Link Trigger (Cross-Project Events)\n\nFor events delivered from a producer project through `x-eve.app_links`, use the\n`app_link` trigger:\n\n```yaml\ntrigger:\n app_link:\n alias: observation\n type: app.observation.created\n```\n\nThis matches consumer-side events with `source=app_link`, filters by the\nconsumer-local subscription alias, and optionally filters by event type. See\n`references/events.md` and `references/manifest.md` for the app-link contract.\n\n### GitHub Push Triggers\n\n```yaml\ntrigger:\n github:\n event: push\n branch: main\n```\n\nBranch patterns support wildcards (e.g., `release/*`, `*-prod`).\n\n### GitHub Pull Request Triggers\n\n```yaml\ntrigger:\n github:\n event: pull_request\n action: [opened, synchronize]\n base_branch: main\n```\n\nSupported PR actions: `opened`, `synchronize`, `reopened`, `closed`.\nBase branch filtering supports wildcard patterns.\n\n### PR Preview Deployment Example\n\nDeploy a preview environment on PR open/update, clean up on close:\n\n```yaml\npipelines:\n pr-preview:\n trigger:\n github:\n event: pull_request\n action: [opened, synchronize]\n base_branch: main\n steps:\n - name: create-preview-env\n action:\n type: env-ensure\n with:\n env_name: ${{ env.pr_${{ github.pull_request.number }} }}\n kind: preview\n - name: deploy\n depends_on: [create-preview-env]\n action:\n type: deploy\n with:\n env_name: ${{ env.pr_${{ github.pull_request.number }} }}\n\n pr-cleanup:\n trigger:\n github:\n event: pull_request\n action: closed\n base_branch: main\n steps:\n - name: cleanup-env\n action:\n type: env-delete\n with:\n env_name: ${{ env.pr_${{ github.pull_request.number }} }}\n```\n\n### Trigger Observability\n\nEvery event records why each candidate trigger did or did not fire. Use this to\ndiagnose \"the event arrived but nothing ran\":\n\n```bash\neve event show \u003cevent-id>\n# Triggers: matched 1 of 3 evaluated\n# ✓ workflow:ingestion-pipeline\n# ✗ workflow:alignment-check (type_mismatch)\n# ✗ pipeline:deploy (branch_mismatch)\n```\n\nEvents carry `trigger_match_count` and `triggers_evaluated[{type, name, matched,\nreason}]`. Common reasons: `source_mismatch`, `type_mismatch`,\n`branch_mismatch`, `action_mismatch`, `no_trigger`, `manual_trigger`. See\n`references/events.md` for the full schema.\n\nTrigger validation also runs during `eve project sync` (not just\n`eve manifest validate`), so unrecognized trigger types, invalid GitHub events,\nunknown system events, and missing cron schedules surface as warnings the\nmoment they're synced.\n\n## API Endpoints\n\n```\nGET /projects/{project_id}/pipelines\nGET /projects/{project_id}/pipelines/{name}\n\n# Pipeline runs\nPOST /projects/{project_id}/pipelines/{name}/run\nGET /projects/{project_id}/pipelines/{name}/runs\nGET /projects/{project_id}/pipelines/{name}/runs/{run_id}\nPOST /pipeline-runs/{run_id}/approve\nPOST /pipeline-runs/{run_id}/cancel\nGET /pipeline-runs/{run_id}/stream\nGET /pipeline-runs/{run_id}/steps/{name}/stream\n\n# Workflows\nGET /projects/{project_id}/workflows\nGET /projects/{project_id}/workflows/{name}\nPOST /projects/{project_id}/workflows/{name}/invoke?wait=true|false\nPOST /projects/{project_id}/notifications/send\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":34452,"content_sha256":"d5d0199e49a6cccbdd7f6c59205e22c1fc42915a768e2f06537a32312da6f653"},{"filename":"references/secrets-auth.md","content":"# Secrets + Auth Reference\n\n## Use When\n- You need to manage secret scope, interpolation, and environment overrides.\n- You need to configure auth, identities, roles, or policy behavior.\n- You need to onboard service principals or troubleshoot permission failures.\n\n## Load Next\n- `references/cli.md` for secret and auth command workflows.\n- `references/manifest.md` for manifest `x-eve.requires` and interpolation.\n- `references/skills-system.md` when auth affects installed capabilities.\n\n## Ask If Missing\n- Confirm scope needed (project, org, user, or system) before setting values.\n- Confirm whether values come from `.eve/dev-secrets.yaml`, API sources, or CI inputs.\n- Confirm required roles/access groups when permission checks are failing.\n\n## Secrets\n\n### Scope Hierarchy\n\nSecrets resolve in priority order: **project > user > org > system**. Values are encrypted at rest and never returned in plaintext. The `show` endpoint returns a masked value (first/last characters only).\n\nAPI paths: `/system/secrets`, `/users/:id/secrets`, `/orgs/:id/secrets`, `/projects/:id/secrets`.\n\n### CLI\n\n```bash\neve secrets list --project proj_xxx\neve secrets set KEY value --project proj_xxx\neve secrets show KEY --project proj_xxx\n\neve secrets ensure --project proj_xxx --keys GITHUB_WEBHOOK_SECRET\neve secrets export --project proj_xxx --keys GITHUB_WEBHOOK_SECRET\n```\n\n`ensure` auto-generates allowlisted secrets (e.g., `GITHUB_WEBHOOK_SECRET`). `export` returns the plaintext value for external configuration -- treat it as sensitive.\n\n### Import Secrets from File\n\nBatch-import secrets from a `KEY=VALUE` env file:\n\n```bash\neve secrets import --org org_xxx --file ./secrets.env\neve secrets import --project proj_xxx --file .env\n```\n\nSupported scopes: `--project`, `--org`, `--user`, `--system` (admin only). Lines starting with `#` and blank lines are ignored. Values are read verbatim after `=` (quotes are not stripped). Each key is upserted as `env_var`.\n\n### Manifest Validation\n\nDeclare required secrets in `eve.yaml`:\n\n```yaml\nx-eve:\n requires:\n secrets: [GITHUB_TOKEN, REGISTRY_TOKEN]\n```\n\nValidate with:\n```bash\neve project sync --validate-secrets\neve project sync --strict\neve secrets validate --project proj_xxx\n```\n\nValidation reports missing secrets with scope-aware remediation hints.\n\n### Interpolation\n\nReference secrets in manifest environment blocks:\n\n```yaml\nenvironment:\n DATABASE_URL: postgres://user:${secret.DB_PASSWORD}@db:5432/app\n API_KEY: ${secret.EXTERNAL_API_KEY}\n```\n\nJob `env_overrides` support the same `${secret.KEY}` placeholders for agent\nsteps, workflow script/shorthand `run` steps, and pipeline `action: { type: run\n}` steps. Values are resolved in memory immediately before the harness or bash\nprocess starts; persisted job rows and `eve job show` continue to expose only\nthe raw placeholder text.\n\nIf an override references a missing secret, the step fails before execution with\n`missing_secret_override` and the execution log includes the unresolved key\nnames. Reserved runtime keys such as `PATH`, `HOME`, and `EVE_*` are rejected by\nschema validation and stripped defensively at execution time for legacy rows.\n\n### Local Dev Secrets\n\nCreate `.eve/dev-secrets.yaml` (gitignored) for local overrides:\n\n```yaml\nsecrets:\n default:\n DB_PASSWORD: dev_password\n staging:\n DB_PASSWORD: staging_password\n```\n\nAPI secrets overlaid by local dev-secrets. Local takes precedence. For k8s production, set secrets via the API.\n\n### Host Env Files\n\nTwo host-level files for local development (never committed):\n\n- **`.env`** (repo root) -- Local secrets and internal tokens.\n- **`system-secrets.env.local`** -- System-level defaults, bootstrapped on API startup. Restart API to pick up changes.\n\n### Required System Vars\n\n| Variable | Where | Purpose |\n|----------|-------|---------|\n| `EVE_SECRETS_MASTER_KEY` | API | Encryption key for secrets at rest |\n| `EVE_INTERNAL_API_KEY` | Worker + API | Internal token for resolve endpoint |\n\n### Worker Injection\n\n- Resolved secrets are injected as **environment variables** for the worker and deployer (allowlisted).\n- File and `ssh_key` secrets are written outside the repo workspace and are not available to agent processes.\n- The worker does **not** write `.eve/secrets.env` into the workspace and does **not** set `EVE_SECRETS_FILE`.\n\n### Git Auth Injection\n\n- **HTTPS clone**: `github_token` secrets are injected into the clone URL for private repo access.\n- **SSH clone**: `ssh_key` secrets are written to a temp key and wired via `GIT_SSH_COMMAND`.\n- Missing auth surfaces explicit errors with remediation hints (`eve secrets set`).\n\n### Fail-Fast on Resolution Failure\n\nThe worker fails fast on secret resolution failure instead of silently substituting\nempty strings. Provider credentials are resolved at the platform layer, not cached\nin the worker. If `EVE_INTERNAL_API_KEY` is missing or incorrect, the attempt fails\nimmediately with a descriptive error.\n\n### Troubleshooting Secret Resolution\n\nIf a job fails during clone or secret resolution:\n\n1. Confirm the secret exists: `eve secrets show \u003cKEY> --project \u003cid>`\n2. Ensure `EVE_INTERNAL_API_KEY` and `EVE_SECRETS_MASTER_KEY` are set for API/worker\n3. Check orchestrator/worker logs for `[resolveSecrets]` warnings\n4. Re-run with corrected secret scope (project > org > system)\n\n### Incident Response\n\nIf you suspect a secret leak:\n\n1. **Contain** -- Rotate the affected secret at the source. Update via `eve secrets set` or `eve secrets import`.\n2. **Invalidate** -- Restart affected services to flush cached credentials. If a token was printed to logs, assume compromise.\n3. **Audit** -- Review job and pipeline logs for leakage patterns. Check correlation IDs.\n4. **Recover** -- Re-run failed jobs after rotation.\n5. **Document** -- Record what leaked, where, and why. Add guardrails if due to missing redaction.\n\n---\n\n## Auth\n\nEve uses **RS256 JWT** tokens with pluggable identity providers (SSH, Nostr). Supabase (HS256) mode is optional when `SUPABASE_JWT_SECRET` is set.\n\n### Configuration\n\n| Variable | Required | Description |\n|----------|----------|-------------|\n| `EVE_AUTH_ENABLED` | No | Enable auth (default `true`) |\n| `EVE_AUTH_PRIVATE_KEY` | Yes | RSA private key (PEM string or file path) |\n| `EVE_BOOTSTRAP_TOKEN` | Prod | One-time token for initial admin creation |\n| `EVE_AUTH_PUBLIC_KEY` | No | Derived from private if omitted |\n| `EVE_AUTH_CHALLENGE_TTL_SECONDS` | No | Challenge validity (default `300`) |\n| `EVE_AUTH_TOKEN_TTL_DAYS` | No | User token TTL in days (default `1`, max `90`) |\n| `EVE_AUTH_KEY_ID` | No | Key identifier in JWKS (default `key-1`) |\n| `EVE_SIGNUP_ALLOWED_EMAIL_DOMAINS` | No | Comma-separated allowlist for SSO self-signup (`/auth/signup` and `/auth/magiclink`). Unset = all domains allowed. |\n\nGenerate keys:\n```bash\nopenssl genrsa -out eve-auth.key 2048\nopenssl rsa -in eve-auth.key -pubout -out eve-auth.pub\nexport EVE_AUTH_PRIVATE_KEY=\"$(cat eve-auth.key)\"\n```\n\n### Bootstrap\n\nCreate the first admin user. Three security modes:\n\n| Mode | Trigger | Token Required |\n|------|---------|----------------|\n| **auto-open** | Fresh deploy, no users | No (10-min window) |\n| **recovery** | Trigger file on host (`/tmp/eve-bootstrap-enable`) | No |\n| **secure** | `EVE_BOOTSTRAP_TOKEN` set | Yes |\n\n```bash\neve auth bootstrap --email [email protected] --token $EVE_BOOTSTRAP_TOKEN\neve auth bootstrap --status\n```\n\nProduction requires `EVE_BOOTSTRAP_TOKEN`. Use your real email -- it becomes your login identity.\nIn local/non-production environments, `eve auth bootstrap` attempts the API recovery path even when bootstrap is marked completed, and can return an existing admin token when recovery mode is allowed.\n\n### Challenge-Response Login\n\nUsers authenticate by signing a challenge with a registered identity. The server selects the appropriate verifier based on identity type.\n\n**CLI (recommended):**\n```bash\neve auth login --email [email protected]\neve auth login --email [email protected] --ttl 30\n```\n\nThe CLI requests a challenge, signs the nonce with your SSH key, submits the signature, and stores the token in `~/.eve/credentials.json`.\n\n**SSH manual flow (curl):**\n```bash\n# 1. Request challenge\ncurl -X POST \"$EVE_API_URL/auth/challenge\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"email\": \"[email protected]\"}'\n# Response: { \"challenge_id\": \"...\", \"nonce\": \"...\", \"expires_at\": \"...\" }\n\n# 2. Sign the nonce (namespace must be \"eve-auth\")\necho -n \"$NONCE\" | ssh-keygen -Y sign -f ~/.ssh/id_ed25519 -n eve-auth\n\n# 3. Verify signature\ncurl -X POST \"$EVE_API_URL/auth/verify\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"challenge_id\": \"...\", \"signature\": \"-----BEGIN SSH SIGNATURE-----\\n...\"}'\n# Response: { \"access_token\": \"...\", \"user_id\": \"...\", \"expires_at\": ... }\n```\n\n**Nostr manual flow (curl):**\n```bash\n# 1. Request challenge (note: provider + pubkey, not email)\ncurl -X POST \"$EVE_API_URL/auth/challenge\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"provider\": \"nostr\", \"pubkey\": \"\u003c64-char-hex-pubkey>\"}'\n\n# 2. Sign a kind-22242 event with [\"challenge\", \"\u003cnonce>\"] tag (client-side)\n\n# 3. Submit signed event\ncurl -X POST \"$EVE_API_URL/auth/verify\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"challenge_id\": \"...\", \"signature\": \"\u003ckind-22242-event-json>\"}'\n\n# With invite code (for unregistered pubkeys):\ncurl -X POST \"$EVE_API_URL/auth/verify\" \\\n -d '{\"challenge_id\": \"...\", \"signature\": \"...\", \"invite_code\": \"abc123...\"}'\n```\n\n### Self-Service Access Requests\n\nUsers can submit access requests without an invite:\n\n```bash\neve auth request-access --org \"My Company\" --email [email protected]\neve auth request-access --org \"My Company\" --ssh-key ~/.ssh/id_ed25519.pub\neve auth request-access --status \u003crequest_id>\n```\n\nAdmins review requests via:\n\n```bash\neve admin access-requests list\neve admin access-requests approve \u003crequest_id>\neve admin access-requests reject \u003crequest_id> --reason \"...\"\n```\n\nList responses use the canonical `{ \"data\": [...] }` envelope. `eve auth list-service-accounts` output also uses the data envelope.\n\nApproval semantics:\n- Approval is transactional (no partial org/user leftovers on failure).\n- Duplicate fingerprints reuse the existing identity owner instead of failing.\n- Re-approving an already-approved request is idempotent.\n- Legacy partial orgs with matching slug+name are reused during approval.\n\n### Access Groups + Scoped Bindings\n\nFirst-class access groups provide fine-grained data-plane authorization. Groups contain users and service principals, and bindings can carry scoped access constraints for org filesystem paths, org document paths, and environment DB schemas/tables.\n\n#### Groups CLI\n\n```bash\neve access groups create --org org_xxx --slug eng-team --name \"Engineering Team\" \\\n [--description \"Backend engineering group\"]\neve access groups list --org org_xxx\neve access groups show eng-team --org org_xxx\neve access groups update eng-team --org org_xxx --name \"Platform Engineering\"\neve access groups delete eng-team --org org_xxx\n\n# Group membership\neve access groups members list eng-team --org org_xxx\neve access groups members add eng-team --org org_xxx --user user_abc\neve access groups members add eng-team --org org_xxx --service-principal sp_xxx\neve access groups members remove eng-team --org org_xxx --user user_abc\n```\n\n#### Scoped Bindings\n\nBindings can carry `--scope-json` to restrict data-plane access:\n\n```bash\neve access bind --org org_xxx --group grp_xxx --role data-reader \\\n --scope-json '{\"orgfs\":{\"allow_prefixes\":[\"/shared/\",\"/reports/\"]}}'\n\neve access bind --org org_xxx --user user_abc --role db-analyst \\\n --scope-json '{\"envdb\":{\"schemas\":[\"public\"],\"tables\":[\"analytics_*\"]}}'\n```\n\nScope structure supports three resource types:\n- `orgfs`: `allow_prefixes`, `read_only_prefixes` for org filesystem paths\n- `orgdocs`: `allow_prefixes`, `read_only_prefixes` for org document paths\n- `envdb`: `schemas`, `tables` for environment database access\n\nBuilt-in roles (`owner` / `admin` / `member`) are granted a **wildcard envdb scope** automatically. Their `envdb:read` / `envdb:write` permissions resolve to access on any schema/table they have permission on, without needing an explicit `envdb` scope block on the membership. Custom roles bound via `eve access bind` still need an explicit scope to access envdb resources.\n\n> Scope narrows; permission grants. A workflow that declares `scope.orgfs.allow_prefixes` (or `scope.orgdocs.*`, `scope.cloud_fs.*`) still needs the **step agent** to declare the matching `orgfs:read` / `orgfs:write` etc. in `access.permissions`. `DEFAULT_AGENT_PERMISSIONS` does not include orgfs / orgdocs / cloud_fs. See `references/agents-teams.md` § Agent Permissions and `references/pipelines-workflows.md` § Workflow Token Scope.\n\n#### Executor default permission sets\n\nWhen a `script:` or `action: { type: run }` step does not declare `permissions:`,\nthe executor falls back to a built-in default. Authors widen with an explicit\nlist on the step.\n\n| Execution type | Default | Includes |\n| --- | --- | --- |\n| `agent` | `DEFAULT_AGENT_PERMISSIONS` | `jobs:*` (read/write/harness_override), `projects:read`, `threads:*`, `envdb:read`, `secrets:read`, `builds:read`, `pipelines:read` |\n| `script` | `DEFAULT_SCRIPT_JOB_PERMISSIONS` | broad platform-ops baseline: `jobs:read+write`, `projects:read`, `envs:read+write`, `envdb:read+write`, `releases:read`, `builds:read`, `pipelines:read`, `secrets:read` |\n| `action: { type: run }` | `DEFAULT_ACTION_RUN_JOB_PERMISSIONS` | least-privilege: `jobs:read+write`, `projects:read`, `envs:read` only — **no** `secrets:read`. Authors opt in to wider access. |\n\nAll three are defined in `packages/shared/src/permissions.ts`. The\n`assertActorCanGrantPermissions` guard rejects any declared permission\nthe invoking actor does not themselves hold.\n\n#### Resource-Specific Access Checks\n\nCheck access against specific resources:\n\n```bash\neve access can --org org_xxx --user user_abc --permission orgfs:read \\\n --resource-type orgfs --resource /reports/q4.md --action read\n\neve access can --org org_xxx --group grp_xxx --permission envdb:read \\\n --resource-type envdb --resource public.analytics --action read\n```\n\n`explain` also shows scope match details:\n\n```bash\neve access explain --org org_xxx --user user_abc --permission orgfs:write\n```\n\n#### Memberships Introspection\n\nInspect the full effective access for any principal:\n\n```bash\neve access memberships --org org_xxx --user user_abc\neve access memberships --org org_xxx --service-principal sp_xxx\n```\n\nReturns: base roles, group memberships, direct bindings, effective bindings (with role expansion and group resolution), effective permissions, and effective scopes for orgfs/orgdocs/envdb.\n\n#### Policy-as-Code (Groups + Scoped Bindings)\n\nThe `.eve/access.yaml` format now supports groups and scoped bindings:\n\n```yaml\naccess:\n groups:\n eng-team:\n name: Engineering Team\n description: Backend engineering group\n members:\n - type: user\n id: user_abc\n - type: service_principal\n id: sp_xxx\n data-team:\n name: Data Analytics Team\n members:\n - type: user\n id: user_def\n\n roles:\n data-reader:\n scope: org\n permissions: [orgfs:read, orgdocs:read, envdb:read]\n\n bindings:\n - roles: [data-reader]\n subject:\n type: group\n id: data-team\n scope:\n orgfs:\n allow_prefixes: [\"/shared/\", \"/reports/\"]\n envdb:\n schemas: [public]\n```\n\n`validate`, `plan`, and `sync` now handle groups, group members, and scoped bindings. The plan output includes group creates/updates/prunes, member adds/removes, and binding scope replacements.\n\n### Credential Check\n\nInspect local AI tool credential availability:\n\n```bash\neve auth creds # Show Claude + Codex cred status\neve auth creds --claude # Only Claude\neve auth creds --codex # Only Codex\n```\n\n### OAuth Token Sync\n\nSync local OAuth tokens into Eve secrets:\n\n```bash\neve auth sync # Sync to user-level (default)\neve auth sync --org org_xxx # Sync to org-level\neve auth sync --project proj_xxx # Sync to project-level\neve auth sync --dry-run # Preview without syncing\n```\n\nThis sets `CLAUDE_CODE_OAUTH_TOKEN` / `CLAUDE_OAUTH_REFRESH_TOKEN` (Claude) and `CODEX_AUTH_JSON_B64` (Codex/Code) at the requested scope.\n\n#### Token Types and Lifetimes\n\n| Token prefix | Type | Lifetime | Recommendation |\n|---|---|---|---|\n| `sk-ant-oat01-*` | `setup-token` (long-lived) | Long-lived | Preferred for jobs and automation |\n| Other `sk-ant-*` | `oauth` (short-lived) | ~15h | Use for interactive dev; regenerate with `claude setup-token` |\n\n`eve auth sync` warns when syncing a short-lived OAuth token. Use `eve auth creds` to inspect token type before syncing:\n\n```bash\neve auth creds # Shows token type (setup-token vs oauth) and Codex expiry\n```\n\n#### Automatic Codex/Code Token Write-Back\n\nAfter each harness invocation, the worker checks if the Codex/Code CLI refreshed `auth.json` during the session. If the token changed, it is automatically written back to the originating secret scope (user/org/project) so the next job starts with a fresh token. This is transparent and non-fatal -- a write-back failure logs a warning but does not affect the job result.\n\n#### Internal Secret Update Endpoint\n\nThe platform exposes `PATCH /internal/secrets/:scope_type/:scope_id/:key` for worker-to-API token write-back:\n- Requires `x-eve-internal-token` header (same `EVE_INTERNAL_API_KEY` used by secret resolution)\n- **Update-only** -- returns 404 if the secret does not already exist (no create semantics)\n- Accepts `{ \"value\": \"...\" }` body\n\n### Token Types\n\n**User Tokens** -- Issued on successful login. Used for API access.\n```json\n{ \"sub\": \"user_abc123\", \"email\": \"[email protected]\", \"type\": \"user\", \"iat\": 1706000000, \"exp\": 1706086400 }\n```\n\n**Job Tokens** -- Scoped tokens issued to running jobs with limited permissions.\n\n**App-Link Tokens** -- Short-lived RS256 JWTs minted for a consumer app-link\nsubscription to call a producer API. They carry `type: app_link`,\n`subscription_id`, `consumer_project_id`, `producer_project_id`,\n`consumer_principal`, `consumer_env`, `producer_env`, `api_name`, and requested\nscopes. Verification re-reads the active subscription and grant, so producer\nrevocation is effective before token expiry. The token audience is\n`project:\u003cproducer_project_id>`.\n\nInternal mint endpoint:\n\n```\nPOST /internal/auth/mint-app-link-token\n```\n\nOnly platform workers/deployers call it with `x-eve-internal-token`; app code\nreceives the minted value through `EVE_APP_LINK_\u003cALIAS>_TOKEN`.\n```json\n{ \"user_id\": \"user_abc123\", \"org_id\": \"org_xyz789\", \"permissions\": [\"job:read\", \"job:write\"], \"type\": \"job\", \"iat\": 1706000000, \"exp\": 1706086400 }\n```\n\n**Service Principal Tokens** -- RS256 JWT with `sub: sp:{principal_id}`, `type: service_principal`, explicit `scopes` array. No implicit role expansion.\n\n### Permissions\n\nEve uses a unified permission model. Job tokens carry a limited `permissions` list scoped to the project/job. Custom roles are additive only.\n\n```bash\neve auth permissions # Full permission catalog\neve auth whoami # Current user + effective permissions\n```\n\nAPI: `GET /auth/permissions` (catalog), `GET /auth/me` (current user).\n\nPermission resolution: `effective = expand(base_role) UNION all(bound_custom_role_permissions)`.\n\n### Identity Management\n\nIdentities can be SSH public keys, Nostr pubkeys, or other provider-specific credentials. Each is linked to a user account.\n\nRegister additional identities:\n```bash\ncurl -X POST \"$EVE_API_URL/auth/identities\" \\\n -H \"Authorization: Bearer $TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"public_key\": \"ssh-ed25519 AAAA... user@laptop\", \"label\": \"laptop\"}'\n```\n\nAdmins can register keys for other users by including `\"email\": \"[email protected]\"` in the body.\n\n### JWKS Endpoint\n\nPublic keys for token verification:\n```bash\ncurl \"$EVE_API_URL/.well-known/jwks.json\"\n# Returns: { \"keys\": [{ \"kty\": \"RSA\", \"kid\": \"key-1\", \"use\": \"sig\", \"alg\": \"RS256\", ... }] }\n```\n\nDuring key rotation, both old and new keys appear in JWKS.\n\n### Key Rotation\n\n**Standard rotation (with grace period):**\n```bash\n# 1. Generate new key pair\nopenssl genrsa -out eve-auth-new.key 2048\nopenssl rsa -in eve-auth-new.key -pubout -out eve-auth-new.pub\n\n# 2. Configure grace period\nexport EVE_AUTH_PRIVATE_KEY=\"$(cat eve-auth-new.key)\"\nexport EVE_AUTH_PUBLIC_KEY=\"$(cat eve-auth-new.pub)\"\nexport EVE_AUTH_PUBLIC_KEY_OLD=\"$(cat eve-auth-old.pub)\"\nexport EVE_AUTH_KEY_ID=\"key-2\"\nexport EVE_AUTH_KEY_ID_OLD=\"key-1\"\n\n# 3. Restart API. New tokens use key-2; old tokens still verify via key-1.\n# 4. After old tokens expire (default 24h), remove OLD vars and restart.\n```\n\n**Emergency rotation (key compromise):**\n```bash\nexport EVE_AUTH_PRIVATE_KEY=\"$(cat eve-auth-new.key)\"\nexport EVE_AUTH_KEY_ID=\"key-emergency-$(date +%s)\"\nunset EVE_AUTH_PUBLIC_KEY_OLD # Do not honor old tokens\n# Restart. All existing tokens are immediately invalidated.\n```\n\n---\n\n## Identity Providers\n\nEve uses a pluggable identity provider framework. Providers register at startup and the auth guard evaluates them in a two-stage chain: **Bearer JWT first**, then provider-specific request auth.\n\n### Provider Interface\n\nEvery provider implements:\n\n```typescript\ninterface IdentityProvider {\n readonly name: string;\n createChallenge(params): Promise\u003cChallengeData>;\n verifyChallenge(challenge, proof, identities): Promise\u003cVerifiedIdentity | null>;\n fingerprint(publicKey: string): Promise\u003cstring>;\n // Optional: per-request auth (no login required)\n extractFromRequest?(req): ExtractedCredential | null;\n verifyRequestCredential?(credential): Promise\u003cVerifiedIdentity | null>;\n}\n```\n\n`fingerprint` computes a deterministic dedup key to prevent duplicate registrations. `VerifiedIdentity` with `identity: null` triggers invite-gated provisioning.\n\n### Auth Chain\n\n```\nRequest --> @Public route? --> allow\n --> Stage 1: Bearer JWT (RS256/HS256) --> success: allow\n --> Stage 2: Provider request auth (registry iterates all providers) --> success: allow\n --> 401 Unauthorized\n```\n\nStage 2 catches all errors and logs warnings. A broken provider does not cause a 500.\n\n### SSH Provider (`github_ssh`)\n\n- **Challenge**: Random 32-byte `base64url` nonce.\n- **Verify**: Runs `ssh-keygen -Y verify` as subprocess. Iterates all registered SSH identities for the user until one matches.\n- **Fingerprint**: `ssh-keygen -lf`, returns MD5 fingerprint (e.g., `MD5:ab:cd:...`).\n- **Request-level auth**: Not supported. SSH requires the challenge/response flow.\n\n### Nostr Provider (`nostr`)\n\nTwo authentication paths:\n\n**Challenge/Verify (login)**: Client signs a kind-22242 event containing a `[\"challenge\", \"\u003cnonce>\"]` tag. Server verifies event ID + Schnorr signature (BIP-340), checks the challenge tag, matches pubkey to registered identities. Unregistered pubkeys trigger invite-gated provisioning.\n\n**NIP-98 Request Auth**: Per-request auth via `Authorization: Nostr \u003cbase64>` header. Client creates a kind-27235 event with URL, method, and body hash tags. Server validates:\n1. Event ID + Schnorr signature\n2. `kind === 27235`\n3. URL tag matches canonical request URL\n4. Method tag matches request method\n5. For non-GET: `payload` tag equals `sha256(body)`\n6. Timestamp within +/-60s of server time\n7. Replay protection via `auth_request_replays` table (120s TTL)\n\n### Invite-Gated Provisioning\n\nWhen an unregistered identity authenticates, the system attempts to provision via org invites.\n\n**Create an invite (admin):**\n```bash\ncurl -X POST \"$EVE_API_URL/auth/invites\" \\\n -H \"Authorization: Bearer $ADMIN_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"org_id\": \"org_xxx\", \"provider_hint\": \"nostr\", \"identity_hint\": \"\u003cpubkey>\", \"role\": \"member\", \"expires_in_hours\": 72}'\n```\n\n**Provisioning flow:**\n1. Unregistered identity authenticates (challenge/verify or NIP-98)\n2. Provider returns `VerifiedIdentity` with `identity: null`\n3. Invite lookup: explicit `invite_code` takes priority, then `identity_hint` matching\n4. Atomic provisioning: create user (synthetic email), create identity row, create org membership with invite role, mark invite used\n\n**List invites:**\n```bash\ncurl \"$EVE_API_URL/auth/invites/org_xxx\" -H \"Authorization: Bearer $ADMIN_TOKEN\"\n```\n\n### Org-Scoped Email Invites\n\nUse the org-scoped invite API when an Eve-compatible app needs to invite a user by email, optionally send an email immediately, and preserve an app return URL through SSO onboarding.\n\n**Permissions:**\n- `orgs:invite` -- create and list org-scoped invites\n- `orgs:members:read` -- list org members and search by email/display-name prefix\n\n**Create an org-scoped invite:**\n```bash\ncurl -X POST \"$EVE_API_URL/orgs/org_xxx/invites\" \\\n -H \"Authorization: Bearer $USER_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"email\": \"[email protected]\",\n \"role\": \"member\",\n \"redirect_to\": \"https://app.example.com/invite/complete\",\n \"project_id\": \"proj_123\"\n }'\n```\n\n`project_id` is optional. When present, it must belong to the org being invited into and resolves project branding for the email subject, body, and `From:` display name. It is also copied into `app_context.project_id` for auditability.\n\n**CLI:**\n```bash\neve org invite [email protected] \\\n --org org_xxx \\\n --project proj_123 \\\n --redirect-to https://app.example.com/invite/complete\n```\n\nProjects define invite email branding in `.eve/manifest.yaml`:\n\n```yaml\nx-eve:\n branding:\n app_name: \"ALL-TRACK\"\n app_logo_url: \"https://sandbox.all-track.co.uk/assets/logo.svg\"\n primary_color: \"#1f6feb\"\n email_from_name: \"ALL-TRACK\"\n reply_to_email: \"[email protected]\"\n support_email: \"[email protected]\"\n support_url: \"https://all-track.co.uk/help\"\n```\n\nRun `eve project sync` after changing the manifest. If an invite omits `project_id` or the project has no branding, Eve Horizon defaults are used. Phase 1 keeps the sender address on the platform default; only the display name is app-branded.\n\n**List org-scoped invites:**\n```bash\ncurl \"$EVE_API_URL/orgs/org_xxx/invites\" -H \"Authorization: Bearer $USER_TOKEN\"\n```\n\n**Member picker lookup:**\n```bash\ncurl \"$EVE_API_URL/orgs/org_xxx/members/search?q=ali\" \\\n -H \"Authorization: Bearer $USER_TOKEN\"\n```\n\n`redirect_to` is stored on the invite record. Invite emails flow through a GoTrue `generate_link` action link and then GoTrue's `/verify` endpoint, which returns to the SSO root with tokens in the hash fragment. If the invite is auto-applied during Supabase token exchange, Eve returns `invite_redirect_to` from `/auth/exchange`, and the SSO callback uses it as a fallback destination when nested redirect params are stripped from the email link. For default invite accepts, the SSO broker establishes the session, routes the user through `/set-password`, then redirects to the final app URL.\n\nIf the originating project sets `x-eve.auth.invite_requires_password: false`, SSO skips `/set-password` and redirects to the final app URL after the session is established.\n\n### App-Scoped Magic-Link Login\n\nProjects can opt into passwordless app login:\n\n```yaml\nx-eve:\n auth:\n login_method: magic_link\n self_signup: false\n invite_requires_password: false\n```\n\nSSO fetches `GET /auth/app-context?project_id=\u003cproject_id>` to render the project-branded login page. The app-scoped magic-link form calls `POST /auth/magic-link`; Eve verifies the project policy and recipient eligibility before generating a GoTrue magic-link action URL and sending the email through the shared branded mailer. `login_method: password_or_magic_link` keeps password login visible while still routing the secondary magic-link request through Eve API. Projects without `x-eve.auth` keep the legacy SSO magic-link path.\n\nMagic-link emails use the same `x-eve.branding` values as invite emails. `self_signup: false` returns generic success for unknown emails but does not call GoTrue and does not send email.\n\n### Magic-Link Confirmation Interstitial\n\nAll Eve-rendered magic-link and invite emails go out with a wrapped URL (`https://sso/m/mlw_\u003c26 base32>`) instead of the raw GoTrue verify URL. The OTP is only revealed when a human clicks \"Sign in\" / \"Accept invite\" on the SSO-rendered interstitial, so corporate scanners (Defender SafeLinks, Mimecast, Proofpoint, Barracuda, Cisco IronPort) that GET every URL in incoming mail no longer burn the single-use token.\n\n- `AuthService.wrapActionLink` stores the GoTrue URL in `magic_link_wraps` (migration `00094_project_auth_config.sql` + `00098_magic_link_wraps.sql`; 1h TTL, 24h retention) and returns the wrap URL.\n- `HEAD/GET /m/:wrap` is fully idempotent — every scanner prefetch increments `get_count` for telemetry; only `POST /m/:wrap` (the button submit) atomically consumes the wrap and 302's to GoTrue's verify endpoint.\n- Wrap tokens are validated against the `mlw_\u003c26 base32>` typeid regex before any DB lookup. The interstitial honours the project-aware redirect allowlist and sets `Cache-Control: no-store` + `Referrer-Policy: no-referrer`.\n- A consumed wrap emits `auth.action_link.wrap_redeemed` on the event spine with `{ org_id, email_hash, kind, get_count, latency_ms }` for monitoring scanner activity.\n\nApp developers do not need to change anything; this is platform-side.\n\n### Domain-Based Signup (Email Allowlist Without Invites)\n\nPre-approve email domains so anyone with a matching address can sign in via magic link without a per-user invite. Each rule maps one domain pattern to one target org, so a single project can serve multiple customer orgs.\n\n```yaml\nx-eve:\n auth:\n login_method: magic_link\n invite_requires_password: false\n org_access:\n mode: allowlist\n allowed_orgs: [org_Alltrack, org_Tesco, org_Morrisons]\n domain_signup:\n enabled: true\n domains:\n - domain: incept5.com\n target_org: org_Alltrack\n - domain: tesco.com\n target_org: org_Tesco\n - domain: morrisons.com\n target_org: org_Morrisons\n```\n\n**v2 schema (2026-05-12, breaking change).** Each entry under `domains` is now an object with a required `target_org`. The v1 list-of-strings shape and the block-level `target_org`/`role` fields are gone; manifest sync rejects them.\n\nThe operator declares the trusted domains; the platform trusts them (no DNS proof). Free-email providers like `gmail.com` emit a manifest coherence warning per rule but are not blocked.\n\nEligibility ordering on `POST /auth/magic-link`:\n\n1. Known user with allowed-org or project membership → branded send.\n2. Pending *explicit* admin-issued invite → generic success; the invite remains the entry point.\n3. Email's domain matches one rule (first-match in declaration order; declare more-specific rules first if precedence matters) → write a one-shot `org_invites` row tagged `app_context.source: domain_signup`, `app_context.org_id: \u003cmatched rule.target_org>`, `app_context.matched_rule: \u003cpattern>` and send branded magic link. The SSO callback consumes the invite and upserts membership into the matched rule's `target_org`.\n4. Otherwise fall through to legacy `self_signup`, then to generic success.\n\nRepeat magic-link requests within 72 hours reuse the existing invite row for the same email + target org (idempotent). Audit events `auth.domain_signup.invite_created` (includes `org_id`, `email_domain`, `matched_rule`, `email_hash`) and `auth.domain_signup.member_attached` flow through the event spine for subscribed webhooks; PII is reduced to a domain string plus a 12-char SHA-256 prefix of the email.\n\nThe public `/auth/app-context` exposes only `auth.org_access.domain_signup_enabled` (bool) — the raw rule list is **never** surfaced unauthenticated. Project admins can fetch `GET /auth/app-context/admin?project_id=...` (or run `eve project auth-context \u003cproject_id>`) for the full rule list with per-rule `target_org`.\n\nTo revoke access for a rule: remove it from the manifest (stops new signups under that rule) and explicitly drop existing memberships with `eve org members remove --org \u003corg> --user \u003cuser>`. Removing the rule does **not** retroactively delete members that already joined.\n\n### Post-Auth Redirect Allowlist (Custom Domains)\n\nBy default the SSO broker only accepts `redirect_to` and CORS origins under the\ncluster domain (`EVE_DEFAULT_DOMAIN`, e.g. `eh1.incept5.dev`). Apps deployed on\ntheir own domain need their origin opted into the allowlist or the SSO drops\nthe redirect and lands users on the SSO root.\n\nThe resolved allowlist is the union of three sources, returned by\n`GET /auth/app-context?project_id=\u003cid>` as `auth.allowed_redirect_origins`:\n\n1. Explicit `x-eve.auth.allowed_redirect_origins` in the manifest (origin-only;\n paths/query/fragments rejected at validate time).\n2. Eligible custom domains owned by the project — `custom_domains` rows with\n `environment_id IS NOT NULL` and status in (`dns_verified`,\n `cert_provisioning`, `active`).\n3. When `auth.org_access.mode: allowlist`, eligible custom domains owned by\n any project in `allowed_orgs` (one hop).\n\nThe SSO broker uses this list for redirect validation in `/callback`, `/login`,\nand `/`, and for CORS on `/session` and `/logout`. Custom-domain apps using\n`@eve-horizon/auth-react` automatically send `project_id` to `/session` and\n`/logout` so CORS can resolve the project-scoped allowlist.\n\nInspect the resolved list:\n\n```bash\neve project auth-context \u003cproject_id>\n```\n\nA signed-in user landing on `/` or `/login` with a validated `redirect_to` is\nimmediately 302'd to the target — the login form is bypassed and there is no\nintermediate \"you can close this tab\" landing page.\n\nThe SSO refresh-token cookie (`eve_sso_rt`, HttpOnly) and UX-hint cookie\n(`eve_sso`) are set on `.\u003cEVE_DEFAULT_DOMAIN>` with `SameSite=None; Secure`\nwhen `EVE_SSO_SECURE_COOKIES=true`, so the `@eve-horizon/auth-react`\n`fetch('/session', { credentials: 'include' })` probe carries them on\ncross-site requests from custom-domain apps. Local k3d (`http://*.lvh.me`)\nfalls back to `SameSite=Lax` because the app and SSO share the `.lvh.me`\nparent. Logged at boot as `[eve-sso] Secure cookies: true (SameSite=none)`.\n\n### Branded Auth Email Delivery + SES Suppression\n\nAll app-branded auth email (org invites, app invites, app-scoped magic-link login, system-admin Supabase invites) goes through a single `MailerService` on the Eve API. When SMTP is pointed at Amazon SES, the mailer adds a pre-send check against the SES account-level suppression list and emits structured log events so silent drops are observable.\n\n| Behavior | Detail |\n| --- | --- |\n| Pre-send check | Calls `GetSuppressedDestination` when `GOTRUE_SMTP_HOST` is `*.amazonaws.com` (or `EVE_MAILER_CHECK_SUPPRESSION=true`). Suppressed → `EmailSuppressedError`, no SMTP send. |\n| Magic-link drop | `sendEligibleMagicLink` swallows `EmailSuppressedError`, returns `{ sent: true }` (enumeration defense), logs `mail.suppressed_drop kind=magic_link to=... reason=... since=...`. |\n| Invite drop | All invite paths (org, app, Supabase admin invites) re-throw — admins see the failure. |\n| Fail-open | Non-`NotFoundException` AWS errors (broken IRSA, throttling) log `mailer.suppression_check_failed` and proceed with SMTP. |\n| Bounce capture | SES → SNS → `POST /webhooks/ses-feedback` (signature-verified, idempotent) → `email_delivery_events` table. |\n\nEnv vars on the API (set per environment, not per project):\n\n| Variable | Default | Purpose |\n| --- | --- | --- |\n| `EVE_MAILER_CHECK_SUPPRESSION` | `auto` | `auto`/`true`/`false`. |\n| `EVE_MAILER_SES_REGION` | parsed from `GOTRUE_SMTP_HOST` | Region for SES SDK calls. |\n| `EVE_SES_CONFIGURATION_SET` | — | Adds `X-SES-CONFIGURATION-SET` header so SES routes events to SNS. |\n| `EVE_SES_FEEDBACK_TOPIC_ARN` | — | Allow-list for the SES feedback webhook. |\n\nPlatform admins (system_admin role) can inspect recent delivery events:\n\n```bash\neve admin email bounces list # last 50\neve admin email bounces list --recipient [email protected] # filter\neve admin email bounces list --event-type Bounce --json # for scripts\n```\n\n`eve env diagnose \u003cproject> \u003cenv>` also surfaces the last 20 events for that env's org members under `recent_email_delivery_events`.\n\nClearing an account-level SES suppression entry is an ops action; the mailer never deletes suppression entries on its own:\n\n```bash\naws sesv2 delete-suppressed-destination --email-address \u003caddr> --region us-west-2\n```\n\n### SSO Self-Signup Domain Restriction\n\nSet `EVE_SIGNUP_ALLOWED_EMAIL_DOMAINS` on the SSO service to gate `/auth/signup` and `/auth/magiclink` to a specific allowlist:\n\n```yaml\n# k8s/base/sso-deployment.yaml\n- name: EVE_SIGNUP_ALLOWED_EMAIL_DOMAINS\n value: \"incept5.com,incept5.co.uk\"\n```\n\n- Comma-separated, case-insensitive on the domain part of the email.\n- Unset = all domains allowed (default behavior).\n- Disallowed signups receive `422 { \"error\": \"email_domain_not_allowed\" }`.\n- The hosted login page renders a hint on the Sign Up tab listing the allowed domains.\n- Invite-based provisioning (org-scoped invites, NIP-98, SSH) is not gated by this setting; only self-signup endpoints are.\n\n---\n\n## App Auth SDKs\n\nFor adding Eve SSO login to deployed apps, use the shared auth packages:\n\n- **`@eve-horizon/auth`** -- Backend middleware (Express). **Recommended**: `eveAuth()` (unified, handles both user and agent tokens, attaches `req.eveIdentity` with `isAgent` boolean) + `eveIdentityGuard()`. Also provides `eveUserAuth()` (user-only), `eveAuthGuard()`, `eveAuthConfig()`, `eveAuthMe()`, `eveAuthMiddleware()` (agent-only, blocking), and lower-level `verifyEveToken()`/`verifyEveTokenRemote()`. Agent job tokens include `agent_slug` and stable `email` (`{agent_slug}@eve.agent`) claims for agent identity, and may include an optional `scope` claim for orgfs/orgdocs/envdb/cloud_fs resource narrowing.\n- **`@eve-horizon/auth-react`** -- Frontend SDK (React). Provides `\u003cEveAuthProvider>` (with optional `projectId` prop for project role resolution), `useEveAuth()` hook (with `orgs`, `activeOrg`, `switchOrg`), `\u003cEveLoginGate>`, `\u003cEveLoginForm>`, and `createEveClient()` fetch wrapper. Also exposes `getStoredToken()`/`storeToken()`/`clearToken()` for direct `sessionStorage` access.\n\n### Auto-Injected Environment Variables\n\nApps deployed to Eve receive these env vars automatically from the platform deployer:\n\n| Variable | Description |\n|----------|-------------|\n| `EVE_API_URL` | Internal API URL (server-to-server) |\n| `EVE_PUBLIC_API_URL` | Public-facing API URL (optional) |\n| `EVE_SSO_URL` | SSO broker URL |\n| `EVE_ORG_ID` | Organization ID |\n| `EVE_PROJECT_ID` | Project ID |\n| `EVE_ENV_NAME` | Environment name |\n| `EVE_SERVICE_TOKEN` | 90-day RS256 JWT (`type: service`) for server-to-server calls back into the Eve API |\n\n`EVE_SERVICE_TOKEN` is auto-minted per service by the deployer on every deploy. Its permissions come from `x-eve.permissions` in the manifest (read-only by default). See [Service Tokens](#service-tokens-deployed-services) below and `references/manifest.md` for the manifest schema.\n\nThe backend middleware reads these automatically. The frontend provider discovers auth config via the backend's `/auth/config` endpoint. Use `${SSO_URL}` in manifest `environment:` blocks for interpolation.\n\n### Token Flow\n\n1. `EveAuthProvider` checks `sessionStorage` for a cached token.\n2. If none, probes the SSO broker `/session` endpoint (root-domain cookie).\n3. If an SSO session exists, gets a fresh RS256 token and caches it.\n4. If no SSO session, shows the login form (SSO redirect or token paste).\n5. All API requests include `Authorization: Bearer \u003ctoken>`.\n\nFor SSE endpoints, the middleware also accepts `?token=` query parameter.\n\nFor development or headless environments, use `eve auth token` to obtain a token for pasting.\n\n**Token staleness**: The `orgs` claim reflects membership at token mint time. With the default 1-day TTL, membership changes can take up to 24h to reflect. Use `strategy: 'remote'` for immediate membership checks.\n\n### Auth-React Org Awareness\n\n`@eve-horizon/auth-react` exposes org membership and switching via the `useEveAuth()` hook:\n\n```typescript\nconst { orgs, activeOrg, switchOrg } = useEveAuth();\n// orgs: EveAuthOrg[] — all orgs the user belongs to\n// activeOrg: EveAuthOrg | null — currently selected org\n// switchOrg(orgId: string): void — switch active org\n\ntype EveAuthOrg = {\n id: string;\n role: 'owner' | 'admin' | 'member';\n};\n```\n\n- The backend already returns memberships in the token/session; the SDK now exposes them.\n- Active org persists in `localStorage` across sessions.\n- Backward compatible: `user.orgId` still works for single-org apps.\n\n### Project Role Resolution\n\nThe `/auth/me` endpoint accepts an `X-Eve-Project-Id` header. When present, it resolves the user's project membership and returns `project_role` (`'owner' | 'admin' | 'member' | null`).\n\n- **Backend**: Use `eveAuthMe({ projectHeader: 'x-eve-project-id' })` to forward the header and include project role in the response.\n- **Frontend**: Pass `projectId` to `\u003cEveAuthProvider>` to send the header automatically. Access via `user.projectRole`.\n- **Custom roles**: Map `project_role` to app-specific roles (e.g. `editor/viewer`) in backend middleware.\n\nSee `references/auth-sdk.md` for full details.\n\n---\n\n## Harness Credentials (BYOK Model)\n\nEve does not manage inference endpoints or proxy LLM traffic. Apps and agents bring their own API keys (BYOK) via Eve secrets. Harnesses call upstream providers directly using resolved secrets — no platform-managed models, no managed catalog, no inference proxy.\n\n### Required Secrets by Harness\n\n| Harness | Secret | Optional |\n|---------|--------|----------|\n| mclaude / claude / zai | `ANTHROPIC_API_KEY` | `ANTHROPIC_BASE_URL` (for custom endpoints) |\n| code / codex | `OPENAI_API_KEY` or `CODEX_AUTH_JSON_B64` | `OPENAI_BASE_URL` |\n| gemini | `GEMINI_API_KEY` or `GOOGLE_API_KEY` | |\n| zai | `Z_AI_API_KEY` | |\n\nFor private repos: `GITHUB_TOKEN` (HTTPS) or `ssh_key` (SSH via `GIT_SSH_COMMAND`).\n\n### Self-Hosted Models (RunPod, vLLM, LM Studio, etc.)\n\nUsers who run their own models store the endpoint URL and API key as Eve secrets. The harness or app uses standard env vars (`OPENAI_BASE_URL`, `OPENAI_API_KEY`) — Eve is not involved in the lifecycle of the GPU endpoint.\n\nFor Tailscale-only endpoints, use private endpoints (platform networking primitive) to make them reachable from cluster pods.\n\n### How It Works\n\n```\nJob created with harness=mclaude\n -> Worker resolves project secrets (ANTHROPIC_API_KEY, etc.)\n -> Harness adapter injects env vars into the harness process\n -> Harness calls provider API directly\n -> Eve never touches inference traffic\n```\n\n---\n\n## Per-Org OAuth Credentials\n\nOrgs bring their own OAuth app credentials for external providers (Google Drive, Slack) instead of relying on cluster-level defaults. Credentials are stored in the `oauth_app_configs` table (one config per org per provider).\n\n### Setup Flow\n\n1. Admin creates an OAuth app in the provider's console (GCP for Google Drive, api.slack.com for Slack).\n2. Admin registers the credentials with Eve:\n\n```bash\neve integrations configure google-drive \\\n --client-id \"xxx.apps.googleusercontent.com\" \\\n --client-secret \"GOCSPX-xxx\" \\\n --label \"Acme Corp Google Drive\"\n\neve integrations configure slack \\\n --client-id \"12345.67890\" \\\n --client-secret \"abc123\" \\\n --signing-secret \"def456\" \\\n --app-id \"A0123ABC\"\n```\n\n3. Initiate the OAuth connection: `eve integrations connect google-drive`\n\n### How It Works\n\n- A single callback URL per provider handles all orgs. Org routing uses the `state` parameter (signed JWT).\n- Token refresh uses the org's own `client_id` + `client_secret` from `oauth_app_configs`.\n- Slack webhook verification uses the org's own `signing_secret` (resolved via org-scoped webhook URL or `team_id` lookup).\n- No cluster-level `EVE_GOOGLE_CLIENT_ID`, `EVE_SLACK_CLIENT_ID`, etc. required.\n\n### CLI\n\n```bash\neve integrations setup-info google-drive # Shows redirect URI, required scopes\neve integrations config google-drive # View current config (secrets redacted)\neve integrations unconfigure google-drive # Remove config\n```\n\n### API\n\n```\nPOST /orgs/:org_id/integrations/providers/:provider/config # Upsert credentials\nGET /orgs/:org_id/integrations/providers/:provider/config # View (redacted)\nDELETE /orgs/:org_id/integrations/providers/:provider/config # Remove\nGET /orgs/:org_id/integrations/providers/:provider/setup-info\n```\n\nPermission: `integrations:write` for create/update/delete, `integrations:read` for view.\n\n---\n\n## Service Tokens (Deployed Services)\n\nEvery deployed service automatically gets `EVE_SERVICE_TOKEN` injected as a platform env var. This is the canonical way for an app to call the Eve API back from inside its own runtime — no manual secret setup, no `eve auth login` from inside a container.\n\n### Token Shape\n\n- RS256 JWT, `type: service`, `sub: service:\u003cservice-name>`\n- Claims: `org_id`, `project_id`, `env_name`, `service_name`, `permissions`\n- TTL: 90 days, refreshed on every deploy\n- Verified by the same JWKS as user/job tokens — `@eve-horizon/auth` middleware accepts it transparently\n\n### Read-Only by Default\n\nService tokens carry **read-only permissions** unless the manifest explicitly grants more. The platform-side defaults are:\n\n```\nprojects:read, jobs:read, threads:read, envs:read,\nsecrets:read, builds:read, pipelines:read, agents:read, events:read\n```\n\nApps that need to write must opt in via `x-eve.permissions` on the service in `.eve/manifest.yaml`:\n\n```yaml\nservices:\n api:\n x-eve:\n permissions: [jobs:write, events:write, threads:write]\n```\n\nDeclared permissions are **merged** with the read-only defaults — list only the extra scopes. Redeploy to mint a new token with the updated scopes. See `references/manifest.md` for the full manifest schema.\n\nThis is a security-relevant default change: an app that previously assumed write access from the platform-injected token will now get `403 Missing required permission` until it declares the scope it needs.\n\n### Using The Token\n\n```bash\ncurl -X POST \"$EVE_API_URL/projects/$EVE_PROJECT_ID/jobs\" \\\n -H \"Authorization: Bearer $EVE_SERVICE_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{ \"description\": \"from app\", \"env_name\": \"'\"$EVE_ENV_NAME\"'\" }'\n```\n\n`EVE_INTERNAL_API_KEY` must be configured on the platform for token minting to succeed. If minting fails the deployer logs a warning and `EVE_SERVICE_TOKEN` is empty — the deploy still proceeds.\n\n---\n\n## Service Principals + Token Minting\n\nApp backends authenticate as services via scoped tokens:\n\n```bash\neve auth create-service-account --name \"pm-app-backend\" --org org_xxx \\\n --scopes \"jobs:create,jobs:read,projects:read\"\neve auth list-service-accounts --org org_xxx\neve auth revoke-service-account --name pm-app-backend --org org_xxx\n```\n\nAPI: `POST /orgs/:id/service-principals`, `POST .../service-principals/:sp_id/tokens` (mint), `DELETE .../tokens/:tok_id` (revoke).\n\nAdmins can also mint tokens without SSH login (useful for bots):\n\n```bash\neve auth mint --email [email protected] --org org_xxx --ttl 90\n```\n\nCreates user and membership if needed. TTL capped at server's `EVE_AUTH_TOKEN_TTL_DAYS`.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":47522,"content_sha256":"b548aca1d2c9470819a17b721ac4d327efa80317831cf6c6471d72f943013242"},{"filename":"references/skills-system.md","content":"# Skills System\n\n## Use When\n- You need to install, update, or reason about skill availability and resolution order.\n- You need pack structure or SKILL discovery conventions across org projects.\n- You need to control which skills are loaded for agents, CI, or repo clones.\n\n## Load Next\n- `references/agents-teams.md` for team/agent workflows that consume skills.\n- `references/overview.md` for environment context and architecture assumptions.\n- `references/cli.md` for install and resolve commands.\n\n## Ask If Missing\n- Confirm whether installation is local, repository-scoped, or organization-scoped.\n- Confirm preferred source (`skills.txt` local path vs remote URL).\n- Confirm whether you are using pack lockfile mode via `x-eve.packs`.\n\nEve Horizon has a public split between **developer skills** and **runtime\nskills**. Skills follow the OpenSkills SKILL.md format -- YAML frontmatter for\nmetadata, imperative instructions in the body, and optional bundled resources --\nbut they are sourced and materialized differently.\n\n- **Developer skills** live in root `skills.txt` and are refreshed with\n `eve skills install`. They target local coding agents (Claude Code, Codex,\n Gemini CLI, Pi) working on the repo.\n- **Runtime skills** live in `.eve/manifest.yaml` (`x-eve.packs`), are pinned by\n `.eve/packs.lock.yaml`, and are materialized for Eve jobs by\n `eve skills materialize`. They are what runtime agents see inside jobs.\n\nRuntime materialization writes canonical skills to `.agents/skills/` (plural)\nand bridges harness-specific layouts (`.claude/skills`, `.pi/skills`) by\nsymlink. The singular `.agent/skills` path is no longer used anywhere -- all\ncode, hooks, and docs canonicalized on `.agents/skills/`.\n\n## SKILL.md Format\n\n```yaml\n---\nname: my-skill # Required. Hyphen-case identifier.\ndescription: One-line summary of what this skill does\n---\n```\n\nWrite the body in imperative form (\"Check the config\", not \"You should check\").\nKeep SKILL.md under 5,000 words. Move detailed content to `references/`.\n\nProgressive disclosure layers:\n- **Metadata** (name, description) -- always available for discovery\n- **Body** (SKILL.md instructions) -- loaded when the skill is invoked\n- **Resources** (`references/`, `scripts/`, `assets/`) -- loaded on demand\n\n| Resource dir | Purpose | Loaded |\n|--------------|---------|--------|\n| `references/` | Detailed documentation, guides | On demand |\n| `scripts/` | Executable utilities | Never (executed via CLI) |\n| `assets/` | Templates, images, configs | Never (used by scripts) |\n\n## Pack Structure\n\nSkills are grouped into **packs** -- directories of related skills. The public\neve-skillpacks repo ships three packs:\n\n```\neve-skillpacks/\n├── eve-work/ # Productive work patterns\n│ ├── README.md\n│ ├── eve-orchestration/ # Parallel decomposition, job relations\n│ ├── eve-job-lifecycle/ # Job states, completion, failure\n│ ├── eve-job-debugging/ # Diagnosing stuck/failed jobs\n│ ├── eve-read-eve-docs/ # Platform reference lookup\n│ └── eve-skill-distillation/\n├── eve-se/ # Platform-specific engineering\n│ ├── README.md\n│ ├── eve-manifest-authoring/\n│ ├── eve-deploy-debugging/\n│ ├── eve-pipelines-workflows/\n│ └── ... # auth, bootstrap, CLI, troubleshooting\n├── eve-design/ # Architecture & design thinking\n│ ├── README.md\n│ └── eve-agent-native-design/\n└── ARCHITECTURE.md\n```\n\nEach pack includes a `README.md` covering purpose, skills, audience, and\ninstallation instructions.\n\n## Installing and Materializing Skills\n\n### Developer Path: `skills.txt` + `eve skills install`\n\nUse this for local coding agents working on the repo. One source per line;\nblank lines and `#` comments are ignored. Always prefix local paths with `./`,\n`../`, `/`, or `~` to distinguish from `org/repo`.\n\n```txt\n./skillpacks/my-pack/* # All skills in a local pack\n./skillpacks/my-pack/specific-skill # One specific skill\nhttps://github.com/incept5/eve-skillpacks # Remote source\n```\n\n```bash\neve skills install # Read skills.txt, install via the upstream skills CLI\neve skills install \u003csource> # Ad-hoc install of a single source\n```\n\nThis is the compatibility path. It shells out to the `skills` binary per\nskill per agent, so it is fine for occasional dev refresh but not for\nclone-time or runtime startup.\n\n### Runtime Path: `x-eve.packs` + `eve skills materialize`\n\nDeclare runtime packs in `.eve/manifest.yaml` for reproducible, lockfile-based\nmaterialization that runs without `skills add` subprocesses:\n\n```yaml\nx-eve:\n install_agents: [claude-code, codex, gemini-cli, pi] # default for all four\n packs:\n - source: ./skillpacks/my-pack\n - source: incept5/eve-skillpacks\n ref: 0123456789abcdef0123456789abcdef01234567 # pinned 40-char SHA\n```\n\n```bash\neve packs status # Show current pack state\neve packs resolve --dry-run # Preview resolution\neve agents sync # Resolve and write packs.lock.yaml\neve skills materialize manifest # Fast-path materialize runtime skills\neve skills materialize manifest --skill-mode software-engineering\neve skills materialize manifest --runtime # Runtime-only: vendored, no fetch\neve skills materialize skills.txt # Fast-path for local skills.txt entries\n```\n\nFlags on `materialize`:\n\n| Flag | Purpose |\n|------|---------|\n| `--skill-mode \u003cname>` | Manifest skill mode to resolve (default: `runtime`) |\n| `--mode symlink\\|copy` | Filesystem mode (default: `symlink`) |\n| `--agents a,b` | Comma-separated agent override |\n| `--runtime` | Consume vendored externals from `.eve/materialized-skills/` only |\n\nThe lockfile `.eve/packs.lock.yaml` pins exact versions. External pack content\nis vendored into `.eve/materialized-skills/\u003csource-id>/\u003cinstall-name>/` so\nruntime startup stays filesystem-only with no network or `skills` subprocess.\n\nTo migrate runtime entries off `skills.txt`, run `eve migrate skills-to-packs`,\nreview the output, merge it into `.eve/manifest.yaml`, and keep only repo-local\ndeveloper skills in `skills.txt`. See `references/manifest.md` for the full\n`x-eve.packs` schema and pack lockfile format.\n\n### Skill Modes\n\n`x-eve.skill_modes` declares named runtime selection policies that jobs can opt\ninto:\n\n```yaml\nx-eve:\n skill_modes:\n runtime:\n pack_set: runtime\n software-engineering:\n pack_set: runtime\n include_skills_txt: true # add dev skills.txt to the materializer\n extra_packs:\n - source: ./private-eve-dev-skills/eve-dev\n```\n\nA job (or `effectiveInvocation.data.skill_mode`) selects a mode. Default for\nboth agent-runtime and worker is `runtime`. `software-engineering` is explicit\nopt-in for repo-focused jobs that need dev skills alongside runtime skills.\n\n### Multiple Sparse Agent Packs\n\n`eve project sync` (and `eve agents sync`) supports multiple sparse agent\npacks, each contributing only a subset of files. The pack resolver auto-detects\n\"simple pack\" layouts -- packs that ship `agents.yaml`, `teams.yaml`, or\n`chat.yaml` at the pack root without an `eve/pack.yaml` descriptor -- so two or\nmore sparse packs can coexist in `x-eve.packs` and merge cleanly into the\nproject's resolved agent/team/chat config.\n\n## Glob Pattern Syntax\n\n| Pattern | Meaning | Example |\n|---------|---------|---------|\n| `./path/*` | All direct child skills | `./skillpacks/my-pack/*` |\n| `./path/**` | All nested skills recursively | `./skillpacks/**` |\n| `./path/skill` | Single specific skill | `./skillpacks/my-pack/my-skill` |\n\nThe installer expands globs, finds directories containing `SKILL.md`, and\ninstalls each. The directory name becomes the skill identifier.\n\n## Runtime Materialization Flow\n\nBoth agent-runtime and worker now materialize runtime skills **before**\n`.eve/hooks/on-clone.sh` runs, via `materializeWorkspaceSkills()` from\n`@eve/shared`. The hook can assume runtime skills are already present, so it\nno longer needs to call `eve skills install`.\n\nPer-job flow:\n\n1. Workspace is cloned (or reused).\n2. Runtime resolves the selected `skill_mode` (default `runtime`).\n3. Manifest packs are materialized into `.agents/skills/\u003cinstall-name>/` --\n local sources via symlink, vendored externals from\n `.eve/materialized-skills/`.\n4. Claude-family harnesses get `.claude/skills` -> `../.agents/skills`. Pi\n gets `.pi/skills`. Codex reads from `.agents/skills/` directly.\n5. `.eve/hooks/on-clone.sh`, `on-acquire.sh`, etc. run.\n6. Harness launches with skills already in place.\n\nWorker software-engineering jobs may explicitly request\n`skill_mode: software-engineering` to also pick up `skills.txt` dev skills via\nthe same fast-path materializer.\n\nInstall targets (`.agents/skills/`, `.claude/skills/`, `.pi/skills/`) are\nalways gitignored. Tracked sources live under repo-local pack paths (listed in\n`skills.txt`), resolved from `.eve/packs.lock.yaml`, or vendored under\n`.eve/materialized-skills/`.\n\n## Skill Resolution and Loading\n\nWhen `skill read \u003cname>` is invoked, OpenSkills searches (first match wins):\n\n1. `./.agents/skills/` (project universal -- canonical, used by codex, gemini-cli, pi)\n2. `~/.agents/skills/` (global universal)\n3. `./.claude/skills/` (project Claude-specific -- symlink to `.agents/skills` after materialize)\n4. `~/.claude/skills/` (global Claude-specific)\n\nProject skills shadow global skills with the same name. Output includes the\nskill body and a base directory for resolving bundled resources.\n\n`.agents/skills/` (plural) is the single canonical universal path. The legacy\nsingular `.agent/skills/` was a typo in the worker image and shared docs and\nis no longer used; everything has been canonicalized on the plural form.\n\n## Naming Conventions\n\n- Use **hyphen-case**: `my-skill`, not `mySkill` or `my_skill`\n- Prefix domain-specific skills to avoid collisions: `eve-`, `team-`\n- The directory name is the skill identifier -- choose it carefully\n\n## Creating a Custom Skill Pack\n\n**1. Create the pack and README:**\n\n```bash\nmkdir -p skillpacks/my-pack\n# Write README.md with purpose, skills, audience, install instructions\n```\n\n**2. Add skills:**\n\n```bash\nmkdir -p skillpacks/my-pack/my-skill\ncat > skillpacks/my-pack/my-skill/SKILL.md \u003c\u003c 'EOF'\n---\nname: my-skill\ndescription: Handles X when user asks about Y\n---\n# My Skill\n\n## When to Use\nLoad this skill when working on X.\n\n## Instructions\nTo accomplish X:\n1. Check configuration in `config/`\n2. Apply patterns from references/patterns.md\n3. Validate the output\nEOF\n```\n\n**3. Register and install:**\n\nFor skills.txt, add `./skillpacks/my-pack/*`. For AgentPacks, add under\n`x-eve.packs` and run `eve agents sync`.\n\n**4. Commit:** Track pack sources and manifest. Never commit install targets.\n\n## Best Practices\n\n**Authoring:** Write in imperative form. Include a \"When to Use\" section so\nagents self-select. Keep the body focused; push detail to `references/`.\n\n**Organization:** Group by domain, not team. Keep packs cohesive. Always\ninclude a README.\n\n**Distribution:** Project-specific skills go in-repo (`skillpacks/`). Team\nskills live in a shared Git repo. Personal skills install to `~/.agents/skills/`.\n\n**Version control:** Commit pack sources and manifests. Gitignore install\ntargets (`.agents/skills/`, `.claude/skills/`).\n\n## Examples\n\n**Personal pack** -- `skills.txt`:\n```txt\n./skillpacks/personal/*\n```\n\n**Team pack via Git** -- `skills.txt`:\n```txt\[email protected]:your-org/team-skills\n```\n\n**Mixed installation** -- `skills.txt`:\n```txt\n./skillpacks/my-pack/* # Local pack (all skills)\n./skillpacks/another-pack/special-skill # Single skill from another pack\nhttps://github.com/incept5/eve-skillpacks # Remote packs\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":11897,"content_sha256":"fa684847231c578fbf779bcdd4e6b551149743597a6b363166e320a7d297fec4"},{"filename":"references/troubleshooting.md","content":"# Troubleshooting (Symptom-First)\n\n## Use When\n- A job, deploy, build, or auth operation is failing and you need a diagnosis path.\n- You have an error message or symptom and need the fix, not the architecture.\n- You need the quick triage sequence before diving into specific docs.\n\n## Load Next\n- `references/deploy-debug.md` for architecture-level debugging (K8s, workers, ingress, custom domains, Sentinel, managed-DB TLS).\n- `references/secrets-auth.md` for auth, SSO, mailer/SES suppression, magic-link wrap interstitial, redirect allowlist, and domain-signup details.\n- `references/integrations.md` for SES feedback webhook and SNS signature verification specifics.\n- `references/manifest.md` for `x-eve.auth` schema (allowed_redirect_origins, org_access, domain_signup v2).\n- `references/builds-releases.md` for build-specific failure analysis.\n- `references/database-ops.md` for managed-DB connection and migration details.\n\n## Ask If Missing\n- Confirm the exact error message or symptom.\n- Confirm the environment (staging, local docker, k3d) and `EVE_API_URL`.\n- Confirm whether the issue is with a job, deploy, build, or system-level operation.\n\n## Quick Triage (Start Here)\n\nRun these three commands first. They cover 80% of issues.\n\n```bash\neve system health --json # 1. Is the platform up?\neve job diagnose \u003cid> # 2. What went wrong with this job?\n./bin/eh status # 3. What's the local environment state?\n```\n\nIf `system health` fails, the platform is down -- check `eve system logs api`.\nIf `job diagnose` returns useful output, follow its recommendations.\nIf `./bin/eh status` shows services stopped, restart with `./bin/eh start \u003cmode>`.\n\n## Auth + Identity Issues\n\n| Symptom | Cause | Fix |\n|---|---|---|\n| `401 Unauthorized` on every request | No token or expired token | `eve auth login --email \u003cemail>` |\n| `403 Forbidden` | Missing permission for action | `eve auth permissions` to check catalog; `eve access explain --org \u003cid> --user \u003cid> --permission \u003cperm>` |\n| \"OAuth token has expired\" | Claude auth stale | `./bin/eh auth extract --save` then redeploy |\n| Bootstrap fails | Bootstrap window closed or token wrong | Check `eve auth bootstrap --status`; use recovery mode or set `EVE_BOOTSTRAP_TOKEN` |\n| Service principal token rejected | Token revoked or scopes insufficient | `eve auth list-service-accounts --org \u003cid>`; recreate if needed |\n| `eve auth creds` shows expired | Local AI tool creds stale | Re-auth with the tool (`claude setup-token`, codex login); then `eve auth sync` |\n\n### Mailer / Magic-Link / Invite Delivery\n\n| Symptom | Cause | Fix |\n|---|---|---|\n| User reports no magic-link or invite email; API logs `Generated GoTrue ... link` and SSO UI says \"If your email has access…\"; no error | Address sits on SES account-level suppression list — SMTP returns 250 OK but SES silently drops | `eve admin email bounces list --recipient \u003caddr>` to inspect persisted feedback rows; grep API logs for `mailer.suppressed` / `mail.suppressed_drop`; runbook: `docs/runbooks/ses-suppression.md`. Pre-flight check + bounce webhook shipped (feats `0514e0bc`, `438ac902`). |\n| Magic-link reported \"already used\" before the user ever clicked | Mail-security scanner (Defender SafeLinks, Mimecast, Proofpoint, Barracuda, IronPort) prefetched the GoTrue OTP link | Expected — magic-link wrap interstitial requires the user to click \"Confirm sign-in\" before the GoTrue URL is revealed. Have them re-request. Wrap rows live in `magic_link_wraps` (migration 00098); `get_count > 1` is normal for protected mailboxes. Fix: `b5ef3ea4`. |\n| Bounce/complaint webhook calls return 4xx; no rows appear in `email_delivery_events` | SNS posts notifications as `text/plain` (raw JSON string), not `application/json`; older controller couldn't parse | Upgrade past `614fed2b` (`fix(webhooks): parse SNS text/plain body in ses-feedback controller`). On a fork, ensure the `/webhooks/ses-feedback` controller parses text/plain bodies. |\n| App invite to a permanently-bounced address: admin sees silent success | Pre-`438ac902` mailer didn't differentiate suppressed addresses from delivered ones | After fix: invite paths re-throw `EmailSuppressedError` so admins see the failure; only the magic-link path swallows it for account-enumeration defense. |\n\n### SSO / Custom-Domain App Auth\n\n| Symptom | Cause | Fix |\n|---|---|---|\n| User signs in via SSO but the app session isn't recognized; `/session` returns 401; session cookies not sent on the cross-site exchange | App lives on a custom domain different from `EVE_SSO_URL`; cookies need `SameSite=None; Secure` to traverse third-party context | Upgrade past `b3d25503` (`fix(sso): SameSite=None on session cookies for custom-domain apps`). Verify `EVE_SSO_SECURE_COOKIES=true`; confirm container log shows `[eve-sso] Secure cookies: true (SameSite=none)`. In browser devtools, check `eve_sso_rt` / `eve_sso` cookies show `SameSite=None; Secure`. Local k3d on `*.lvh.me` keeps `SameSite=Lax` (same-site). |\n| `ALL-TRACK`-style invite or magic link strands user on SSO landing; \"Continue to Sign In\" goes to `/login` (dead end for already-signed-in user) | App's off-cluster origin isn't on the redirect allowlist; SSO rewrote `redirect_to` to `${EVE_SSO_URL}/?eve_org_id=...` | Declare the app origin under manifest `x-eve.auth.allowed_redirect_origins` (origin-only, `https` except `localhost`/`*.lvh.me`). Project's own eligible custom domains auto-include. Verify with `eve project auth-context \u003cproject_id>` — resolved `allowed_redirect_origins` should list the app host. Fix: `4fa8a1aa`. |\n| App that previously had domain-signup working on `release-v0.1.281+` now fails manifest sync | v2 breaking change: `x-eve.auth.org_access.domain_signup.domains` is now a list of `{ domain, target_org, role }` objects; v1 list-of-strings + block-level `target_org` is rejected | Migrate manifest to v2 shape (one `target_org` per rule, declared in precedence order). Existing memberships are not retroactively removed. See `docs/system/auth.md#domain-based-signup`. Breaking change: `e402cee4`. |\n\n### Job Token Resource Scope\n\n| Symptom | Cause | Fix |\n|---|---|---|\n| A job that previously worked now hits 403 on Cloud FS / org filesystem / org docs / envdb access | Scoped job-token enforcement: `ScopedAccessService` now checks the token's `scope` claim (paths, mounts, schemas/tables), not just permission names | `eve job show \u003cid> --json` and inspect `token_scope`; confirm the workflow step or parent declared the right scope; cross-check against the access binding's `scope_json`. Tokens with no `scope` keep legacy permission-name-only behavior. Shipped: `906424e9`; migration `00096_jobs_token_scope.sql`. |\n\n## Secret Resolution Issues\n\n| Symptom | Cause | Fix |\n|---|---|---|\n| Job fails during clone | `GITHUB_TOKEN` missing or wrong scope | `eve secrets show GITHUB_TOKEN --project \u003cid>`; re-set with `eve secrets set` |\n| \"secret resolution failed\" | `EVE_INTERNAL_API_KEY` missing | Set on both API and worker; restart |\n| Empty env var in runner | Secret not at correct scope | Check scope hierarchy: project > user > org > system |\n| `[resolveSecrets]` warnings in logs | Master key or internal key mismatch | Verify `EVE_SECRETS_MASTER_KEY` and `EVE_INTERNAL_API_KEY` are set |\n| Secret shows `null` | Secret exists at wrong scope | `eve secrets list --project \u003cid>` vs `--org \u003cid>` to find it |\n\n## Deploy + Environment Issues\n\n| Symptom | Cause | Fix |\n|---|---|---|\n| Deploy hangs at \"deploying\" | Pipeline step stuck or health check loop | `eve pipeline logs \u003cpipeline> \u003crun-id> --follow` to find stuck step |\n| `status: degraded` after deploy | Pods unhealthy | `eve env diagnose \u003cproject> \u003cenv>` for K8s events |\n| Ingress returns 404 | Missing ingress config or DNS | Check manifest `x-eve.ingress.public: true`; verify `EVE_DEFAULT_DOMAIN` |\n| `eve tcp-ingress test` fails or TCP listener is unreachable | Listener not ready, k3d port not mapped, app not listening, or AWS/klipper LoadBalancer issue | `eve env diagnose \u003cproject> \u003cenv> --json | jq '.tcp_ingress'`; local k3d needs `./bin/eh k8s start --tcp-ports \u003cports> --recreate`; check `./bin/eh kubectl get svc -A -l eve.tcp_ingress=true`. |\n| \"Service X ready check failed\" | Container crash or config error | `eve env logs \u003cproject> \u003cenv> --service \u003cname>` |\n| Rollback needed | Bad deploy | `eve env rollback \u003cproject> \u003cenv>` |\n| Env stuck in unknown state | K8s unreachable | `eve env recover \u003cproject> \u003cenv>` to analyze and suggest recovery |\n| Deploy bypasses pipeline | Used `--direct` flag | Re-run without `--direct` to use configured pipeline |\n| Bare `HTTP request failed` in pipeline logs | Old K8s client error leaking unwrapped | Should not happen — every K8s call is wrapped now. If seen, capture `attempt_id` and escalate; the wrapper is in `apps/worker/src/deployer/k8s-error.ts`. |\n| `[app_crash_loop]` failure in deploy logs | Container exits non-zero on start (bad migration, missing env var) | `eve env logs \u003cproject> \u003cenv> \u003cservice> --previous` for the boot stack trace. |\n| `[image_pull_error]` failure | Bad image tag, missing pull secret, or registry auth | `eve env diagnose`; verify the digest exists; check `imagePullSecret`. |\n| `[readiness_timeout]` failure | Pods up but probes never pass | `eve env diagnose`; review probe config; check downstream dependencies. |\n| `[dependency_timeout]` failure | `depends_on` service never reached ready | `eve env logs \u003cproject> \u003cenv> \u003cdep-service>` to find the blocker. |\n| `[manifest_invalid]` failure | K8s rejected manifest (422/400) or manifest drift vs. ref | `eve manifest validate`; if drift, `eve project sync --ref \u003csha>` then redeploy. |\n| `[ingress_conflict]` failure | Another env owns the hostname (first-bind-wins) | `eve domain list`; `eve domain transfer \u003chost> --to \u003cenv>`; redeploy losing then winning env. |\n| `eve env show` reports DRIFT (last-applied != last-ready) | Apply succeeded but readiness failed; cluster is on a newer release than the rollback base | `eve env diagnose`; redeploy a known-good ref or `eve env rollback`. |\n| Manifest fix not picked up by re-deploy | Used to require a separate `eve project sync` | No longer needed — `eve env deploy --ref \u003csha>` resolves the manifest from the ref. If still stale, ensure `--repo-dir` points at the repo with the updated `.eve/manifest.yaml`. |\n\n### Custom Domain Issues\n\n| Symptom | Cause | Fix |\n|---|---|---|\n| Custom domain stuck in `pending_dns` | DNS not pointing at platform ingress | `eve domain status \u003chostname>` shows the expected target; update DNS, then `eve domain verify \u003chostname>` and redeploy. |\n| Custom domain `dns_error` after working before | DNS record was changed or removed | Restore the A/CNAME; run `eve domain verify`. |\n| Custom domain `cert_error` | cert-manager HTTP-01 challenge failed | Check the ClusterIssuer has an HTTP-01 solver (custom domains never use `EVE_DEFAULT_TLS_SECRET`); verify port 80 is reachable from the internet. |\n| Multi-AZ ingress IPs not matching DNS | `EVE_PLATFORM_INGRESS_IP` set to one IP but DNS uses another | Set `EVE_PLATFORM_INGRESS_IP` to the comma-separated list of all LB IPs. |\n| Deploy fails with `ingress_conflict` for a custom domain | Hostname is already bound to another env (first-bind-wins) | `eve domain list` to find the owner; `eve domain transfer \u003chost> --to \u003cenv>` or `eve domain unbind \u003chost>`. |\n\n### TLS / Managed DB Issues\n\n| Symptom | Cause | Fix |\n|---|---|---|\n| Node `pg` throws `self signed certificate in certificate chain` | Pod missing the managed-DB CA bundle | Re-deploy; verify `kubectl -n \u003cns> get cm eve-db-trust` exists; ensure the env has at least one cloud managed DB tenant. |\n| `no pg_hba.conf entry for host ... SSL off` | App connecting without SSL despite `verify-full` URL | Confirm `DATABASE_URL` carries `sslmode=verify-full`; remove any app-side `ssl: { rejectUnauthorized: false }` override. |\n| Migration job fails on TLS but app pods work | Job render path skipped trust injection in an older deploy | Re-deploy; both Deployments and Jobs now mount the trust ConfigMap. |\n\n### Sentinel Alerts\n\n| Symptom | Cause | Fix |\n|---|---|---|\n| Slack alert: `Environment degraded — N pods ImagePullBackOff` | Sentinel watchdog detected pull failures | `eve env diagnose \u003cproject> \u003cenv>`; fix the image tag/digest; redeploy. |\n| Slack alert: `Circuit-breaker activated — scaled to 0 replicas` | Env stayed degraded past `EVE_ENV_HEALTH_CIRCUIT_BREAK_AFTER_*` thresholds | Investigate root cause via `eve env diagnose`; once fixed, `eve env deploy` re-scales the Deployment. |\n| Sentinel posts no alerts despite known-bad envs | `EVE_ENV_HEALTH_ENABLED != true`, `environments.namespace` not yet backfilled, or Slack settings missing | Check orchestrator env vars; verify `sentinel.enabled`, `sentinel.slack.channel_id`, `sentinel.slack.integration_id` system settings; recently-deployed envs auto-populate `namespace`. |\n| Same alert keeps re-posting | Issue signature changed each tick | Sentinel deduplicates per `(env, signature)` for 4h — varying signatures (e.g., new pod name each restart) defeat dedup. Investigate the underlying churn. |\n\n## Job Execution Issues\n\n| Symptom | Cause | Fix |\n|---|---|---|\n| Job won't start (stuck in ready) | Orchestrator unhealthy or concurrency full | `eve system orchestrator status`; check `ORCH_CONCURRENCY` |\n| Job blocked | Unresolved dependencies | `eve job dep list \u003cid>` to see blockers |\n| Job failed immediately | Clone, secret, or workspace error | `eve job diagnose \u003cid>` -- check first attempt's error |\n| Job stuck active (\"running for Xs\") | Harness hanging or worker crash | `eve job diagnose \u003cid>`; check runner-logs if K8s |\n| Harness logs missing | Startup error, not harness | `eve job runner-logs \u003cid>` for K8s pod logs |\n\n## Build Issues\n\n| Symptom | Cause | Fix |\n|---|---|---|\n| Build fails | Dockerfile error or registry auth | `eve build diagnose \u003cbuild_id>` for last 30 lines of BuildKit output |\n| \"registry auth failed\" | Wrong registry config | Use `registry: \"eve\"` for managed; set `REGISTRY_USERNAME`/`REGISTRY_PASSWORD` for BYO |\n| Build not triggered | Pipeline not configured | Check `environments.\u003cenv>.pipeline` in manifest |\n| Image not found after build | Tag mismatch | `eve build show \u003cid>` for artifacts; check release tag |\n| Build timeout | Large image or slow network | Check BuildKit resource limits; consider multi-stage builds |\n\n## Network + Connectivity Issues\n\n| Symptom | Cause | Fix |\n|---|---|---|\n| `ECONNREFUSED` to API | Wrong `EVE_API_URL` or service down | `./bin/eh status` to verify; correct URL for mode |\n| k3d ingress 502 | Service not ready or wrong namespace | `eve system pods` to check; wait for rollout |\n| Can't reach `*.lvh.me` | DNS or k3d not running | `lvh.me` resolves to 127.0.0.1; ensure k3d cluster is up |\n| Webhook delivery fails | Endpoint unreachable or HMAC mismatch | `eve webhooks deliveries \u003cid>` for delivery logs |\n\n## Real-Time Debugging (Multi-Terminal)\n\nFor active issues, use three terminals simultaneously:\n\n```bash\n# Terminal 1: Watch job status\nwatch -n 5 'eve job show \u003cid> --verbose 2>&1 | head -30'\n\n# Terminal 2: Stream harness output\neve job follow \u003cid>\n\n# Terminal 3: Runner pod logs (K8s only)\neve job runner-logs \u003cid>\n```\n\nStartup errors (clone, workspace, auth) appear in orchestrator/worker/runner logs, **not** in `follow`.\n\n```bash\neve system logs api -f # API errors\neve system logs orchestrator -f # Job claim/dispatch issues\neve system logs worker -f # Workspace/harness issues\n```\n\n## Local Stack Troubleshooting\n\n### Docker Compose\n\n```bash\n./bin/eh status # Check current mode and ports\n./bin/eh start docker # Restart stack\ndocker logs eve-api -f # API logs\ndocker logs eve-orchestrator -f # Orchestrator logs\ndocker logs eve-worker -f # Worker logs\n```\n\nLogs for local dev mode at `/tmp/eve-{api,orchestrator,worker}.log`.\n\n### K3d Stack\n\n```bash\neve local status [--watch] # Full status dashboard\neve local health # Health check (exits non-zero if unhealthy)\neve local logs \u003cservice> -f # Stream service logs\neve local reset --force # Nuclear: destroy + recreate\n```\n\nIf k3d cluster is stale or corrupted, `eve local reset --force` is the fastest recovery.\n\n## Common Error Reference\n\n| Error Message | Meaning | Fix |\n|---|---|---|\n| \"OAuth token has expired\" | Claude auth token stale | `./bin/eh auth extract --save` then redeploy |\n| \"git clone failed\" | Repo inaccessible or token wrong | Check `GITHUB_TOKEN` secret scope |\n| \"Orchestrator restarted while attempt was running\" | Job orphaned on restart | Auto-retries via recovery; no action needed |\n| \"secret resolution failed\" | Internal API key missing/wrong | Set `EVE_INTERNAL_API_KEY` on API + worker |\n| \"insufficient permissions\" | RBAC deny | `eve access explain --org \u003cid> --user \u003cid> --permission \u003cperm>` |\n\n## Debugging Checklist\n\nWhen all else fails, work through this systematically:\n\n1. **Platform health**: `eve system health --json` -- is the API responding?\n2. **Environment state**: `./bin/eh status` -- correct mode, URLs, ports?\n3. **Auth state**: `eve auth status` -- valid token? correct org context?\n4. **Secrets**: `eve secrets list --project \u003cid>` -- all required keys present?\n5. **Job/deploy diagnosis**: `eve job diagnose \u003cid>` or `eve env diagnose \u003cproj> \u003cenv>`\n6. **Logs**: `eve system logs \u003cservice> -f` -- any errors in API/orchestrator/worker?\n7. **K8s pods** (last resort): `eve system pods` -- all pods running?\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":17713,"content_sha256":"3c55cd34c1f6b8a9a458a2eb64eba5ae8eb91e7689eb45b8ebdeb104e41e426c"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Eve Read Docs (Load First)","type":"text"}]},{"type":"paragraph","content":[{"text":"Purpose: provide a compact, public, always-available distillation of Eve Horizon system docs. Use this when private system docs are not accessible.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"When to Use","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Any question about how to use Eve Horizon via CLI or API.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Any question about ","type":"text"},{"text":".eve/manifest.yaml","type":"text","marks":[{"type":"code_inline"}]},{"text":", pipelines, workflows, jobs, or secrets.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Any question about events, triggers, agents, teams, builds, or deployments.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"How to Use","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Start with ","type":"text"},{"text":"references/overview.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for core concepts, IDs, and the reference index.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use the task router below to choose the smallest set of references for the request.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Open only the relevant reference files and avoid loading unrelated docs.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Ask for missing project or environment inputs before giving prescriptive commands.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Task Router (Progressive Access)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Platform orientation, environment URLs, architecture, Eve Dashboard, system app pattern: ","type":"text"},{"text":"references/overview.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Command syntax, flags, and CLI workflows (includes cloud-fs, endpoint, ingest, traces, env logs --follow/--filter, env diagnose --request, ","type":"text"},{"text":"eve tcp-ingress test","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"eve app-links","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"eve admin email bounces list [--recipient|--event-type|--limit|--json]","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"eve org invite --project --redirect-to","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"eve project auth-context","type":"text","marks":[{"type":"code_inline"}]},{"text":" commands): ","type":"text"},{"text":"references/cli.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Fine-grained CLI intents:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/cli-auth.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" (auth + access + policy)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/cli-org-project.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" (init, org/project setup, docs, fs sync)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/cli-jobs.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" (jobs and execution controls, per-job harness/env overrides, app-link injection via ","type":"text"},{"text":"--with-links","type":"text","marks":[{"type":"code_inline"}]},{"text":", scoped job tokens via ","type":"text"},{"text":"jobs.token_scope","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/cli-pipelines.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" (builds, releases, pipelines, workflows)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/cli-deploy-debug.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" (deploy, recovery, local stack, CLI troubleshooting, env logs follow/filter, env diagnose --request, traces query)","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Manifest authoring, config structure, app CLI framework, cross-project app links (","type":"text"},{"text":"x-eve.app_links","type":"text","marks":[{"type":"code_inline"}]},{"text":"), toolchain declarations, cloud FS mounts, per-org OAuth, app undeploy/delete, custom domains, public TCP ingress (","type":"text"},{"text":"x-eve.tcp_ingress","type":"text","marks":[{"type":"code_inline"}]},{"text":"), stable egress (hostNetwork v2), workflow env_overrides + conditional steps + step-level harness/harness_options + step git controls + retry tails + file refs + Slack notifications + resource ref policies, manifest-driven service token permissions, ","type":"text"},{"text":"x-eve.branding","type":"text","marks":[{"type":"code_inline"}]},{"text":" (logo/color/From-name), ","type":"text"},{"text":"x-eve.auth.login_method","type":"text","marks":[{"type":"code_inline"}]},{"text":" (","type":"text"},{"text":"magic_link","type":"text","marks":[{"type":"code_inline"}]},{"text":"), ","type":"text"},{"text":"x-eve.auth.self_signup","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"x-eve.auth.invite_requires_password","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"x-eve.auth.org_access","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"x-eve.auth.domain_signup","type":"text","marks":[{"type":"code_inline"}]},{"text":" v2 rule list (","type":"text"},{"text":"[{domain, target_org, role}]","type":"text","marks":[{"type":"code_inline"}]},{"text":"), ","type":"text"},{"text":"x-eve.auth.allowed_redirect_origins","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"jobs.token_scope","type":"text","marks":[{"type":"code_inline"}]},{"text":" axes (","type":"text"},{"text":"orgfs","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"orgdocs","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"envdb","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"cloud_fs","type":"text","marks":[{"type":"code_inline"}]},{"text":"): ","type":"text"},{"text":"references/manifest.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Pipelines, workflows, triggers, event-driven automation, auto-trigger, event/app/app_link triggers, workflow input forwarding, step optimization, per-step ","type":"text"},{"text":"with_apis","type":"text","marks":[{"type":"code_inline"}]},{"text":", workflow env_overrides + conditional steps + step-level harness + retry-failed + file refs + Slack notifications, event→trigger observability (trigger_match_count, triggers_evaluated), scoped job tokens (workflow/step/invocation scope intersection into ","type":"text"},{"text":"jobs.token_scope","type":"text","marks":[{"type":"code_inline"}]},{"text":"): ","type":"text"},{"text":"references/pipelines-workflows.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"references/events.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Job lifecycle, scheduling, execution debugging, agent-native monitoring, production hardening, per-job HOME isolation, per-job harness/env overrides, app-link env/CLI injection, learning loop (","type":"text"},{"text":"system.job.attempt.completed","type":"text","marks":[{"type":"code_inline"}]},{"text":", carryover context), stuck-job prevention + stale recovery + env-gate scope, scoped job tokens (","type":"text"},{"text":"jobs.token_scope","type":"text","marks":[{"type":"code_inline"}]},{"text":" axes: ","type":"text"},{"text":"orgfs","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"orgdocs","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"envdb","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"cloud_fs","type":"text","marks":[{"type":"code_inline"}]},{"text":"): ","type":"text"},{"text":"references/jobs.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Build, release, and deployment behavior: ","type":"text"},{"text":"references/builds-releases.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"references/deploy-debug.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Private endpoints (Tailscale), worker toolchain-on-demand, app undeploy/delete, custom domains debugging (first-bind-wins, cert-manager TLS, ","type":"text"},{"text":"eve domain list|verify|status|transfer|unbind|remove","type":"text","marks":[{"type":"code_inline"}]},{"text":"), public TCP ingress diagnostics, stable egress (hostNetwork v2), DeployFailure taxonomy + cluster snapshot + manifest_hash from deploy ref + ","type":"text"},{"text":"eve env diagnose","type":"text","marks":[{"type":"code_inline"}]},{"text":", Platform Sentinel (env health monitoring + Slack alerts): ","type":"text"},{"text":"references/deploy-debug.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Agents, teams, chat routing, embedded app conversations, agent aliases, staged dispatch, chat delivery, chat progress, structured conversation event streams (","type":"text"},{"text":"cevt_*","type":"text","marks":[{"type":"code_inline"}]},{"text":"), chat continuity by Eve ","type":"text"},{"text":"thr_*","type":"text","marks":[{"type":"code_inline"}]},{"text":" id, chat regex case-insensitive, agent learning loop hooks, agent-runtime org auto-discovery (no ","type":"text"},{"text":"org_default","type":"text","marks":[{"type":"code_inline"}]},{"text":"): ","type":"text"},{"text":"references/agents-teams.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"references/gateways.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Secrets, auth, access control, identity providers, BYOK model credentials, per-org OAuth credential storage, manifest-driven service token permissions + auto-injected ","type":"text"},{"text":"EVE_SERVICE_TOKEN","type":"text","marks":[{"type":"code_inline"}]},{"text":" (read-only defaults), app-link tokens (","type":"text"},{"text":"type: app_link","type":"text","marks":[{"type":"code_inline"}]},{"text":"), SSO self-signup email domain restriction (","type":"text"},{"text":"EVE_SIGNUP_ALLOWED_EMAIL_DOMAINS","type":"text","marks":[{"type":"code_inline"}]},{"text":"), per-agent envdb wildcard scope (built-in roles), app magic-link login opt-in (","type":"text"},{"text":"x-eve.auth.login_method: magic_link","type":"text","marks":[{"type":"code_inline"}]},{"text":"), magic-link confirmation interstitial (wrap tokens; prevents drive-by scanner redemption), domain-signup v2 rule list (","type":"text"},{"text":"[{domain, target_org, role}]","type":"text","marks":[{"type":"code_inline"}]},{"text":"), project-scoped redirect allowlist (","type":"text"},{"text":"x-eve.auth.allowed_redirect_origins","type":"text","marks":[{"type":"code_inline"}]},{"text":"), platform-guaranteed ","type":"text"},{"text":"SameSite=None","type":"text","marks":[{"type":"code_inline"}]},{"text":" on ","type":"text"},{"text":"eve_sso","type":"text","marks":[{"type":"code_inline"}]},{"text":" session cookies for custom-domain apps: ","type":"text"},{"text":"references/secrets-auth.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Skills installation, packs, resolution order, materialization fast-path + ","type":"text"},{"text":".agents/skills/","type":"text","marks":[{"type":"code_inline"}]},{"text":" canonicalization + sparse pack support + ","type":"text"},{"text":"eve skills materialize","type":"text","marks":[{"type":"code_inline"}]},{"text":" runtime path: ","type":"text"},{"text":"references/skills-system.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Harness selection, sandbox policy, BYOK model setup, shared invoke, toolchain-on-demand, chat harness profiles, per-job harness override (","type":"text"},{"text":"--harness-override-file","type":"text","marks":[{"type":"code_inline"}]},{"text":") + env override (","type":"text"},{"text":"--env-override","type":"text","marks":[{"type":"code_inline"}]},{"text":"), harness-profile-validation endpoint, chat hint propagation, Phase 4 step-template expressions, Codex reasoning + harness model normalization, Opus 4.7 + GPT-5.5 model registrations: ","type":"text"},{"text":"references/harnesses.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Object store, org filesystem sync, share tokens, public paths, GCS storage, cloud FS (Google Drive), app bucket credential separation: ","type":"text"},{"text":"references/object-store-filesystem.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Document ingestion (upload, processing, download, callbacks): ","type":"text"},{"text":"references/ingest.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Document ingestion pipeline (end-to-end flow, agentpack, media processing, chat files, doc.ingest workflow trigger reliability fixes): ","type":"text"},{"text":"references/document-ingestion.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Eve SDK overview, install, quick-start, token flow, embedded conversations, chat SDKs, exports, structured conversation event streams, chat continuity by ","type":"text"},{"text":"thr_*","type":"text","marks":[{"type":"code_inline"}]},{"text":" id, branded app login pattern, ","type":"text"},{"text":"useEveAppAccess()","type":"text","marks":[{"type":"code_inline"}]},{"text":", in-app admin invites via ","type":"text"},{"text":"POST /auth/app-invites","type":"text","marks":[{"type":"code_inline"}]},{"text":": ","type":"text"},{"text":"references/eve-sdk.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Auth SDK deep-dive, ","type":"text"},{"text":"@eve-horizon/auth","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"@eve-horizon/auth-react","type":"text","marks":[{"type":"code_inline"}]},{"text":", app SSO middleware, token verification, project role resolution, and org awareness, magic-link login SDK opt-in, domain-signup v2 SDK behavior, magic-link confirmation interstitial transparency to ","type":"text"},{"text":"EveAuthProvider","type":"text","marks":[{"type":"code_inline"}]},{"text":": ","type":"text"},{"text":"references/auth-sdk.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Build agent-friendly CLIs for app APIs, manifest declaration, bundling, distribution, env var contract: ","type":"text"},{"text":"references/app-cli.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"OAuth app credentials (BYOA), Google Drive mounts, cloud FS browse/search, Slack install smoothing, gateway hot-load, per-org OAuth, chat file materialization, integrations, Slack connect, GitHub setup, identity linking, membership requests, API chat provider (no-op for polling clients; 4 built-in providers: slack, nostr, webchat, api), app org access + in-app admin invites (","type":"text"},{"text":"POST /auth/app-invites","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"eve org invite --project --redirect-to","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"eve project auth-context","type":"text","marks":[{"type":"code_inline"}]},{"text":"), app-branded invite + magic-link emails (logo, color, From-name via ","type":"text"},{"text":"x-eve.branding","type":"text","marks":[{"type":"code_inline"}]},{"text":"), project-scoped redirect allowlist: ","type":"text"},{"text":"references/integrations.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"references/gateways.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Observability, request diagnostics, service logs (","type":"text"},{"text":"eve env logs --follow","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"--filter k=v","type":"text","marks":[{"type":"code_inline"}]},{"text":"), traces (","type":"text"},{"text":"eve traces query","type":"text","marks":[{"type":"code_inline"}]},{"text":"), ","type":"text"},{"text":"eve env diagnose --request \u003creq_id>","type":"text","marks":[{"type":"code_inline"}]},{"text":", cost tracking, receipts, analytics, event→trigger observability (trigger_match_count, triggers_evaluated, ","type":"text"},{"text":"eve event show","type":"text","marks":[{"type":"code_inline"}]},{"text":"), SES mailer reliability (pre-flight suppression check, bounce webhook, ","type":"text"},{"text":"email_delivery_events","type":"text","marks":[{"type":"code_inline"}]},{"text":" table, ","type":"text"},{"text":"eve admin email bounces list [--recipient|--event-type|--limit|--json]","type":"text","marks":[{"type":"code_inline"}]},{"text":"): ","type":"text"},{"text":"references/observability.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Database provisioning, migrations, SQL, managed DB operations, managed DB TLS trust (verify-full default + CA bundle injection): ","type":"text"},{"text":"references/database-ops.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Symptom-first troubleshooting across auth, secrets, deploy, jobs, builds, DeployFailure taxonomy + diagnose, Platform Sentinel alert interpretation, stuck-job prevention + stale recovery, SES drops (suppression / bounces / ","type":"text"},{"text":"email_delivery_events","type":"text","marks":[{"type":"code_inline"}]},{"text":"), missing magic-link or invite emails, magic-link confirmation interstitial expectations, custom-domain ","type":"text"},{"text":"SameSite=None","type":"text","marks":[{"type":"code_inline"}]},{"text":" cookies, redirect allowlist mismatches, scoped job token denials, domain-signup v2 migration: ","type":"text"},{"text":"references/troubleshooting.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"LLM Wiki pattern, wiki-maintaining agents, knowledge base architecture, near-instant indexing, CLI enhancements (","type":"text"},{"text":"eve docs patch","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"diff","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"watch","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"sync","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"list --tree","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"search --path/--context","type":"text","marks":[{"type":"code_inline"}]},{"text":", bulk-write): ","type":"text"},{"text":"references/llm-wiki.md","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Index","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/overview.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- Architecture, core concepts, IDs, job phases, BYOK inference, document ingestion, cloud FS, private endpoints, Eve Dashboard + system app pattern, reference index.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/cli.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- CLI quick reference: all commands by category with flags and options, including cloud-fs, endpoint, ingest, traces query, env logs --follow/--filter, env diagnose --request, ","type":"text"},{"text":"eve tcp-ingress test","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"eve app-links","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"eve admin email bounces list [--recipient|--event-type|--limit|--json]","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"eve org invite --project --redirect-to","type":"text","marks":[{"type":"code_inline"}]},{"text":", and ","type":"text"},{"text":"eve project auth-context","type":"text","marks":[{"type":"code_inline"}]},{"text":" commands.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/manifest.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- Manifest v2 spec: services, environments, pipelines, workflows, x-eve extensions, app CLI framework, cross-project app links (","type":"text"},{"text":"x-eve.app_links","type":"text","marks":[{"type":"code_inline"}]},{"text":"), toolchain declarations, cloud FS mounts, per-org OAuth, app undeploy/delete, custom domains, public TCP ingress (","type":"text"},{"text":"x-eve.tcp_ingress","type":"text","marks":[{"type":"code_inline"}]},{"text":"), stable egress (","type":"text"},{"text":"x-eve.networking.egress: stable","type":"text","marks":[{"type":"code_inline"}]},{"text":"), workflow env_overrides + conditional steps + step-level harness/harness_options + step git controls + retry tails + file refs + Slack notifications + resource ref policies, manifest-driven service token permissions, ","type":"text"},{"text":"x-eve.branding","type":"text","marks":[{"type":"code_inline"}]},{"text":" (logo/color/From-name), ","type":"text"},{"text":"x-eve.auth.login_method: magic_link","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"x-eve.auth.self_signup","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"x-eve.auth.invite_requires_password","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"x-eve.auth.org_access","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"x-eve.auth.domain_signup","type":"text","marks":[{"type":"code_inline"}]},{"text":" v2 rule list ","type":"text"},{"text":"[{domain, target_org, role}]","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"x-eve.auth.allowed_redirect_origins","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"jobs.token_scope","type":"text","marks":[{"type":"code_inline"}]},{"text":" axes (","type":"text"},{"text":"orgfs","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"orgdocs","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"envdb","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"cloud_fs","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/events.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- ","type":"text"},{"text":"Event type catalog","type":"text","marks":[{"type":"strong"}]},{"text":" (all sources + payloads) and ","type":"text"},{"text":"trigger syntax","type":"text","marks":[{"type":"strong"}]},{"text":" (github, slack, system, cron, manual, app_link).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/jobs.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- Job lifecycle, phases, CLI, git/workspace controls, scheduling hints, agent-native monitoring, production hardening, per-job HOME isolation, per-job harness/env overrides, app-link env/CLI injection, learning loop (","type":"text"},{"text":"system.job.attempt.completed","type":"text","marks":[{"type":"code_inline"}]},{"text":", carryover context, ","type":"text"},{"text":"user","type":"text","marks":[{"type":"code_inline"}]},{"text":" memory category), stuck-job prevention + stale recovery + env-gate scope (action jobs only), scoped job tokens via ","type":"text"},{"text":"jobs.token_scope","type":"text","marks":[{"type":"code_inline"}]},{"text":" (axes: ","type":"text"},{"text":"orgfs","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"orgdocs","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"envdb","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"cloud_fs","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/builds-releases.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- Build system (specs, runs, artifacts), releases, deploy model, promotion patterns.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/agents-teams.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- Agent/team/chat YAML schemas, sync flow, slug rules, embedded app conversations, agent aliases, staged team dispatch, chat outbound delivery, chat progress updates, dispatch modes, coordination threads, structured conversation event streams (","type":"text"},{"text":"cevt_*","type":"text","marks":[{"type":"code_inline"}]},{"text":"), chat continuity by ","type":"text"},{"text":"thr_*","type":"text","marks":[{"type":"code_inline"}]},{"text":" id, chat regex case-insensitive, agent learning loop hooks, agent-runtime org auto-discovery.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/pipelines-workflows.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- Pipeline steps, triggers, workflow invocation, auto-trigger, event/app triggers, workflow input forwarding, step optimization, per-step ","type":"text"},{"text":"with_apis","type":"text","marks":[{"type":"code_inline"}]},{"text":", build-release-deploy pattern, env_overrides + conditional steps + step-level harness + step git controls + retry-failed + file refs + Slack notifications + resource ref policies, event→trigger observability (trigger_match_count, triggers_evaluated), scoped job tokens (workflow/step/invocation scope intersection into ","type":"text"},{"text":"jobs.token_scope","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/secrets-auth.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- Secrets scopes, interpolation, auth model, identity providers, OAuth sync, service principals, access visibility, custom roles, policy-as-code, BYOK model credentials, per-org OAuth credential storage, manifest-driven service token permissions + auto-injected ","type":"text"},{"text":"EVE_SERVICE_TOKEN","type":"text","marks":[{"type":"code_inline"}]},{"text":" (read-only defaults), app-link tokens (","type":"text"},{"text":"type: app_link","type":"text","marks":[{"type":"code_inline"}]},{"text":"), SSO self-signup email domain restriction (","type":"text"},{"text":"EVE_SIGNUP_ALLOWED_EMAIL_DOMAINS","type":"text","marks":[{"type":"code_inline"}]},{"text":"), per-agent envdb wildcard scope, app magic-link login opt-in (","type":"text"},{"text":"x-eve.auth.login_method: magic_link","type":"text","marks":[{"type":"code_inline"}]},{"text":"), magic-link confirmation interstitial (wrap tokens; prevents drive-by scanner redemption), domain-signup v2 per-rule list (","type":"text"},{"text":"[{domain, target_org, role}]","type":"text","marks":[{"type":"code_inline"}]},{"text":"), project-scoped redirect allowlist (","type":"text"},{"text":"x-eve.auth.allowed_redirect_origins","type":"text","marks":[{"type":"code_inline"}]},{"text":"), platform-guaranteed ","type":"text"},{"text":"SameSite=None","type":"text","marks":[{"type":"code_inline"}]},{"text":" on ","type":"text"},{"text":"eve_sso","type":"text","marks":[{"type":"code_inline"}]},{"text":" session cookies for custom-domain apps.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/skills-system.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- Skills format, skills.txt, install flow, discovery priority, materialization fast-path + ","type":"text"},{"text":".agents/skills/","type":"text","marks":[{"type":"code_inline"}]},{"text":" canonicalization + sparse pack support + ","type":"text"},{"text":"eve skills materialize","type":"text","marks":[{"type":"code_inline"}]},{"text":" runtime path.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/deploy-debug.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- K8s architecture, worker images, deploy polling, ingress/TLS, secrets provisioning, workspace janitor, private endpoints (Tailscale), worker toolchain-on-demand, app undeploy/delete, CLI debugging workflows, real-time debugging, env-specific debugging, custom domains debugging (first-bind-wins, cert-manager TLS, ","type":"text"},{"text":"eve domain","type":"text","marks":[{"type":"code_inline"}]},{"text":" subcommands), public TCP ingress diagnostics, stable egress (hostNetwork v2), DeployFailure taxonomy + cluster snapshot + manifest_hash from deploy ref + ","type":"text"},{"text":"eve env diagnose","type":"text","marks":[{"type":"code_inline"}]},{"text":", Platform Sentinel (env health monitoring + Slack alerts).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/harnesses.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- Harness selection, profiles, auth priority, sandbox flags, BYOK model setup, shared invoke module, toolchain-on-demand, harness profiles for chat, per-job harness override (","type":"text"},{"text":"--harness-override-file","type":"text","marks":[{"type":"code_inline"}]},{"text":") + env override (","type":"text"},{"text":"--env-override","type":"text","marks":[{"type":"code_inline"}]},{"text":"), harness-profile-validation endpoint, chat hint propagation, Phase 4 step-template expressions, Codex reasoning + harness model normalization, Opus 4.7 + GPT-5.5 model registrations.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/gateways.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- Gateway plugin architecture, Slack + Nostr + webchat + API providers (4 built-in), thread keys, structured conversation event streams (","type":"text"},{"text":"cevt_*","type":"text","marks":[{"type":"code_inline"}]},{"text":"), chat continuity by Eve ","type":"text"},{"text":"thr_*","type":"text","marks":[{"type":"code_inline"}]},{"text":" id, embedded app conversations, API chat provider (no-op for polling clients).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/cli-auth.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- CLI auth, service accounts, access roles, and policy-as-code.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/cli-org-project.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- CLI commands for org/project setup, docs, FS sync, and resolver URIs.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/cli-jobs.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- CLI job lifecycle: create/list/update, attempt tracking, result/monitoring/attachments, app-link injection (","type":"text"},{"text":"--with-links","type":"text","marks":[{"type":"code_inline"}]},{"text":"), per-job harness override (","type":"text"},{"text":"--harness-override-file","type":"text","marks":[{"type":"code_inline"}]},{"text":") + env override (","type":"text"},{"text":"--env-override","type":"text","marks":[{"type":"code_inline"}]},{"text":"), scoped job tokens via ","type":"text"},{"text":"jobs.token_scope","type":"text","marks":[{"type":"code_inline"}]},{"text":" (axes: ","type":"text"},{"text":"orgfs","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"orgdocs","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"envdb","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"cloud_fs","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/cli-pipelines.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- CLI build/release/pipeline/workflow command reference.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/cli-deploy-debug.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- CLI environment deploy/recover/lifecycle and local k3d stack, env logs --follow/--filter, env diagnose --request, traces query.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/object-store-filesystem.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- Object store, org filesystem sync protocol, share tokens, public paths, app buckets, access control, native GCS storage, cloud FS (Google Drive mounts), app bucket credential separation.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/ingest.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- Document ingest lifecycle: upload, processing, download URLs, callbacks, CORS, event integration.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/document-ingestion.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- Document ingestion pipeline: end-to-end flow, ingest:// URI scheme, agentpack, media processing (ffmpeg + whisper), chat file materialization, integration points, doc.ingest workflow trigger reliability fixes.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/eve-sdk.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- Eve SDK overview: auth + chat packages, install, quick-start patterns, token flow, embedded conversation pane, backend/frontend exports, environment variables, structured conversation event streams, chat continuity by ","type":"text"},{"text":"thr_*","type":"text","marks":[{"type":"code_inline"}]},{"text":" id, branded app login pattern, ","type":"text"},{"text":"useEveAppAccess()","type":"text","marks":[{"type":"code_inline"}]},{"text":", in-app admin invites via ","type":"text"},{"text":"POST /auth/app-invites","type":"text","marks":[{"type":"code_inline"}]},{"text":", app magic-link login opt-in.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/auth-sdk.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- Eve Auth SDK deep-dive: middleware behavior, verification strategies, token types, SSO session bootstrap, NestJS patterns, project role resolution, org awareness, migration guide, magic-link login SDK opt-in, domain-signup v2 SDK behavior, magic-link confirmation interstitial transparency to ","type":"text"},{"text":"EveAuthProvider","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/integrations.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- OAuth app credentials (BYOA), Google Drive cloud FS mounts, Slack install smoothing, gateway hot-load, per-org OAuth, chat file materialization, external integrations (Slack, GitHub), identity resolution tiers, membership requests, CLI linking, app org access + in-app admin invites (","type":"text"},{"text":"POST /auth/app-invites","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"eve org invite --project --redirect-to","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"eve project auth-context","type":"text","marks":[{"type":"code_inline"}]},{"text":"), app-branded invite + magic-link emails (logo/color/From-name via ","type":"text"},{"text":"x-eve.branding","type":"text","marks":[{"type":"code_inline"}]},{"text":"), project-scoped redirect allowlist.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/observability.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- Correlation IDs, app service logs (","type":"text"},{"text":"eve env logs --follow","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"--filter k=v","type":"text","marks":[{"type":"code_inline"}]},{"text":"), request diagnostics (","type":"text"},{"text":"eve env diagnose --request","type":"text","marks":[{"type":"code_inline"}]},{"text":"), traces (","type":"text"},{"text":"eve traces query","type":"text","marks":[{"type":"code_inline"}]},{"text":"), execution receipts, cost tracking, analytics, OTEL config, provider discovery, event→trigger observability (trigger_match_count, triggers_evaluated, ","type":"text"},{"text":"eve event show","type":"text","marks":[{"type":"code_inline"}]},{"text":"), SES mailer reliability (pre-flight suppression check, bounce webhook, ","type":"text"},{"text":"email_delivery_events","type":"text","marks":[{"type":"code_inline"}]},{"text":" table, ","type":"text"},{"text":"eve admin email bounces list [--recipient|--event-type|--limit|--json]","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/database-ops.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- Managed DB provisioning, migrations, SQL access, schema/RLS inspection, scaling/reset/destroy, managed DB TLS trust (verify-full default + CA bundle injection).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/app-cli.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- App CLI framework: manifest declaration, env var contract, bundling, distribution, image-mode CLIs for cross-project app links, implementation patterns.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/troubleshooting.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- Symptom-first diagnostic tables for auth, secrets, deploy, jobs, builds, network issues, DeployFailure taxonomy + ","type":"text"},{"text":"eve env diagnose","type":"text","marks":[{"type":"code_inline"}]},{"text":", Platform Sentinel alert interpretation, stuck-job prevention + stale recovery, SES drops (suppression / bounces / ","type":"text"},{"text":"email_delivery_events","type":"text","marks":[{"type":"code_inline"}]},{"text":"), missing magic-link or invite emails, magic-link confirmation interstitial expectations, custom-domain ","type":"text"},{"text":"SameSite=None","type":"text","marks":[{"type":"code_inline"}]},{"text":" cookies, redirect allowlist mismatches, scoped job token denials, domain-signup v2 migration.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/llm-wiki.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" -- LLM Wiki pattern: two-layer substrate, agent workflow, near-instant indexing, operations (ingest/query/lint/bulk), CLI enhancements (","type":"text"},{"text":"eve docs patch","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"diff","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"watch","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"sync","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"list --tree","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"search --path/--context","type":"text","marks":[{"type":"code_inline"}]},{"text":", bulk-write), relationship to Company as Intelligence.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Intent Coverage Matrix","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Intent","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Minimum references","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Expected output","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Authenticate or inspect permissions","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/cli-auth.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/secrets-auth.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Session state, token/permission validation result","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bootstrap org/project resources","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/cli-org-project.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/manifest.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Org/project IDs, members, manifest sync status","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Submit and monitor work","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/cli-jobs.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/jobs.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Job IDs, phase transitions, attempt logs","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Build/deploy a version","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/cli-pipelines.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/builds-releases.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/pipelines-workflows.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Pipeline run ID, build/release artifacts, deployment trace","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Recover from runtime issues","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/cli-deploy-debug.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/deploy-debug.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/cli-jobs.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Diagnose output, recovery target, mitigation command plan","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Trace a deployed app request","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/observability.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/cli-deploy-debug.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/deploy-debug.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Request logs, deploy metadata, K8s events, audit rows, trace spans","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Inspect platform behavior or events","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/events.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/agents-teams.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Canonical event stream view, routing path","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Install/update skills for agents","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/skills-system.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/overview.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Installed pack/skill set and resolution order","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Monitor costs, receipts, or analytics","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/observability.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/cli.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Receipt breakdown, analytics counters, cost totals","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Provision or operate environment databases","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/database-ops.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/manifest.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Migration status, query results, managed DB state","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Sync files, share links, or configure org filesystem","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/object-store-filesystem.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/cli-org-project.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Sync status, share tokens, public path URLs","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Upload, process, or download documents via ingest","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/ingest.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/document-ingestion.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/events.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Ingest IDs, download URLs, callback payloads, processing status","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Configure ingest agentpack or media processing","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/document-ingestion.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/agents-teams.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Pack import, profile selection, media tool availability","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Understand how Slack files reach agents","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/document-ingestion.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/gateways.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Chat file flow, attachment index, workspace layout","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Add SSO auth to an app or verify tokens","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/eve-sdk.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/auth-sdk.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/secrets-auth.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"SDK setup code, token verification, SSO flow","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Add an embedded agent conversation pane","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/eve-sdk.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/agents-teams.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/gateways.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Conversation API shape, SDK setup code, route policy, stream resume behavior","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Configure OAuth app credentials or connect Google Drive","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/integrations.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"BYOA config status, mount IDs, browse/search results","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Connect Slack/GitHub or resolve external identities","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/integrations.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/agents-teams.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Integration status, identity binding, membership requests","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Build an agent-friendly CLI for an app API","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/app-cli.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/manifest.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CLI source, esbuild bundle, manifest declaration, tested commands","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Connect Google Drive or browse cloud FS","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/object-store-filesystem.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/integrations.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Mount ID, browse/search results, cloud FS event triggers","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Set up private endpoints (Tailscale)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/deploy-debug.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/cli.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Endpoint name, in-cluster DNS, health check status","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Declare or use toolchains","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/manifest.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/harnesses.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Toolchain list, init container config, PATH setup","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Set up agent aliases or vanity names","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/agents-teams.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Alias binding, slug resolution, sync validation","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Check chat delivery status or progress","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/agents-teams.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/gateways.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Delivery status, thread messages, progress updates","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Configure event or app triggers for workflows","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/pipelines-workflows.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/events.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Trigger config, event payload forwarding, matched workflow","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Undeploy or delete an app/environment","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/deploy-debug.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/manifest.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Deploy status, cleanup sequence, cascade-delete","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Configure BYOK model credentials","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/secrets-auth.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/harnesses.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Secret scope, harness env mapping, provider key","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Set up per-org OAuth credentials (BYOA)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/integrations.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/manifest.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"OAuth app config, provider connection, credential storage","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Configure GCS native storage","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/object-store-filesystem.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Storage backend config, Workload Identity binding","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Monitor jobs with agent-native tooling","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/jobs.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/cli-jobs.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Job status, monitoring output, production safeguards","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Set up staged team dispatch (council)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/agents-teams.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Team config, staged flag, lead/member coordination","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Configure per-step ","type":"text"},{"text":"with_apis","type":"text","marks":[{"type":"code_inline"}]},{"text":" in workflows","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/pipelines-workflows.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/manifest.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Step overrides, API injection, workflow-level defaults","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Resolve project roles in auth SDK","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/auth-sdk.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/secrets-auth.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Project role claim, ","type":"text"},{"text":"X-Eve-Project-Id","type":"text","marks":[{"type":"code_inline"}]},{"text":" header, middleware config","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Configure custom domains for deployed apps","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/cli.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/manifest.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/deploy-debug.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Domain registration, DNS verification, TLS activation","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bind a custom domain","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/manifest.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/deploy-debug.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Domain binding, first-bind-wins behavior, cert-manager TLS status","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Expose or diagnose a public raw TCP listener","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/manifest.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/deploy-debug.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/cli.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"x-eve.tcp_ingress","type":"text","marks":[{"type":"code_inline"}]},{"text":" config, listener state, TCP probe result","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Diagnose a failure from symptoms","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/troubleshooting.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/deploy-debug.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Root cause, fix command, recovery path","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Override harness or env per job","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/cli-jobs.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/jobs.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/harnesses.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Override file path, env keys, validated harness profile","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Validate a harness profile","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/harnesses.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Validation result, normalized model, sandbox/auth check","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Configure conditional or step-level harness in workflows","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/pipelines-workflows.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/manifest.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Step harness/harness_options block, condition expression, env_overrides","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Diagnose a failed deploy","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/deploy-debug.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/troubleshooting.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"DeployFailure code, cluster snapshot, manifest_hash, diagnose output","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Configure stable egress (hostNetwork)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/manifest.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/deploy-debug.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"x-eve.networking.egress: stable","type":"text","marks":[{"type":"code_inline"}]},{"text":" config, source IP, pod placement","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Interpret Platform Sentinel alerts","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/troubleshooting.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/deploy-debug.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Alert source, env health probe, Slack message, remediation","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Stream service logs or filter on JSON keys","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/observability.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/cli-deploy-debug.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Live log stream, filtered key/value matches","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Diagnose a single request end-to-end","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/observability.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/cli-deploy-debug.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Request timeline, spans, logs, K8s context for ","type":"text"},{"text":"req_*","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Query OTEL traces from CLI","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/observability.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/cli.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Trace query results, span tree, latency breakdown","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Continue an Eve thread by ","type":"text"},{"text":"thr_*","type":"text","marks":[{"type":"code_inline"}]},{"text":" ID","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/agents-teams.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/eve-sdk.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Thread resume call, conversation continuity, structured event stream","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Edit, diff, or watch wiki docs from CLI","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/llm-wiki.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"eve docs patch/diff/watch/sync","type":"text","marks":[{"type":"code_inline"}]},{"text":" flow, near-instant index status","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Restrict SSO self-signup by email domain","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/secrets-auth.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"EVE_SIGNUP_ALLOWED_EMAIL_DOMAINS","type":"text","marks":[{"type":"code_inline"}]},{"text":" config, allowlist behavior","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Materialize skills at job runtime","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/skills-system.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"eve skills materialize","type":"text","marks":[{"type":"code_inline"}]},{"text":" invocation, ","type":"text"},{"text":".agents/skills/","type":"text","marks":[{"type":"code_inline"}]},{"text":" layout, sparse pack subset","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use the agent learning loop / job.attempt.completed","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/jobs.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/agents-teams.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Event payload, carryover context, ","type":"text"},{"text":"user","type":"text","marks":[{"type":"code_inline"}]},{"text":" memory writes","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Adopt the system app pattern (deploy a platform-tier app)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/overview.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Pattern definition, system app boundaries, cross-link to fullstack-app-design","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Configure manifest-driven service tokens","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/secrets-auth.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/manifest.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Permission scopes, auto-injected ","type":"text"},{"text":"EVE_SERVICE_TOKEN","type":"text","marks":[{"type":"code_inline"}]},{"text":", read-only defaults","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Verify managed DB TLS trust","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/database-ops.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"verify-full","type":"text","marks":[{"type":"code_inline"}]},{"text":" default, CA bundle injection path, connection string","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Separate app bucket credentials","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/object-store-filesystem.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"App bucket credential scope, isolation boundary","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Opt an app into passwordless magic-link login","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/secrets-auth.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/manifest.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"x-eve.auth.login_method: magic_link","type":"text","marks":[{"type":"code_inline"}]},{"text":" config, interstitial flow, SDK behavior","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Auto-signup users by email domain into an org with a role (Path C)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/secrets-auth.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/manifest.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"x-eve.auth.domain_signup","type":"text","marks":[{"type":"code_inline"}]},{"text":" v2 rule list ","type":"text"},{"text":"[{domain, target_org, role}]","type":"text","marks":[{"type":"code_inline"}]},{"text":", applied role","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Allow redirect to a custom-domain app origin after invite/magic-link","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/secrets-auth.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/manifest.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"x-eve.auth.allowed_redirect_origins","type":"text","marks":[{"type":"code_inline"}]},{"text":" config, allowed origin match","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Brand invite + magic-link emails for an app","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/integrations.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/manifest.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"x-eve.branding","type":"text","marks":[{"type":"code_inline"}]},{"text":" logo/color/From-name, branded email preview","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Send an in-app admin invite to a new user","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/integrations.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/eve-sdk.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"POST /auth/app-invites","type":"text","marks":[{"type":"code_inline"}]},{"text":" call, ","type":"text"},{"text":"eve org invite --project --redirect-to","type":"text","marks":[{"type":"code_inline"}]},{"text":", redirect target","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Inspect SES bounces or suppressed recipients","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/observability.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/cli.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/troubleshooting.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"eve admin email bounces list","type":"text","marks":[{"type":"code_inline"}]},{"text":" output, ","type":"text"},{"text":"email_delivery_events","type":"text","marks":[{"type":"code_inline"}]},{"text":" rows, suppression status","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Constrain a job's token to specific paths or mounts","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/jobs.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/pipelines-workflows.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/manifest.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"jobs.token_scope","type":"text","marks":[{"type":"code_inline"}]},{"text":" axes (","type":"text"},{"text":"orgfs","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"orgdocs","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"envdb","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"cloud_fs","type":"text","marks":[{"type":"code_inline"}]},{"text":"), workflow/step intersection","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Diagnose missing magic-link / invite email","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/troubleshooting.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/observability.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"email_delivery_events","type":"text","marks":[{"type":"code_inline"}]},{"text":" row, suppression/bounce reason, interstitial expectation","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Migrate domain-signup manifest from v1 to v2","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/manifest.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/secrets-auth.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Replace string list with ","type":"text"},{"text":"[{domain, target_org, role}]","type":"text","marks":[{"type":"code_inline"}]},{"text":" rule list, per-rule role","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Hard Rules","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Eve is ","type":"text"},{"text":"API-first","type":"text","marks":[{"type":"strong"}]},{"text":"; the CLI only needs ","type":"text"},{"text":"EVE_API_URL","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Do ","type":"text"},{"text":"not","type":"text","marks":[{"type":"strong"}]},{"text":" assume URLs, ports, or environment state--ask if unknown.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"These references describe shipped platform behavior only.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If anything is missing or unclear, ask for the missing inputs.","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"eve-read-eve-docs","author":"@skillopedia","source":{"stars":0,"repo_name":"eve-skillpacks","origin_url":"https://github.com/incept5/eve-skillpacks/blob/HEAD/eve-work/eve-read-eve-docs/SKILL.md","repo_owner":"incept5","body_sha256":"c7101a637b9b7a35bfec3280e6b078340a298794246f2fb05323ca6ad6e5de1f","cluster_key":"dd76233287ad3abdf984e2e16e03449afa1ea474056e6985244a201216f2afc8","clean_bundle":{"format":"clean-skill-bundle-v1","source":"incept5/eve-skillpacks/eve-work/eve-read-eve-docs/SKILL.md","attachments":[{"id":"2abc7ee0-7e04-599b-be3b-8a8ec1e9a8f7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2abc7ee0-7e04-599b-be3b-8a8ec1e9a8f7/attachment.md","path":"references/agents-teams.md","size":28585,"sha256":"5f43ca7beb87f5f38d9cc2a1de3f63c8c87fce8ade384a8a44febdef7bb998db","contentType":"text/markdown; charset=utf-8"},{"id":"1f316dfd-8a0e-5dbe-b737-169262fcaebd","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1f316dfd-8a0e-5dbe-b737-169262fcaebd/attachment.md","path":"references/app-cli.md","size":12394,"sha256":"f9ae04a2233efdb1bc8eb518d425451e66b4f0147710d2380f4bed983dc0bdd2","contentType":"text/markdown; charset=utf-8"},{"id":"c6c8c3db-b50f-5b75-9541-0666699cd9ad","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c6c8c3db-b50f-5b75-9541-0666699cd9ad/attachment.md","path":"references/auth-sdk.md","size":24997,"sha256":"25c82779a6f7629c2fb6860bc8f82fe93c526557df07dbd3a47f30312aaeb2e7","contentType":"text/markdown; charset=utf-8"},{"id":"5f8695bf-7a69-596b-8496-12e155c51c1c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5f8695bf-7a69-596b-8496-12e155c51c1c/attachment.md","path":"references/builds-releases.md","size":13401,"sha256":"846c78da707207e5368437fe534750166681d7a20c08461da13c866f265412a7","contentType":"text/markdown; charset=utf-8"},{"id":"465d3eab-8901-5356-80dd-a80d39ad88ca","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/465d3eab-8901-5356-80dd-a80d39ad88ca/attachment.md","path":"references/cli-auth.md","size":9057,"sha256":"49b055a4450b4c25ad170e7e97b002b4abf3abe3a3d70a2152241246a0ad9447","contentType":"text/markdown; charset=utf-8"},{"id":"631fe199-a71d-5b77-bd35-41cfa67aca81","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/631fe199-a71d-5b77-bd35-41cfa67aca81/attachment.md","path":"references/cli-deploy-debug.md","size":10115,"sha256":"baad3852fa5ce695ebf33d2a088a2e451e4b3f331bd212595027b085b133e051","contentType":"text/markdown; charset=utf-8"},{"id":"9a0a120a-439b-571f-95bb-cc15177ec907","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9a0a120a-439b-571f-95bb-cc15177ec907/attachment.md","path":"references/cli-jobs.md","size":8770,"sha256":"8b76bb9e82592a3b62e076e4802ba01ab14c3dfe76861bb4f50d12a3cedbf245","contentType":"text/markdown; charset=utf-8"},{"id":"acbcbd03-50de-568e-b9cc-2ae77f7f4bd0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/acbcbd03-50de-568e-b9cc-2ae77f7f4bd0/attachment.md","path":"references/cli-org-project.md","size":13398,"sha256":"08e91a7c88e070122082222aa39d21156e7b6392babf65f6316ecfc02448015a","contentType":"text/markdown; charset=utf-8"},{"id":"7e2d736f-61c5-55bc-b877-9046da4f9409","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7e2d736f-61c5-55bc-b877-9046da4f9409/attachment.md","path":"references/cli-pipelines.md","size":3612,"sha256":"ba3f03ac190176f5abffb6c33132754a02935a6031fbdff679858167b31556b1","contentType":"text/markdown; charset=utf-8"},{"id":"e600c26a-9f69-5df0-bf4e-bdba102722a7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e600c26a-9f69-5df0-bf4e-bdba102722a7/attachment.md","path":"references/cli.md","size":56148,"sha256":"73b8d4796ac0727fa39bebf42d72be6d4972cda377abff0b1e2c8e008bca4c37","contentType":"text/markdown; charset=utf-8"},{"id":"34ff551b-eea1-5fb5-8c09-5a1cedeee272","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/34ff551b-eea1-5fb5-8c09-5a1cedeee272/attachment.md","path":"references/database-ops.md","size":12609,"sha256":"87498c4d68c5273a38e972ebf27c29d6bc2772b1665125f6d8c665a51407b726","contentType":"text/markdown; charset=utf-8"},{"id":"28da77bc-e1b5-5c04-8a9e-b0fc44db6adb","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/28da77bc-e1b5-5c04-8a9e-b0fc44db6adb/attachment.md","path":"references/deploy-debug.md","size":44740,"sha256":"f92510a4b3276de5ec91a1208e38f8e70a744e74629f0660bebf62587c3bae26","contentType":"text/markdown; charset=utf-8"},{"id":"01ee2c9a-5214-5eba-b757-5c23f972feeb","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/01ee2c9a-5214-5eba-b757-5c23f972feeb/attachment.md","path":"references/document-ingestion.md","size":12873,"sha256":"cb9c53bb3c5576f0bb905d71218264bb9be976910e6dad9ffa4b999c0c099962","contentType":"text/markdown; charset=utf-8"},{"id":"432bdd67-9ef6-5949-96f3-3f94f71a2cc5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/432bdd67-9ef6-5949-96f3-3f94f71a2cc5/attachment.md","path":"references/eve-sdk.md","size":13327,"sha256":"63ee263dbeb63f0c2634906f7eb9c9003c6c70fe4d8d33ef3c5c4f9e15692c9d","contentType":"text/markdown; charset=utf-8"},{"id":"55177bdc-42a5-54f9-878e-8460ba79f1a5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/55177bdc-42a5-54f9-878e-8460ba79f1a5/attachment.md","path":"references/events.md","size":13932,"sha256":"921994ddfd8abefbcb8e8ed61c256027a0877b22f48452589c5080fffc18a7bf","contentType":"text/markdown; charset=utf-8"},{"id":"a4354c9c-d8a2-5ad8-933b-1ed8709b8a8a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a4354c9c-d8a2-5ad8-933b-1ed8709b8a8a/attachment.md","path":"references/gateways.md","size":16879,"sha256":"b2b5e8e1a3763e9ecd1f3daca60cacf5eb91cf0b5891ed21c53280c7d1e0f6ef","contentType":"text/markdown; charset=utf-8"},{"id":"6824327a-2e60-51f2-9612-9383e065dd08","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6824327a-2e60-51f2-9612-9383e065dd08/attachment.md","path":"references/harnesses.md","size":31709,"sha256":"c795957ce279fc8c94d46ddf37b80434a8bde7f8e3c04bd7051d5863da0c3b0a","contentType":"text/markdown; charset=utf-8"},{"id":"f27f319d-5410-5580-9c24-d5ff50edb5b0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f27f319d-5410-5580-9c24-d5ff50edb5b0/attachment.md","path":"references/ingest.md","size":10298,"sha256":"6fb6bb9bf413b360c7aabb7a262d603c8aa9d2e73040427db3cca11f473b0dbb","contentType":"text/markdown; charset=utf-8"},{"id":"3e3a72a9-f33e-589c-9b9a-464b03764ee5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3e3a72a9-f33e-589c-9b9a-464b03764ee5/attachment.md","path":"references/integrations.md","size":22080,"sha256":"b180699a8aee6e2f5d42e121ff45997df91241b7e1b881a989208e559e23380f","contentType":"text/markdown; charset=utf-8"},{"id":"cd8db4c6-29a2-5c82-8605-96234c3d2a0b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/cd8db4c6-29a2-5c82-8605-96234c3d2a0b/attachment.md","path":"references/jobs.md","size":33608,"sha256":"b8d35007059ef5ca0e4df8386ee2bfed904cdb70aab0ba30ce9edd6b012b19a6","contentType":"text/markdown; charset=utf-8"},{"id":"ef7b959a-1cba-58da-81aa-24d47af088a5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ef7b959a-1cba-58da-81aa-24d47af088a5/attachment.md","path":"references/llm-wiki.md","size":10362,"sha256":"2bf39d13257e5e2deb96b373d621e3b8772760086e8e31e423388150240b80be","contentType":"text/markdown; charset=utf-8"},{"id":"61ca3ae1-7561-5b3b-8bd5-5cd97e66dd10","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/61ca3ae1-7561-5b3b-8bd5-5cd97e66dd10/attachment.md","path":"references/manifest.md","size":63043,"sha256":"26375fd3ccf4c65e4b9601c4ac61d7475921e103be875781085bf4ec4b0bab32","contentType":"text/markdown; charset=utf-8"},{"id":"f102cea0-b2e9-56c4-bfe3-95b5ba992cf2","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f102cea0-b2e9-56c4-bfe3-95b5ba992cf2/attachment.md","path":"references/object-store-filesystem.md","size":14174,"sha256":"57ab8b2c2b93dc8531c262174b5d08ba009a15844519fb8f1b3a1ee58f7c4fcf","contentType":"text/markdown; charset=utf-8"},{"id":"ed9a625a-32c0-55ab-b6ca-bd5418541e71","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ed9a625a-32c0-55ab-b6ca-bd5418541e71/attachment.md","path":"references/observability.md","size":13600,"sha256":"847abfa6e7c9eefd69e9d6961e0bdece2835f1bb7a47cce9dce70c03af726bb5","contentType":"text/markdown; charset=utf-8"},{"id":"662f10df-4ee1-5db6-b219-56ba5d660c8b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/662f10df-4ee1-5db6-b219-56ba5d660c8b/attachment.md","path":"references/overview.md","size":21260,"sha256":"f879077033c6b8eb1be37db1f5a93d6b6abb9c9b5b265adc8db47dba5e462130","contentType":"text/markdown; charset=utf-8"},{"id":"7c3bcd89-4a74-5c2e-9b28-5555c636d951","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7c3bcd89-4a74-5c2e-9b28-5555c636d951/attachment.md","path":"references/pipelines-workflows.md","size":34452,"sha256":"d5d0199e49a6cccbdd7f6c59205e22c1fc42915a768e2f06537a32312da6f653","contentType":"text/markdown; charset=utf-8"},{"id":"bc7d2727-5ec4-5d32-aa4d-d716466b9a81","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/bc7d2727-5ec4-5d32-aa4d-d716466b9a81/attachment.md","path":"references/secrets-auth.md","size":47522,"sha256":"b548aca1d2c9470819a17b721ac4d327efa80317831cf6c6471d72f943013242","contentType":"text/markdown; charset=utf-8"},{"id":"15bf00eb-80b1-5fc0-a891-fe613910ab1f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/15bf00eb-80b1-5fc0-a891-fe613910ab1f/attachment.md","path":"references/skills-system.md","size":11897,"sha256":"fa684847231c578fbf779bcdd4e6b551149743597a6b363166e320a7d297fec4","contentType":"text/markdown; charset=utf-8"},{"id":"2b03071d-d764-5e47-9ec9-f445c16b14c3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2b03071d-d764-5e47-9ec9-f445c16b14c3/attachment.md","path":"references/troubleshooting.md","size":17713,"sha256":"3c55cd34c1f6b8a9a458a2eb64eba5ae8eb91e7689eb45b8ebdeb104e41e426c","contentType":"text/markdown; charset=utf-8"}],"bundle_sha256":"09b758567d854b6616b1e674e75bad7565d1870348230be84487028255084e2e","attachment_count":29,"text_attachments":29,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"eve-work/eve-read-eve-docs/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"web-development","category_label":"Web"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"web-development","triggers":["eve docs","eve horizon docs","read eve docs","eve cli","eve manifest","eve pipelines","eve workflows","eve job","eve secrets","eve auth","eve events","eve triggers","eve agents","eve teams","eve builds","eve releases","eve deploy","eve tcp ingress","eve tcp-ingress","tcp_ingress","EVE_TCP_INGRESS_PROVIDER","eve observability","eve traces","eve env logs","eve env diagnose","eve filesystem","eve fs sync","eve object store","eve sdk","eve chat sdk","eve conversation sdk","eve embedded conversation","eve conversations","eve app conversations","eve auth sdk","eve sso","eve integrations","eve slack","eve github","eve identity","eve ingest","eve ingestion","eve document","eve pdf","eve media","eve audio","eve video","eve whisper","eve ffmpeg","eve google drive","eve cloud fs","eve cloud-fs","eve gcs","eve oauth","eve per-org oauth","eve BYOA","eve BYOK","eve endpoint","eve tailscale","eve private endpoint","eve toolchain","eve harness","eve harness override","eve harness validate","eve env override","eve job env override","eve job harness override","eve staged dispatch","eve agent alias","eve chat delivery","eve chat progress","eve event trigger","eve app trigger","eve workflow optimization","eve undeploy","eve app delete","eve traces query","eve env logs follow","eve env logs filter","eve env diagnose request","eve domain","eve custom domain","custom domain","eve stable egress","egress","hostNetwork egress","deploy failure","DeployFailure","deploy diagnose","eve sentinel","platform sentinel","env health monitoring","eve service token","EVE_SERVICE_TOKEN","eve signup domain","signup domain restriction","managed db tls","db tls trust","verify-full","eve docs patch","eve docs diff","eve docs watch","eve docs sync","eve docs list tree","app bucket credentials","eve app storage","eve skills materialize","runtime skills","sparse pack","system.job.attempt.completed","learning loop","carryover context","embedded conversation","conversation events","cevt","system app pattern","eve dashboard","step level harness","conditional workflow step","workflow env_overrides","pipeline env propagation","workflow retry failed","workflow file ref","chat case insensitive","org_default","org auto discovery","app cli","app links","app-links","app_link","cross project app links","cross-project app links","agent cli","cli for agents","cli wrapper","eve wiki","llm wiki","wiki pattern","knowledge base","wiki maintenance","eve magic link","eve magic-link","magic link login","passwordless app","magic link opt-in","eve magic link interstitial","magic link wrap","wrap token","eve domain signup","domain signup v2","path c signup","auto signup","eve allowed redirect origins","redirect allowlist","app redirect allowlist","eve app branding","app-branded email","app branded invite","branded magic link","eve app invites","app org access","admin invite","in-app invite","eve org invite project","eve auth context","app-context","eve project auth-context","samesite none","eve_sso cookie","custom domain cookie","email_delivery_events","ses bounce","ses suppression","eve admin email","eve admin email bounces","scoped job token","jobs.token_scope","token scope","workflow scope","step scope","EVE_PROJECT_ID"],"import_tag":"clean-skills-v1","description":"Load first. State-today index of distilled Eve Horizon system docs with task-based routing for CLI/API usage, manifests, pipelines, jobs, secrets, agents, builds, events, and debugging."}},"renderedAt":1782988366341}

Eve Read Docs (Load First) Purpose: provide a compact, public, always-available distillation of Eve Horizon system docs. Use this when private system docs are not accessible. When to Use - Any question about how to use Eve Horizon via CLI or API. - Any question about , pipelines, workflows, jobs, or secrets. - Any question about events, triggers, agents, teams, builds, or deployments. How to Use 1. Start with for core concepts, IDs, and the reference index. 2. Use the task router below to choose the smallest set of references for the request. 3. Open only the relevant reference files and avoi…