Skip to content

Unchained-Labs/otter

Repository files navigation

Otter

CI

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.

Features

  • 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_HOME trust model.
  • Streaming execution chunks (output_chunk) via SSE.
  • Post-run setup.sh hook execution with streamed logs.
  • Vibe programmatic mode uses --output streaming for 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.

Quick Start (Docker Compose)

Prepare local secrets:

cp .env.example .env
# edit .env and set MISTRAL_API_KEY

Install Mistral Vibe on host and bootstrap host ~/.vibe:

./scripts/install_mistral_vibe.sh
./scripts/bootstrap_host_vibe_home.sh

Use 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 0
docker compose up --build

Check health:

curl http://localhost:8080/healthz

Environment Variables

  • OTTER_DATABASE_URL (required), example: postgres://otter:otter@postgres:5432/otter
  • OTTER_REDIS_URL (required), example: redis://redis:6379
  • OTTER_LISTEN_ADDR (default 0.0.0.0:8080)
  • OTTER_CORS_ALLOWED_ORIGINS (default http://localhost:5173, comma-separated)
  • OTTER_VIBE_BIN (default vibe)
  • OTTER_VIBE_HOME_BASE (default /var/lib/otter/vibe)
  • OTTER_VIBE_MODEL (optional, example mistral-large-3)
  • OTTER_VIBE_PROVIDER (optional, example mistral)
  • OTTER_VIBE_EXTRA_ENV (optional, comma-separated KEY=VALUE pairs forwarded to vibe process)
  • OTTER_API_BASE_URL (default http://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 omits workspace_id)
  • OTTER_MAX_ATTEMPTS (default 5)
  • OTTER_WORKER_CONCURRENCY (default 1)
  • OTTER_ALLOWED_ROOTS (optional :-separated allowlist)
  • OTTER_RUNTIME_ENABLED (default false, enables sibling container runtime)
  • OTTER_RUNTIME_DOCKER_SOCKET (default unix:///var/run/docker.sock)
  • OTTER_RUNTIME_NETWORK (default kymatics_default)
  • OTTER_RUNTIME_CONTAINER_PREFIX (default otter-ws)
  • OTTER_RUNTIME_IMAGE_PREFIX (default otter/workspace)
  • OTTER_RUNTIME_DEFAULT_HOST (default http://localhost, used to compose preview URLs)
  • OTTER_RUNTIME_MAX_LOG_LINES (default 2000)
  • MISTRAL_API_KEY (read from .env, passed to server/worker/vibe process)

Selecting a Vibe model

Otter now supports explicit model/provider passthrough to the Vibe subprocess via environment:

OTTER_VIBE_MODEL=mistral-large-3
OTTER_VIBE_PROVIDER=mistral

These are forwarded as VIBE_MODEL / MISTRAL_MODEL and VIBE_PROVIDER for compatibility.

API Endpoints (MVP)

  • POST /v1/projects, GET /v1/projects
  • POST /v1/workspaces, GET /v1/workspaces
  • GET /v1/workspaces/{id}/tree, GET /v1/workspaces/{id}/file
  • POST /v1/workspaces/{id}/command and POST /v1/workspaces/command support shell_session_id for persistent shell cwd across commands (cd state is retained per session).
  • POST /v1/prompts (workspace_id optional when OTTER_DEFAULT_WORKSPACE_PATH is configured)
  • GET /v1/jobs/{id}
  • GET /v1/jobs/{id}/events
  • GET /v1/events/stream (includes output_chunk events)
  • POST /v1/jobs/{id}/cancel
  • POST /v1/jobs/{id}/pause
  • POST /v1/jobs/{id}/resume
  • POST /v1/jobs/{id}/preview-url (set demo URL for browser preview, body: { "preview_url": "http://host:port" })
  • GET /v1/queue
  • PATCH /v1/queue/{id} (update queue position via priority)
  • GET /v1/history
  • GET /v1/runtime/workspaces/{id} (runtime container status + preferred preview URL)
  • POST /v1/runtime/workspaces/{id}/start|stop|restart
  • GET /v1/runtime/workspaces/{id}/logs?tail=200
  • GET /v1/runtime/workspaces/{id}/shell/ws (interactive command channel via websocket)

Repository Structure

otter/
├── otter-core/
├── otter-server/
├── otter-worker/
├── docs/
├── docker-compose.yml
└── Dockerfile

Documentation

  • docs/architecture.md
  • docs/api.md
  • docs/workspace-trust-model.md
  • docs/prompt-to-result-flow.md
  • docs/operations-nuc.md
  • docs/runbook.md

Local Development

The current repository targets modern Rust toolchains. Build with stable Rust:

cargo check

Run services:

cargo run -p otter-server
cargo run -p otter-worker

Install and run pre-commit hooks:

pip install pre-commit
pre-commit install
pre-commit run --all-files

Notes

  • Worker execution requires the vibe binary to be available in the runtime environment.
  • Docker image installs mistral-vibe and exposes vibe in PATH.
  • scripts/bootstrap_host_vibe_home.sh mirrors /home/wardn/.vibe into ${HOME}/.vibe and writes MISTRAL_API_KEY from otter/.env.
  • In production, put the API behind authentication and TLS termination.

Execution Contract For Vibe

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.sh at 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.

Queue Pause/Resume

  • 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.

About

Rust orchestration engine for Mistral Vibe (or any other vibe cli): queue, schedule, and execute prompts in isolated workspaces with streaming events, runtime controls, and full job history.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors