Hello!
When I first saw Conductor.build almost five months ago, it was early, there were significant issues for using it with a Ruby on Rails app, but with time and how AI tooling is moving quickly, things seem to be much more stable these days.
I picked it up again a few weeks ago and have been making it an active part of my AI workflow and wanted to share some helpful scripts / setup to maybe help you along the way. In one project I added my scripts to, it was ~100 lines of changes with most being bin scripts being added for Conductor.
What the heck does Conductor.build even do?
In a fairly common type of workflow, developers tend to use branches to checkout semi-isolated sections of the code bases, make their changes and then create pull requests to get them into the main branch. This is great for many situations, but has some fun edge cases with things like database migrations and so forth. Working on DB changes in one branch, but need to make changes in a different branch? Congratulations, now your schema shows the columns from the other branches.
There's another method I hadn't heard about until ~8 months ago, git worktrees, but to be transparent, I've found them a royal pain to use manually. So when Conductor popped on the radar for me, I realized it basically automated managing Worktrees for me.
What wasn't clear was that it was essentially checking out the source code to its own folder, and not bringing anything that lived in my .gitignore along with it (aka the environment variables, encrypted credentials keys and so forth), so some things weren't obvious until I ran into those. Then I'd have to find my files I needed and copy them into the conductor worktree. Royal pain. Anyway, Conductor realized this and added supports for scripts!
Conductor provides scripts + some Environment Variables
Conductor provides a few helpful variables that can be used to make things isolated:
CONDUCTOR_ROOT_PATH -> the path of the main checkout (aka your main branch, what everything spins up off of)
CONDUCTOR_PORT -> port assigned to your app's workspace, IE (55050, 55060...) This is actually the first of 10 ports assigned to the workspace.
CONDUCTOR_WORKSPACE_NAME -> Workspace name, likely a city name from Conductor's process to spin up trees.
CONDUCTOR_WORKSPACE_PATH -> the path of the workspace directory.
There are three scripts Conductor supports at the moment:
Setup
Run
Archive
Betcha can't name what those scripts do from just the name. I'm mostly joking of course, they do exactly what they are named for. 10/10 naming job.
Setup
For most Ruby on Rails apps the basic setup script is going to look really similar across the board.
Symlink the .bundle for private gem server credentials
Run dependency installs (bundle install, yarn install, etc)
-
Copy the files you need that .gitignore ignored. Things like a .env file, config/master.key, config/credentials/development.key or whatever else you need.
If you use Disk ActiveStorage locally, copy those files over too so there's no broken images/files.
Copy the DB over for an isolated DB copy (if you want!)
Run any migrations or whatever else you want to do here.
Run
For the Run script, you really just want to ensure your app knows the PORT conductor gives each Workspace and ensure your app code responds accordingly. Then I've been executing my bin/dev script after passing along the PORT var from conductor.
Also ensure your things like your mailers that send out use the correct port / domain for each workspace.
Archive
Teardown the database, any local ActiveStorage files you might have made on that workspace, and so forth. If you needed isolated API keys that you automatically spun up, you can archive them here if you needed a space to do so.
Gotchas?
I've spotted a few issues on my more advanced projects, that use custom routing for things like subdomains and so forth, but I think with some crafty-ness you could solve them (I mean just set Claude to the task after sharing the context about your application with 'em).
For example it's not uncommon for some apps to want;
admin.domain -> admin center
app.domain -> main app
domain -> marketing
I've been solving this myself by running Caddy outside of Conductor and having my scripts spin up a domain for them and add to the running Caddyfile.
Show me the code
conductor.json (committed at the root of your repo, so your teammates can use the same scripts)
{
"scripts": {
"setup": "bin/conductor-setup",
"run": "bin/conductor-server",
"archive": "bin/conductor-archive"
}
}bin/conductor-archive
#!/usr/bin/env zsh
set -eo pipefail
echo "==> Archiving workspace: ${CONDUCTOR_WORKSPACE_NAME}"
# Nothing to do — Conductor removes the worktree automatically. Do any extra teardown you need to do.
# For non-SQLite databases:
bin/rails db:drop
echo "==> Archive complete"bin/conductor-server
#!/usr/bin/env zsh
set -eo pipefail
REPO_ROOT="${0:a:h:h}"
# Port priority: CONDUCTOR_PORT > PORT > 3000
export PORT="${CONDUCTOR_PORT:-${PORT:-3000}}"
cd "$REPO_ROOT"
exec bin/devand for the big one;
bin/conductor-setup
#!/usr/bin/env zsh
set -eo pipefail
echo "==> Conductor setup for workspace: ${CONDUCTOR_WORKSPACE_NAME}"
REPO_ROOT="${0:a:h:h}"
# Symlink .env files from root repo if they exist
if [[ -n "$CONDUCTOR_ROOT_PATH" ]]; then
for env_file in ".env" ".env.development.local"; do
src="$CONDUCTOR_ROOT_PATH/$env_file"
dest="$REPO_ROOT/$env_file"
if [[ -f "$src" && ! -e "$dest" ]]; then
echo "==> Symlinking $env_file from root repo"
ln -sf "$src" "$dest"
fi
done
# Copy master.key if not present
if [[ -f "$CONDUCTOR_ROOT_PATH/config/master.key" && ! -f "$REPO_ROOT/config/master.key" ]]; then
echo "==> Copying master.key from root repo"
cp "$CONDUCTOR_ROOT_PATH/config/master.key" "$REPO_ROOT/config/master.key"
fi
# Copy per-environment credential keys (config/credentials/*.key)
if [[ -d "$CONDUCTOR_ROOT_PATH/config/credentials" ]]; then
for key_file in "$CONDUCTOR_ROOT_PATH"/config/credentials/*.key(N); do
dest="$REPO_ROOT/config/credentials/$(basename "$key_file")"
if [[ ! -f "$dest" ]]; then
mkdir -p "$REPO_ROOT/config/credentials"
echo "==> Copying $(basename "$key_file") from root repo"
cp "$key_file" "$dest"
fi
done
fi
# Copy SQLite databases and Active Storage files from root repo
if [[ -d "$CONDUCTOR_ROOT_PATH/storage" ]]; then
mkdir -p "$REPO_ROOT/storage"
for db_file in "$CONDUCTOR_ROOT_PATH"/storage/*.sqlite3(N); do
dest="$REPO_ROOT/storage/$(basename "$db_file")"
if [[ ! -f "$dest" ]]; then
echo "==> Copying $(basename "$db_file") from root repo (safe backup)"
sqlite3 "$db_file" ".backup '$dest'" # NOTE: this app used SQLite. Customize to your database type (ie Postgres)
fi
done
# Copy Active Storage blob files (if any exist)
blobs_copied=false
for blob_dir in "$CONDUCTOR_ROOT_PATH"/storage/??/??(N); do
if [[ -d "$blob_dir" ]]; then
rel_path="${blob_dir#$CONDUCTOR_ROOT_PATH/storage/}"
mkdir -p "$REPO_ROOT/storage/$rel_path"
if [[ -n "$(ls -A "$blob_dir" 2>/dev/null)" ]]; then
cp -rn "$blob_dir"/* "$REPO_ROOT/storage/$rel_path/"
blobs_copied=true
fi
fi
done
if $blobs_copied; then
echo "==> Copied Active Storage blobs from root repo"
fi
fi
fi
# Install dependencies
echo "==> Installing Ruby dependencies"
cd "$REPO_ROOT"
bundle install
# Run migrations in case the copied DB is behind
echo "==> Running migrations"
bin/rails db:prepare
echo "==> Setup complete"A few callouts:
This app used SQLite, but my Postgres app setup was me basically running rails db:prepare to run setup + seeds vs copying the SQLite DB manually.
I didn't need bundle credentials so I didn't copy them in on this app. John Nunemaker went into more detail on his blog + conductor's docs: https://docs.conductor.build/quickstart/rails
App wise I needed two changes for a fresh-ish Rails app.
config/environments/development.rb:
config.action_mailer.default_url_options = { host: "localhost", port: ENV.fetch("PORT", 3000).to_i }Just needed to add the ENV.fetch for the port.
For Postgres (and I suspect MySQL apps):
config/database.yml:
database: gentle_brief_development<%= "_#{ENV['CONDUCTOR_WORKSPACE_NAME'].gsub(/[^a-zA-Z0-9_-]/, '_')}" if ENV['CONDUCTOR_WORKSPACE_NAME'] %>
# then for the test DB do the same thing
database: gentle_brief_test<%= "_#{ENV['CONDUCTOR_WORKSPACE_NAME'].gsub(/[^a-zA-Z0-9_-]/, '_')}" if ENV['CONDUCTOR_WORKSPACE_NAME'] %>
That was like all I needed to do. You'll need to customize the setup script for your app and if you have the more complicated routing setup, but for fresh-rails apps this tends to work really freaking well.
Other sources for helpful info:
https://gist.github.com/jeremysmithco/1aa202287fc64333a24f8ad059d64d1b (for you Docker inclined users!)
Happy coding!
Andrea