Scientific imaging, tool-guided analysis, and reproducible model workflows in one local workbench.
Quick start (Docker) · What you launch · Choose a model endpoint · Run from source · Troubleshooting
BisQue Ultra gives you one surface for scientific images, datasets, metadata, model calls, and long-running tool workflows. An existing BisQue service stores the data, the Go control plane owns runs and access, Deep Agents workers execute long-running tool work, and React keeps the whole process visible. The model layer stays replaceable, so you can point the same platform at any OpenAI-compatible server without rewriting the application around a single vendor.
If you want one sentence to hold the whole system in your head, use this one: BisQue Ultra is a scientific workbench whose data layer is an existing BisQue deployment, whose control plane is Go, whose workers are Deep Agents, whose interface is React, and whose language model can come from any OpenAI-compatible server.
Production deployment and operator runbooks are intentionally kept in private internal documentation rather than the public repo.
The fastest way to run the entire stack on a fresh Linux or macOS machine. You need only Docker (Docker Desktop on macOS, Docker Engine + Compose plugin on Linux) and a local model server — no Go, Node, Python, or uv/pnpm required; the images build everything.
1. Start a model server on your host. Ollama is the shortest path:
ollama serve # if it isn't already running
ollama pull gpt-oss:20b # the stack's default model (any OpenAI-compatible model works)2. Build and launch the whole stack:
docker compose up --build # or: make up (same thing; auto-loads .env.docker if present)This builds and starts seven services — Postgres, NATS (JetStream), the libbioimage image service + its convert worker, the Go control plane, the Deep Agents worker, and the frontend. The first build compiles the native imaging engine (libbioimage/imgcnv) from source: budget ~10–30 minutes the first time (fast on native amd64 Linux; emulated on Apple Silicon). Later runs start in seconds.
3. Open the app: http://localhost:5174
You are signed in automatically as a local guest (dev auth — no account, no cloud). Start chatting (the worker calls your Ollama), or open Resources to drag in a scientific image (CZI, ND2, OME-TIFF, NIfTI, DICOM, and 90+ more), a multi-page TIFF z-stack, or a video, and explore it in the Scientific Viewer (tiles, z-scrub, video posters).
Point at a different model — no rebuild needed:
ULTRA_MODEL_NAME=qwen3:32b-fp16 docker compose up # another Ollama model
# vLLM on the host instead of Ollama:
ULTRA_MODEL_BASE_URL=http://host.docker.internal:8001/v1 \
ULTRA_MODEL_NAME=openai/gpt-oss-120b docker compose upTo set the model, ports, or auth persistently, copy the template and pass it through:
cp .env.docker.example .env.docker # then edit it
docker compose --env-file .env.docker up --buildStop with make down (or docker compose down); durable state — Postgres, uploaded files, and NATS JetStream — persists across restarts. Use make down-clean (docker compose down -v) to also wipe it. The defaults reach a host model at http://host.docker.internal:11434/v1 (host-gateway is wired for Linux). BisQue import features are off unless you set ULTRA_BISQUE_ROOT_URL.
Scale the agent worker tier. The worker is a NATS JetStream queue-group consumer, so you can run several against one stack: make scale-workers N=3. Before doing so, set an aggregate sandbox cap in .env.docker (ULTRA_SANDBOX_MAX_CONCURRENCY, e.g. 8, plus a ULTRA_SANDBOX_MEMORY=8g per-container cap) so the replicas can't oversubscribe the single host's Docker daemon.
Prefer to run the services directly from source (with hot reload, no Docker for the app code)? See Run from source below.
You are starting seven layers:
backend/controlplane/serves the BisQue Ultra API (port8000).- Local Postgres and NATS JetStream provide durable state and job dispatch.
- The libbioimage image service (
backend/deepagents_runtime/Dockerfile.imaging) decodes/serves 90+ scientific formats — tiles, slices, histograms, scalar volumes, and ffmpeg video posters — plus a convert worker that builds tiled pyramids on upload. backend/deepagents_runtime/runs the durable Deep Agents worker.frontend/serves the web client (port5174).- Your model server (Ollama or vLLM) answers OpenAI-style chat requests.
- (Optional) An existing BisQue deployment provides shared image/dataset/metadata services when
ULTRA_BISQUE_ROOT_URLis set.
Those layers are deliberately separate. If a page loads but chat fails, the frontend is alive and the API, worker, model server, or durable transport is not. If BisQue imports fail, check the configured BisQue URL and linked credentials before debugging the frontend. That separation is a feature, because it lets you debug the system by following the symptom instead of guessing.
If you want hot reload or to iterate on the app code without rebuilding images, run the services directly from source instead of the Docker Compose path above. This needs the language toolchains installed and does not include the libbioimage image service unless you build and run it yourself (the Docker Compose path is the supported way to get the imaging engine). Leave ULTRA_CONTROL_IMAGE_SERVICE_URL empty to use the legacy native image path.
Install the tools this path assumes:
- Go 1.25+ for the control plane
- uv for Python dependency management
- pnpm 11.5+ (and Node 22+) for the frontend
- Docker with Compose for local Postgres and NATS
You also need access to a BisQue deployment. For local development, point the app at a reachable BisQue host in .env. For staging and production, set ULTRA_CONTROL_BISQUE_ROOT_URL in the server-side environment.
You also need one model backend:
- vLLM if you want high-throughput serving for large open-weight models
- Ollama if you want the shortest path from a workstation to a working local assistant
BisQue Ultra keeps the active worker model contract deliberately simple: the Deep Agents runtime reads an OpenAI-compatible endpoint from OPENAI_BASE_URL, OPENAI_MODEL, and OPENAI_API_KEY.
That design matters. It means you can keep the orchestration stable while changing only one layer:
- vLLM for chat and tool reasoning
- Ollama through its OpenAI-compatible
/v1route - a remote OpenAI-compatible server today, another tomorrow
The public env template still includes provider-oriented knobs such as LLM_PROVIDER, LLM_*, OLLAMA_*, and CODEGEN_* for local tooling and future routing work. For the production-like Go + Deep Agents stack in this repo, set the OPENAI_* values explicitly; those are the values passed to the worker.
Start from the template:
cp .env.example .envThe public template is now local-first. It no longer points to an internal lab server. Out of the box it assumes:
- BisQue on
http://localhost:8080or another URL you set explicitly - API on
http://localhost:8000 - frontend on
http://localhost:5174 - vLLM on
http://localhost:8001/v1 - Ollama on
http://localhost:11434/v1
You do not need to fill every variable. The important moves are to set the BisQue URL you actually use and decide which inference engine should answer model requests.
Choose vLLM when you want a stronger open-weight model, better throughput, or a server that can feed multiple users without turning sluggish. In this repo, vLLM is treated as an OpenAI-compatible endpoint. That is why the environment keys still say OPENAI_BASE_URL and OPENAI_MODEL even when the actual server is vLLM.
If you want the .env.example defaults to work without extra renaming, launch vLLM with a served model name that matches the config:
vllm serve openai/gpt-oss-120b \
--host 0.0.0.0 \
--port 8001 \
--served-model-name gpt-oss-120b \
--api-key EMPTYThen keep this shape in .env:
OPENAI_BASE_URL=http://localhost:8001/v1
OPENAI_MODEL=gpt-oss-120b
OPENAI_API_KEY=EMPTYThree details are worth understanding:
- The app talks to the OpenAI-compatible route, so the base URL must end in
/v1. - The model name in BisQue Ultra must match the model name vLLM exposes.
- For local OpenAI-compatible servers, a placeholder key like
EMPTYis often enough. This app already handles that convention.
If you want a different model, change both the vLLM launch command and OPENAI_MODEL. Keep them synchronized. When they drift, the API may stay healthy while completions fail with a model-not-found error.
Choose Ollama when you value simplicity more than throughput. The setup is lighter, the commands are easier to remember, and the cost of experimentation is lower. The tradeoff is that very heavy reasoning or large multimodal workloads may feel better on vLLM-backed hardware.
Start Ollama and pull a model:
ollama serve
ollama pull qwen2.5:14b-instructThen set:
OPENAI_BASE_URL=http://localhost:11434/v1
OPENAI_MODEL=qwen2.5:14b-instruct
OPENAI_API_KEY=EMPTYThe /v1 suffix matters here too. BisQue Ultra uses an OpenAI client under the hood. It does not talk to Ollama’s older native endpoints directly. That single design choice is why the same backend can pivot between Ollama and vLLM without changing the orchestration code.
Install the backend:
uv syncInstall the frontend:
pnpm --dir frontend installIf you skip one half, the failure mode will tell on itself. Missing Python dependencies usually break worker or smoke checks. Missing frontend packages usually leave Vite unable to build or serve.
For the production-like V2 stack, use the control stack launcher:
make restart-control-stackThat starts the Go control plane, local Postgres, NATS JetStream, the Deep Agents worker, the RareSpot worker, and the React frontend. It is the path to use when validating durable users, long autonomous runs, refresh/reconnect behavior, and artifact hydration from past chats.
Check the stack with:
make status-control-stackStop it with:
make stop-control-stackThe status output should report store_backend=postgres and dispatch_mode=nats_jetstream. If it reports the in-memory store, user/admin state and past-chat hydration are not production-representative.
Run the control-plane integration gate:
make verify-integrationThat check answers the question that matters most for the modern app:
- Can the Go control plane persist and dispatch durable work through Postgres and NATS?
For BisQue connectivity, verify the actual URL configured in BISQUE_ROOT or ULTRA_CONTROL_BISQUE_ROOT_URL, then link credentials through the app. The public repo no longer carries the embedded BisQue deployment; staging and production should point at the operator-managed BisQue service directly.
You can also check the live endpoints directly:
curl -fsS http://127.0.0.1:8000/v1/health
curl -I -fsS http://localhost:5174These ports are easy to confuse because they all belong to one system but not to one process.
8080: common local BisQue service port when you point at a local instance8000: BisQue Ultra API5174: BisQue Ultra frontend8001: example local vLLM endpoint11434: default Ollama endpoint
If the frontend says the API is unavailable, look at 8000. If the API is healthy but chat hangs, inspect the worker, NATS, and model endpoint you configured. If BisQue import or browsing fails, check the configured BisQue root URL and the linked credentials.
Usually the API or the model backend is down.
Check:
make status-control-stack
curl -fsS http://127.0.0.1:8000/v1/healthIf the API is healthy, your next suspect is the model server. Make sure the base URL ends in /v1 and the model name in .env matches the model name the server actually exposes.
This usually means the environment is incomplete, not that the whole architecture is broken. Run:
make status-control-stack
make control-testCheck the BisQue URL and credentials configured for the control plane:
make status-control-stack
curl -fsS "${BISQUE_ROOT:-http://localhost:8080}/image_service/formats"If the BisQue endpoint is not reachable from the app host, fix that connection before debugging the viewer or chat UI.
The usual mistake is a served-model-name mismatch. If you launch:
vllm serve openai/gpt-oss-120bthen your request model may need to be openai/gpt-oss-120b unless you set --served-model-name gpt-oss-120b.
Make sure you are pointing at the OpenAI-compatible route:
OPENAI_BASE_URL=http://localhost:11434/v1
OPENAI_MODEL=qwen2.5:14b-instructnot just:
http://localhost:11434The repo does not vendor large model weights or scientific checkpoints. If you want the full imaging tool surface locally, provision these separately:
data/models/medsam2/checkpoints/data/models/sam3/- YOLO or prairie-dog weights such as
RareSpotWeights.ptandyolo26x.pt
The absence of those assets does not stop the web stack from booting. It only narrows which tools can run successfully.
backend/controlplane/: Go API, auth/session handling, run control, durable store, and OpenAPI contractbackend/deepagents_runtime/: Python Deep Agents worker runtime, tools, live trace checks, and RareSpot bridgefrontend/: React and Vite clientscripts/: startup and smoke-check helpers
These are the local checks that mirror the active GitHub verification workflows:
uv sync --frozen --extra dev
pnpm --dir frontend install --frozen-lockfile
make quality
uv run pytest -q
pnpm --dir frontend lint
pnpm --dir frontend typecheck
pnpm --dir frontend test:unit
pnpm --dir frontend build
pnpm --dir frontend bundle:check
pnpm --dir frontend test:smoke
./scripts/release_codescan.sh./scripts/release_codescan.sh is the public-release hygiene pass. It scans first-party repo surfaces for secrets, internal-style hostnames, operator-specific storage roots, and other values that should stay in private runbooks instead of the public tree.
For production autonomous-run durability, use the dedicated gate:
make autonomy-gateThis runs the Go control-plane soak test, the live Postgres + NATS integration gate, the Python Deep Agents worker transport tests, deterministic Deep Agents autonomy-quality/routing tests, and the frontend autonomous-chat recovery slice. Together they cover leases, durable worker heartbeats, ack extension, NAK redelivery, cancellation, terminal-event handling, live-trace quality scoring, paper/RareSpot preload routing, refresh-safe stream recovery, stale-conversation recovery, V2 idempotency, and artifact hydration from past chats.
In the production control-plane path, Postgres is the source of truth for users, organizations, threads, messages, runs, run events, artifacts, idempotency keys, worker heartbeats, and Go-owned run leases. NATS JetStream is the durable job/event/cancel transport. The Go control plane also sweeps expired worker leases and requeues the affected non-terminal runs, which keeps long autonomous chats recoverable across worker failure, browser refresh, and control-plane reconnects.
When a full local Go + NATS + Python worker + model stack is already running, run the opt-in live autonomy smoke:
make autonomy-live-smokeThis sends a real two-turn coding task through V2, requires durable code/plot artifacts with verified downloads, and checks that the follow-up uses prior context without corrupting the persisted thread transcript. It is intentionally separate from CI because it depends on a live model and worker stack.
If you already know which model you want, this is the whole story:
cp .env.example .env
uv sync
pnpm --dir frontend install
make restart-control-stack
make verify-integrationThen point your browser at http://localhost:5174.
The rest of this README exists to make that path legible, not longer.
