Skip to content

Commit 239787b

Browse files
committed
feat: add durable PostgreSQL orchestration workflows
1 parent 31e9bac commit 239787b

34 files changed

Lines changed: 5255 additions & 245 deletions

README.md

Lines changed: 143 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ experiments.
88
`pg_stand` plan.
99
- Keeps human-facing component CLIs small while exposing a typed MCP workflow
1010
to an AI agent.
11-
- Records immutable plan hashes, run state, component versions, and artifact
12-
hashes.
11+
- Records immutable plan hashes, durable run state, append-only events,
12+
component versions, and artifact hashes.
1313
- Validates and compares `pg_diag` JSON reports without treating a partial
1414
collection as a successful complete run.
1515
- Plans and runs `pg_perf_bench` only against an explicitly selected disposable
@@ -62,7 +62,7 @@ The control layers are deliberately separate:
6262
agent skills workflow and interpretation rules
6363
|
6464
v
65-
pg-play-mcp thirteen typed, high-level operations
65+
pg-play-mcp twenty-six typed, high-level operations
6666
|
6767
v
6868
pg_play core validation, planning, state, comparison
@@ -221,8 +221,12 @@ The `pg-play` CLI contains only complete experiment operations:
221221
| `capabilities` | Read installed component contracts |
222222
| `validate MANIFEST` | Validate the manifest and non-mutating component inputs |
223223
| `plan MANIFEST` | Calculate the current read-only plan and its hash |
224-
| `run MANIFEST --plan-hash HASH --run-id ID` | Execute exactly that plan |
225-
| `status MANIFEST --run-id ID` | Read durable run state |
224+
| `start MANIFEST --plan-hash HASH --run-id ID` | Start exactly that plan in a detached worker and return immediately |
225+
| `status MANIFEST --run-id ID` | Read durable state and detect a lost worker |
226+
| `events MANIFEST --run-id ID [--after-sequence N]` | Read ordered durable events, with cursor pagination |
227+
| `cancel MANIFEST --run-id ID [--reason TEXT]` | Request cooperative cancellation |
228+
| `resume MANIFEST --plan-hash HASH --run-id ID` | Verify and resume a failed, cancelled, or interrupted attempt |
229+
| `run MANIFEST --plan-hash HASH --run-id ID` | Execute synchronously for compatibility |
226230
| `inspect-report REPORT.json` | Validate and summarize one diagnostic artifact |
227231
| `compare-reports BASELINE.json CANDIDATE.json` | Produce deterministic summary deltas |
228232
| `inspect-benchmark-report REPORT.json` | Validate and summarize one benchmark artifact |
@@ -241,17 +245,22 @@ pg-workload init --directory workload
241245
pg-play validate experiment.yaml
242246
pg-play plan experiment.yaml > plan.json
243247
244-
# Copy plan_hash from the reviewed plan.
245-
pg-play run experiment.yaml \
248+
# Copy plan_hash from the reviewed plan. This command returns after spawning
249+
# the durable worker; it does not wait for the experiment to finish.
250+
pg-play start experiment.yaml \
246251
--plan-hash sha256:... \
247252
--run-id baseline-001
248253
249254
pg-play status experiment.yaml --run-id baseline-001
255+
pg-play events experiment.yaml --run-id baseline-001 --after-sequence 0
250256
pg-play inspect-report .pg_play/experiments/pg18-mixed-baseline/baseline-001/report.json
251257
```
252258

253-
`run` recalculates the plan and rejects a stale hash. A run id is immutable;
254-
retry a changed or failed experiment under a new id.
259+
`start` recalculates the plan and rejects a stale hash. A `run_id` remains
260+
permanently bound to the same manifest and plan. Use `resume` with that same id
261+
for a verified failed, cancelled, or interrupted attempt; use a new id when the
262+
manifest or plan changes. `run` retains the old synchronous behavior for
263+
scripts that explicitly need it.
255264

256265
## MCP server
257266

@@ -264,11 +273,28 @@ pg-play-mcp
264273
Configure an MCP client to launch that executable with no shell wrapper. The
265274
server exposes only:
266275

276+
See [Agent client setup](https://github.com/O2eg/pg_play/blob/main/docs/agent-client-setup.md)
277+
for concise MCP and Agent Skills configuration examples for Codex CLI, Claude
278+
Code, Hermes Agent, Kimi Code CLI, Gemini CLI, and OpenCode.
279+
267280
- `component_capabilities`
281+
- `plan_live_diagnostics`
282+
- `start_live_diagnostics`
283+
- `live_diagnostics_status`
284+
- `live_diagnostics_events`
285+
- `cancel_live_diagnostics`
286+
- `plan_configuration_review`
287+
- `collect_configuration_facts`
288+
- `generate_configuration_candidate`
289+
- `compare_configuration_candidate`
268290
- `validate_experiment`
269291
- `plan_experiment`
292+
- `start_experiment`
270293
- `run_experiment`
271294
- `experiment_status`
295+
- `experiment_events`
296+
- `cancel_experiment`
297+
- `resume_experiment`
272298
- `inspect_diagnostic_report`
273299
- `compare_diagnostic_reports`
274300
- `inspect_benchmark_report`
@@ -279,23 +305,115 @@ server exposes only:
279305
- `teardown_experiment`
280306

281307
An agent should validate, plan, show the mutation to the user, and then call
282-
`run_experiment` with the unchanged hash and a new run id. The implementation
283-
uses the stable [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)
284-
1.x line and intentionally excludes the 2.x prerelease API.
308+
`start_experiment` with the unchanged hash and a new run id. It can poll
309+
`experiment_status` and page through `experiment_events` without holding one
310+
long MCP request open. The implementation uses the stable
311+
[MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk) 1.x line
312+
and intentionally excludes the 2.x prerelease API.
313+
314+
The durable schemas are also exposed as `pgplay://run-state-schema` and
315+
`pgplay://run-event-schema`.
285316

