<!--
  Full-page Markdown export (rendered HTML → GFM).
  Source: https://neotoma.io/primitives/timeline-events
  Generated: 2026-04-27T12:50:31.549Z
-->
# Timeline events

A timeline event is an immutable, source-anchored record that fixes one entity in time at a specific date drawn verbatim from a source field. It is a primitive record type, distinct from the application-level event entity (calendar/meeting), and distinct from system observability events.

Derived as a side-effect of writing an entity snapshot. Sources produce observations; observations produce snapshots; snapshots produce timeline events for any field that parses as a date.

## Schema[#](#schema)

timeline\_events table (Postgres / hosted)

SQL / TS

Schema or pattern reference for this primitive.

CREATE TABLE timeline\_events ( id UUID PRIMARY KEY DEFAULT gen\_random\_uuid(), event\_type TEXT NOT NULL, event\_timestamp TIMESTAMP WITH TIME ZONE NOT NULL, source\_id UUID REFERENCES sources(id), source\_field TEXT, entity\_id UUID, metadata JSONB DEFAULT '{}', created\_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), user\_id UUID NOT NULL );

| Field | Type | Purpose |
| --- | --- | --- |
| id | UUID | Deterministic, SHA-256 of (source\_id, entity\_id, source\_field, event\_timestamp) projected into UUID shape |
| event\_type | TEXT | Stable label like InvoiceIssued, FlightDeparture, TaskCompleted |
| event\_timestamp | TIMESTAMPTZ | ISO 8601 in UTC, drawn verbatim from a source field |
| source\_id | UUID | Owning source, used for RLS filtering |
| source\_field | TEXT | The exact entity/field name that produced the date (invoice\_date, due\_date, …) |
| entity\_id | UUID | Subject of the event (the entity whose snapshot field carried the date) |
| metadata | JSONB | Optional event-level provenance (agent attribution, tier, schema version) |

## Four invariants[#](#four-invariants)

Source-linked: every event has source\_id and source\_field. Deterministic: id is SHA-256(source\_id, entity\_id, source\_field, event\_timestamp) shaped as a UUIDv4, re-derivation upserts the same row. Timestamp-normalized: only strings matching strict date shapes (or numeric epoch values in a sane range) are accepted. Immutable: events are never mutated; reinterpretation produces new rows alongside old ones.

◆

## How events are derived[#](#derivation)

Three writers invoke timeline event derivation: structured store\_structured ingestion, AI interpretation completion, and reducer snapshot recomputation. The writer selects fields via the schema's temporal\_fields declaration (preferred) or a curated allow-set + strict date-shape regex (legacy fallback). System fields like created\_at, updated\_at, and computed\_at are denylisted.

◆

## Deterministic ID, idempotent upsert[#](#deterministic-id)

generateTimelineEventId hashes the four-tuple and shapes the first 32 hex chars as a UUIDv4. Upserts use onConflict: 'id', re-deriving an event with identical inputs converges on the same row. A reinterpretation that emits a different timestamp for the same (source, entity, field) writes a new event row alongside the old one; nothing is retroactively rewritten.

◆

## Not the same as system observability events[#](#not-system-events)

Timeline events are user-facing temporal records anchored to source data. System observability events (source.created, ingestion.failed, …) are emitted by the platform and live in a separate subsystem. The application-level event entity type (calendar/meeting) is also distinct, that is an entity, not a timeline\_events row.

◆

## Read path[#](#read-path)

GET /api/timeline returns paginated events for the authenticated user, filtered by entity\_id, event\_type, start\_date, or end\_date. The MCP layer exposes list\_timeline\_events with the same filters. Both paths defensively load the user's source set first and only return events whose source\_id is in that set.

## Invariants[#](#invariants)

Every timeline event satisfies the following constraints:

MUST

-   Derive only from extracted source date fields, never inferred, predicted, or computed
-   Carry a non-null source\_id, source\_field, and event\_timestamp
-   Use the deterministic generateTimelineEventId hash so re-derivation is idempotent
-   Pass attribution policy enforcement before write
-   Be filtered by source ownership on every read path

MUST NOT

-   Be created by agents through a direct write surface
-   Mutate after creation
-   Inherit dates from created\_at, updated\_at, or other system fields
-   Be derived from string values that fail strict date-shape validation
-   Be returned across user boundaries, source-scoped filtering is required even where user\_id matches

## Related[#](#related)

-   [Timeline events subsystem doc](https://github.com/markmhendrickson/neotoma/blob/main/docs/subsystems/timeline_events.md) , Derivation rules, deterministic ID, event type mapping, read paths
-   [Timeline events doctrine](https://github.com/markmhendrickson/neotoma/blob/main/docs/foundation/timeline_events.md) , Foundational invariants and generation rules
-   [Sources](/primitives/sources) , Every event traces back to a source
-   [Observations](/primitives/observations) , Date fields on observations are what feed timeline derivation
-   [Replayable timeline](/replayable-timeline) , How deterministic timeline reconstruction works end-to-end
-   [Implementation](https://github.com/markmhendrickson/neotoma/blob/main/src/services/timeline_events.ts) , src/services/timeline\_events.ts, derivation and upsert

## Where to go next[#](#more)

-   [All primitive record types](/primitives) , index of sources, interpretations, observations, relationships, and timeline events
-   [Architecture](/architecture) , how the primitives compose into Neotoma's deterministic state
-   [Terminology](/terminology) , canonical glossary of terms used across Neotoma docs