Worlds
Diff and merge
See exactly what a world changed, and land it: three-way merges, conflicts, partial merges, rebase and undo.
SoonThese pages describe Chronos Cloud, which opens from the waitlist. Running Chronos yourself? Self-host docs
DIFF
DIFF -- the session's world since its forkDIFF WORLD a -- a since its forkDIFF WORLD a TO b -- what turns a into b: any two worlds or moments... [AS SQL] -- the change as statements instead of rows... READERS -- what reads the columns it changes| Column | Type | Meaning |
|---|---|---|
table | text | the table (or view, sequence, function, schema) |
id | text | the row’s key; null for a change to the table itself |
change | text | insert, update, delete; create table, alter table, drop table; views, sequences, functions, schemas |
before | jsonb | the row before, or null |
after | jsonb | the row after, or null |
columns | jsonb | the columns that changed; for a table, what changed in it ("+email text", "-age") |
Destructive first. A notice leads with what each table loses, the tables that delete most first (leads: 4,812 deleted, 69 updated, 0 inserted), and the deleted rows come before the other changes. With LIMIT n, pages stay in key order. AS SQL gives one statement per row (INSERT, UPDATE, DELETE, ALTER TABLE): run them elsewhere to make the same change.
diff world agent_7;diff world 'main@-1 hour' to main as sql; -- everything main did in the last hour, as SQLDIFF … READERS
What a change affects beyond its rows: everything in the database that reads each column the diff changes, so a reviewer sees what depends on a field.
| kind | Traced how |
|---|---|
view, materialized view, merge check | column by column through their queries, views on views inlined |
primary key, unique, foreign key, check, index | by their columns, expressions and WHERE |
trigger, function | when their event fires; functions where their text names the table and column |
client | programs seen reading the column over Postgres, by role and application_name, with when they last did |
registered reader | things outside SQL you name by hand: REGISTER READER export ON leads (status) |
coverage | the last rows: what was traced, and what couldn’t be (queries built as strings for EXECUTE) |
MARK READER billing CRITICAL marks a reader of any kind; critical readers are listed first, and a merge policy with critical = false holds an agent’s merge that changes a column one reads. Give each integration its own role or application_name so it shows by name.
diff world cleanup readers;-- table | columns | kind | reader | detail-- leads | status | check | leads_status_check | CHECK (status IN ('new', 'won'))-- leads | status | view | open_leads |MERGE
MERGE WORLD [name] [clause ...]name defaults to the session’s world. A whole merge into the parent drops the world, and a session on it moves to the parent. Returns MERGE n, the rows applied. Clauses, in any order:
| Clause | Effect |
|---|---|
INTO world | merge into another open world instead of the parent; the world stays open |
ONLY TABLES (t, …) | just those tables; the world stays open with the rest |
ONLY KEYS ('t/1', …) | just those rows; the world stays open with the rest |
USING OURS / USING THEIRS | a row both sides changed: keep this world’s, or the parent’s |
BY COLUMNS | a row both sides changed merges column by column; only a column both changed conflicts |
RESOLVE ('t/1' = OURS | THEIRS | DELETE | '<row as JSON>', …) | settle rows one by one |
AT VERSION n | merge only if the world is still at version n: the version a person reviewed |
CONFIRM DELETES n | the number of rows it deletes, given by the person approving when the policy asks for it |
TARGET n | with rows settled: only if the parent is as the dry run that named n saw it |
CONFIRM | after the server stopped unexpectedly: merge the world as it is now (check DIFF first) |
DRY RUN | change nothing; show what would happen |
Settling runs RESOLVE first, then BY COLUMNS, then USING. Keys of several columns are written 't/(1,abc)'.
merge world agent_7 by columns resolve ('orders/7' = theirs, 'orders/9' = '{"id": 9, "status": "paid"}');merge world agent_7 only tables (orders);merge world agent_7b into agent_7;Dry runs
MERGE WORLD w … DRY RUN (the MCP tool merge_preview) shows what the merge would do with the same clauses, row by row, and changes nothing.
| Column | Meaning |
|---|---|
table, id | the row; id null for a table, view or sequence itself |
outcome | apply, conflict, by columns, picked ours, picked theirs, picked row, kept ours, kept theirs, summary or blocked |
detail | why: for a conflict, which columns each side changed |
base, ours, theirs | the row at the fork, in this world, in the parent |
result | the row after the merge; null for a conflict |
It leads with a summary row per table, then the rows it deletes, then the rest. A last blocked row means the merge would be refused or held: by constraints, a merge check (with the rows it finds), or the agent’s policy (with the rules it breaks). It also says the world’s version, to merge exactly what it showed.
Conflicts
A row both sides changed since the fork is a conflict, even when both wrote the same value. By default the merge fails with 40001, naming up to 10 rows, and nothing merges.
- Look first: a dry run says, per row, what each side did (“both changed status”, “deleted here, changed by the parent”).
- By columns: where the sides changed different columns, combine them (
BY COLUMNS). Two changes to one column always conflict.ALTER TABLE leads SET (merge_by_columns = true)makes every merge into that table do this; turn it on only where columns don’t depend on each other. - Row by row:
RESOLVEeach row as ours, theirs, deleted, or a row you give. - All at once:
USING OURSorUSING THEIRSfor whatever is left. - Or redo it: drop the world and fork a fresh one: often right, since the agent decided on data that’s gone.
Both sides changing one table’s columns or constraints differently can’t be settled by picking a side: merge one, then redo the other’s change.
Part of a world, or into another
ONLY TABLES (orders)takes a table whole: its schema, rows, unique and reference rows.ONLY KEYS ('orders/7')takes rows with their own unique and reference rows; one tied to a row left out is refused (22023): add it, or useONLY TABLES.- Rows merged stay merged: the world’s fork point moves on for them, so a later merge of the rest doesn’t take them again.
DIFFshows what’s left. INTO yapplies the world’s changes to another world, three-way against its own fork point; the world stays open, its changes still to merge into its parent.
Approve what you read
A world’s version moves on with every write to it. A person who reviewed it at version 12 merges with AT VERSION 12: if its agent wrote more meanwhile, or part of it merged, nothing merges (55000) and the error says so. The dashboard’s Merge button always sends the version you were shown; the MCP merge tool takes version.
show reviews; -- version 12 for agent_7merge world agent_7 at version 12; -- lands only if agent_7 is still at 12REBASE WORLD
REBASE WORLD [name] [USING OURS | THEIRS] [BY COLUMNS] [RESOLVE (...)] [AT VERSION n]Moves a world’s fork point up to its parent as it is now, its own changes kept on top, without changing the parent. A world that runs for hours picks up what changed under it and settles conflicts as they come, not all at its merge. Returns REBASE n, the parent’s rows it took in.
- Refused with nothing changed on a conflict nothing settles (
40001), rows both sides added under one unique value (23505), or, for a world that checks its reads, a row it read that changed (40001). - No policy judges a rebase; the merge after it is judged. An agent whose policy has
overwrite = falsecan’t keep its rows over its parent’s (42501). RESTORE WORLDto before a rebase is refused (55000).
UNDO MERGE
UNDO MERGE [OF] WORLD name [SKIP CHANGED]Puts back, in the world it merged into, every row name’s latest merge changed, as an ordinary write (so it can be undone too). name is the merged world: it’s gone, but its name still works. Rows changed again since are named (40001) and nothing is undone; SKIP CHANGED leaves those and undoes the rest. The merge must be within the history window. The dashboard’s Undo on a merged world runs this with SKIP CHANGED.
undo merge of world agent_7 skip changed;Errors
| SQLSTATE | When |
|---|---|
40001 | rows changed on both sides; both sides changed one table’s columns |
23505 | both sides added rows with the same unique values |
23503, 23514 | the merged result breaks a foreign key, a CHECK, or a merge check |
42501 | the agent may not merge this, or its merge policy holds it for a person (queued in Reviews) |
55000 | not at AT VERSION n; a missing or wrong CONFIRM DELETES; a world open when the server stopped (check DIFF, then CONFIRM) |
22023 | ONLY KEYS given something that isn’t 'table/id', or a row tied to one left out |
0A000 | main has no parent |
3D000 | no such world |
Durability
Merges into main are on disk before they return. Writes inside other worlds are batched for speed; if the server stops before they’re saved, the world’s merge says how many writes were lost (with a version) or refuses until you CONFIRM (without one), rather than merging less than was written.