Hemp

Getting Started With Conductor Using Laravel


Conductor is a Mac app that lets you run multiple coding agents in parallel. If you've been using Claude Code or similar AI coding assistants, Conductor takes things to the next level by managing isolated workspaces where agents can work independently without stepping on each other's toes.

I've been using Conductor with my Laravel projects for a while now, and I've landed on a simple workflow that makes spinning up new workspaces pretty much effortless. Here's how I do it.


The conductor.json File

At the heart of Conductor's integration is a simple conductor.json file that lives in the root of your project:

{
"scripts": {
"setup": "bash scripts/setup.sh",
"run": "bash scripts/run.sh",
"archive": "bash scripts/archive.sh"
}
}
{
"scripts": {
"setup": "bash scripts/setup.sh",
"run": "bash scripts/run.sh",
"archive": "bash scripts/archive.sh"
}
}

This file defines three lifecycle scripts that Conductor calls at different points:

  • setup: Runs when a new workspace is created. This is where you install dependencies, set up the database, and configure your local environment.
  • run: Runs when you want to start your development servers. Conductor can display the output in its terminal panel.
  • archive: Runs when you're done with a workspace and want to clean things up.

The beauty of this approach is that it's just shell scripts. No special DSL to learn, no complex configuration format. If you can write a bash script, you can configure Conductor.


The Setup Script

The setup script is the workhorse. It takes a fresh clone of your repository and turns it into a fully working development environment. Here's mine:

#!/usr/bin/env bash
set -e
 
# Copy .env from Conductor root (contains all secrets and config)
# Falls back to .env.example if no root .env exists
if [ -n "$CONDUCTOR_ROOT_PATH" ] && [ -f "$CONDUCTOR_ROOT_PATH/.env" ]; then
cp "$CONDUCTOR_ROOT_PATH/.env" .env
set -a && source .env && set +a
else
cp .env.example .env
fi
 
branch=$(basename "$PWD")
site=$(basename "$(dirname "$PWD")" .dev)
site_name="${site}-${branch}"
 
sed -i '' "s|^APP_URL=.*|APP_URL=https://${site_name}.test|" .env
 
herd link "$site_name"
herd isolate 8.4 --site="$site_name"
herd secure "$site_name"
herd restart
 
herd composer config http-basic.nova.laravel.com "${NOVA_EMAIL}" "${NOVA_LICENSE_KEY}"
herd composer install --no-interaction --prefer-dist --optimize-autoloader
 
herd php artisan key:generate
 
rm -f database/database.sqlite
touch database/database.sqlite
herd php artisan migrate --seed
 
npm install
npm run build
#!/usr/bin/env bash
set -e
 
# Copy .env from Conductor root (contains all secrets and config)
# Falls back to .env.example if no root .env exists
if [ -n "$CONDUCTOR_ROOT_PATH" ] && [ -f "$CONDUCTOR_ROOT_PATH/.env" ]; then
cp "$CONDUCTOR_ROOT_PATH/.env" .env
set -a && source .env && set +a
else
cp .env.example .env
fi
 
branch=$(basename "$PWD")
site=$(basename "$(dirname "$PWD")" .dev)
site_name="${site}-${branch}"
 
sed -i '' "s|^APP_URL=.*|APP_URL=https://${site_name}.test|" .env
 
herd link "$site_name"
herd isolate 8.4 --site="$site_name"
herd secure "$site_name"
herd restart
 
herd composer config http-basic.nova.laravel.com "${NOVA_EMAIL}" "${NOVA_LICENSE_KEY}"
herd composer install --no-interaction --prefer-dist --optimize-autoloader
 
herd php artisan key:generate
 
rm -f database/database.sqlite
touch database/database.sqlite
herd php artisan migrate --seed
 
npm install
npm run build

Let's break this down:

Environment Setup

if [ -n "$CONDUCTOR_ROOT_PATH" ] && [ -f "$CONDUCTOR_ROOT_PATH/.env" ]; then
cp "$CONDUCTOR_ROOT_PATH/.env" .env
set -a && source .env && set +a
else
cp .env.example .env
fi
if [ -n "$CONDUCTOR_ROOT_PATH" ] && [ -f "$CONDUCTOR_ROOT_PATH/.env" ]; then
cp "$CONDUCTOR_ROOT_PATH/.env" .env
set -a && source .env && set +a
else
cp .env.example .env
fi

This is where the magic happens. Conductor provides a $CONDUCTOR_ROOT_PATH variable that points to the project root - one level above your workspace. We keep a fully-configured .env file there with all our secrets, database settings, and API keys. The script copies it into the workspace, then sources it so variables like NOVA_EMAIL and NOVA_LICENSE_KEY are available for the rest of the script.

If you haven't set up a root .env yet, it falls back to .env.example so things don't blow up.

Laravel Herd Integration

This is where it gets interesting:

branch=$(basename "$PWD")
site=$(basename "$(dirname "$PWD")" .dev)
site_name="${site}-${branch}"
 
