Docs / Start here
Getting started
This guide takes about ten minutes:
- Install Changeward.
- Connect a sample Postgres database.
- Make, inspect and undo changes yourself.
- Hand the database to a coding agent.
Install
Changeward is a single binary, changeward.
From source (until the first release). You need Go 1.27+ and a C compiler; the Postgres parser uses cgo.
git clone https://github.com/srawlin/changeward
cd changeward
make build # → bin/changeward
export PATH="$PWD/bin:$PATH"
changeward version
Docker: docker build -t changeward . builds an Alpine image with changeward, git and kubectl.
Packages are built by the release workflow (.github/workflows/release.yml) once a version is tagged:
| Channel | Command |
|---|---|
| Homebrew | brew install srawlin/tap/changeward |
| npm (no install) | npx -y @changeward/cli … |
| PyPI (no install) | uvx changeward … |
| Container | ghcr.io/srawlin/changeward |
1. A sample database
docker run -d --name shop-db -e POSTGRES_PASSWORD=pg -p 5433:5432 postgres:18-alpine
sleep 3
docker exec shop-db psql -U postgres -c "CREATE DATABASE shop"
docker exec shop-db psql -U postgres -d shop -c "
CREATE TABLE customers(id bigserial PRIMARY KEY, email text NOT NULL UNIQUE,
plan text NOT NULL DEFAULT 'free', last_seen date);
INSERT INTO customers(email, plan, last_seen)
SELECT 'user'||g||'@example.com', CASE WHEN g % 10 = 0 THEN 'pro' ELSE 'free' END, DATE '2026-10-01' - g * 7
FROM generate_series(1, 200) g"
2. Connect it
mkdir shop && cd shop
changeward connect shop --dsn 'postgres://postgres:pg@localhost:5433/shop?sslmode=disable' --env personal
This writes changeward.yaml in the current directory. The audit store goes in .changeward/. --env binds
the connection to an environment class: personal, shared-dev, staging, prod or prod-restricted. The
class sets the default policy, and an agent can never change it. Keep secrets out of the file with
--dsn '${SHOP_DATABASE_URL}'.
3. Make some changes
Start a session, so the changes are grouped and can be undone together:
$ changeward session start --agent demo
session s_30f9a360ec02 started on shop (personal)
$ changeward exec --intent "add a status column" \
"ALTER TABLE customers ADD COLUMN status text NOT NULL DEFAULT 'active'"
plan p_cc3e0861f0ad · shop (personal) · reversible · 0 rows · live-dry-run
#1 ALTER TABLE customers ADD COLUMN status text NOT NULL DEFAULT 'active'
ALTER TABLE on public.customers (200 rows)
locks: public.customers=AccessExclusiveLock
change c_cef3901905f6 (#1) — auto-approved
policy: auto-approved (builtin/personal-auto)
change c_cef3901905f6 — verified · 0 rows · ALTER TABLE
$ changeward exec --intent "archive customers inactive for two years" \
"UPDATE customers SET status = 'archived' WHERE last_seen < DATE '2024-10-19'"
plan p_805e48ce4b33 · shop (personal) · reversible · 99 rows · live-dry-run
#1 UPDATE public.customers — 99 rows
~ id=102 status: "active" → "archived"
~ id=103 status: "active" → "archived"
…
change c_0abd3564f023 (#2) — verified · 99 rows · UPDATE 99
$ changeward exec "DELETE FROM customers WHERE plan = 'free' AND status = 'archived'"
plan p_f0d05ccc3d5d · shop (personal) · reversible · 89 rows · live-dry-run
#1 DELETE public.customers — 89 rows
- id=121 {email="user121@example.com" id=121 last_seen="2024-06-06" plan="free" status="archived"}
…
change c_7587bd7fb6bd (#3) — verified · 89 rows · DELETE 89
Each change was planned first. The statement ran in a transaction that was always rolled back, which
captured the exact rows and before/after values. Policy then decided. In personal, reversible changes are
auto-approved. Changeward applied the change, checked that only the planned rows moved, and kept the undo data.
4. Look and undo
$ changeward log
SESSION s_30f9a360ec02 agent=demo user=you env=personal target=shop
#1 20:47 ALTER TABLE customers ADD COLUMN status text NOT NULL DEF... reversible verified
#2 20:47 UPDATE customers SET status = 'archived' WHERE last_seen ... (99 rows) reversible verified
#3 20:47 DELETE FROM customers WHERE plan = 'free' AND status = 'a... (89 rows) reversible verified
$ changeward undo 2 --yes
undoing c_0abd3564f023 first requires undoing 1 later change(s) that touch the same objects:
c_7587bd7fb6bd DELETE FROM customers WHERE plan = 'free' AND status = 'a...
undone c_7587bd7fb6bd DELETE FROM customers WHERE plan = 'free' AND status = 'a...
undone c_0abd3564f023 UPDATE customers SET status = 'archived' WHERE last_seen ...
$ changeward rewind --yes # undo everything left in the session
undone c_cef3901905f6 ALTER TABLE customers ADD COLUMN status text NOT NULL DEF...
Undo is dependency-aware. Undoing step 2 first undid step 3, which deleted rows that step 2 had changed.
Undo is guarded too. A row is restored only if it still matches what the change left behind. If someone edited it since, the undo stops with a conflict instead of overwriting their work.
changeward ui shows the same timeline in a local web view, with diffs, decisions and the audit log.
5. See production policy
Connect the same database as prod, and try a direct write:
$ changeward connect shop-prod --dsn 'postgres://postgres:pg@localhost:5433/shop?sslmode=disable' --env prod
$ changeward exec --target shop-prod "UPDATE customers SET plan = 'pro' WHERE id = 1"
plan p_3985405c7f7f · shop-prod (prod) · reversible · 1 rows · live-dry-run
#1 UPDATE public.customers — 1 rows
~ id=h:6b86b273 plan
change c_15571c41ae7a (#4) — rejected
policy: denied: direct production writes need an incident or ticket reference (changeward session start --incident ...); production direct changes need out-of-band approval by someone other than the proposer
In production, row values are hidden from the plan, and keys are hashed. A direct write needs:
- an incident reference:
changeward session start --target shop-prod --incident INC-123; - approval from someone other than the proposer:
changeward approve PLAN_IDfrom another identity, or Slack.
Write your own rules in changeward.policy.yaml; see policy.md.
6. Give it to your agent
changeward install claude-code # or codex, opencode, gemini, cursor
Restart the agent and ask for a change in plain language. It will call propose_change, show you the plan, and
apply_change once policy allows it. If it reaches for psql instead, the hook blocks the command and points it
back to Changeward. Agent-specific details: agents/.
Next steps
- Rehearse migrations in pull requests (or GitLab).
- Promote a session to a migration and a PR:
changeward promote --pr. - Run a shared executor so agents never hold production credentials.
- Checkpoints:
changeward session start --checkpointmakes even irreversible changes restorable.
Edit this page on GitHub · synced from 89c04c9