286317
## Agent skills
287318

288-
The wheel contains two optional workflow skills under `pg_play/skills/`:
319+
The wheel contains five optional workflow skills under `pg_play/skills/`:
289320

290-
- `run-postgres-experiment` — validate, plan, execute, and recover a run;
291-
- `analyze-postgres-experiment` — inspect reports, compare controlled runs,
292-
and design the next single-variable iteration.
321+
- `run-postgres-experiment` — validate, plan, start, and monitor a new run;
322+
- `recover-postgres-experiment` — inspect, cancel, and safely resume an
323+
existing durable run;
324+
- `diagnose-live-postgres` — capture a bounded, read-only diagnostic window on
325+
an existing PostgreSQL host and interpret the validated report;
326+
- `review-postgres-configuration` — inspect an existing server with a minimal
327+
read-only diagnostic set, generate a candidate, and compare changed settings;
328+
- `analyze-postgres-experiment` — inspect reports, compare or join controlled
329+
runs, and design the next single-variable iteration.
293330

294331
Skills contain procedural guidance; they do not reimplement orchestration or
295332
invoke raw component commands. Skill registration is agent-runtime-specific,
296333
so installing a Python wheel does not automatically activate them in every
297334
agent product.
298335

336+
## Live-server incident diagnostics
337+
338+
`plan_live_diagnostics` accepts PostgreSQL and SSH access references, one of
339+
the reviewed intents (`performance`, `locks`, `io`, or `cpu`), and a bounded
340+
snapshots window. It returns missing inputs, the exact versioned item allowlist,
341+
safety limits, and a content hash without connecting to the target. Passwords,
342+
private-key contents, arbitrary SQL, shell commands, tags, and caller-selected
343+
item ids are not accepted.
344+
345+
`start_live_diagnostics` stores the unchanged plan in a new immutable capture
346+
directory and starts a detached worker. The worker invokes `pg_diag snapshots`
347+
in full remote mode, writes portable JSON and HTML, validates the JSON artifact,
348+
and records ordered events and terminal state. MCP disconnection does not stop
349+
the worker. Use `live_diagnostics_status` and `live_diagnostics_events` to
350+
observe it and `cancel_live_diagnostics` for cooperative cancellation.
351+
352+
Capture duration is limited to 30–900 seconds, interval to 5–60 seconds, and
353+
the schedule to at most 121 snapshots. A lost worker produces `interrupted`;
354+
the existing evidence is retained and a new time window requires a new capture
355+
id rather than a misleading resume or merge.
356+
357+
## Existing-server configuration review
358+
359+
`pg_play` exposes a deliberately read-only review workflow for an existing
360+
PostgreSQL server. `plan_configuration_review` reports missing database, SSH,
361+
and tuning-intent inputs. `collect_configuration_facts` runs one bounded
362+
`pg_diag one-shot` collection containing only server version, effective
363+
`pg_settings`, database size, CPU, RAM, filesystem, mount, disk, and extension
364+
inventory items. It stores both the original diagnostic report and a compact
365+
`pg_diag/configuration-facts-v1` artifact.
366+
367+
`generate_configuration_candidate` combines those observed facts with the
368+
explicit database duty, storage class, replication mode, and PITR intent. The
369+
result remains a `pg_configurator/v1` candidate. Finally,
370+
`compare_configuration_candidate` writes JSON and Markdown containing only
371+
changed or unobserved parameters, including current source, apply mode,
372+
pending-restart state, calculation rule, and warnings.
373+
374+
The workflow never applies configuration, reloads PostgreSQL, restarts a
375+
service, or treats a candidate as a benchmark-proven optimum. During collection,
376+
`pg_diag` remote mode opens a bounded dynamic local SSH forward to the requested
377+
PostgreSQL endpoint and closes it with the collection session. Passwords and
378+
private-key contents are not accepted; callers provide passfile, key, and strict
379+
known-hosts paths.
380+
381+
## Recovery model
382+
383+
Each run directory contains `state.json`, the original `experiment.yaml`, the
384+
reviewed `plan.json`, `postgresql-parameters.json`, append-only `events.jsonl`,
385+
and `worker.log`. `start_experiment` launches a detached worker with its own
386+
session, so closing or crashing the MCP stdio process does not terminate the
387+
experiment. If the worker itself disappears, the next `experiment_status`
388+
changes an active run to `interrupted`.
389+
390+
Cancellation is a durable request in `cancel.request.json`. The worker checks
391+
it before every step and while a component is running. A running component is
392+
started in its own process group; before pg_play signals a process left behind
393+
by a crashed worker, it verifies the recorded PID start time, operating-system
394+
user, and executable identity.
395+
396+
Resume is intentionally conservative:
397+
398+
| Step | Safe resume rule |
399+
| --- | --- |
400+
| stand | Recalculate a read-only pg_stand plan and reconcile only the reviewed desired configuration |
401+
| benchmark | Retry only through pg_perf_bench's idempotent dedicated-database reset |
402+
| prepare database | Retry the declarative database preparation operation |
403+
| install workload | Retry the idempotent profile installation operation |
404+
| start/stop workload | Reconcile the requested running/stopped state |
405+
| diagnostics | Retry the read-only collection operation |
406+
407+
Before a resume, pg_play verifies the manifest hash, plan hash, configuration
408+
artifact, installed component versions, the exact allowlisted resume policy
409+
for every recorded step, and all recorded artifact paths, sizes, and SHA-256
410+
hashes that are available.
411+
412+
Completed benchmark and diagnostic steps must have report artifacts. A valid
413+
completed step is reused; an incomplete step or a step with missing or changed
414+
artifacts is rerun only when its operation is in the allowlist above. Any
415+
unknown step or changed core artifact blocks recovery.
416+
299417
## Determinism and safety
300418

