# How MemPalace Handles Temporal Entity-Relationship Queries with Time Filtering

> Discover how MemPalace handles temporal entity-relationship queries with time filtering using ISO-8601 intervals and normalized text comparisons in SQLite.

- Repository: [MemPalace/mempalace](https://github.com/MemPalace/mempalace)
- Tags: how-to-guide
- Published: 2026-06-06

---

**The MemPalace knowledge graph implements temporal entity-relationship queries with time filtering by storing ISO-8601 validity intervals in SQLite and using normalized text comparisons to resolve "as-of" moments.**

The MemPalace open-source project maintains a fully offline knowledge graph capable of answering temporal entity-relationship queries with time filtering without external databases. By embedding `valid_from` and `valid_to` columns directly into the SQLite triples table and normalizing all temporal inputs to canonical UTC strings, the system determines historical truth using lexical comparisons against stored intervals.

## Schema Design for Temporal Facts

In [`mempalace/knowledge_graph.py`](https://github.com/MemPalace/mempalace/blob/main/mempalace/knowledge_graph.py), the knowledge graph persists relationships (triples) in a SQLite table that includes two optional temporal columns: `valid_from` and `valid_to`. These columns hold ISO-8601 dates or canonical UTC datetimes, allowing every fact to be scoped to a specific time interval. A `NULL` value in `valid_to` indicates the fact remains current indefinitely.

## Input Validation and Normalization

Before any temporal value reaches the database, the system validates and normalizes it through `sanitize_iso_temporal` in [`mempalace/config.py`](https://github.com/MemPalace/mempalace/blob/main/mempalace/config.py) (lines 38-53). This function guarantees that only fully-qualified dates (`YYYY-MM-DD`) or UTC timestamps (`YYYY-MM-DDTHH:MM:SSZ`) are accepted, rejecting ambiguous or malformed inputs at the API boundary.

Both `add_triple` and `invalidate` methods invoke this sanitizer, ensuring that all temporal boundaries stored in the graph are comparable using simple text collation.

## Converting Dates to Comparable Keys

Because SQLite stores timestamps as plain text, the graph must create comparison keys that enable correct interval logic. The helpers `_temporal_start_key` and `_temporal_end_key` in [`mempalace/knowledge_graph.py`](https://github.com/MemPalace/mempalace/blob/main/mempalace/knowledge_graph.py) (lines 56-66) handle this conversion:

- **Date-only values** expand to the start of day (`...T00:00:00Z`) or end of day (`...T23:59:59Z`)
- **Full timestamps** pass through with UTC normalization

This expansion ensures that a query for "2025-06-15" matches facts valid during that entire day, not just exact timestamp matches.

## SQL-Level Temporal Filtering

The private function `_temporal_filter_sql` generates a SQL fragment that safely compares stored `valid_from`/`valid_to` against the requested "as-of" instant. It leverages `_sql_temporal_start_expr` and `_sql_temporal_end_expr` (lines 106-126) to transform column values into comparable UTC strings on the fly.

The generated clause follows this pattern:

```sql
AND (t.valid_from IS NULL OR <start_expr> <= ?)
AND (t.valid_to   IS NULL OR <end_expr>   >= ?)

```

The two placeholders receive the same normalized "as-of" key, returning only facts where the query moment falls inside the validity interval.

## Executing Time-Filtered Queries

The public entry point `query_entity` in [`mempalace/knowledge_graph.py`](https://github.com/MemPalace/mempalace/blob/main/mempalace/knowledge_graph.py) (lines 64-71) accepts three arguments:

- `name`: The entity to look up
- `as_of`: Optional ISO-8601 timestamp triggering the temporal filter
- `direction`: `"outgoing"`, `"incoming"`, or `"both"` to control edge direction

When `as_of` is provided, `query_entity` incorporates the SQL fragment from `_temporal_filter_sql` and returns a list of dictionaries containing the relationship, its validity period, and a boolean `current` flag (`True` when `valid_to` is `NULL`).

## Managing Fact Lifecycles

Beyond querying, the graph provides explicit lifecycle management:

- **`add_triple`**: Inserts new facts with optional validity windows, calling `sanitize_iso_temporal` and rejecting inverted intervals (where `valid_from` exceeds `valid_to`)
- **`invalidate`**: Sets `valid_to` to a supplied "ended" timestamp using the same normalization logic, effectively closing a fact without deleting historical data

## Complete Implementation Example

The following example demonstrates initializing the graph, adding temporally-scoped facts, querying with time filters, and invalidating obsolete relationships:

```python
from mempalace.knowledge_graph import KnowledgeGraph

# -------------------------------------------------

# 1️⃣ Initialise the graph (creates ~/.mempalace/… if missing)

kg = KnowledgeGraph()

# -------------------------------------------------

# 2️⃣ Add some facts with temporal scopes

kg.add_triple(
    subject="Max",
    predicate="child_of",
    obj="Alice",
    valid_from="2015-04-01",          # date‑only → start of day

)

kg.add_triple(
    subject="Max",
    predicate="does",
    obj="swimming",
    valid_from="2025-01-01T12:00:00Z", # explicit UTC timestamp

    valid_to="2026-01-01T00:00:00Z",   # expires after a year

)

# -------------------------------------------------

# 3️⃣ Query “what was true about Max in June 2025?”

facts_june = kg.query_entity("Max", as_of="2025-06-15")
print("June 2025 facts:", facts_june)

# → Returns the “child_of” fact (still open) and the “does‑swimming” fact

#   because the query instant falls between its valid_from/valid_to.

# -------------------------------------------------

# 4️⃣ Query “what was true about Max in February 2027?”

facts_feb = kg.query_entity("Max", as_of="2027-02-01")
print("Feb 2027 facts:", facts_feb)

# → Only the “child_of” fact remains; the swimming fact has expired.

# -------------------------------------------------

# 5️⃣ Invalidate a fact (e.g., Max stops swimming)

kg.invalidate("Max", "does", "swimming", ended="2026-02-15")

# -------------------------------------------------

# 6️⃣ Retrieve the full timeline for Max (chronological order)

timeline = kg.timeline(entity_name="Max")
for entry in timeline:
    print(entry)

```

## Summary

- **MemPalace stores temporal metadata** in `valid_from` and `valid_to` columns using ISO-8601 format in SQLite
- **Input sanitization** via `sanitize_iso_temporal` in [`mempalace/config.py`](https://github.com/MemPalace/mempalace/blob/main/mempalace/config.py) ensures all dates are comparable UTC strings
- **Comparison keys** generated by `_temporal_start_key` and `_temporal_end_key` convert date-only values to day boundaries
- **SQL filtering** uses `_temporal_filter_sql` with text comparisons to resolve "as-of" queries without external databases
- **`query_entity`** serves as the primary interface for temporal entity-relationship queries with time filtering, returning validity intervals and current status

## Frequently Asked Questions

### What date formats does MemPalace accept for temporal queries?

MemPalace accepts fully-qualified ISO-8601 dates (`YYYY-MM-DD`) or UTC timestamps (`YYYY-MM-DDTHH:MM:SSZ`) through the `as_of` parameter. The `sanitize_iso_temporal` function in [`mempalace/config.py`](https://github.com/MemPalace/mempalace/blob/main/mempalace/config.py) rejects ambiguous formats or missing timezone indicators, ensuring consistent UTC normalization before storage or comparison.

### How does the knowledge graph handle facts without an end date?

Facts with `valid_to` set to `NULL` are considered perpetually current. The SQL generated by `_temporal_filter_sql` explicitly checks `t.valid_to IS NULL` in the `WHERE` clause, allowing these open-ended facts to match any "as-of" query that occurs after their `valid_from` timestamp. The `query_entity` result includes a `current` boolean flag set to `True` for such records.

### Can I query for relationships that were valid during an entire date range?

The current implementation in [`mempalace/knowledge_graph.py`](https://github.com/MemPalace/mempalace/blob/main/mempalace/knowledge_graph.py) supports point-in-time queries via the `as_of` parameter rather than range-overlaps. To find facts covering a duration, you would execute multiple point queries or post-filter results from `timeline` to verify that `valid_from` precedes your range start and `valid_to` exceeds your range end.

### How does MemPalace handle timezone-aware timestamps?

All temporal inputs are normalized to canonical UTC representations by `sanitize_iso_temporal`. The system stores and compares timestamps as UTC strings ending with `Z`, ensuring that temporal entity-relationship queries with time filtering return consistent results regardless of the client's local timezone.