Skip to content

Parallel Agents Workflow

1. Overview

Each parallel task gets one git worktree per repo, an isolated .env.test with a unique DB port, and a dedicated Docker Postgres container.

When multiple AI agents work simultaneously on independent tasks, they need isolated environments to avoid conflicts on:

  • The same working tree: two agents writing to bautista-backend/ at the same time corrupt each other's uncommitted changes.
  • Shared config files: .env.test, php/constants.php, and constants.php contain environment values (DB host, port, credentials). Overwriting them mid-run breaks the other agent's test suite.
  • The single Docker Postgres test container bound to port 65432: only one process owns that port; a second agent trying to start or reset the same container would conflict or destroy in-progress test data.

The solution is to run each agent in its own set of git worktrees (one per affected repo), each with its own generated config pointing to a distinct port, and its own Docker container.


2. Prerequisites

  • Git 2.43+ — required for git worktree subcommand support.
  • Docker — required for backend integration tests (Postgres container per task).
  • Bash — all scripts are plain Bash, no external dependencies.
  • All four repos checked out under /var/www/Bautista/:
    • /var/www/Bautista/bautista-backend/
    • /var/www/Bautista/bautista-app/
    • /var/www/Bautista/informes/
    • /var/www/Bautista/docs/

3. Quick Start

bash
# Start a new parallel task
./scripts/parallel-worktree-setup.sh fix-login-validation

# Check active tasks
./scripts/parallel-worktree-list.sh

# Clean up when done
./scripts/parallel-worktree-teardown.sh fix-login-validation

If you only need worktrees for a subset of repos, use the --repos flag:

bash
# Backend only (skip app, informes, docs worktrees)
./scripts/parallel-worktree-setup.sh fix-login-validation --repos=backend

4. What Gets Created

Worktrees and generated files

RepoWorktree pathConfig generatedSymlink created
bautista-backendbautista-backend/.worktrees/{task}/.env.test (worktree root + migrations/)vendor/bautista-backend/vendor/
bautista-appbautista-app/.worktrees/{task}/php/constants.phpnode_modules/bautista-app/node_modules/
informesinformes/.worktrees/{task}/constants.phpvendor/informes/vendor/
docsdocs/.worktrees/{task}/(none)(none)

Port registry

scripts/.worktree-ports is auto-generated and gitignored. It tracks all active tasks so that teardown can release ports and remove containers. Each line is one registered task. See Section 8 for the format.


5. Script Reference

parallel-worktree-setup.sh <task-name> [--repos=all|backend|app|informes|docs]

Creates git worktrees, generates isolated config files, creates symlinks for shared dependency directories, assigns a unique DB port offset, and starts a Docker Postgres container for the task.

ArgumentRequiredDescription
task-nameYesIdentifier for the parallel task. Alphanumeric characters, hyphens, and underscores only.
--reposNoComma-separated list of repos to create worktrees for. Default: all. Valid values: all, backend, app, informes, docs.

parallel-worktree-teardown.sh <task-name> [--push]

Removes all worktrees for the task across all repos (regardless of which --repos value was used at setup), deregisters the port offset, and stops and removes the Docker container.

ArgumentRequiredDescription
task-nameYesMust match a task registered in scripts/.worktree-ports.
--pushNoPush all task branches to origin before removing worktrees. Includes the migrations submodule branch inside the backend worktree.

Notes:

  • Removes ALL worktrees for the task across all repos, even if setup was run with --repos=backend originally.
  • Docker container stop/remove is best-effort: if the container is already gone, teardown continues without error.
  • With --push: branches are pushed before worktrees are removed, so the push happens from within the worktree context.
  • With --push: migrations branch is pushed independently to git@bitbucket.org:huellasnet/migrations.git since it is its own submodule repo.

parallel-worktree-list.sh

No arguments. Prints a table of all active tasks with their offset, DB port, container name, and registration timestamp.

Exit codes

CodeMeaning
0Success
1User error (invalid task name, task already registered, task not found)
2Environment error (submodule init failed, required file missing)
3Partial failure (some repos succeeded, at least one failed)

6. Branch Persistence Policy

Al terminar una tarea

Cuando un agente finaliza su trabajo en un worktree, los commits quedan en la rama {task-name} del worktree. El worktree y la rama son independientes — eliminar uno no elimina el otro.

worktree eliminado ──→ rama {task-name} sigue existiendo en el repo original

Opciones al completar la tarea

Opción A — Eliminar worktree, trabajar desde la rama local

bash
# Teardown libera el worktree y el container, pero la rama persiste
./scripts/parallel-worktree-teardown.sh {task-name}

# La rama queda en el repo original — podés switchear cuando quieras
cd /var/www/Bautista/bautista-app
git checkout {task-name}
git log --oneline -5   # los commits del agente están acá

Opción B — Push automático con --push al hacer teardown (recomendado para trabajo sin supervisión)

bash
# Un solo comando: push de todas las ramas + teardown
./scripts/parallel-worktree-teardown.sh {task-name} --push

El flag --push hace push en este orden:

  1. bautista-backendorigin/{task-name} en bautista-backend.git
  2. bautista-apporigin/{task-name} en bautista-app.git
  3. informesorigin/{task-name} en informes.git (si existe worktree)
  4. docsorigin/{task-name} en bautista.git (si existe worktree)
  5. migrationsorigin/{task-name} en migrations.git (submodule anidado dentro de backend)

Los repos sin worktree activo para esa tarea se saltan automáticamente.

Opción C — Push manual previo (control fino por repo)

