Parallel AI Agents: Stop Sharing a Test Database
Prepared and checked using AI. Sources are linked in the article.
Parallel AI agents sharing a test database need separate database targets, even when their code lives in separate Git worktrees. Give each agent a unique database name or its own database container, and make both migrations and tests use that target. A Git worktree separates checked-out files and worktree-specific Git state; it does not assign a database connection to the processes you start there.
If two agents use the same test database, one test run can create, change, or remove objects while the other is using them. The immediate fix is a stable mapping: one worktree, one database target, and one set of commands that always carries that target. This article uses PostgreSQL and Django for a concrete example, then shows how to apply the same mapping to Docker Compose. For more on the code side of the setup, see Git worktree isolation for parallel AI agents.
Choose the boundary before starting agents
| Setup | Give each agent | Main trade-off |
|---|---|---|
| One PostgreSQL server | A different development database and a different test database | Fewer server processes, but connection settings and privileges need care. |
| Docker Compose | A different project name, database container, and data volume | More resources, but the database service and its data are separate for each agent. |
| SQLite | A different database file path, or a framework-managed test database | Simple for projects that already use SQLite; it does not exercise PostgreSQL-specific behavior. |
The SQLite distinction matters: Django uses an in-memory test database by default for SQLite. In-memory SQLite databases are separate across agent processes; SQLite documents that connections sharing one must be in the same process. For PostgreSQL and other non-SQLite backends, Django normally prefixes the configured database name with test_. In that naming setup, each agent needs a different configured name. A hard-coded TEST.NAME shared across worktrees would defeat the mapping. Django documents both settings.
Give each worktree a PostgreSQL database
The following names and paths are illustrative examples. From the main checkout, create two worktrees for independent tasks:
git worktree add -b agent/auth ../agent-auth
git worktree add -b agent/billing ../agent-billing
The git worktree add -b form creates a branch and a linked working tree. Name the databases after the worktrees so the mapping is visible when reading a command or reviewing agent output. On a local PostgreSQL server, create the two development databases:
createdb -h 127.0.0.1 -U "$DB_USER" agent_auth
createdb -h 127.0.0.1 -U "$DB_USER" agent_billing
These commands assume DB_USER is set to a role that can create databases and that the server is reachable at the example host. PostgreSQL’s createdb reference documents -h, -U, and the database-name argument. If your server uses different connection details, use the same host, port, and role that the application will use.
In an illustrative Django settings.py, read the database name from an environment variable. Adapt the other connection settings to your project:
import os
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": os.environ["AGENT_DB_NAME"],
"USER": os.environ["DB_USER"],
"PASSWORD": os.environ["DB_PASSWORD"],
"HOST": os.environ.get("DB_HOST", "127.0.0.1"),
"PORT": os.environ.get("DB_PORT", "5432"),
}
}
Here AGENT_DB_NAME is an example variable defined by this configuration, not a built-in Django setting. Django uses DATABASES["default"]["NAME"] for the normal connection. Leave TEST.NAME unset unless you also derive it from the agent identifier: by default, Django will create test_agent_auth for agent_auth and test_agent_billing for agent_billing. The role configured under USER needs sufficient privileges to create test databases. Django explains its test database naming and creation requirements.
Run commands from the relevant worktree. Assume DB_USER and DB_PASSWORD are already provided to each shell through your project’s normal local configuration:
cd ../agent-auth
AGENT_DB_NAME=agent_auth python manage.py migrate
AGENT_DB_NAME=agent_auth python manage.py test
In the other worktree, use the other name:
cd ../agent-billing
AGENT_DB_NAME=agent_billing python manage.py migrate
AGENT_DB_NAME=agent_billing python manage.py test
Django’s migrate command applies migration files to the configured database. Its test runner creates a separate test database, runs the tests, and normally destroys that database afterward. Each agent can therefore change its own development schema and run tests against a different test database, provided every invocation uses the right configuration.
If you use python manage.py test --keepdb, Django preserves that agent’s test database between runs and applies migrations to keep it current. Reuse can save database setup work, but it makes the database’s lifecycle longer; know which agent owns it before cleaning up. The behavior of --keepdb is documented by Django.
Verify the connection before a migration
A name in a shell prompt is not proof of where the application connected. Before giving an agent permission to run a schema change, ask the application to report its actual connection. In the example agent-auth worktree, Django’s dbshell passes the connection settings to PostgreSQL’s psql client:
AGENT_DB_NAME=agent_auth python manage.py dbshell -- -c 'SELECT current_database(), inet_server_addr(), inet_server_port(), session_user;'
Check that the database is agent_auth, the server address and port match the intended PostgreSQL endpoint, and the session user matches DB_USER. Repeat with agent_billing and expect that database name. PostgreSQL documents these session information functions; the address and port functions return NULL for a Unix-domain socket connection. Django documents the dbshell -- argument pass-through. Checking the name alone cannot distinguish two servers that each have a database called agent_auth.
Also inspect every path that can open a database connection: the migration command, test runner, application process, seed task, and any background worker the tests start. The practical rule is to pass the same agent identifier through all of them. If a task writes to a second database configured under another Django alias, give that alias an agent-specific target too; Django’s DATABASES setting supports multiple named connections. Isolation fails as soon as one path falls back to a shared target.
Database names alone are an operational boundary, not a permission boundary. If all agents use one PostgreSQL role with access to every database, a changed connection string can still point an agent at another agent’s data. For stronger separation, assign roles and privileges per agent as well; PostgreSQL defines database-level CONNECT and CREATE privileges. Keep production and staging credentials out of these local agent environments.
Use separate Compose projects when you need separate servers
If the application already runs in Docker Compose, give each worktree a different Compose project name. Docker documents -p and COMPOSE_PROJECT_NAME as ways to select that name and describes project names as a way to isolate copies of an environment. Compose project-name documentation.
For example, a project could use the following illustrative fragment of compose.yaml. It assumes an existing app service with a working Dockerfile and Django at manage.py:
services:
app:
build: .
environment:
AGENT_DB_NAME: app
DB_HOST: db
DB_USER: app
DB_PASSWORD: ${DB_PASSWORD:?set DB_PASSWORD}
db:
image: postgres:17
environment:
POSTGRES_DB: app
POSTGRES_USER: app
POSTGRES_PASSWORD: ${DB_PASSWORD:?set DB_PASSWORD}
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
volumes:
db-data:
The PostgreSQL image documents POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD, and the data-volume path for version 17 and earlier. Official image reference. The ${DB_PASSWORD:?set DB_PASSWORD} expression is Compose’s required-variable syntax; provide that variable through your existing local secret setup. This example publishes no fixed host database port. Services in one Compose project’s network can reach the database by its service name, db. Docker’s Compose networking guide explains that lookup. The database healthcheck uses pg_isready so Compose can distinguish a ready database from a container that is merely running.
From each example worktree, use a distinct project name:
cd ../agent-auth
docker compose -p agent_auth build app
docker compose -p agent_auth up -d --wait db
docker compose -p agent_auth run --rm app python manage.py migrate
docker compose -p agent_auth run --rm app python manage.py test
Use agent_billing with the same commands from ../agent-billing. The up --wait option waits for the healthcheck to pass before the migration and test commands start. This example has no source bind mount, so rebuild the app image after changing code or migrations in its build directory and before the next run. compose run --rm runs a one-off command using the service configuration and removes its command container afterward. In this design, both stacks may call their internal database app: the different Compose project networks and database volumes supply the separation. Compose scopes ordinary named volumes by project name; an explicitly named or external volume can instead be shared, so check those declarations before relying on project isolation. Compose volume reference.
After the work is complete, docker compose -p agent_auth down removes that project’s containers and network while keeping its named volume. Add --volumes only when you intend to delete its database data; Docker documents that option. Remove the linked Git worktree separately with git worktree remove ../agent-auth after its changes are handled. Git documents worktree removal. Keeping the cleanup steps separate makes it clear which command affects code and which affects database data.
Where Parallel Code fits
Parallel Code is our free, open-source desktop app for macOS and Linux. It runs AI coding agents in parallel, each in its own Git worktree, and offers a diff-first review surface. Its worktrees give agents separate code directories; use the database naming or Compose setup above for the database processes those agents run.
Frequently asked questions
Does a Git worktree automatically isolate the test database?
No. Git worktrees manage separate working directories and worktree-specific Git state. Give each agent a distinct database connection through your application settings or a separate Compose project.
Can two agents share one PostgreSQL server?
Yes, if they use different development and test database names and every command connects to the intended one. In the Django example, different DATABASES["default"]["NAME"] values produce different default test database names; verify each connection before running migrations. Django test database guide.
Is a unique Compose project name enough?
It separates the ordinary project network and project-scoped volumes, but inspect any explicitly named or external volumes and shared host port mappings. Keep each app process connected to the db service in its own project network, as described in Docker’s networking guide.
Should I keep test databases between agent runs?
Django’s --keepdb preserves a test database and applies migrations on later runs. Use it when repeated setup costs matter, but keep its name tied to one agent and choose a fresh run when you need a newly created test database.