Skip to content

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.

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:

Terminal window
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 lab

Startup excerpt, with the model identifier replaced by selected-model:

examples/measured/session-opening-excerpt.txt
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.

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:

examples/proposals/1/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 text allows 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:

examples/migrations/1790000200_add_shipments_carrier_and_tracking.up.sql
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.

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:

examples/measured/approval-excerpt.txt
Ptah is asking for artifact.write:migrations.
create: 1790000200_add_shipments_carrier_and_tracking.down.sql
create: 1790000200_add_shipments_carrier_and_tracking.up.sql
Allow? [n]o / [o]nce / [s]ession: o

We 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.

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:

Terminal window
ptah migrations validate --dir migrations
ptah migrations up --migrations-dir migrations --verify-sum --dry-run
ptah migrations up --migrations-dir migrations --verify-sum
ptah schema compare --schema-file expected-schema.sql --schemas app --exit-code
python application.py

The dry run left the database unchanged. Deployment and schema comparison passed. The client tested the behavior described in the conversation:

examples/measured/client-excerpt.txt
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.

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