sed -i '' "s|^APP_URL=.*|APP_URL=https://${site_name}.test|" .env
branch=$(basename "$PWD")
site=$(basename "$(dirname "$PWD")" .dev)
site_name="${site}-${branch}"
 
sed -i '' "s|^APP_URL=.*|APP_URL=https://${site_name}.test|" .env

Conductor creates workspaces in a structured directory like ~/conductor/workspaces/projectname/branchname. We use this structure to automatically generate unique site names for Laravel Herd.

So if you're working on a branch called feature-invoices in the pushsilver project, you'd get a site at https://pushsilver-feature-invoices.test. Each workspace gets its own domain, SSL certificate, and clean isolation. Pretty slick.

The sed command updates the APP_URL in .env to match, so Laravel generates correct URLs everywhere.

Herd Site Setup

herd link "$site_name"
herd isolate 8.4 --site="$site_name"
herd secure "$site_name"
herd restart
herd link "$site_name"
herd isolate 8.4 --site="$site_name"
herd secure "$site_name"
herd restart

We link the site, isolate it to a specific PHP version (8.4 in this case), secure it with SSL, and restart Herd. The isolate command is super useful when different projects need different PHP versions.

Composer Dependencies

herd composer config http-basic.nova.laravel.com "${NOVA_EMAIL}" "${NOVA_LICENSE_KEY}"
herd composer install --no-interaction --prefer-dist --optimize-autoloader
herd composer config http-basic.nova.laravel.com "${NOVA_EMAIL}" "${NOVA_LICENSE_KEY}"
herd composer install --no-interaction --prefer-dist --optimize-autoloader

If you're using Laravel Nova or other private Composer repositories, configure authentication first. The credentials come from your root .env file. We use herd composer instead of just composer to ensure we're using the isolated PHP version. The --no-interaction flag is important because this runs unattended.

Application Key

herd php artisan key:generate
herd php artisan key:generate

Generate a fresh application key for encryption. Standard Laravel stuff.

Database Setup

rm -f database/database.sqlite
touch database/database.sqlite
herd php artisan migrate --seed
rm -f database/database.sqlite
touch database/database.sqlite
herd php artisan migrate --seed

I use SQLite for Conductor workspaces. It's simple, requires no external services, and each workspace gets its own isolated database. We remove any existing database file first to ensure a clean slate, then create a fresh one and run migrations. Easy.

Frontend Build

npm install
npm run build
npm install
npm run build

Install Node dependencies and build the assets. I run build rather than dev during setup so the workspace is immediately usable. You don't want to wait around for Vite to spin up just to verify things are working.


The Run Script

The run script starts your development servers:

#!/usr/bin/env bash
set -e
 
npx concurrently -k \
-n "queue,schedule,logs,vite" \
-c "#93c5fd,#c4b5fd,#fb7185,#fdba74" \
"herd php artisan queue:listen --tries=1 --timeout=0" \
"herd php artisan schedule:work" \
"herd php artisan pail --timeout=0" \
"npm run dev"
#!/usr/bin/env bash
set -e
 
npx concurrently -k \
-n "queue,schedule,logs,vite" \
-c "#93c5fd,#c4b5fd,#fb7185,#fdba74" \
"herd php artisan queue:listen --tries=1 --timeout=0" \
"herd php artisan schedule:work" \
"herd php artisan pail --timeout=0" \
"npm run dev"

I use concurrently to run multiple processes at once. Here's what each one does:

  • queue:listen: Processes background jobs (with --tries=1 to fail fast during development)
  • schedule:work: Runs Laravel's scheduler in the foreground
  • pail: Laravel's beautiful log tail command with real-time output
  • npm run dev: Vite's development server for hot module replacement

The -n flag gives each process a name, and -c assigns hex colors so you can tell them apart in the output. The -k flag ensures all processes are killed when you stop the script.

Note that I don't run php artisan serve here - Laravel Herd handles the web server. Herd is already serving my app at the .test domain we configured in setup.


The Archive Script

When you're done with a workspace, the archive script cleans up:

#!/usr/bin/env bash
set -e
 
# Derive site name (same logic as setup.sh)
branch=$(basename "$PWD")
site=$(basename "$(dirname "$PWD")" .dev)
site_name="${site}-${branch}"
 
# Unsecure and unlink from Laravel Herd
if herd links | grep -q "$site_name"; then
herd unsecure "$site_name" 2>/dev/null || true
herd unlink "$site_name"
fi
#!/usr/bin/env bash
set -e
 
# Derive site name (same logic as setup.sh)
branch=$(basename "$PWD")
site=$(basename "$(dirname "$PWD")" .dev)
site_name="${site}-${branch}"
 
# Unsecure and unlink from Laravel Herd
if herd links | grep -q "$site_name"; then
herd unsecure "$site_name" 2>/dev/null || true
herd unlink "$site_name"
fi

