Assembly
Assembly is the post-run compose step. Where the per-document phases each handle one document, assembly works across documents: it groups them and folds each group into a single composed record. It runs once, after every document in the pipeline reaches a terminal state, so it composes the resolved values (the latest cell version per field), not raw extraction. Assembly is how a pipeline turns many related documents into one authoritative row.
Assembly is driven by two fields you name in the rail. The grouping field decides which documents belong together. Within each group, the anchor field identifies the Anchor document: the one whose anchor field matches a configured anchor value. The anchor seeds the composed record, and the other documents in the group act as Amendments that override selected fields per the rule. A group with no anchor, or more than one, is a conflict and composes nothing, so an ambiguous group is surfaced rather than guessed.
The composed overrides are written back onto the anchor record as new cell versions with an assembly source. This means the anchor cell version history reads extraction, then resolution, then assembly, in order, and each assembly cell records the override detail: the previous value, the source document it came from, and the rule that produced it. The version history is the assembly audit trail. The full composed product is also materialized as its own record set for delivery.
A common shape is a base document amended by later ones: an original order and its revisions, or a master agreement and its addenda. The original is the anchor; each amendment overrides the fields it changes. The composed record reflects the current state of the agreement, while the version history on every field preserves exactly which document set each value and when. This gives you both the answer and its provenance in one place.
Configuring assembly
Assembly is configured as an assembly stage in the rail, naming the grouping and anchor fields and the amendable field set. Because assembly composes the resolved values, place your resolution and validation stages before it so the values it folds together are already normalized and checked. After a run you can re-compose without redoing extraction by re-running only the assembly step over the existing cells. The stage compiles only when both the grouping field and the anchor field are set, and the field pickers offer only fields that occur on more than one document — a field unique to a single document cannot form a group.
curl -X PUT https://api.talonic.com/v1/schemas/sch_delivery_notes/rail \
-H "Authorization: Bearer $TALONIC_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"rail": [
{ "type": "registry", "name": "Field Registry" },
{ "type": "schema", "name": "Extraction" },
{ "type": "resolve", "name": "Normalize",
"json": { "policy_ids": ["pol_dates_amounts"] } },
{ "type": "assembly", "name": "Assembly",
"json": { "grouping_field": "contract_id",
"anchor_field": "document_type" } }
]
}'Assembly configuration surface (selected keys)
| Parameter | Type | Description |
|---|---|---|
| grouping_field / anchor_field | core | The bucket key and the field that identifies the anchor document class. Both are required for the assembly phase to compile. |
| anchor_values | election | Which values of the anchor field mark a document as anchor material, in preference order. |
| signed_field / date_field | election | Tie-breakers for electing one anchor among several candidates: signed status first, then date recency. |
| amendable_fields | override | The fields an amendment document may contribute. Can be made document-type-conditional, so a rate confirmation and an invoice each amend only their own field set. |
| anchor_fallback | resilience | What to do with a group that has no anchor: "none" surfaces the group as unresolved; "newest" composes with the newest document acting as anchor and flags the record. |
| absence_values / placeholder_values | hygiene | Values that mean "not present" (an absence value may fill an empty field but never overwrites a substantive one) and anchor values that count as empty, such as "0". |
| override_review | gate | Puts every amendment overwrite under review: "warning" flags the overwritten field in place, "blocking" routes it into the review queue as the synthetic Overwrite Review gate. |
Anchor election and overwrite review
When a group holds several anchor candidates, assembly elects one deterministically down a ladder: identical-content candidates collapse first, then the configured anchor-value preference order decides, then signed-newest, then date recency over the configured date fields (deciding only when every candidate carries the field), and only past all of that does it fall back to the most-filled document or document order. The early rungs record a quiet election receipt; only the last-resort rungs keep a composition warning on the record, so a clean election never adds noise. A reviewer can also pin the anchor for a group explicitly, and the pinned document wins on every later compose.
With override review enabled, every field an amendment overwrites passes through a synthetic pipeline-scoped gate labeled Overwrite Review before it ships. In warning mode the overwritten field is flagged in place; in blocking mode it lands in the review queue like any gated field, and a blocked post-assembly field reopens its anchor document as partial until the reviewer decides. The gate is built from the assembly configuration itself — there is no separate validation stage to manage — and its sensitivity can distinguish similar overwrites (a reformatted date) from genuinely different ones.
{
"override_review": {
"mode": "blocking",
"fields": ["total_amount", "delivery_date"]
}
}
// mode: "off" | "warning" | "blocking".
// The optional fields list scopes which overwrites are reviewed.Free-text merge guidance refines how the composition model chooses between candidate values: general instructions plus optional per-field guidance items. Guidance steers selection only — the platform copies the chosen value verbatim from the cited source cell, so the merge can never invent or reformat a value, and every composed cell still cites exactly one source document.