<!--
Full-page Markdown export (rendered HTML → GFM).
Source: https://neotoma.io/id/schemas/merge-policies
Generated: 2026-04-27T12:50:40.563Z
-->
# Merge policies
Merge policies are the per-field configuration the reducer uses to collapse a stream of observations into a single entity snapshot. They are declarative, every policy is a strategy plus an optional tie-breaker, no inline code. This is what makes snapshot composition deterministic and replayable: the same observations and the same merge\_policies always produce the same snapshot.
Sit inside reducer\_config on every schema\_registry row. Read once per snapshot recomputation. The reducer never falls back to ad-hoc logic, fields without an explicit policy use the documented default last\_write.
## Schema[#](#schema)
ReducerConfig and MergePolicy (TypeScript)
SQL / TS
Schema or pattern reference for this concept.
interface ReducerConfig { merge\_policies: Record<string, MergePolicy>; } interface MergePolicy { strategy: | "last\_write" | "highest\_priority" | "most\_specific" | "merge\_array"; tie\_breaker?: "observed\_at" | "source\_priority"; } // Example: invoice { "merge\_policies": { "vendor\_name": { "strategy": "highest\_priority" }, "amount\_due": { "strategy": "last\_write" }, "status": { "strategy": "last\_write" }, "aliases": { "strategy": "merge\_array" }, "line\_items": { "strategy": "merge\_array" } } }
## Four strategies[#](#strategies)
last\_write picks the most recent observation by observed\_at, the right default for fields that change over time (status, amount, address). highest\_priority picks the observation with the highest source\_priority, the right choice for identity-shaped fields where a user correction (1000) should always beat a structured agent write (100) or AI extraction (0). most\_specific picks the observation with the highest specificity\_score, useful when one source produces dense, schema-aligned facts and another produces shallow ones. merge\_array unions array values across observations, used for aliases, tags, and other accumulating sets.
◆
## Tie-breakers[#](#tie-breakers)
When two observations score equally under the chosen strategy, the tie\_breaker decides. observed\_at favours the more recent observation; source\_priority favours the higher-priority writer. The default tie-breaker for last\_write and most\_specific is observed\_at; for highest\_priority it is source\_priority. Ties are resolved deterministically, the reducer never picks at random.
◆
## Default behaviour for unmapped fields[#](#default-behaviour)
If a field appears in observations but has no entry in merge\_policies, for instance, a removed field that still has historical observations, the reducer falls back to last\_write. This keeps schema removal from corrupting historic snapshots: removed fields drop out of new snapshots via schema-projection filtering, but until then the policy is well-defined.
◆
## Source priority ladder[#](#priority-ladder)
highest\_priority leans on the source\_priority ladder set on each observation: 0 for AI interpretations, 100 for structured agent writes (store\_structured), 1000 for explicit user corrections via the correct() path. This is what guarantees user corrections always win without requiring the reducer to know what 'a correction' is, corrections are just observations at priority 1000.
## Invariants[#](#invariants)
MUST
- Be declarative, strategy + optional tie\_breaker, no inline code
- Be deterministic, same observations + same policies ⇒ same snapshot
- Cover identity-shaped fields with highest\_priority so corrections override AI
- Use merge\_array for accumulating sets (aliases, tags, line items, …)
- Resolve ties with the documented tie\_breaker, never randomly
MUST NOT
- Run arbitrary code, merge logic lives in the reducer, not in policies
- Use confidence as a merge signal, confidence is advisory only
- Mix strategy types within a single field across versions without a major version bump
- Override schema-projection filtering, removed fields drop out of snapshots regardless of policy
## Related[#](#related)
- [Reducer subsystem doc](https://github.com/markmhendrickson/neotoma/blob/main/docs/subsystems/reducer.md) , Reducer architecture, merge implementation, snapshot computation
- [Schema registry](/schemas/registry) , Where reducer\_config lives
- [Observations](/primitives/observations) , Source priority ladder and observation shape
- [Entity snapshots](/primitives/entity-snapshots) , Reducer output and per-field provenance map
- [Determinism doctrine](https://github.com/markmhendrickson/neotoma/blob/main/docs/architecture/determinism.md) , Why declarative merge keeps snapshots reproducible
## Where to go next[#](#more)
- [All schema concepts](/schemas) , registry, merge policies, storage layers, versioning
- [Primitive record types](/primitives) , sources, observations, snapshots, and the rest of Neotoma's atoms
- [Schema management workflows](/schema-management) , CLI commands for listing, validating, and evolving schemas