This removes the site from Herd, cleaning up the SSL certificate and the symlink. The 2>/dev/null || true bit ensures the script doesn't fail if the site was never secured in the first place.


The Workflow

With these scripts in place, the workflow is dead simple:

  1. Create a new workspace in Conductor for whatever I'm working on. Conductor clones my repo into an isolated directory and runs the setup script.

  2. Wait a minute while dependencies install, the database seeds, and Herd gets configured.

  3. Start coding. The agent has a fully working Laravel app to work with. Database, queues, assets - everything is ready to go.

  4. Run the dev servers when I need to see changes live. Conductor shows me the output from the queue, logs, and Vite all in one place.

  5. Archive when done. Conductor cleans up the Herd configuration and I can delete the workspace.

But the real power shows when you're running multiple agents in parallel. Each one gets its own workspace, its own database, its own Herd site. They can all be working on different features without stepping on each other.


Tips and Tricks

Use SQLite for Simplicity

MySQL and Postgres are great, but for AI-assisted development in isolated workspaces, SQLite just removes a lot of complexity. No need to create databases, manage users, or worry about connection limits. Each workspace is completely self-contained.

Managing Secrets

Here's the catch with automated setup scripts: you need API keys and tokens, but you can't commit them to the repo. The solution is to keep a complete .env file at your project root that gets copied into each workspace.

Conductor provides a $CONDUCTOR_ROOT_PATH environment variable that points to the project root - one level above your workspace. The directory structure looks like this:

$CONDUCTOR_ROOT_PATH/
├── .env <-- your complete config with secrets
├── main/ <-- workspace
├── feature-invoices/ <-- workspace
└── fix-bug/ <-- workspace
$CONDUCTOR_ROOT_PATH/
├── .env <-- your complete config with secrets
├── main/ <-- workspace
├── feature-invoices/ <-- workspace
└── fix-bug/ <-- workspace

Create a .env file at your project root with all your settings:

# $CONDUCTOR_ROOT_PATH/.env
APP_NAME=MyApp
APP_ENV=local
APP_DEBUG=true
APP_URL=https://placeholder.test
 
DB_CONNECTION=sqlite
 
NOVA_EMAIL=your@email.com
NOVA_LICENSE_KEY=your-license-key
TORCHLIGHT_TOKEN=tok_your_actual_token
STRIPE_SECRET=sk_test_...
# $CONDUCTOR_ROOT_PATH/.env
APP_NAME=MyApp
APP_ENV=local
APP_DEBUG=true
APP_URL=https://placeholder.test
 
DB_CONNECTION=sqlite
 
NOVA_EMAIL=your@email.com
NOVA_LICENSE_KEY=your-license-key
TORCHLIGHT_TOKEN=tok_your_actual_token
STRIPE_SECRET=sk_test_...

Then copy and source it at the start of your setup script:

if [ -n "$CONDUCTOR_ROOT_PATH" ] && [ -f "$CONDUCTOR_ROOT_PATH/.env" ]; then
cp "$CONDUCTOR_ROOT_PATH/.env" .env
set -a && source .env && set +a
else
cp .env.example .env
fi
if [ -n "$CONDUCTOR_ROOT_PATH" ] && [ -f "$CONDUCTOR_ROOT_PATH/.env" ]; then
cp "$CONDUCTOR_ROOT_PATH/.env" .env
set -a && source .env && set +a
else
cp .env.example .env
fi

The set -a makes all variables exported automatically, so things like NOVA_EMAIL become available to the rest of your script. The setup script updates the APP_URL after copying, so each workspace gets its own unique domain. Everything else - database settings, API keys, feature flags - comes straight from your root .env.

This approach is dead simple: maintain one .env file per project, and every workspace inherits it automatically.

Make Scripts Idempotent

If someone runs the setup script twice, it shouldn't break things. Use commands that can be safely re-run, like touch for creating files that might already exist, or add checks before destructive operations.

Consider What the Agent Needs

Your scripts should create an environment where an AI agent can be productive. That means:

  • Migrations should run without errors
  • Seeders should create enough data to work with
  • Environment should be configured for development
  • No manual steps required

The goal is zero human intervention between cloning the repo and having a working app.


Conclusion

Conductor has changed how I work with AI coding assistants. Instead of one agent making changes to my main development environment, I can have multiple agents working in parallel on completely isolated copies of my project. Honestly, it feels like having a small team of developers who each get their own machine.

The conductor.json configuration is intentionally simple. It's just three shell scripts that do exactly what you'd do manually when setting up a new development environment. The difference is that now it's automated, repeatable, and happens in seconds.

If you're using Laravel Herd (and you really should be), the integration is particularly smooth. Each workspace gets its own domain and SSL certificate automatically. No more juggling Valet configurations or remembering which port each project is running on.

Give it a try. Set up your conductor.json, write your scripts, and let the agents do their thing. I think you'll be surprised how much smoother things get.

↑ Back to the top