# How to Use the Knowledge Graph with Temporal Validity Windows in MemPalace

> Learn to use temporal validity windows in MemPalace's knowledge graph. Query facts valid at specific times using SQLite and the as of parameter. Master temporal data management.

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

---

**MemPalace implements a temporal entity-relationship graph on top of SQLite where each fact (triple) stores optional `valid_from` and `valid_to` timestamps, enabling you to query the exact state of knowledge at any specific point in time using the `as_of` parameter.**

The MemPalace repository provides a sophisticated **temporal knowledge graph** that moves beyond static fact storage to capture when information is true. By defining validity windows for every triple in [`mempalace/knowledge_graph.py`](https://github.com/MemPalace/mempalace/blob/main/mempalace/knowledge_graph.py), the system supports historical queries, temporal reasoning, and fact invalidation while maintaining backward compatibility with legacy data.

## Understanding Temporal Validity Windows

MemPalace models knowledge as time-bound facts. Each triple (subject → predicate → object) exists within a temporal window defined by:

- **`valid_from`**: The timestamp when the fact becomes true (required)
- **`valid_to`**: The timestamp when the fact ceases to be true (optional; NULL indicates currently valid)

The graph automatically normalizes temporal inputs using `sanitize_iso_temporal` from [`mempalace/config.py`](https://github.com/MemPalace/mempalace/blob/main/mempalace/config.py). Date-only strings like `2025-01-01` expand to `2025-01-01T00:00:00Z` for comparisons, while full UTC datetime strings pass through verbatim.

## Initializing the Knowledge Graph

Create a `KnowledgeGraph` instance to establish the SQLite backend. The constructor accepts an optional custom database path; otherwise, it defaults to `~/.mempalace/knowledge_graph.sqlite3`.

```python
from mempalace.knowledge_graph import KnowledgeGraph

# Initialize with default location

kg = KnowledgeGraph()

# Or specify a custom database path

# kg = KnowledgeGraph(db_path="/tmp/my_kg.sqlite3")

```

## Adding Facts with Temporal Boundaries

Use **`add_triple`** to insert facts with explicit validity periods. The method validates and normalizes all temporal strings before storage.

### Adding Open-Ended Facts

For facts with no defined end date, omit the `valid_to` parameter:

```python

# A person's birth date (valid from a specific date, ongoing)

kg.add_triple(
    subject="Alice",
    predicate="born_on",
    obj="1990-05-12",
    valid_from="1990-05-12"  # Internally stored as 1990-05-12T00:00:00Z

)

# An ongoing hobby with no expiration

kg.add_triple(
    subject="Bob",
    predicate="loves",
    obj="chess",
    valid_from="2025-01-01"
)

```

### Adding Time-Bounded Facts

Specify both `valid_from` and `valid_to` for temporary relationships:

```python

# A project active between specific dates

kg.add_triple(
    subject="ProjectX",
    predicate="active",
    obj="true",
    valid_from="2023-07-01",
    valid_to="2024-03-15"
)

```

## Querying Facts at Specific Points in Time

The **`query_entity`** method supports temporal filtering through the `as_of` parameter, which triggers the internal `_temporal_filter_sql` mechanism.

### Point-in-Time Queries

Retrieve only the facts that were valid at a specific moment:

```python

# What was true about Alice on 1995-01-01?

facts_1995 = kg.query_entity("Alice", as_of="1995-01-01")
print(facts_1995)

# Returns the 'born_on' triple because the interval covers that date

# Check ProjectX status during its active period

facts_jan2024 = kg.query_entity("ProjectX", as_of="2024-01-01")
print(facts_jan2024)

# Includes the 'active' fact (still within valid window)

```

### Current State Queries

Omit `as_of` to retrieve only currently valid facts (where `valid_to` is NULL):

```python

# Current facts only (no temporal filter applied)

current_facts = kg.query_entity("Bob")
print(current_facts)

# Shows 'loves' relationship because valid_to is NULL

```

Internally, the query layer converts stored date-only values to comparable UTC strings using `_temporal_start_key` and `_temporal_end_key` to ensure accurate interval containment checks.

## Invalidating Facts to Close Validity Windows

Use **`invalidate`** to mark a fact as no longer true. This method sets the `valid_to` timestamp while validating that the end date never predates the existing `valid_from`.

```python

# Bob stopped loving chess on 2026-02-01

kg.invalidate(
    subject="Bob",
    predicate="loves",
    obj="chess",
    ended="2026-02-01"
)

# Verify the fact no longer appears after that date

expired_check = kg.query_entity("Bob", as_of="2027-01-01")
print(expired_check)  # Empty; the triple is expired

```

## Querying Relationships Temporally

The **`query_relationship`** method applies the same temporal filtering to specific predicates:

```python

# Find all projects active as of a specific date

active_facts = kg.query_relationship("active", as_of="2023-12-31")
print(active_facts)  # Returns projects valid at that moment

```

## Key Implementation Files

The temporal functionality spans several core files in the MemPalace repository:

- **[`mempalace/knowledge_graph.py`](https://github.com/MemPalace/mempalace/blob/main/mempalace/knowledge_graph.py)**: Core implementation containing the `KnowledgeGraph` class, schema management, and `_temporal_filter_sql` logic for interval queries.
- **[`mempalace/config.py`](https://github.com/MemPalace/mempalace/blob/main/mempalace/config.py)**: Provides `sanitize_iso_temporal` for validating and normalizing all temporal strings used by the knowledge graph.
- **[`mempalace/ids.py`](https://github.com/MemPalace/mempalace/blob/main/mempalace/ids.py)**: Generates deterministic IDs via `make_triple_id`, ensuring uniqueness across temporal inserts and updates.
- **[`tests/test_knowledge_graph.py`](https://github.com/MemPalace/mempalace/blob/main/tests/test_knowledge_graph.py)**: Comprehensive test suite validating temporal window behaviors including addition, querying, and invalidation operations.

## Summary

- **MemPalace** stores facts in SQLite with explicit `valid_from`/`valid_to` timestamps managed by `KnowledgeGraph` in [`knowledge_graph.py`](https://github.com/MemPalace/mempalace/blob/main/knowledge_graph.py).
- **Date-only strings** automatically expand to midnight UTC boundaries, while full UTC datetimes pass through unchanged via `sanitize_iso_temporal`.
- **`add_triple`** creates facts with temporal windows; **`invalidate`** closes them by setting `valid_to`.
- **`query_entity`** and **`query_relationship`** support point-in-time retrieval using the `as_of` parameter, internally filtering via `_temporal_filter_sql`.
- Omitting `valid_to` creates indefinitely valid facts; omitting `as_of` queries only currently valid facts.

## Frequently Asked Questions

### What happens if I omit the `valid_to` parameter when adding a fact?

The fact receives a NULL `valid_to` value in the database, indicating it remains valid indefinitely. When querying without an `as_of` timestamp, the system returns only these open-ended facts, while temporal queries include them if the `as_of` date falls after their `valid_from` timestamp.

### How does MemPalace handle timezone-aware timestamps in validity windows?

According to [`config.py`](https://github.com/MemPalace/mempalace/blob/main/config.py), the `sanitize_iso_temporal` function processes date-only strings by appending `T00:00:00Z` (midnight UTC), while full UTC datetime strings are preserved verbatim. The system standardizes on UTC for all internal comparisons to ensure consistent temporal filtering across different input formats.

### Can I query for facts that were valid during an entire date range rather than a single point?

The current implementation in [`knowledge_graph.py`](https://github.com/MemPalace/mempalace/blob/main/knowledge_graph.py) supports only point-in-time queries via the `as_of` parameter. To find facts valid across a range, you would need to execute multiple point-in-time queries or retrieve all facts for the entities involved and perform interval intersection logic in your application code.

### What validation prevents creating facts that end before they begin?

The **`invalidate`** method explicitly checks that the supplied `ended` timestamp (which becomes `valid_to`) does not predate the existing `valid_from` timestamp stored for that triple. This temporal consistency check ensures the validity window always represents a logical forward-moving time interval.