Add Shipment Tracking in a Ptah Assist Session
Start a conversation, change a requirement, and watch Assist revise the SQL before you approve the migration files.
Ptah helps you plan, review, and apply database migrations. Try in your browser
You ask for shipment tracking. Assist reads the project and proposes SQL. Then you remember a missing requirement: different carriers can reuse the same tracking code. You tell it in the same conversation, and it revises the migration before writing anything.
We ran this conversation with Ptah Assist 0.12.0, a real model, and a synthetic PostgreSQL 18.6 database. These are the actual questions and reply excerpts. The terminal transcript and conversation record retain the rest. No model-produced SQL was manually edited.
Open the conversation
Section titled “Open the conversation”The starting project has a migration creating app.shipments, a declared
schema, and two queued shipments. Each row has id, order_ref, and state.
The lab provider profile and disposable database connection are configured;
the example setup supplies the starting files.
Start Assist once:
ptah assist \ --workspace . --migrations-dir migrations --schema-dir schema \ --dialect postgres --server-version 18 \ --database-class ephemeral --database-name lab \ --allow-write migrations --provider-profile labStartup excerpt, with the model identifier replaced by selected-model:
Ptah Assist. selected-model via lab.Ask a question, or /help.>Enter the following messages at >. Assist stays open between replies and
saves them in the same conversation.
Describe the change
Section titled “Describe the change”You, at >:
Our shipping service needs carrier tracking codes. A shipment may not have a code yet, but once it does, no other shipment may use the same code. Keep our existing queued shipments valid. Inspect this project’s migration files and the app schema in the lab database, then propose a reversible migration. Do not write files or deploy anything yet.
Assist read the original migration pair, declared schema, and ptah.sum.
It proposed 1790000200_add_shipments_tracking_code.up.sql:
ALTER TABLE app.shipments ADD COLUMN tracking_code text;ALTER TABLE app.shipments ADD CONSTRAINT shipments_tracking_code_unique UNIQUE (tracking_code);Assist:
Nullable by default — PostgreSQL
ADD COLUMN tracking_code textallows NULLs. A NULL value means “no tracking code assigned yet.”
Its down migration dropped the constraint and new column. Rolling back would lose stored codes. We had supplied no column name, SQL type, constraint, filename, or rollback SQL. Assist chose them from the requirement.
The first database inspection used the default schema and missed the existing
app table. We corrected that in the next message. The migration and schema
files still matched their starting hashes.
Change the requirement in the same session
Section titled “Change the requirement in the same session”You, at >:
A detail I missed: different carriers can reuse the same tracking code. Track the carrier as well, and reject duplicate carrier/code pairs only. A shipment should have both values or neither. Revise your proposal, but don’t write files yet. Also, the baseline migration is already applied in the app schema and two queued shipments exist. Inspect the app schema explicitly before revising.
Assist inspected app explicitly and confirmed the three-column table.
Its revised proposal was:
ALTER TABLE app.shipments ADD COLUMN carrier text;ALTER TABLE app.shipments ADD COLUMN tracking_code text;ALTER TABLE app.shipments ADD CONSTRAINT shipments_tracking_both_or_neither CHECK ( (carrier IS NULL AND tracking_code IS NULL) OR (carrier IS NOT NULL AND tracking_code IS NOT NULL));ALTER TABLE app.shipments ADD CONSTRAINT shipments_carrier_tracking_code_unique UNIQUE (carrier, tracking_code);A carrier now gets its own namespace for tracking codes. The CHECK prevents
either field from being filled alone. Existing rows receive (NULL, NULL),
so the queued shipments remain valid.
This happened inside the original process. We did not restart Assist or dictate replacement SQL. The second proposal still had not changed any files.
Ask it to create the files
Section titled “Ask it to create the files”You, at >:
Yes, create the revised migration files. Keep database deployment separate; do not deploy anything.
Assist previewed the revised patch and requested the write. Ptah displayed both filenames and asked for approval. Selected prompt lines:
Ptah is asking for artifact.write:migrations. create: 1790000200_add_shipments_carrier_and_tracking.down.sql create: 1790000200_add_shipments_carrier_and_tracking.up.sqlAllow? [n]o / [o]nce / [s]ession: oWe selected o for once. The first apply was refused because the preview
lacked the current artifact digest. After further refused tool calls, Assist
read the current artifact and refreshed its preview. The SQL was unchanged.
We reviewed and approved the same patch again. The
apply record retains every refusal and
the successful write.
Assist:
Migration files created successfully. Both passed Ptah’s verification gates (migration-integrity and migration-sql).
The files matched the reviewed preview, and Ptah refreshed ptah.sum.
The down migration removes the constraints and both columns; populated
tracking values are lost on rollback. The original migration and declared
schema stayed unchanged. Only migration writes were enabled.
We entered /session to display the saved conversation and /exit to leave
Assist. One process and one saved session covered the whole exchange.
Check what the migration does
Section titled “Check what the migration does”After leaving Assist, an independent query found the original three columns. We then deployed the files through the regular CLI and ran the Python client:
ptah migrations validate --dir migrationsptah migrations up --migrations-dir migrations --verify-sum --dry-runptah migrations up --migrations-dir migrations --verify-sumptah schema compare --schema-file expected-schema.sql --schemas app --exit-codepython application.pyThe dry run left the database unchanged. Deployment and schema comparison passed. The client tested the behavior described in the conversation:
Existing queued shipments kept both tracking fields NULL.Same carrier, same code: refused with SQLSTATE 23505.Different carrier, same code: accepted.Carrier only or code only: both refused with SQLSTATE 23514.Multiple shipments without tracking: accepted; lookup by pair: passed.Fresh replay produced the same schema and client output. Retry found no pending migration. Rollback restored the original columns and preserved four shipment rows, while discarding tracking data. Both disposable databases were removed.
These checks used a tiny database and sequential requests. They did not measure production lock time. Review a busy table’s rollout separately; the migration linter walkthrough and CI example cover deployment checks.
Run the example with your model
Section titled “Run the example with your model”The supporting files include the initial migration,
synthetic seed, questions, proposals, final files, and client checks. Start from
initial/migrations/; the top-level migrations/ contains the completed result.
Our lab profile uses an OpenAI-compatible endpoint. The
example profile shows its shape with substituted
identifiers. Keep your profile outside the project, select it with
PTAH_ASSIST_CONFIG, and supply its credential through MODEL_API_KEY.
Assist uses PTAH_DATABASE_URL; regular migrations use PTAH_DB_URL, and
the Python client uses DATABASE_URL.
The model received project SQL, paths, and schema metadata. It did not retrieve shipment rows. Public records replace private provider and model identifiers; another model may choose different SQL.
The interactive CLI keeps the conversation open while Assist calls Ptah’s tools. Here, the requirement and its clarification produced a reviewed migration pair before a separate deployment.
To inspect the same tool and approval boundary from an external client, follow the MCP Inspector walkthrough.
Put Ptah to work
Example files
- application.py
- assist.example.yaml
- expected-schema.sql
- initial/migrations/1790000100_create_shipments.down.sql
- initial/migrations/1790000100_create_shipments.up.sql
- initial/migrations/ptah.sum
- measured/after-assist-before-deployment.stdout.txt
- measured/after-dry-run.stderr.txt
- measured/after-dry-run.stdout.txt
- measured/after-first-request-files.json
- measured/after-revision-files.json
- measured/agent-audit.json
- measured/answer-1.md
- measured/answer-2.md
- measured/answer-3.md
- measured/application-before-write.sha256.txt
- measured/application.stderr.txt
- measured/application.stdout.txt
- measured/apply-results.json
- measured/approval-excerpt.txt
- measured/approval-full.txt
- measured/before-deployment.stderr.txt
- measured/before-deployment.stdout.txt
- measured/cleanup-assist_chat_replay.stderr.txt
- measured/cleanup-assist_chat_replay.stdout.txt
- measured/cleanup-assist_chat.stderr.txt
- measured/cleanup-assist_chat.stdout.txt
- measured/cleanup-confirm.stderr.txt
- measured/cleanup-confirm.stdout.txt
- measured/client-excerpt.txt
- measured/commands.json
- measured/conversation.json
- measured/create-replay.stderr.txt
- measured/create-replay.stdout.txt
- measured/deploy.stderr.txt
- measured/deploy.stdout.txt
- measured/dry-run.stderr.txt
- measured/dry-run.stdout.txt
- measured/final-file-check.json
- measured/initial-migration.stderr.txt
- measured/initial-migration.stdout.txt
- measured/integrity.stderr.txt
- measured/integrity.stdout.txt
- measured/interactive-command.json
- measured/interactive-terminal.txt
- measured/postgres-version.stderr.txt
- measured/postgres-version.stdout.txt
- measured/provider-env-test-first.json
- measured/provider-env-test.json
- measured/provider-test.json
- measured/replay-application.stderr.txt
- measured/replay-application.stdout.txt
- measured/replay-schema.stderr.txt
- measured/replay-schema.stdout.txt
- measured/replay-seed.stderr.txt
- measured/replay-seed.stdout.txt
- measured/replay.stderr.txt
- measured/replay.stdout.txt
- measured/retry.stderr.txt
- measured/retry.stdout.txt
- measured/rollback-columns.stderr.txt
- measured/rollback-columns.stdout.txt
- measured/rollback-row-count.stderr.txt
- measured/rollback-row-count.stdout.txt
- measured/rollback.stderr.txt
- measured/rollback.stdout.txt
- measured/schema-compare.stderr.txt
- measured/schema-compare.stdout.txt
- measured/seed.stdout.txt
- measured/session-opening-excerpt.txt
- measured/session-start.txt
- measured/version.stderr.txt
- measured/version.stdout.txt
- measured/write-result.json
- migrations/1790000100_create_shipments.down.sql
- migrations/1790000100_create_shipments.up.sql
- migrations/1790000200_add_shipments_carrier_and_tracking.down.sql
- migrations/1790000200_add_shipments_carrier_and_tracking.up.sql
- migrations/ptah.sum
- proposals/1/1790000200_add_shipments_tracking_code.down.sql
- proposals/1/1790000200_add_shipments_tracking_code.up.sql
- proposals/2/1790000200_add_shipments_carrier_and_tracking.down.sql
- proposals/2/1790000200_add_shipments_carrier_and_tracking.up.sql
- README.md
- request-1.txt
- request-2.txt
- request-3.txt
- requirements.txt
- schema/schema.sql
- seed.sql
- verified.json