Reconciliation
Managing stale observations when external data changes outside the context.
Every time an action produces output, RARS records it as an observation: a set of statements attributed to a specific agent, action, and process. Observations accumulate. If you invoke GetTask today and get tasks:status "open", then invoke it again tomorrow and get tasks:status "in-progress", both observations exist in the graph. The task now has two status values.
This is by design. The observation model preserves history and enables provenance queries ("who said what, when"). But it means the default graph reflects the union of all observations, not just the latest. Without reconciliation, stale statements from previous invocations persist alongside current ones.
Reconciliation is the mechanism for retracting statements that a new observation supersedes.
How Observations Work
An observation is a provenance record using RDF-Star reification. When an action produces output, RARS creates an rars-os:Observation in the metadata graph (urn:rars-mtx:graph:metadata) that links to each individual statement via quoted triples. Each statement also gets a validity period tracking when it became valid.
A first invocation of GetTask might produce this observation:
# In urn:rars-mtx:graph:metadata
<urn:observation:01HQXYZ> a rars-os:Observation ;
rars-os:accordingTo tasks:TaskAgent ;
rars-os:recordedIn <urn:process:invocation-001> ;
rars-os:attests
<< task:123 tasks:title "Review Q3" >> ,
<< task:123 tasks:status "open" >> ,
<< task:123 tasks:assignee "alice" >> .
# Each statement gets a validity period (open-ended, no validTo)
<urn:validity:01HQXY1> rdf:reifies << task:123 tasks:status "open" >> ;
rars-os:validDuring <urn:period:01HQXY2> .
<urn:period:01HQXY2> a rars-os:ValidityPeriod ;
rars-os:validFrom <urn:dt:01HQXY3> .
<urn:dt:01HQXY3> a cmns-dt:DateTime ;
cmns-dt:hasDateTimeValue "2025-03-01T10:00:00Z"^^xsd:dateTime .The base triples (task:123 tasks:title "Review Q3", etc.) are written to the default graph. The observation metadata, reifiers, and validity periods live in urn:rars-mtx:graph:metadata.
The Accumulation Problem
A week later, the task's status changed in the external system. A second invocation produces a new observation:
<urn:observation:01HRZAB> a rars-os:Observation ;
rars-os:accordingTo tasks:TaskAgent ;
rars-os:recordedIn <urn:process:invocation-002> ;
rars-os:attests
<< task:123 tasks:title "Review Q3" >> ,
<< task:123 tasks:status "closed" >> ,
<< task:123 tasks:assignee "alice" >> .Without reconciliation, the default graph now contains both tasks:status "open" (from the first observation) and tasks:status "closed" (from the second). Both statements have open validity periods. Any query for the task's status returns both values.
What Reconciliation Does
With reconciliation, the second observation also retracts the stale statement. The observation includes both rars-os:attests and rars-os:retracts:
<urn:observation:01HRZAB> a rars-os:Observation ;
rars-os:accordingTo tasks:TaskAgent ;
rars-os:recordedIn <urn:process:invocation-002> ;
rars-os:attests
<< task:123 tasks:title "Review Q3" >> ,
<< task:123 tasks:status "closed" >> ,
<< task:123 tasks:assignee "alice" >> ;
rars-os:retracts
<< task:123 tasks:status "open" >> .The retracted statement (tasks:status "open") is moved to the stale graph (urn:rars-mtx:graph:stale) and its validity period is closed with an rars-os:validTo timestamp. The default graph now only contains the current state. The full history is preserved in the metadata and stale graphs for audit.
When You Need Reconciliation
Reconciliation is relevant whenever an action brings data into the graph from an external source that can change independently.
Service integrations: the most common case. External APIs are the source of truth for their data. When you query them, the response reflects the current state of the external system. Previous observations about the same resources may now be stale.
Query actions: even read-only queries can bring in new information. If a task's status changed in the external system since the last query, the new observation has different values. Without reconciliation, both the old and new values coexist.
Sync and refresh actions: actions that periodically refresh a dataset from a source of truth. Records may have been updated, deleted, or added since the last sync.
Any handler type: reconciliation is configured on the handler, not on the action classification. Service integrations are the most common case, but script handlers that materialize computed data can also benefit.
Configuring Reconciliation
Add rars-act:onReconcile to your handler with an rars-os:UpdateScript that defines what to retract:
tasks:GetTaskIntegration
a rars-act:ServiceIntegration ;
rars-act:service tasks:TaskAPI ;
rars-act:requestTemplate tasks:GetTaskRequest ;
rars-act:responseTemplate tasks:TaskResponseMap ;
rars-act:healingEnabled true ;
rars-act:onReconcile tasks:TaskReconciliation .
tasks:TaskReconciliation
a rars-os:UpdateScript ;
rars-os:update """
PREFIX tasks: <https://example.org/spec/tasks#>
PREFIX rars-act: <https://poliglot.io/rars/spec/actions#>
PREFIX rars-os: <https://poliglot.io/rars/spec/os#>
DELETE { ?s ?p ?o }
WHERE {
?_process rars-os:parent ?invocation .
?invocation rars-act:subject ?s .
?s ?p ?o .
FILTER(?p IN (tasks:title, tasks:status, tasks:assignee, tasks:priority))
FILTER NOT EXISTS {
GRAPH ?changeset { ?s ?p ?o }
}
}
""" .How It Executes
- The handler executes normally and produces output statements
- The output is staged in a temporary named graph (
?changeset) - The reconciliation script runs with two bindings:
?_process: the reconciliation script execution?changeset: the named graph containing the handler's new output
- The script's DELETE statements identify what to retract
- Everything is committed as a single atomic observation with both
rars-os:attests(new statements from the handler) andrars-os:retracts(removed statements from the reconciliation)
The key is the FILTER NOT EXISTS { GRAPH ?changeset { ?s ?p ?o } } pattern. This says: retract any existing statement about the subject's mapped properties that is NOT present in the new output. If the property value didn't change, it appears in both the graph and the changeset, so it's not retracted. If it changed, the old value is only in the graph (not in the changeset), so it gets retracted.
Reconciliation Patterns
Single-Resource Actions
For actions that operate on one resource (like GetTask with a subject), scope the reconciliation to the subject:
rars-os:update """
DELETE { ?s ?p ?o }
WHERE {
?_process rars-os:parent ?invocation .
?invocation rars-act:subject ?s .
?s ?p ?o .
FILTER(?p IN (tasks:title, tasks:status, tasks:assignee))
FILTER NOT EXISTS {
GRAPH ?changeset { ?s ?p ?o }
}
}
"""This only retracts stale properties on the specific task that was queried. Other tasks in the graph are unaffected.
Batch Sync Actions
For actions that refresh an entire dataset (like SyncTasks that lists all tasks), reconcile across all resources of the type:
rars-os:update """
DELETE { ?s ?p ?o }
WHERE {
?s a tasks:Task ;
?p ?o .
FILTER(?p IN (tasks:title, tasks:status, tasks:assignee))
FILTER NOT EXISTS {
GRAPH ?changeset { ?s ?p ?o }
}
}
"""This retracts stale properties on ALL tasks, not just one. If a task was deleted from the external system and doesn't appear in the new sync, its properties get retracted because they won't be in ?changeset.
Selective Property Reconciliation
The FILTER(?p IN (...)) clause controls which properties are reconciled. Only list properties that your response mapping produces. Don't include properties that come from other sources (user annotations, computed values, relationships set by other actions).
# Only reconcile properties that come from the external API
FILTER(?p IN (tasks:title, tasks:status, tasks:assignee, tasks:externalId))
# DON'T include properties managed by other sources
# tasks:internalNotes -- set by users
# tasks:priority -- set by triage action
# rdf:type -- set by the response mapping's rml:classIf you reconcile a property that's also set by another action, you'll retract the other action's data whenever your sync runs. Scope reconciliation to exactly the properties your handler is the source of truth for.
Handling Deleted Records
When a resource was deleted from the external system, it won't appear in the sync response at all. The batch reconciliation pattern handles this naturally: all properties of that resource fail the FILTER NOT EXISTS check (nothing in ?changeset matches), so they're all retracted.
If you also want to retract the rdf:type (so the resource no longer appears as a tasks:Task), include it in your filter:
FILTER(?p IN (rdf:type, tasks:title, tasks:status, tasks:assignee))Be careful with this. Retracting rdf:type means the resource effectively disappears from typed queries. Other observations that reference it will have dangling references.
When to Skip Reconciliation
Not every action needs reconciliation.
Create actions: producing new resources that didn't exist before. Nothing to reconcile.
Append-only patterns: adding records without affecting existing ones (log entries, event streams).
Actions where accumulation is correct: if your domain genuinely tracks historical values (e.g., a price history where every observed price is valid data), don't reconcile.
Actions where another mechanism manages state: if a script handler orchestrates multiple sub-actions and explicitly manages what gets committed, adding reconciliation to each sub-action creates conflicts.
Reconciliation and Provenance
Reconciliation produces a richer provenance record than a simple observation. A reconciled observation has:
rars-os:attests: the new statements (from the handler output)rars-os:retracts: the removed statements (from the reconciliation DELETE)
Both are linked to the same observation, the same process, and the same agent. This means provenance queries can answer: "When was this property value replaced? What replaced it? Which agent and process were responsible?"
Without reconciliation, stale statements have no retraction record. They just sit in the graph until something else happens to overwrite them (which may never happen).
See Also
- Service Integration Actions: handler configuration and healing
- Provenance: how observations and retractions are tracked
- Named Graphs: how the
?changesetgraph works - Response Mapping: defining what properties the handler produces