Appearance
pom.yml reference
Every key Pomelo reads from pom.yml, the file at the project root that says how to build and run the project. It holds only that: the app's own behavior lives in Settings. Values can use templates. Pomelo ignores a key not listed here.
Top level
The keys at the top of pom.yml.
| Key | Type | Default | What it does |
|---|---|---|---|
session | string | pomelo | The project's name. It prefixes database names, holders, hostnames and the shared Docker compose project. |
default_branch | string | main | The branch every repo's main workspace follows; a repo's default_branch overrides it. |
repos | map | - | The project's repositories; see Repos. |
shared_services | map | - | Services one instance of serves every workspace; see Shared services. |
presets | map | - | Reusable repo fragments; see Presets. |
environments | map of maps | - | Profiles that point services at deployed URLs: <profile>: { <repo>.<service>: <url> }. Under that profile {{<repo>.<service>.url}} (and .host, .port, .ws) resolves to the URL and the dev proxy forwards there; other services stay local. The URL is used as written, without templates. |
preset | string or list | - | Presets whose services run once per workspace, outside any repo. |
seed | list of commands | - | Runs once in the workspace folder when a workspace is created, before each repo's seed. |
prepare_main | list | reset, migrate, seed, snapshot | The phases Prepare Main runs, in order: reset, migrate, seed and snapshot, which saves main's databases as the main__baseline snapshot new workspaces copy from without disconnecting main. Other names are skipped; a list with none of them runs only reset. |
sync | map | - | Keep Main Fresh; see Sync. |
agents | map | - | A policy for the workspace's coding agents; see Agents. |
workspaces | map | - | Ignored. Workspace groups; nothing reads them now. |
combinations | map | - | Ignored. Repo combinations; config_normalize deletes them. |
code_agents | map | - | Ignored. Agent switches; the app's Settings hold these now. |
ui | map | - | Ignored. UI preferences; the app's Settings hold these now. |
plugins | map | - | Ignored. Plugin settings; config_normalize deletes them. |
Examples:
yaml
session: myproject
default_branch: main
environments:
staging:
api.server: https://api.staging.example.com
preset: [gateway]
seed: [./scripts/seed-all.sh]
prepare_main: [migrate, seed, snapshot]Repos
Each entry of repos: is one repository, keyed by its clone folder name.
| Key | Type | Default | What it does |
|---|---|---|---|
repos.<repo>.alias | string | the repo's key | The short name hostnames and templates use for this repo ({{<alias>.<service>.url}}; the key works too). Rename Alias in Settings > Project rewrites the references. |
repos.<repo>.default_branch | string | the top-level default_branch | This repo's main branch, when it differs from the project's. |
repos.<repo>.preset | string or list | - | Presets to apply; they only fill what the repo left unset, in order. |
repos.<repo>.databases | map | - | Databases each workspace gets, created automatically: <name>: <name template>, named <session>_<resolved template>. Reach one with {{db.<name>}}. Only the branch tokens work in the template. |
repos.<repo>.shared_services | list | - | The shared services this repo uses; see A repo's shared services. Starting the repo's services starts these. A declared shared service no repo lists is flagged by config_doctor (shared.unwired). |
repos.<repo>.seed_from_main | bool | false | New workspaces copy this repo's databases from the main workspace instead of starting empty, and skip its seed. |
repos.<repo>.profiles | list | - | The environments profiles this repo's services may switch between; local is always offered. Each must be defined in environments. |
repos.<repo>.proxy_port | port | - | Ignored. Only a fallback port for a template that names no service; service ports are leased. |
repos.<repo>.shell_env | string | - | KEY=value ... put before every service command of the repo. |
repos.<repo>.env | map | - | The repo's environment, with templates resolved; services get it injected, and it is written to .env.local where the repo keeps env files (a root .env or .env.development, apps/<name>/.env, a service's dir). If any value is a map, the keys are file names instead: each file gets its own keys on top of *, the base for every file. Edit it here, never in the generated file. |
repos.<repo>.services | map | - | The repo's long-running processes; see Services. |
repos.<repo>.lifecycle | map | - | How the repo is built and run; see Repo lifecycle. |
repos.<repo>.pre_start | command | - | Same as lifecycle.pre_start. |
repos.<repo>.commands | map | - | Same as lifecycle.commands. |
repos.<repo>.setup | list of commands | - | Same as lifecycle.setup. |
repos.<repo>.migrate | list of commands | - | Same as lifecycle.migrate. |
repos.<repo>.seed | list of commands | - | Same as lifecycle.seed. |
repos.<repo>.pre_delete | list of commands | - | Same as lifecycle.pre_delete. |
repos.<repo>.copy | list of globs | - | Same as lifecycle.copy. |
repos.<repo>.tasks | list | - | Same as lifecycle.tasks. |
repos.<repo>.shortcuts | list | - | Older name of tasks. |
repos.<repo>.plugins | map | - | Ignored. Plugin settings; config_normalize deletes them. |
Examples, each written under repos.<repo>:
yaml
alias: api
preset: [rails]
databases:
main: "{{branch.safe}}"
test: "{{branch.safe}}_test"
shared_services: [postgres, redis]
seed_from_main: true
profiles: [staging]
shell_env: RAILS_LOG_TO_STDOUT=1
env:
DATABASE_URL: postgresql://{{shared.postgres.url}}/{{db.main}}
env:
"*":
REDIS_URL: redis://{{shared.redis.host}}:{{shared.redis.port}}/{{shared.redis.slot}}
.env.development.local:
DATABASE_URL: postgresql://{{shared.postgres.url}}/{{db.main}}
.env.test.local:
DATABASE_URL: postgresql://{{shared.postgres.url}}/{{db.test}}Repo lifecycle
A repo's lifecycle: block groups how it is built and run. Every key here also works directly on the repo; when both are set, the lifecycle: value wins.
| Key | Type | Default | What it does |
|---|---|---|---|
repos.<repo>.lifecycle.pre_start | command | - | Runs before every service of the repo starts, in the same shell (for example nvm use). |
repos.<repo>.lifecycle.commands | map | - | Named commands. Each becomes a task; install, generate and migrate, in that order, are the setup a new workspace runs unless setup is set, and migrate is what Prepare Main and refreshes run unless migrate is set. |
repos.<repo>.lifecycle.setup | list of commands | commands install, generate, migrate | Steps run right after the worktree is created, in the worktree with the repo's env, joined with &&. |
repos.<repo>.lifecycle.migrate | list of commands | commands.migrate | The repo's migration steps, run by Prepare Main and Keep Main Fresh. |
repos.<repo>.lifecycle.seed | list of commands | - | Runs after setup when a workspace is created; skipped when seed_from_main copies the databases. |
repos.<repo>.lifecycle.pre_delete | list of commands | - | Runs in the repo before its worktree is deleted; a failure only warns. |
repos.<repo>.lifecycle.copy | list of globs | - | Files copied from the main workspace's checkout into each new worktree; * works in the last path part, and files the worktree already has are kept. |
repos.<repo>.lifecycle.tasks | list | - | Quick commands; see Tasks. |
repos.<repo>.lifecycle.shortcuts | list | - | Older name of tasks. |
Examples, each written under repos.<repo>.lifecycle:
yaml
pre_start: nvm use
commands:
install: bundle install
migrate: bin/rails db:migrate
test: bin/rspec
setup: [npm ci, npm run build]
seed: [bin/rails db:seed]
copy: [config/master.key]Services
Each entry of a repo's (or preset's) services: is one long-running process. name: <cmd> is short for name: { cmd: <cmd> }.
| Key | Type | Default | What it does |
|---|---|---|---|
repos.<repo>.services.<service>.cmd | command | - | The command that runs the service, in the login shell with the resolved env. It gets $PORT and $BIND_IP. |
repos.<repo>.services.<service>.type | backend, frontend or worker | - | A backend or frontend gets a $PORT unless port: false; a worker gets none unless port: true. |
repos.<repo>.services.<service>.dir | path | the repo folder | The folder, inside the repo, the service runs in (a monorepo app). |
repos.<repo>.services.<service>.port | bool | true for backend and frontend | Whether the service gets a leased $PORT. |
repos.<repo>.services.<service>.depends_on | list | - | Services of the same repo that start first. |
repos.<repo>.services.<service>.env | map | - | Environment for this service only, on top of the repo's. |
repos.<repo>.services.<service>.pre_start | command | the repo's pre_start | Replaces the repo's pre_start for this service. |
repos.<repo>.services.<service>.shell_env | string | the repo's shell_env | Replaces the repo's shell_env for this service. |
repos.<repo>.services.<service>.proxy_port | port | - | Ignored. Nothing reads it; the dev proxy forwards to the leased port. |
repos.<repo>.services.<service>.profiles | list | the repo's profiles | Replaces the repo's profiles for this service. |
repos.<repo>.services.<service>.modes | map | - | Named alternative commands, switched in the app without editing the config. |
repos.<repo>.services.<service>.mode | string | - | The mode used when none is picked in the app; cmd runs when there is none. |
repos.<repo>.services.<service>.tasks | list | - | Quick commands for this service; see Tasks. |
repos.<repo>.services.<service>.shortcuts | list | - | Older name of tasks. |
repos.<repo>.services.<service>.healthcheck | map | - | When the service counts as ready; see Service healthcheck. |
repos.<repo>.services.<service>.queue | map | - | The job queue this worker drains; see Service queue. |
Examples, each written under repos.<repo>.services.<service>:
yaml
cmd: bin/rails s -p $PORT -b $BIND_IP
type: backend
dir: apps/web
port: false
depends_on: [server]
modes:
dev: npm run dev -- --port $PORT
prod: npm run start -- -p $PORT
mode: dev
healthcheck: { http: /health }
queue: { kind: sidekiq }Service healthcheck
When a service counts as ready, for pom start --wait and pom status: give http or cmd. Without a healthcheck a service with a port is ready once the port listens.
| Key | Type | Default | What it does |
|---|---|---|---|
repos.<repo>.services.<service>.healthcheck.http | path | - | A path on the service's own port; ready once a GET answers 2xx or 3xx. |
repos.<repo>.services.<service>.healthcheck.cmd | command | - | A shell command run in the service's folder with its env; ready once it exits 0. |
repos.<repo>.services.<service>.healthcheck.interval | duration | 1s | Time between checks. |
repos.<repo>.services.<service>.healthcheck.timeout | duration | 3s | How long one check may take before it counts as failed. |
Examples, each written under repos.<repo>.services.<service>.healthcheck:
yaml
http: /health
cmd: bin/rails runner 'ActiveRecord::Base.connection'
interval: 500ms
timeout: 5sService queue
The background-job queue a worker service drains, in the workspace's Redis slot. pom queue wait-idle <service> waits until it is empty, e.g. before a test checks what a job did.
| Key | Type | Default | What it does |
|---|---|---|---|
repos.<repo>.services.<service>.queue.kind | string | - | sidekiq or bullmq. |
repos.<repo>.services.<service>.queue.prefix | string | bull | BullMQ's key prefix. |
repos.<repo>.services.<service>.queue.queues | list | every queue found | The queues to watch. |
repos.<repo>.services.<service>.queue.redis | string | the repo's first Redis | The shared Redis service the queue lives in. |
Examples, each written under repos.<repo>.services.<service>.queue:
yaml
kind: bullmq
prefix: bull
queues: [default, mailers]
redis: redisTasks
A tasks: list (older name shortcuts:) on a repo, its lifecycle, a service or a preset adds quick commands to the app and the agents.
| Key | Type | Default | What it does |
|---|---|---|---|
tasks[].key | string | - | A short name for the task. |
tasks[].desc | string | - | What the task does, shown next to it. |
tasks[].cmd | command | - | The command the task runs, in the repo. |
Examples, each written under tasks[]:
yaml
key: migrate
desc: Run migrations
cmd: bin/rails db:migrateA repo's shared services
A repo's shared_services: lists the shared services it uses: a name, or name: { db_name: <template> }.
| Key | Type | Default | What it does |
|---|---|---|---|
repos.<repo>.shared_services[].<name>.db_name | template | - | The database this repo uses on that shared service, as a name template taking the branch tokens. |
Examples, each written under repos.<repo>.shared_services[].<name>:
yaml
db_name: "{{branch.safe}}_reports"Shared services
Each entry of shared_services: runs once for every workspace: a Docker image or a cmd, never both. postgres, redis, minio, opensearch and zincsearch (by name or type:) get a working image, ports, credentials and healthcheck filled in.
| Key | Type | Default | What it does |
|---|---|---|---|
shared_services.<name>.type | string | the service's name | The well-known service to fill defaults from, when the name differs: postgres, redis, minio, opensearch or zincsearch. |
shared_services.<name>.image | string | filled for well-known services | The Docker image to run. Either this or cmd. |
shared_services.<name>.cmd | command | - | A command run once for every workspace instead of a container. It gets $PORT and $BIND_IP; {{shared.<name>.url}} is http://127.0.0.1:<port>. Either this or image. |
shared_services.<name>.repo | repo key | the project folder | For a cmd: the repo whose main-workspace checkout it runs in. Must be in repos. |
shared_services.<name>.port | port | leased | For a cmd: the port it is told in $PORT. No other shared service may want it. |
shared_services.<name>.ports | list | filled for well-known services | For an image: the container ports to publish ("5432" or "host:container"). The first number is the preferred host port; Pomelo keeps it when free, else takes the next free one within 100, else a random one, and the choice sticks. Templates read it as {{shared.<name>.port}}. |
shared_services.<name>.environment | map | filled for well-known services | Environment for the container or command. In a cmd service, $PORT and ${PORT} in a value become its port. |
shared_services.<name>.volumes | list | filled for well-known services | For an image: Docker volumes. |
shared_services.<name>.command | string | - | For an image: the container's command. |
shared_services.<name>.healthcheck | map | filled for well-known services | When the service counts as up; see Shared service healthcheck. |
shared_services.<name>.db_user | string | postgres for a Postgres | For an image: the login {{shared.<name>.url}} and .user carry. |
shared_services.<name>.db_password | string | postgres for a Postgres | For an image: the password {{shared.<name>.url}} and .pass carry. |
shared_services.<name>.capacity | number | - | For an image: how many workspaces share one instance. Each gets a slot, {{shared.<name>.slot}} (a Redis database number, for example); when an instance is full another starts at base port + instance. |
shared_services.<name>.slot_reset | string | - | For an image with capacity: a command run inside the instance's container to empty one slot, with {{slot}} replaced by its number. It runs when a workspace gives its slot back (deleted, or its folder is gone) and before a slot is handed to a new workspace, so no workspace sees another's data; a slot whose reset fails is not handed out. The redis preset empties the Redis database. |
shared_services.<name>.host | string | localhost | The host the app's database and storage browsers connect to. Templates always use 127.0.0.1. |
Examples, each written under shared_services.<name>:
yaml
type: postgres
image: postgres:16
cmd: node scripts/mock-as.js
repo: api
port: 4010
ports: ["5432"]
capacity: 64
slot_reset: redis-cli -n {{slot}} FLUSHDB | grep -qx OKShared service healthcheck
When the shared service counts as up.
| Key | Type | Default | What it does |
|---|---|---|---|
shared_services.<name>.healthcheck.test | command or list | - | A shell command, or a Docker-style [CMD, ...] list, that succeeds once the service is up. A cmd service waits up to 30 seconds for it before the services that use it start. |
shared_services.<name>.healthcheck.interval | duration | - | For an image: time between checks. |
shared_services.<name>.healthcheck.timeout | duration | - | For an image: how long one check may take. |
shared_services.<name>.healthcheck.retries | number | - | For an image: failed checks before the container counts as unhealthy. |
Examples, each written under shared_services.<name>.healthcheck:
yaml
test: curl -sf http://127.0.0.1:$PORT/health
interval: 5s
timeout: 3s
retries: 10Presets
Each entry of presets: is a reusable repo fragment. A repo (or the workspace, with the top-level preset:) names presets; a preset only fills what the repo left unset. Inside a preset, write the lifecycle keys flat (setup:, commands:), not under lifecycle:.
| Key | Type | Default | What it does |
|---|---|---|---|
presets.<preset>.preset | string or list | - | Presets this one builds on; they apply first. |
presets.<preset>.services | map | - | Services the preset adds, with the fields of Services; one is added only when the repo has none by that name. |
presets.<preset>.env | map | - | Environment keys the repo left unset, merged key by key. Keep it a flat map. |
presets.<preset>.pre_start | command | - | As on a repo. |
presets.<preset>.commands | map | - | As on a repo, merged key by key. |
presets.<preset>.setup | list of commands | - | As on a repo. |
presets.<preset>.migrate | list of commands | - | As on a repo. |
presets.<preset>.seed | list of commands | - | As on a repo. |
presets.<preset>.pre_delete | list of commands | - | As on a repo. |
presets.<preset>.copy | list of globs | - | As on a repo. |
presets.<preset>.seed_from_main | bool | false | As on a repo. |
presets.<preset>.tasks | list | - | As on a repo. |
presets.<preset>.shortcuts | list | - | Older name of tasks. |
Sync
Keep Main Fresh: pulling the main workspace on a schedule.
| Key | Type | Default | What it does |
|---|---|---|---|
sync.auto_push | bool | false | Push committed work on a timer. |
sync.interval_sec | number | 180 | Seconds between pushes when auto_push is on; at least 30. |
sync.refresh_main | bool | false | Keep Main Fresh: pull and migrate the main workspace on a schedule. Once it is set in the app, the app's choice wins. |
sync.refresh_interval_sec | number | 1800 | Seconds between Keep Main Fresh runs. |
Examples, each written under sync:
yaml
refresh_main: true
refresh_interval_sec: 1800Agents
What the workspace's coding agents may do: a policy command Pomelo asks before every tool call an agent makes.
| Key | Type | Default | What it does |
|---|---|---|---|
agents.policy | command | - | Run before every tool call a coding agent in a workspace makes, in the workspace folder. It reads {tool_name, tool_input, session_id, role, workspace, origin, driven_by} as JSON on stdin and prints {"decision": "allow" | "deny" | "ask", "reason": "..."}. ask shows the agent's permission prompt; in a session an orchestrator drives it denies with pending approval <id> until pom agent approve records an approval for that call. A failure, a non-zero exit, a timeout or an unreadable config denies the call. Without a policy, the agent's own permission prompts apply. |
agents.policy_timeout_sec | int | 5 | How long the policy command may take before the tool call is denied. |
Examples, each written under agents:
yaml
agents:
policy: ./scripts/agent-policy.sh
policy_timeout_sec: 10code_agents (ignored)
Read so older files load, but nothing uses it: agent behavior is in the app's Settings.
| Key | Type | Default | What it does |
|---|---|---|---|
code_agents.disabled | bool | - | Ignored. Nothing reads it. |
code_agents.only | list | - | Ignored. Nothing reads it. |
code_agents.notify_disabled | bool | - | Ignored. Nothing reads it. |
ui (ignored)
Read so older files load, but nothing uses it: the app's Settings hold this.
| Key | Type | Default | What it does |
|---|---|---|---|
ui.editor | string | - | Ignored. Nothing reads it; Settings > Editor > External Editor replaced it. |
Removed keys
These keys do nothing any more. config_normalize (or pom config normalize) deletes the ones it can.
| Key | Instead |
|---|---|
schema_version | Version marker of an older format. |
proxy | Routing is automatic: /_pom_dev/<repo>/<service> and <service>.<repo>.<branch>.localhost. |
webhook | Webhooks are routed automatically at /<repo>/<service>. |
global_services | Replaced by shared_services. |
shared_stable_ports | Shared service ports are leased automatically. |
e2e | Removed with exposes: and {{var:}}. |
jira | The app's Settings hold the Jira connection. |
archive | Removed. |
repos.<repo>.exposes | Published a {{var:}} variable; use a service ref such as {{<repo>.<service>.url}}. |
repos.<repo>.env_switch | Profiles switch services through environments. |
repos.<repo>.lifecycle.create | Workspace create runs setup (or the install, generate and migrate commands). |
repos.<repo>.lifecycle.refresh | Refreshes run migrate. |
repos.<repo>.services.<service>.exposes | Published a {{var:}} variable; use a service ref such as {{<repo>.<service>.url}}. |