301419
- Every component returns the exact `pg_play/component/v1` envelope in hidden
@@ -310,12 +428,14 @@ agent product.
310428
component contract does not yet provision a client certificate for the
311429
dedicated workload role. Direct TLS use of each component remains available.
312430
- `pg_stand apply` verifies its component plan hash; `pg_play run` verifies the
313-
combined plan hash. Machine-mode `pg_perf_bench benchmark` independently
431+
combined plan hash. `start` and `resume` enforce the same hash. Machine-mode
432+
`pg_perf_bench benchmark` independently
314433
verifies a content-sensitive benchmark plan hash before resetting its database.
315434
- Subprocesses receive argument arrays with `shell=False`.
316435
- Password-bearing CLI arguments and password-bearing machine output are
317436
rejected.
318-
- Background workload stop verifies PID ownership before signaling a process.
437+
- Detached component cleanup verifies PID start time, process ownership, and
438+
executable identity before signaling a process group.
319439
- A failed cleanup cannot hide the original collection failure; a cleanup
320440
failure after successful diagnostics marks the run partial.
321441
- `pg_diag` partial artifacts are retained and explicitly marked partial.
@@ -331,9 +451,11 @@ run evidence.
331451
The first contract covers one configuration candidate, one managed stand,
332452
an optional controlled benchmark, selected workload profiles, a one-shot or
333453
snapshots report, and deterministic diagnostic and benchmark comparison.
334-
Automatic extraction of OS facts and generation or
335-
application of TuneD/systemd artifacts remains a roadmap item; it is not
336-
silently approximated by the current implementation.
454+
Existing-server incident capture is separately limited to the four reviewed
455+
read-only profiles and one bounded snapshots window per immutable capture id.
456+
Configuration review extracts the bounded CPU, RAM, filesystem, mount, and disk
457+
facts listed above. Generation or application of TuneD/systemd artifacts remains
458+
a roadmap item; it is not silently approximated by the current implementation.
337459

338460
## Development
339461

0 commit comments

Comments
 (0)