History Sync
When a new federation link forms, the fresh peer has none of the other side’s earlier content. History sync closes that gap: the receiver pulls a bounded tail of the peer’s recent broadcast actions and feeds them through the ordinary inbound pipeline, so a new follow does not open onto an empty wall.
When It Runs
A HistoryFetchTask is scheduled whenever a federation link is newly established:
- an FLLW action creating a follow, and
- a CONN action reaching the Connected state (either side of the handshake).
Scheduling is fire-and-forget — a failure to enqueue is logged and never breaks the
action that triggered it. The task is keyed history_sync:{tn_id}:{peer_id_tag}, which
collapses the several branches that can fire for one logical connection (a CONN’s
on_receive and on_accept, a CONN-ACC followed by an FLLW) into a single fetch.
The Fetch
The task issues a single request to the peer:
GET /api/outbox?since={unix_seconds}&limit={n}since is derived as now - since_days × 86400. Both bounds come from settings, with
defaults applied when unset:
| Setting | Default | Meaning |
|---|---|---|
federation.history_sync.since_days |
30 | How far back the receiver asks for |
federation.history_sync.limit |
10 | How many primary actions the receiver asks for |
A limit of 0 disables history sync for that tenant — the task returns immediately
without contacting the peer.
The serving side clamps the request against its own
federation.history_sync.limit setting and then against a hard cap of 100 items,
regardless of what the caller asked for. The cap bounds both the response size and the
per-call signing cost, since each primary is served with a freshly minted STAT.
What the Outbox Returns
The response uses the same envelope as inbox delivery — a primary token plus a
related array of dependent tokens:
{
"items": [
{ "token": "…", "related": ["…"] }
]
}Which action types count as primary is derived from the action DSL rather than
hardcoded: every type whose behaviour flags say broadcast = true and not
local_only, minus an auxiliary list (APRV, STAT) that only ever rides along with a
primary. Today that yields POST and REPOST; a future broadcast content type
extends history sync automatically by virtue of its DSL definition.
Auxiliary tokens in related do not count against the item limit.
Items come in two shapes:
- Case A — the tenant’s own wall post.
tokenis the POST or REPOST;relatedcarries a freshly minted STAT with its current counters. - Case B — the audience bridge. A third-party post published to the tenant’s wall.
tokenis the tenant’s most recent active APRV approving that post, andrelatedcarries the third-party post itself plus a STAT. The receiver follows the tenant, so the APRV passes its inbound permission check and the bridged post is processed through the pre-approved related-token path — which is how content the receiver has no direct relationship with still arrives legitimately.
Ingestion
For each item the receiver pre-stores every related token as an inbound action linked
to the primary, then processes the primary. The primary’s post-store hook drives the
related tokens through the standard related-action pipeline — byte for byte the same
protocol as /inbox ingestion, not a parallel path.
sequenceDiagram
participant R as Receiver
participant P as Peer
R->>P: GET /api/outbox?since&limit
P-->>R: items[] (token + related)
loop each item
R->>R: pre-store related as inbound actions
R->>R: process primary token
R->>R: post-store hook processes related
end
Re-running a sync is safe. Action IDs are content-addressed (action_id = hash(token)),
and the inbound-action insert ignores duplicates on (tenant, action_id), so an
overlapping schedule or a scheduler retry silently converges instead of raising
conflicts.
Failure Handling
Note
If the peer answers 403 Permission Denied, the task is dropped quietly rather than retried. That response means the peer has not federated back yet — or has revoked the link — and the next federation event will schedule a fresh sync anyway. Network and server errors are distinct: those propagate, and the scheduler retries them under its normal backoff.
An individual item that fails to ingest is logged and skipped; the rest of the batch still lands.
See Also
- Action Delivery - The inbox path history sync reuses
- Trust & Distribution - Related-action bundling and auto-approval
- Relationships - FLLW and CONN, which trigger the sync
- Task Scheduler - How HistoryFetchTask is queued and retried