bash
# Push selectivo desde el worktree antes del teardown
cd /var/www/Bautista/bautista-app/.worktrees/{task-name}
git push origin {task-name}

# Para migrations (submodule dentro de backend worktree)
cd /var/www/Bautista/bautista-backend/.worktrees/{task-name}/migrations
git push origin {task-name}

# Luego teardown sin --push (ramas ya están en remoto)
./scripts/parallel-worktree-teardown.sh {task-name}

Rama en migrations

Al crear un worktree de backend, el script también crea una rama {task-name} dentro del submodule migrations/. Esto permite:

  • Commitear migraciones de base de datos dentro del contexto de la tarea
  • Publicar esa rama a migrations.git junto con el resto al usar --push
  • Revisarla en PR independiente o en conjunto con bautista-backend

Regla para agentes

Los agentes NO deben mergear la rama del worktree a develop ni a ninguna otra rama. El merge lo hace el desarrollador cuando lo decida. Al terminar la tarea, el agente solo confirma:

  1. Los commits están en la rama {task-name} del worktree
  2. tsc, tests y build pasan en esa rama
  3. Reporta el hash del último commit
  4. Si usó --push: confirma que todas las ramas están publicadas en remoto

7. Agent Constraints

Agents working inside a parallel worktree MUST follow these rules:

  • Use the worktree path for all writes, not the main repo path. Example: write to bautista-backend/.worktrees/fix-login-validation/src/..., not to bautista-backend/src/....

  • Do NOT run composer install, composer update, or composer require inside a worktree that has a symlinked vendor/ directory. The symlink points to the shared vendor in the main repo. Running Composer there would overwrite or corrupt that shared directory, breaking all other worktrees and the main working tree. If a dependency change is required, create a dedicated worktree with a real (non-symlinked) vendor copy.

  • Do NOT run npm install, npm ci, or npm update inside a worktree that has a symlinked node_modules/ directory. Same reason as above — it would corrupt the shared node_modules/ used by bautista-app/.

  • Run teardown when done. Do not leave worktrees and Docker containers behind. Port offsets accumulate in the registry and are not released until teardown runs.

  • Do NOT edit scripts/.worktree-ports directly. This file is managed exclusively by parallel-worktree-setup.sh and parallel-worktree-teardown.sh. Manual edits can desync the port registry and cause offset collisions or orphaned containers.

  • Do NOT run git worktree add manually inside the worktree directories. Always use the setup script, which handles config generation, symlinks, port assignment, and container startup as a coordinated unit.


8. Port Assignment

The main working tree uses port 65432 for its Docker Postgres test container. Each parallel task is assigned a unique offset:

  • Base port: 65432 (main working tree)
  • Offset range: 1 to 99 (maximum 99 concurrent parallel tasks)
  • Offset assignment: the minimum unused offset found in scripts/.worktree-ports

Example: two concurrent tasks, teardown, and reuse

Task A setup → offset 1 → port 65433, container postgres_bautista_testing_task-a
Task B setup → offset 2 → port 65434, container postgres_bautista_testing_task-b
Task A teardown → offset 1 released
Task C setup → offset 1 reused → port 65433, container postgres_bautista_testing_task-c

Task C reuses offset 1 because it was freed by Task A's teardown, even though Task B (offset 2) is still active.

Registry file format

# scripts/.worktree-ports (managed by scripts — do not edit manually)
task-a  1  1740000100
task-b  2  1740000200

Columns: task-name, offset, unix timestamp of registration.


9. Troubleshooting

"Error: task already registered"

Run ./scripts/parallel-worktree-list.sh to see all active tasks.

  • If the task is legitimately still running in another agent: use a different task name.
  • If the task is stale (the worktree no longer exists on disk but the registry entry is still there): run teardown to clean up the registry entry and any leftover containers:
    bash
    ./scripts/parallel-worktree-teardown.sh {task-name}
  • If you want a completely fresh start for the same logical change: use a different task name (e.g., append -v2).

"Orphaned worktree found"

The worktree directory exists on disk but the task name is not present in scripts/.worktree-ports. This can happen if the setup script was interrupted before writing to the registry.

Clean up the orphaned worktree manually:

bash
git -C /var/www/Bautista/bautista-backend worktree remove .worktrees/{name} --force
# repeat for other repos as needed
git -C /var/www/Bautista/bautista-app worktree remove .worktrees/{name} --force
git -C /var/www/Bautista/informes worktree remove .worktrees/{name} --force
git -C /var/www/Bautista/docs worktree remove .worktrees/{name} --force

Then re-run setup with the desired task name.

"Docker container still running after teardown"

Teardown stops Docker containers best-effort but does not fail if the container is absent or already stopped. To check for lingering containers:

bash
docker ps | grep bautista_testing_

To manually stop a specific container:

bash
docker stop postgres_bautista_testing_{task-name}

"Integration tests fail: connection refused"

The test database connection is failing. Diagnose in order:

  1. Check whether the Docker container is running for this worktree:
    bash
    docker ps | grep {task-name}
  2. If not running: vendor/bin/phpunit (run from inside the worktree) triggers container startup via BaseEnvironmentTest. Verify this base class is being invoked.
  3. Check that .env.test in the worktree root has the correct isolated port — it should be DB_PORT=6543N where N is the offset (not 65432, which belongs to the main working tree):
    bash
    grep DB_PORT /var/www/Bautista/bautista-backend/.worktrees/{task-name}/.env.test
  4. Verify that DOCKER_COMPOSE_PATH in that same .env.test is an absolute path pointing inside the worktree, not the main repo.