pg_play is the AI-ready orchestration layer for reproducible PostgreSQL
experiments.
- Recreates the same PostgreSQL stand, backend activity, and diagnostic window.
- Applies a versioned
pg_configuratorcandidate only through a reviewedpg_standplan. - Keeps human-facing component CLIs small while exposing a typed MCP workflow to an AI agent.
- Records immutable plan hashes, durable run state, append-only events, component versions, and artifact hashes.
- Validates and compares
pg_diagJSON reports without treating a partial collection as a successful complete run. - Plans and runs
pg_perf_benchonly against an explicitly selected disposable database, then validates and compares benchmark artifacts and TPS evidence. - Installs every component while preserving their independent use.
The independently installable components are:
pg_stand— reproducible PostgreSQL stands;pg_workload— profile-driven backend activity emulation;pg_diag— diagnostic JSON and HTML artifacts;pg_configurator— version-aware PostgreSQL configuration candidates.pg_perf_bench— controlled pgbench execution and environment evidence.
pg_play
|
+-------------+-------------+
| |
v |
pg_configurator |
| versioned config |
v |
pg_stand ------------------------+
|
v
PostgreSQL stand
/ | \
v v v
pg_workload pg_perf_bench pg_diag
| | |
| benchmark report |
+--------- run ----------+----> diagnostic report
|
change one reviewed input
|
+----> rebuild, rerun, compare
The control layers are deliberately separate:
agent skills workflow and interpretation rules
|
v
pg-play-mcp twenty-six typed, high-level operations
|
v
pg_play core validation, planning, state, comparison
|
v
component adapters argv arrays + strict JSON envelopes
|
+---- pg_configurator
+---- pg_stand
+---- pg_workload
+---- pg_diag
`---- pg_perf_bench
MCP does not expose arbitrary shell, SQL, Docker, or raw component-command tools. Each component remains pleasant to use directly: orchestration flags are hidden from its primary help and its normal human output is unchanged.
python -m pip install pg-playThis installs compatible versions of all five component distributions. They remain available through their own commands:
pg-stand --help
pg-workload --help
pg-diag --help
pg-configurator --help
pg-perf-bench --helppg_play/v1 is strict: unknown fields, secret-bearing configurator inputs,
missing projects, invalid profile names, and invalid diagnostic windows are
errors.
api_version: pg_play/v1
kind: PostgreSQLExperiment
metadata:
id: pg18-mixed-baseline
spec:
artifact_root: .pg_play/experiments/pg18-mixed-baseline
stand:
config: ../pg_stand/configs/single.yaml
project: ./stand
configurator:
inputs:
db_cpu: 4
db_ram: 8Gi
pg_version: "18"
db_duty: mixed
workload:
project: ./workload
profiles: [pagila, simple_stock]
scale: 1.0
database: workload_db
user: workload_user
install: true
stop_after_report: true
pgbench_duration_seconds: 30
job_interval_seconds: 5
resource_guard:
disk_max_used_pct: 90
mem_min_available_pct: 10
mem_min_available_mb: 2048
cpu_max_pct: 90
cpu_window_seconds: 60
check_interval: 5
diagnostics:
mode: snapshots
collection_mode: remote-db-only
duration_seconds: 60
interval_seconds: 10
report_name: pg18-mixed-diagnostics
benchmark: # optional
database: pg_perf_bench_test # dedicated and disposable
report_name: pg18-mixed-benchmark
benchmark_type: default
clients: [1, 4, 16] # or times_seconds, never both
init_command: >-
ARG_PGBENCH_PATH -i -s 10 -h ARG_PG_HOST -p ARG_PG_PORT
-U ARG_PG_USER ARG_PG_DATABASE
workload_command: >-
ARG_PGBENCH_PATH -T 60 -c ARG_PGBENCH_CLIENTS -j ARG_PGBENCH_CLIENTS
-h ARG_PG_HOST -p ARG_PG_PORT -U ARG_PG_USER ARG_PG_DATABASE
command_timeout: 120
system_metrics_interval: 1
drop_os_caches: false
collect_pg_logs: true
phases:
benchmark: true
workload_diagnostics: true
recreate_workload_database: trueTo use a packaged pg_perf_bench maximum-TPS profile, replace
benchmark_type, init_command, workload_command, and workload_path with:
workload_profile: imdb # imdb or pagila
workload_scale: 1.0
workload_duration_seconds: 30
clients: [1, 2, 4, 8, 16]The profile supplies its schema, deterministic generator, SQL query set and
command templates. pg_play includes the selected profile and scale in the
reviewed benchmark plan. pg_perf_bench selects the newest local pgbench/psql
pair automatically; optional pgbench_path and psql_path overrides are
accepted only when they are not older than the newest installed clients. The
pg_diag OS sampler runs during every benchmark window; use
system_metrics_interval to control its cadence and
system_metrics_duration only when a custom command has no pgbench -T or
--time option.
Paths are resolved relative to the manifest. spec.stand.project defaults to
the manifest directory and fixes where pg_stand stores state, credentials,
and storage regardless of the caller's current directory. Credentials never
belong in this file. pg_play obtains the stand-owned administrator credential, creates a
random workload credential, and stores project-local passfiles with mode
0600.
The workload resource guard is always enabled by pg_play. Its thresholds are
manifest inputs and therefore part of the reviewed plan hash; override them
only when the host policy is explicitly known.
The packaged JSON Schema is available as the MCP resource
pgplay://experiment-schema.
pg_diag naming is the reference for equivalent options. Components now use
--host, --port, --database, --user, --password, --out, and
--pg-version wherever those concepts apply. Existing pg_perf_bench --pg-*,
pg_workload --pg-major/--workload-user, pg_stand --postgres-version, and
pg_configurator --output-file-name spellings remain compatibility aliases.
Secrets are the deliberate exception: pg_workload continues to accept
passwords only through environment/passfile mechanisms.
All five components use the same hidden orchestration options:
--machine, --request-id, and --component-capabilities. Their advertised
machine_interface object makes these names machine-verifiable.
The pg-play CLI contains only complete experiment operations:
| Command | Effect |
|---|---|
capabilities |
Read installed component contracts |
validate MANIFEST |
Validate the manifest and non-mutating component inputs |
plan MANIFEST |
Calculate the current read-only plan and its hash |
start MANIFEST --plan-hash HASH --run-id ID |
Start exactly that plan in a detached worker and return immediately |
status MANIFEST --run-id ID |
Read durable state and detect a lost worker |
events MANIFEST --run-id ID [--after-sequence N] |
Read ordered durable events, with cursor pagination |
cancel MANIFEST --run-id ID [--reason TEXT] |
Request cooperative cancellation |
resume MANIFEST --plan-hash HASH --run-id ID |
Verify and resume a failed, cancelled, or interrupted attempt |
run MANIFEST --plan-hash HASH --run-id ID |
Execute synchronously for compatibility |
inspect-report REPORT.json |
Validate and summarize one diagnostic artifact |
compare-reports BASELINE.json CANDIDATE.json |
Produce deterministic summary deltas |
inspect-benchmark-report REPORT.json |
Validate and summarize one benchmark artifact |
compare-benchmark-reports BASELINE.json CANDIDATE.json |
Check server, environment and methodology identity, then produce TPS deltas |
benchmark-profiles |
List packaged maximum-TPS workload profiles |
benchmark-join-tasks |
List documented benchmark JOIN scenarios |
join-benchmark-reports --report ... --join-task TASK --out DIR --report-name NAME |
Join only the explicitly named benchmark reports |
teardown MANIFEST [--clear-stand-data] |
Stop workload processes and remove the managed stand |
Typical flow:
mkdir -p stand workload
pg-workload init --directory workload
pg-play validate experiment.yaml
pg-play plan experiment.yaml > plan.json
# Copy plan_hash from the reviewed plan. This command returns after spawning
# the durable worker; it does not wait for the experiment to finish.
pg-play start experiment.yaml \
--plan-hash sha256:... \
--run-id baseline-001
pg-play status experiment.yaml --run-id baseline-001
pg-play events experiment.yaml --run-id baseline-001 --after-sequence 0
pg-play inspect-report .pg_play/experiments/pg18-mixed-baseline/baseline-001/report.jsonstart recalculates the plan and rejects a stale hash. A run_id remains
permanently bound to the same manifest and plan. Use resume with that same id
for a verified failed, cancelled, or interrupted attempt; use a new id when the
manifest or plan changes. run retains the old synchronous behavior for
scripts that explicitly need it.
Start the stdio server with:
pg-play-mcpConfigure an MCP client to launch that executable with no shell wrapper. The server exposes only:
See Agent client setup for concise MCP and Agent Skills configuration examples for Codex CLI, Claude Code, Hermes Agent, Kimi Code CLI, Gemini CLI, and OpenCode.
component_capabilitiesplan_live_diagnosticsstart_live_diagnosticslive_diagnostics_statuslive_diagnostics_eventscancel_live_diagnosticsplan_configuration_reviewcollect_configuration_factsgenerate_configuration_candidatecompare_configuration_candidatevalidate_experimentplan_experimentstart_experimentrun_experimentexperiment_statusexperiment_eventscancel_experimentresume_experimentinspect_diagnostic_reportcompare_diagnostic_reportsinspect_benchmark_reportcompare_benchmark_reportsbenchmark_profilesbenchmark_join_tasksjoin_benchmark_reportsteardown_experiment
An agent should validate, plan, show the mutation to the user, and then call
start_experiment with the unchanged hash and a new run id. It can poll
experiment_status and page through experiment_events without holding one
long MCP request open. The implementation uses the stable
MCP Python SDK 1.x line
and intentionally excludes the 2.x prerelease API.
The durable schemas are also exposed as pgplay://run-state-schema and
pgplay://run-event-schema.
The wheel contains five optional workflow skills under pg_play/skills/:
run-postgres-experiment— validate, plan, start, and monitor a new run;recover-postgres-experiment— inspect, cancel, and safely resume an existing durable run;diagnose-live-postgres— capture a bounded, read-only diagnostic window on an existing PostgreSQL host and interpret the validated report;review-postgres-configuration— inspect an existing server with a minimal read-only diagnostic set, generate a candidate, and compare changed settings;analyze-postgres-experiment— inspect reports, compare or join controlled runs, and design the next single-variable iteration.
Skills contain procedural guidance; they do not reimplement orchestration or invoke raw component commands. Skill registration is agent-runtime-specific, so installing a Python wheel does not automatically activate them in every agent product.
plan_live_diagnostics accepts PostgreSQL and SSH access references, one of
the reviewed intents (performance, locks, io, or cpu), and a bounded
snapshots window. It returns missing inputs, the exact versioned item allowlist,
safety limits, and a content hash without connecting to the target. Passwords,
private-key contents, arbitrary SQL, shell commands, tags, and caller-selected
item ids are not accepted.
start_live_diagnostics stores the unchanged plan in a new immutable capture
directory and starts a detached worker. The worker invokes pg_diag snapshots
in full remote mode, writes portable JSON and HTML, validates the JSON artifact,
and records ordered events and terminal state. MCP disconnection does not stop
the worker. Use live_diagnostics_status and live_diagnostics_events to
observe it and cancel_live_diagnostics for cooperative cancellation.
Capture duration is limited to 30–900 seconds, interval to 5–60 seconds, and
the schedule to at most 121 snapshots. A lost worker produces interrupted;
the existing evidence is retained and a new time window requires a new capture
id rather than a misleading resume or merge.
pg_play exposes a deliberately read-only review workflow for an existing
PostgreSQL server. plan_configuration_review reports missing database, SSH,
and tuning-intent inputs. collect_configuration_facts runs one bounded
pg_diag one-shot collection containing only server version, effective
pg_settings, database size, CPU, RAM, filesystem, mount, disk, and extension
inventory items. It stores both the original diagnostic report and a compact
pg_diag/configuration-facts-v1 artifact.
generate_configuration_candidate combines those observed facts with the
explicit database duty, storage class, replication mode, and PITR intent. The
result remains a pg_configurator/v1 candidate. Finally,
compare_configuration_candidate writes JSON and Markdown containing only
changed or unobserved parameters, including current source, apply mode,
pending-restart state, calculation rule, and warnings.
The workflow never applies configuration, reloads PostgreSQL, restarts a
service, or treats a candidate as a benchmark-proven optimum. During collection,
pg_diag remote mode opens a bounded dynamic local SSH forward to the requested
PostgreSQL endpoint and closes it with the collection session. Passwords and
private-key contents are not accepted; callers provide passfile, key, and strict
known-hosts paths.
Each run directory contains state.json, the original experiment.yaml, the
reviewed plan.json, postgresql-parameters.json, append-only events.jsonl,
and worker.log. start_experiment launches a detached worker with its own
session, so closing or crashing the MCP stdio process does not terminate the
experiment. If the worker itself disappears, the next experiment_status
changes an active run to interrupted.
Cancellation is a durable request in cancel.request.json. The worker checks
it before every step and while a component is running. A running component is
started in its own process group; before pg_play signals a process left behind
by a crashed worker, it verifies the recorded PID start time, operating-system
user, and executable identity.
Resume is intentionally conservative:
| Step | Safe resume rule |
|---|---|
| stand | Recalculate a read-only pg_stand plan and reconcile only the reviewed desired configuration |
| benchmark | Retry only through pg_perf_bench's idempotent dedicated-database reset |
| prepare database | Retry the declarative database preparation operation |
| install workload | Retry the idempotent profile installation operation |
| start/stop workload | Reconcile the requested running/stopped state |
| diagnostics | Retry the read-only collection operation |
Before a resume, pg_play verifies the manifest hash, plan hash, configuration artifact, installed component versions, the exact allowlisted resume policy for every recorded step, and all recorded artifact paths, sizes, and SHA-256 hashes that are available.
Completed benchmark and diagnostic steps must have report artifacts. A valid completed step is reused; an incomplete step or a step with missing or changed artifacts is rerun only when its operation is in the allowlist above. Any unknown step or changed core artifact blocks recovery.
- Every component returns the exact
pg_play/component/v1envelope in hidden machine mode and advertisespg_play/capabilities/v1through the common--component-capabilitiesflag. - Plans hash normalized configuration, workload profile contents, scheduler state, and current stand state.
- Parameters owned by stand topology, TLS, fixed CSV logging, or diagnostic
preloads remain under
pg_stand; the plan records those candidate values separately and passes only non-owned parameters topg_stand. - Managed TLS stands are rejected during validation in
pg_play/v1: the component contract does not yet provision a client certificate for the dedicated workload role. Direct TLS use of each component remains available. pg_stand applyverifies its component plan hash;pg_play runverifies the combined plan hash.startandresumeenforce the same hash. Machine-modepg_perf_bench benchmarkindependently verifies a content-sensitive benchmark plan hash before resetting its database.- Subprocesses receive argument arrays with
shell=False. - Password-bearing CLI arguments and password-bearing machine output are rejected.
- Detached component cleanup verifies PID start time, process ownership, and executable identity before signaling a process group.
- A failed cleanup cannot hide the original collection failure; a cleanup failure after successful diagnostics marks the run partial.
pg_diagpartial artifacts are retained and explicitly marked partial.pg_playnever automatically destroys a stand.
Remote OS collection uses strict SSH host verification. For a newly created
local pg_stand, pg_play captures its host key into the run directory before
starting pg_diag; the captured file is permission-restricted and retained as
run evidence.
The first contract covers one configuration candidate, one managed stand, an optional controlled benchmark, selected workload profiles, a one-shot or snapshots report, and deterministic diagnostic and benchmark comparison. Existing-server incident capture is separately limited to the four reviewed read-only profiles and one bounded snapshots window per immutable capture id. Configuration review extracts the bounded CPU, RAM, filesystem, mount, and disk facts listed above. Generation or application of TuneD/systemd artifacts remains a roadmap item; it is not silently approximated by the current implementation.
python3 -m venv .venv
.venv/bin/pip install -e ../pg_stand -e ../pg_workload -e ../pg_diag \
-e ../pg_configurator -e ../pg_perf_bench
.venv/bin/pip install -e '.[dev]'
.venv/bin/ruff check .
.venv/bin/ruff format --check .
.venv/bin/pytestFor coordinated releases, publish the component distributions before tagging
pg_play: first pg_configurator, pg_diag, pg_stand, and pg_workload,
then pg_perf_bench (which depends on pg_diag), and finally pg_play.
Ordinary branch CI checks out the component sources so a coordinated source
change can be tested before those versions reach PyPI. Tagged publish jobs use
the package index deliberately and therefore enforce this release order.