---
title: `ASOF`
url: https://nexyron.tech/docs/cypher/clauses/time/asof
description: ASOF is a query level prefix that runs a normal Cypher query with a point in time read context for declared temporal properties and stream backed abstraction features.
---

# `ASOF`

## Generic Description

`ASOF` is a query-level prefix that runs a normal Cypher query with a point-in-time read context for declared temporal properties and stream-backed abstraction features.

```cypher
ASOF 1743465600000
MATCH (m:Member)
RETURN m.status, m.plan
```

The timestamp can be an epoch-millisecond integer or a literal datetime constructor:

```cypher
ASOF datetime('2026-04-01T00:00:00Z')
MATCH (m:Member)
RETURN m.status, m.plan
```

## Consumer-Level Explanation

Use `ASOF` when you want to ask: “what would this normal query have seen at this time?”

Without `ASOF`, property reads behave exactly as they do today. A query such as:

```cypher
MATCH (m:Member)
RETURN m.status, m.plan
```

reads the current graph and the current materialized property values.

With `ASOF`, the shape of the query stays the same, but declared temporal properties are read from their hidden history as of the supplied timestamp. Non-temporal properties, labels, relationships, and normal graph structure continue to behave as current graph data.

That means `m.status` becomes time-aware only if `status` is declared as a temporal property for one of `m`'s labels. If `m.plan` is an ordinary property, `m.plan` stays the normal current value.

Materialized abstraction features use the same latest-as-of behavior:

```cypher
ASOF 1743465600000
RETURN actor(Member:'member-102').churn_risk AS churn_risk
```

The selector reads the materialized feature stream for the `Member` abstraction subject and returns the latest `churn_risk` value at or before the `ASOF` timestamp. Without `ASOF`, it returns the latest materialized stream value available now.

## Conceptual Explanation

`ASOF` is not a separate feature-query language and it is not a full database snapshot restore.

It is a read context carried through the normal Cypher planning and execution path. The planner attaches a temporal-property resolver only when the logical plan contains an `ASOF` timestamp. The resolver is consulted by property access in filters and projections:

-   `m.status`
-   `WHERE m.status = 'active'`
-   `RETURN m`
-   expressions that evaluate a property through the normal expression evaluator

For declared temporal properties, the resolver reads the latest temporal-property history row at or before the `ASOF` timestamp. If the property is declared temporal but has no value yet at that time, it resolves as `null`. If the property is not declared temporal for the node labels, the normal current property value is used.

For object-valued temporal properties, this is still a normal property read. `m.profile_state` under `ASOF` returns the latest object value at or before the timestamp, not a timestamped history row. Use `nexyron.temporal_history` when the query needs `ts`, `property`, and `value` rows.

`ASOF` also caps temporal-property procedure reads in the same query. If `nexyron.temporal_history` or `nexyron.temporal_rollup` receives `null` as its upper bound, the effective upper bound is the `ASOF` timestamp. If it receives an explicit upper bound later than the `ASOF` timestamp, the effective upper bound is the smaller `ASOF` timestamp.

The same cap applies to `nexyron.feature_history`. A feature-history call under `ASOF` will not return stream points after the query-level timestamp, even when `to` is omitted or later than the `ASOF` timestamp.

This design keeps existing feature contracts and user queries clean. A stored feature query can stay as ordinary Cypher, and snapshot/backfill execution can run that same query under an `ASOF` context when the caller supplies an `at` timestamp.

## Advanced Example

```cypher
ASOF datetime('2026-04-01T00:00:00Z')
MATCH (m:Member)-[:BELONGS_TO]->(plan:Plan)
WHERE m.status = 'active'
RETURN plan.name AS plan_name,
       count(m) AS active_members,
       avg(m.risk_score) AS avg_risk_score
ORDER BY active_members DESC
```

In this query:

-   `m.status` is resolved from temporal history if `:Member.status` is declared temporal.
-   `m.risk_score` is resolved from temporal history if `:Member.risk_score` is declared temporal.
-   `plan.name` remains the current plan name unless it is also declared as a temporal property on `:Plan`.
-   The `MATCH` structure still uses the current graph topology.

For abstraction features, subject ids can be literals, parameters, or query variables:

```cypher
ASOF 1743465600000
WITH 'member-102' AS member_id
RETURN actor(Member:<member_id>).churn_risk AS churn_risk
```

`<member_id>` is selector sugar for the visible Cypher variable `member_id`. The feature name is resolved against the registered feature owned by the `Member` abstraction, usually a fully qualified registry name such as `actor.churn_risk` or `member.churn_risk`.

## Real Use Cases

-   Backfilling semantic snapshot rows without changing every stored feature query to include timestamp filters.
-   Replaying membership, account, device, or risk-state features as they existed before a training cutoff.
-   Auditing a decision by running the same ordinary query under the historical property state that should have been visible at decision time.
-   Reading a materialized abstraction feature with `actor(Member:'id').param` so the query surface behaves like temporal node-property access.
-   Comparing current graph structure with historical temporal-property values when only selected properties are modeled as changing streams.

## Real Limitations And Tradeoffs

-   `ASOF` currently applies to declared temporal properties, not to labels, relationships, or arbitrary historical graph topology.
-   It is read-only. Mutating statements under `ASOF` are rejected because the prefix describes a historical read context, not a write mode.
-   The timestamp must be known when the query is translated: use an epoch-millisecond integer or a literal `datetime('...')` expression.
-   If a temporal property did not have any history row at or before the requested time, it resolves to `null`; Nexyron does not silently fall back to the current value for declared temporal properties.
-   Abstraction feature selectors read materialized feature streams. If no snapshot materialization has written the feature stream point for the subject at or before the effective time, the selector resolves to `null`; it does not compute the feature query on demand.
-   The selector form is `actor(Member:'id').param`, `stage(Store:$id).param`, `society(Region:<region_id>).param`, or `environment(Context:'id').param`. The part after the dot is a feature selector that must resolve to exactly one registered feature on that abstraction.
-   For windowed history analytics such as counts, averages, rates, rolling windows, or raw event timelines, use `nexyron.temporal_history`, `nexyron.temporal_rollup`, `TIME`, `WINDOW`, or temporal sequence functions. When those temporal-property procedures run under `ASOF`, their upper time bound is capped to the `ASOF` timestamp.
