Sandboxed Dev DB/Redis/Mongo

By default, an agent gating its own work against a real database (when toolchain matching is on) does so against RoboCo's own production Postgres — reachable creds injected straight into the agent's environment. The sandboxed dev DB/Redis/Mongo subsystem replaces that with something safer: a throwaway sibling container the agent requests on demand, only when its work actually needs one.

The capability is off by default, and even armed it only applies to projects that opt in.

What it does

Nothing is provisioned at spawn. When ROBOCO_SANDBOX_DB_ENABLED is on and a project's sandbox_services column names the services it wants, a developer or QA agent calls the request_sandbox do-verb the moment its work needs a database — the orchestrator provisions inline and hands back creds in that same call:

  • On-demand, role-scoped. Only developer and qa roles carry request_sandbox in their spawn manifest — the DB-needing gate roles. Spawn itself injects nothing but a marker env ROBOCO_SANDBOX_SERVICES_AVAILABLE=<csv> (never creds) for an opted-in project, plus one briefing line naming request_sandbox() explicitly, so an agent knows the tool exists before it needs it.
  • The guard chain, in order. ROBOCO_SANDBOX_DB_ENABLED off → refused. No active, project-bound task (the agent hasn't give_me_work'd yet) → refused. The project has no sandbox_services opted in → refused. A requested service outside the project's opted set → refused, naming the allowed set. The orchestrator handle is unavailable (e.g. mid-restart) → refused, but this is the one retryable guard.
  • Always the whole opted-in set. ensure_sandbox provisions the project's entire opted-in service set on first call, regardless of which subset this particular request_sandbox call named — so a later call for any subset or superset is a guaranteed cache hit (same creds, no docker calls) and can never trigger a mid-session teardown of a container another part of the session is still using. The response is filtered back down to just what this call asked for.
  • Cache hits are re-verified live. A cached entry is checked against the real container (is_live) before being trusted; a container that died out-of-band (OOM-killed, manually removed) evicts the stale entry and triggers a fresh full-set provision with new creds. Concurrent calls for the same agent are serialized behind a per-agent-slug lock so they can't race each other's provision/teardown.
  • Creds only ever live in the verb's envelope. A successful call returns one entry per service in the ok-envelope's evidence, each with a ready-to-export env sub-dict — ROBOCO_TEST_DB_*, ROBOCO_TEST_REDIS_*, or ROBOCO_TEST_MONGO_*. There is no spawn-time creds injection at all.
  • A provisioning failure is retryable, not a refusal. If the sandbox itself can't be created (image pull, readiness timeout), the verb returns a retryable invalid_state envelope — there's no spawn left to refuse, since the request happens well after the agent is already running.
  • Three engines, pluggable. The service set is a registry, not a hardcoded pair: postgres:16-alpine, redis:8-alpine, and mongo:8 (MongoDB ships no Alpine variant). Adding a fourth engine is one class plus one registry line.
  • Extensions and modules, allowlisted. A bare sandbox isn't always enough — a task that needs pgvector or RediSearch can ask for it. See "DB extensions and Redis modules" below.
  • Released at the end of the engagement, not just at container death. release_sandbox fires best-effort on the successful exit of i_am_done, unclaim, i_am_idle, pass_review/fail_review, and i_documented, so an agent that finishes and moves on doesn't leave a sidecar running for the rest of its session. Container-removal teardown and a grace-windowed orphan janitor (at orchestrator startup and every reaper tick) remain the backstop for whatever that misses.
  • Throwaway, isolated. Each sandbox is a fresh sibling container (tmpfs data directory, so nothing persists), labeled roboco.sandbox=1, with random per-sandbox credentials, capped at 512MB memory / 1 CPU each. Docker-in-agent stays structurally absent — the sandbox is created by the orchestrator, never by the agent itself.
Per-project opt-in, not global

Turning the master flag on doesn't change anything by itself. A project only participates when its sandbox_services column is set (e.g. ["postgres", "redis", "mongo"]) from the project's settings page (Sandbox card); sandbox_extensions (e.g. {"postgres": ["vector"]}) is a separate, optional column on top of that — a project can opt into sandboxes with no extensions at all. Projects without it keep whatever behavior they had before (no sandbox, and — if toolchain matching is on — the legacy prod-creds injection, unless DB network isolation is also in play).

Known ceiling: the cache is in-memory only

The orchestrator's creds cache lives in process memory, keyed by agent slug. An orchestrator restart forgets every live sandbox. The next request_sandbox call for that agent re-provisions from scratch (pre-clearing any still-running stale container first) and returns fresh creds — a connection to the forgotten sandbox simply fails loudly rather than silently.

DB extensions and Redis modules

A bare sandbox is Postgres or Redis with nothing extra loaded. Some work genuinely needs more — a task exercising a pgvector column, or a Redis cache that needs RediSearch — so a project can whitelist specific extensions/modules and an agent can request them the same call it requests the sandbox itself:

  • A fixed allowlist per engine, not free-form CREATE EXTENSION: Postgres offers vector, postgis, pg_trgm, citext, uuid-ossp; Redis offers the search (RediSearch), json (RedisJSON), and bloom (RedisBloom) modules. Mongo has none to offer. A superuser-language extension like plpython3u can never appear here — the allowlist itself is the containment against a sandbox becoming an RCE vector.
  • Two layers, both validated. A project can set a standing set (sandbox_extensions, e.g. {"postgres": ["vector"]}) from its settings page's Sandbox card, and request_sandbox accepts an additional per-call extensions argument for a one-off need. Both are checked against the same allowlist; a per-call ask for a service the project hasn't opted into, or a feature outside that engine's allowlist, is rejected with the allowed set named rather than silently ignored.
  • Activated after the container is ready, not baked in. Extensions are enabled via a docker exec step once the base readiness probe passes — never through bind-mounts or initdb scripts — so a bare sandbox and an extended one boot the same way.
  • Surfaced in the evidence, not guessed. A service with active features returns an available_extensions (Postgres) or available_modules (Redis) list alongside its creds in the request_sandbox evidence, so the agent knows what it can use without probing for it.

Enable it

Settings → Feature Flags → "Sandboxed per-agent test DB/Redis" arms the subsystem. Then, per project, open its settings page → Sandbox card and check the services you want (Postgres / Redis / Mongo) — and, for Postgres/Redis, whichever extensions or modules that service should carry.

Takes effect on the next backend restart

The feature flag persists in the settings store and applies on the next backend restart. The per-project sandbox_services selection applies to that project's next request_sandbox call.

See the environment reference for the full flag list.

What changes when it's on

  • With the flag off, nothing changes: request_sandbox refuses every call, regardless of a project's sandbox_services setting.
  • With the flag on, an opted-in project's developer/QA agents can request a sandbox mid-session instead of relying on RoboCo's production database credentials — and get one back within that same call.
  • A project that hasn't set sandbox_services sees no change at all.

Next

DB network isolation — the compose-level topology change this pairs with. → Toolchain matching — the subsystem that decides whether an agent's gate needs a real database at all. → back to Optional subsystems.

llms.txt