Manual · Querying
Time Travel Queries
Query your agent's data as it existed at any moment in the past — without maintaining a separate history table, changelog, or audit log. SapixDB's as_of query makes this possible at zero extra storage cost.
timestamp_hlc (a Hybrid Logical Clock value) set at write time. as_of is simply a filter: return records where timestamp_hlc ≤ your_timestamp. No separate history table. No changelog to maintain. No additional writes.How as_of works
When a record is written to SapixDB, the agent stamps it with a timestamp_hlc value derived from the wall clock at the time of the write. This value is immutable — it never changes after the record is created.
An as_of query provides a cutoff HLC. The query engine returns every record whose timestamp_hlc is less than or equal to that cutoff — which is exactly the set of records that existed at that moment in time. Records written after the cutoff are invisible to the query.
{
"type": "as_of",
"timestamp_hlc": 109870428282265600,
"limit": 100
}| Parameter | Type | Required | Description |
|---|---|---|---|
type | string | yes | Always "as_of" |
timestamp_hlc | uint64 | yes | HLC cutoff. Records with timestamp_hlc ≤ this value are returned. |
limit | integer | no | Maximum records to return (default 100) |
select | string[] | no | Field projection — return only named fields in each payload |
filter | object | no | Additional filter applied after the as_of cutoff |
timestamp_hlc. Using ts_hlc will cause a validation error.HLC timestamps
SapixDB uses Hybrid Logical Clocks. An HLC value is a 64-bit unsigned integer. The upper 48 bits are the Unix timestamp in milliseconds; the lower 16 bits are a logical counter used to break ties when multiple writes happen within the same millisecond.
Converting wall time to HLC
To convert a wall-clock moment to an HLC value suitable for as_of, multiply Unix milliseconds by 65536 (= 2^16). This sets the logical counter to zero, meaning the result is the earliest possible HLC for that millisecond — ensuring all records written at or before that moment are included.
import time # Current moment as HLC now_hlc = int(time.time() * 1000) * 65536 print(now_hlc) # e.g. 109870428282265600 # A specific past moment import datetime dt = datetime.datetime(2026, 6, 1, 0, 0, 0, tzinfo=datetime.timezone.utc) past_hlc = int(dt.timestamp() * 1000) * 65536 print(past_hlc)
// Current moment
const nowHlc = BigInt(Date.now()) * 65536n;
// A specific past moment
const dt = new Date("2026-06-01T00:00:00Z");
const pastHlc = BigInt(dt.getTime()) * 65536n;Decoding an HLC back to wall time
hlc = 109870428282265600 # Extract milliseconds (shift right 16 bits) unix_ms = hlc >> 16 # bit shift unix_ms2 = hlc // 65536 # integer division — same result import datetime wall = datetime.datetime.fromtimestamp(unix_ms / 1000, tz=datetime.timezone.utc) print(wall) # 2026-06-27T12:34:56+00:00
Step-by-step: write → capture → write → query
The canonical pattern for testing time travel: write record A, capture the HLC at that moment, write record B, then run as_of with the midpoint. Only record A should appear.
# Step 1 — Write record A
HASH_A=$(curl -s -X POST http://localhost:7475/v1/agents/inventory/records/json \
-H "Content-Type: application/json" \
-H "Authorization: Bearer spx_root_YOUR_ROOT_KEY" \
-d '{"data": {"item": "widget", "qty": 100}}' \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d['content_hash'])")
echo "Wrote A: $HASH_A"
# Step 2 — Capture HLC between A and B
MIDPOINT_HLC=$(python3 -c "import time; print(int(time.time()*1000)*65536)")
echo "Midpoint HLC: $MIDPOINT_HLC"
# Step 3 — Write record B (after the midpoint)
curl -s -X POST http://localhost:7475/v1/agents/inventory/records/json \
-H "Content-Type: application/json" \
-H "Authorization: Bearer spx_root_YOUR_ROOT_KEY" \
-d '{"data": {"item": "widget", "qty": 75, "note": "sold 25 units"}}' \
| python3 -m json.tool
# Step 4 — as_of MIDPOINT — only record A is visible
curl -s -X POST http://localhost:7475/v1/agents/inventory/query \
-H "Content-Type: application/json" \
-H "Authorization: Bearer spx_root_YOUR_ROOT_KEY" \
-d "{\"type\": \"as_of\", \"timestamp_hlc\": $MIDPOINT_HLC, \"limit\": 100}" \
| python3 -m json.toolThe response to step 4 will contain only the record written in step 1. The updated record from step 3 is invisible because its timestamp_hlc is greater than the midpoint.
as_of vs time_range
| Query type | What it returns | Typical use |
|---|---|---|
as_of | All records with timestamp_hlc ≤ cutoff — a snapshot of the entire agent state at that moment | Compliance snapshot, AI replay, point-in-time restore |
time_range | Records written between a start and end HLC — a window of writes | Audit log of changes in a period, incremental sync, recent activity feed |
Production patterns
Daily compliance snapshot
Regulators often require that you can reproduce exactly what data you held at the end of a business day. Compute the HLC for 23:59:59 UTC of the target date and run as_of. No snapshot job. No S3 export. Query on demand.
import datetime
def end_of_day_hlc(year: int, month: int, day: int) -> int:
dt = datetime.datetime(year, month, day, 23, 59, 59, 999000,
tzinfo=datetime.timezone.utc)
return int(dt.timestamp() * 1000) * 65536
cutoff = end_of_day_hlc(2026, 6, 30)
print(f"End-of-day HLC for 2026-06-30: {cutoff}")CUTOFF=$(python3 -c "
import datetime
dt = datetime.datetime(2026, 6, 30, 23, 59, 59, 999000, tzinfo=datetime.timezone.utc)
print(int(dt.timestamp() * 1000) * 65536)
")
curl -s -X POST http://localhost:7475/v1/agents/customers/query \
-H "Content-Type: application/json" \
-H "Authorization: Bearer spx_root_YOUR_ROOT_KEY" \
-d "{"type": "as_of", "timestamp_hlc": $CUTOFF, "limit": 10000}" \
| python3 -m json.toolReplaying what an AI agent saw at decision time
When an AI agent makes a decision, record the HLC at the moment of that decision alongside the output. Later, you can replay exactly the data context the agent had — essential for debugging incorrect decisions or satisfying audit requirements.
import time, json, requests
# Before querying context
decided_at_hlc = int(time.time() * 1000) * 65536
# Fetch context the agent actually saw
context = requests.post(
"http://localhost:7475/v1/agents/customers/query",
json={"type": "as_of", "timestamp_hlc": decided_at_hlc, "limit": 100},
headers={"Authorization": "Bearer spx_root_YOUR_ROOT_KEY"},
).json()
decision = run_ai_model(context["records"])
# Store decision with the HLC so it can be replayed
requests.post(
"http://localhost:7475/v1/agents/decisions/records/json",
json={
"data": {
"decision": decision,
"decided_at_hlc": decided_at_hlc,
"context_count": context["count"],
}
},
headers={"Authorization": "Bearer spx_root_YOUR_ROOT_KEY"},
)# Retrieve the stored decision
decision_record = ... # fetch from decisions agent
# Replay: get exactly the same context the agent saw
replay_context = requests.post(
"http://localhost:7475/v1/agents/customers/query",
json={
"type": "as_of",
"timestamp_hlc": decision_record["decided_at_hlc"],
"limit": 100,
},
headers={"Authorization": "Bearer spx_root_YOUR_ROOT_KEY"},
).json()
# replay_context["records"] is identical to what the agent sawCombining as_of with filter and select
as_of supports both filter and select. The HLC cutoff is applied first, then the filter narrows the result set, then projection strips fields.
CUTOFF=$(python3 -c "
import datetime
dt = datetime.datetime(2026, 6, 1, 0, 0, 0, tzinfo=datetime.timezone.utc)
print(int(dt.timestamp() * 1000) * 65536)
")
curl -s -X POST http://localhost:7475/v1/agents/users/query \
-H "Content-Type: application/json" \
-H "Authorization: Bearer spx_root_YOUR_ROOT_KEY" \
-d "{
\"type\": \"as_of\",
\"timestamp_hlc\": $CUTOFF,
\"limit\": 1000,
\"select\": [\"user_id\", \"email\"],
\"filter\": {
\"field\": \"plan\",
\"op\": \"eq\",
\"value\": \"enterprise\"
}
}" | python3 -m json.tool