<!--
Full-page Markdown export (rendered HTML → GFM).
Source: https://neotoma.io/zh/primitives/timeline-events
Generated: 2026-04-27T12:50:31.910Z
-->
# 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