---
title: Merges
description: How records from several producers fold together under a type's merge policy, and how people merge two records by hand.
sidebar:
  order: 13
---

## What it is

Merging decides when two findings are the same record. There are two mechanisms:

- **Merge policy**, per type: whether different producers that find the same key share one record (`merge`) or keep their own (`separate`).
- **Hand merge**, per record: a person says this record is really that one.

## Why it exists

Two routines on one Desk can both find Northwind Goods. Most of the time people want one company with both routines' findings. Sometimes they want each routine's view kept apart. And identity rules are not perfect: a company seen once by domain and once by a slightly different name needs a person to say "same thing".

## How it works

### Merge policy

Each type declares `merge` in the definition (`merge` by default). People can override it per type in `settings/workspace` under `mergePolicy`, which seed fills from the definition:

```json
"mergePolicy": {"company": "merge", "role": "merge"}
```

- `merge`: the id is `<type>--<slug(key)>`. Every producer that finds the key writes its own `bySource` slice of the same record.
- `separate`: the id is `<type>--<slug(producerId--key)>`. Each producer gets its own record.

Each run records the policy it used as `mergePolicy` on the run document. Flipping the policy applies to new ids only; existing documents are never re-keyed.

### Hand merge

On a record page, a person opens "Merge into another <type>", searches, and picks the target. The Desk writes `merges/<entityId>` `{into: <targetId>, at, by}` and says: "A hand merge is recorded now. Applies from the next run, which folds the two records together."

From then on, the write contract resolves the merged id to its target whenever a run sees it, so new sightings land on the target. Merges can chain (A into B, B into C); a cycle fails the plan with `hand merge cycle: A -> B -> A`. Unmerge deletes `merges/<entityId>`.

> Since 0.5.0: a side-by-side merge on the record page (pick two records, choose each field's value, confirm, undo) and bulk Merge for exactly two selected records. It still writes `merges/<entityId>`.

## Worked example

Hiring Signals and a second producer, `lead-signals`, share a Desk. Both find `northwind.example`. With `company: merge`, there is one record, `company--northwind-example`, with `bySource` slices for both producers and `sources: ["hiring-signals", "lead-signals"]`.

A third run records a company as "Northwind Goods Pty Ltd" with no domain, so it keys by name as `company--northwind-goods-pty-ltd`. A person merges it into `company--northwind-example`. The next time any run sees that name, the sighting goes to `company--northwind-example`.

## Reference

| Item | Path or field | Writer |
|---|---|---|
| Type default | `definition.json` `types.<type>.merge` | Builder |
| Workspace override | `settings/workspace` `values.mergePolicy.<type>` | People |
| Policy used by a run | `runs/<runId>` `mergePolicy` | Producers |
| Hand merge | `merges/<entityId>` `{into, at, by}` | People |

When producers share a type, compile requires the same merge default in each definition.

## Common pitfalls

- **Flipping to `separate` and expecting a split.** Old records stay merged. Only new ids follow the new policy.
- **Merging across types.** The Desk only offers records of the same type.
- **Expecting the merge to show at once.** The hand merge is recorded immediately; the fold happens on the next run.

## Related

- [Records](/concepts/records)
- [The Desk: Records](/desk/records)
