Otter is a Rust orchestration service and reusable library for running Mistral Vibe programmatically at scale.
It accepts prompts through HTTP endpoints, queues and schedules work, executes vibe --prompt in isolated trusted workspaces, and stores full execution history in PostgreSQL.
- Rust workspace architecture:
otter-core: orchestration domain library.otter-server: HTTP API.otter-worker: async queue consumer.
- PostgreSQL persistence for projects, workspaces, jobs, outputs, and events.
- Redis-backed queueing and worker retry lifecycle.
- Isolated per-workspace
VIBE_HOMEtrust model. - Streaming execution chunks (
output_chunk) via SSE. - Post-run
setup.shhook execution with streamed logs. - Vibe programmatic mode uses
--output streamingfor pseudo-live frontend feedback. - Workspace filesystem APIs (
tree/file) for frontend explorer UX. - Request tracing + lifecycle logs for operations visibility.
- Dockerized runtime with Compose stack for local and NUC deployment.
Prepare local secrets:
cp .env.example .env
# edit .env and set MISTRAL_API_KEYInstall Mistral Vibe on host and bootstrap host ~/.vibe:
./scripts/install_mistral_vibe.sh
./scripts/bootstrap_host_vibe_home.shUse the local endpoint CLI for quick API tests:
./scripts/otter-cli.sh smoke
./scripts/otter-cli.sh projects list
./scripts/otter-cli.sh queue list 20 0docker compose up --buildCheck health:
curl http://localhost:8080/healthzOTTER_DATABASE_URL(required), example:postgres://otter:otter@postgres:5432/otterOTTER_REDIS_URL(required), example:redis://redis:6379OTTER_LISTEN_ADDR(default0.0.0.0:8080)OTTER_CORS_ALLOWED_ORIGINS(defaulthttp://localhost:5173, comma-separated)OTTER_VIBE_BIN(defaultvibe)OTTER_VIBE_HOME_BASE(default/var/lib/otter/vibe)OTTER_VIBE_MODEL(optional, examplemistral-large-3)OTTER_VIBE_PROVIDER(optional, examplemistral)OTTER_VIBE_EXTRA_ENV(optional, comma-separatedKEY=VALUEpairs forwarded to vibe process)OTTER_API_BASE_URL(defaulthttp://otter-server:8080, used in system prompt instructions so Vibe can post job preview URLs)OTTER_DEFAULT_WORKSPACE_PATH(optional fallback workspace root when enqueue omitsworkspace_id)OTTER_MAX_ATTEMPTS(default5)OTTER_WORKER_CONCURRENCY(default1)OTTER_ALLOWED_ROOTS(optional:-separated allowlist)OTTER_RUNTIME_ENABLED(defaultfalse, enables sibling container runtime)OTTER_RUNTIME_DOCKER_SOCKET(defaultunix:///var/run/docker.sock)OTTER_RUNTIME_NETWORK(defaultkymatics_default)OTTER_RUNTIME_CONTAINER_PREFIX(defaultotter-ws)OTTER_RUNTIME_IMAGE_PREFIX(defaultotter/workspace)OTTER_RUNTIME_DEFAULT_HOST(defaulthttp://localhost, used to compose preview URLs)OTTER_RUNTIME_MAX_LOG_LINES(default2000)MISTRAL_API_KEY(read from.env, passed to server/worker/vibe process)
Otter now supports explicit model/provider passthrough to the Vibe subprocess via environment:
OTTER_VIBE_MODEL=mistral-large-3
OTTER_VIBE_PROVIDER=mistralThese are forwarded as VIBE_MODEL / MISTRAL_MODEL and VIBE_PROVIDER for compatibility.
POST /v1/projects,GET /v1/projectsPOST /v1/workspaces,GET /v1/workspacesGET /v1/workspaces/{id}/tree,GET /v1/workspaces/{id}/filePOST /v1/workspaces/{id}/commandandPOST /v1/workspaces/commandsupportshell_session_idfor persistent shell cwd across commands (cdstate is retained per session).POST /v1/prompts(workspace_idoptional whenOTTER_DEFAULT_WORKSPACE_PATHis configured)GET /v1/jobs/{id}GET /v1/jobs/{id}/eventsGET /v1/events/stream(includesoutput_chunkevents)POST /v1/jobs/{id}/cancelPOST /v1/jobs/{id}/pausePOST /v1/jobs/{id}/resumePOST /v1/jobs/{id}/preview-url(set demo URL for browser preview, body:{ "preview_url": "http://host:port" })GET /v1/queuePATCH /v1/queue/{id}(update queue position via priority)GET /v1/historyGET /v1/runtime/workspaces/{id}(runtime container status + preferred preview URL)POST /v1/runtime/workspaces/{id}/start|stop|restartGET /v1/runtime/workspaces/{id}/logs?tail=200GET /v1/runtime/workspaces/{id}/shell/ws(interactive command channel via websocket)
otter/
├── otter-core/
├── otter-server/
├── otter-worker/
├── docs/
├── docker-compose.yml
└── Dockerfile
docs/architecture.mddocs/api.mddocs/workspace-trust-model.mddocs/prompt-to-result-flow.mddocs/operations-nuc.mddocs/runbook.md
The current repository targets modern Rust toolchains. Build with stable Rust:
cargo checkRun services:
cargo run -p otter-server
cargo run -p otter-workerInstall and run pre-commit hooks:
pip install pre-commit
pre-commit install
pre-commit run --all-files- Worker execution requires the
vibebinary to be available in the runtime environment. - Docker image installs
mistral-vibeand exposesvibeinPATH. scripts/bootstrap_host_vibe_home.shmirrors/home/wardn/.vibeinto${HOME}/.vibeand writesMISTRAL_API_KEYfromotter/.env.- In production, put the API behind authentication and TLS termination.
Otter composes a system prompt that enforces safe and repeatable project execution:
- Always work in a dedicated project subfolder under workspace.
- Install dependencies before run/build.
- Generate a production-ready Dockerfile at project root.
- Build and run the generated app inside Docker as the primary execution path.
- Generate an executable
setup.shat project root. - Start the generated app/service in background.
- Use the provided Job ID and call Otter preview URL API when runtime URL is known:
POST /v1/jobs/{job_id}/preview-url
- Print clear run/stop instructions and project location.
- Include app access information (URL and host port) in the final output.
After a successful vibe execution, Otter attempts to run setup.sh and streams its output back as output_chunk events.
- Queued jobs can be paused without changing status from
queued. - Paused queued jobs are not claimable by workers until resumed.
- Resume re-enqueues the job message so it can run immediately when capacity is available.