Diff versions on the server as fork point, branch history and end state
Decision
Compute the diff between two versions on the server, deterministically, and return it as typed data. One service method serves both GraphQL and MCP. The app and agents read the diff and never derive it themselves.
A diff has three parts. The first is the fork point, the lowest common ancestor of the two tips. Two versions with no common ancestor, or from different trips, are a typed error. The second is each branch's modifications since the fork, oldest first, which shows how the versions diverged. The third summarizes the end states. Destinations, accommodations, journey legs and reservations each report what exists only on one side and what differs. Destinations also report reorders, and trip-level differences come as a closed enum of fields.
Every reference carries its display name, and each side's names come from its own trip.
Why
Agents used to compare two versions by reading both trips. In evals they invented differences and missed real ones, most often on smaller models. A diff is a gap between two states that the tree fully determines, like a discrepancy, so the server should compute it once. Entity ids derive from the modification that created them (ADR-0049), so an entity keeps its id on both sides and new entities on different branches never share one. The summary can therefore match entities by raw id. Names travel with the diff because a client usually has only the active version loaded, and an entity that exists only on the other branch would otherwise show up as unknown.
Rejected alternatives
- Letting the agent compare two trips itself. Unreliable, as the evals showed.
- Only the history, or only the end state. Neither works alone. A long branch of edits that cancel each other out looks big as history and empty as an end state.
- Trip-level fields as a list of strings. A closed enum makes adding a field a deliberate change that the compiler makes the summary handle.
- Merging, or applying one branch on top of another. Versions are separate explorations (ADR-0046). Someone who wants to combine ideas does it by hand.
- A written summary. A version's name and description already say what it is. The diff is the structured difference, and anything that wants to narrate it can read from that.
Consequences
One load of the trip's modifications, walked from both tips, yields both histories. A new trip-level field touches the enum and the summarizer, and the compiler forces both. A modification's stored text joins an MCP tool's action and the model's reasoning in one string, so a client that wants to style them apart has to split it.