Temporal Event Tracking
Give your agent a sense of time. Store events as they happen and answer temporal questions — "what changed last week?", "when did this start?", "what happened between March and May?" — with precision.
- Running Dakera server (Quickstart guide)
- Understanding of Dakera's hybrid retrieval and temporal inference engine
- Familiarity with ISO 8601 date formats
The Problem
Users ask time-anchored questions constantly: "What happened to my portfolio last quarter?", "When did you say BTC would cross $100k?", "What trades have I made since March?" Pure vector search has no concept of time — it returns results by semantic similarity regardless of when events occurred. A query for "portfolio changes" returns all portfolio-related memories equally, whether they happened yesterday or 18 months ago.
Adding dates to a relational database and running SQL queries is brittle and requires structured schemas. What you need is a flexible event log that stores unstructured financial events and lets you query them with natural language temporal expressions: "last quarter," "the week Bitcoin dropped," "before the rate hike."
Dakera's retrieval engine runs a temporal inference layer over your query. When you ask for "events last month," it resolves this relative expression against the current timestamp, adjusts the BM25 scoring to weight recency-matched memories, and uses the vector component to find semantically relevant matches within that time window. The result is temporal precision without requiring you to write date-range filters.
Architecture: Event Timeline with Query Windows
Events are stored with ISO 8601 timestamps embedded in the memory content itself — not just in metadata. This makes the timestamp searchable by the BM25 component. The temporal inference engine then handles relative time resolution, so both "2026-01-15" and "three months ago" work as query terms.
Step-by-Step Implementation
-
Embed the ISO date in the memory content stringDo not just put the date in metadata. Include it in the content text itself: "On 2026-03-21, sold 0.2 BTC at $112,000." This makes the date searchable by BM25 keyword matching. Dakera's temporal inference then also resolves relative time expressions against this embedded date.
-
Classify event types with tagsTag each event with a category:
trade,dividend,rebalance,alert,price-target. This enables precision retrieval: "all trades in the last quarter" vs. "all dividend events in 2025." Tags are free-text and you can define your own taxonomy. -
Assign importance based on event significanceNot all events are equal. A large portfolio rebalance deserves importance 0.9; a minor dividend receipt is 0.6. High-importance events surface first in general queries. For time-window queries, all events within the window are candidates, so importance acts as a secondary ranking signal.
-
Use semantic temporal queries for recallWrite queries as you would speak them: "major portfolio changes and trades in the last 90 days," "risk events around the March interest rate announcement," "all Bitcoin transactions since I opened the account." Dakera's temporal inference engine resolves these into scored recall results.
-
Archive old events with reduced importanceEvents older than 1 year should have their importance reduced via
update_importanceso they do not crowd out recent events in general queries. For specific historical queries ("what did I do in 2025?"), the explicit date in the content will still match them precisely.
Implementation
# Store a portfolio event with embedded date
curl -X POST http://localhost:3300/v1/memory/store -H "Authorization: Bearer dk-..." -H "Content-Type: application/json" -d '{
"agent_id": "portfolio-alice",
"content": "On 2026-01-15, bought 0.5 BTC at $94,200. Total BTC position: 0.8 BTC.",
"importance": 0.88,
"memory_type": "episodic",
"tags": ["event", "trade", "crypto", "buy"]
}'
curl -X POST http://localhost:3300/v1/memory/store -H "Authorization: Bearer dk-..." -H "Content-Type: application/json" -d '{
"agent_id": "portfolio-alice",
"content": "On 2026-03-21, BTC hit $112,000. Sold 0.2 BTC at $112,000 (profit: $3,560). Remaining: 0.6 BTC.",
"importance": 0.92,
"memory_type": "episodic",
"tags": ["event", "trade", "crypto", "sell", "profit"]
}'
curl -X POST http://localhost:3300/v1/memory/store -H "Authorization: Bearer dk-..." -H "Content-Type: application/json" -d '{
"agent_id": "portfolio-alice",
"content": "On 2026-04-10, TSLA missed earnings by 18%. Sold all 15 TSLA shares at $148 (entry: $196, loss: -$720).",
"importance": 0.90,
"memory_type": "episodic",
"tags": ["event", "trade", "equity", "sell", "loss"]
}'
# Temporal query: events in the last 3 months
curl "http://localhost:3300/v1/memory/recall?agent_id=portfolio-alice&query=portfolio+trades+and+events+in+the+last+3+months&top_k=20&min_importance=0.6" -H "Authorization: Bearer dk-..."
# Targeted query: only losses
curl "http://localhost:3300/v1/memory/recall?agent_id=portfolio-alice&query=losing+trades+and+portfolio+losses+this+year&top_k=10" -H "Authorization: Bearer dk-..."from dakera import DakeraClient
from datetime import datetime, date
from typing import Optional
client = DakeraClient(base_url="http://localhost:3300", api_key="dk-...")
class PortfolioEventTracker:
"""Financial event tracker using Dakera temporal memory."""
IMPORTANCE_BY_TYPE = {
"major_trade": 0.92,
"rebalance": 0.88,
"minor_trade": 0.78,
"dividend": 0.65,
"alert": 0.70,
"price_target": 0.75,
}
def __init__(self, user_id: str):
self.agent_id = f"portfolio-{user_id}"
def track_event(
self,
event_description: str,
event_type: str,
event_date: Optional[date] = None,
importance: Optional[float] = None,
tags: Optional[list] = None
) -> dict:
"""
Store a portfolio event with embedded date.
Always embeds the ISO date in the content so BM25 can match it.
"""
dt = event_date or date.today()
date_str = dt.strftime("%Y-%m-%d")
# Embed date in content for BM25 matching
content = f"On {date_str}, {event_description}"
base_tags = ["event", event_type.replace("_", "-")]
if tags:
base_tags.extend(tags)
return client.store_memory(
agent_id=self.agent_id,
content=content,
memory_type="episodic",
importance=importance or self.IMPORTANCE_BY_TYPE.get(event_type, 0.75),
tags=base_tags
)
def query_events(self, query: str, top_k: int = 20) -> list[dict]:
"""Natural language temporal query."""
results = client.recall(
agent_id=self.agent_id,
query=query,
top_k=top_k,
min_importance=0.5
)
return results.get("memories", [])
def get_recent_summary(self, period: str = "last 3 months") -> str:
"""Build a portfolio summary for a time period."""
events = self.query_events(
f"all portfolio trades, dividends, and significant events in the {period}",
top_k=30
)
if not events:
return f"No events found for {period}."
lines = [f"Portfolio events ({period}):"]
for e in events:
lines.append(f" - {e['content']}")
return "
".join(lines)
# Track events as they happen
tracker = PortfolioEventTracker("alice")
tracker.track_event(
"bought 0.5 BTC at $94,200. Total BTC position: 0.8 BTC.",
event_type="major_trade",
event_date=date(2026, 1, 15),
tags=["crypto", "buy"]
)
tracker.track_event(
"BTC hit $112,000. Sold 0.2 BTC at $112,000 (profit: $3,560). Remaining: 0.6 BTC.",
event_type="major_trade",
event_date=date(2026, 3, 21),
tags=["crypto", "sell", "profit"]
)
tracker.track_event(
"TSLA missed earnings by 18%. Sold all 15 TSLA shares at $148 (entry: $196, loss: -$720).",
event_type="major_trade",
event_date=date(2026, 4, 10),
tags=["equity", "sell", "loss", "earnings"]
)
tracker.track_event(
"received AAPL dividend: $23.40 (10 shares x $2.34/share).",
event_type="dividend",
event_date=date(2026, 2, 10),
tags=["equity", "income"]
)
# Natural language temporal queries
print(tracker.get_recent_summary("last 3 months"))
# User asks: "How did my crypto do this year?"
crypto_events = tracker.query_events("cryptocurrency Bitcoin BTC trades performance this year")
for e in crypto_events:
print(e["content"])import { DakeraClient } from '@dakera-ai/dakera';
const client = new DakeraClient({ baseUrl: 'http://localhost:3300', apiKey: 'dk-...' });
interface PortfolioEvent {
description: string;
eventType: 'major_trade' | 'rebalance' | 'minor_trade' | 'dividend' | 'alert';
eventDate?: Date;
importance?: number;
tags?: string[];
}
const IMPORTANCE_MAP: Record<string, number> = {
major_trade: 0.92,
rebalance: 0.88,
minor_trade: 0.78,
dividend: 0.65,
alert: 0.70,
};
class PortfolioEventTracker {
private agentId: string;
constructor(userId: string) {
this.agentId = `portfolio-${userId}`;
}
async trackEvent(event: PortfolioEvent): Promise<void> {
const dt = event.eventDate ?? new Date();
const dateStr = dt.toISOString().split('T')[0]; // YYYY-MM-DD
const content = `On ${dateStr}, ${event.description}`;
const baseTags = ['event', event.eventType.replace('_', '-')];
const allTags = event.tags ? [...baseTags, ...event.tags] : baseTags;
await client.storeMemory(this.agentId, {
content,
memoryType: 'episodic',
importance: event.importance ?? IMPORTANCE_MAP[event.eventType] ?? 0.75,
tags: allTags,
});
}
async queryEvents(query: string, topK = 20): Promise<Array<{content: string}>> {
const results = await client.recall(this.agentId, query, {
top_k: topK,
min_importance: 0.5,
});
return results.memories;
}
async getPortfolioSummary(period: string): Promise<string> {
const events = await this.queryEvents(
`all portfolio trades, dividends, significant events in the ${period}`,
30
);
if (!events.length) return `No events found for ${period}.`;
return events.map(e => `- ${e.content}`).join('
');
}
}
// Usage
const tracker = new PortfolioEventTracker('alice');
await tracker.trackEvent({
description: 'bought 0.5 BTC at $94,200. Total BTC: 0.8 BTC.',
eventType: 'major_trade',
eventDate: new Date('2026-01-15'),
tags: ['crypto', 'buy'],
});
await tracker.trackEvent({
description: 'BTC hit $112,000. Sold 0.2 BTC (profit: $3,560). Remaining: 0.6 BTC.',
eventType: 'major_trade',
eventDate: new Date('2026-03-21'),
tags: ['crypto', 'sell', 'profit'],
});
await tracker.trackEvent({
description: 'TSLA missed earnings by 18%. Sold 15 shares at $148 (loss: -$720).',
eventType: 'major_trade',
eventDate: new Date('2026-04-10'),
tags: ['equity', 'sell', 'loss'],
});
// Temporal query
const summary = await tracker.getPortfolioSummary('last 3 months');
console.log(summary);use dakera_rs::{Client, StoreMemoryRequest, RecallRequest};
use chrono::NaiveDate;
let client = Client::new("http://localhost:3300", "dk-...");
// Helper: format event with embedded ISO date
fn make_event_content(date: NaiveDate, description: &str) -> String {
format!("On {}, {}", date.format("%Y-%m-%d"), description)
}
let agent_id = "portfolio-alice";
// Track BTC buy
client.store_memory(agent_id, StoreMemoryRequest {
content: make_event_content(
NaiveDate::from_ymd_opt(2026, 1, 15).unwrap(),
"bought 0.5 BTC at $94,200. Total BTC position: 0.8 BTC."
),
memory_type: "episodic".into(),
importance: Some(0.88),
tags: vec!["event".into(), "trade".into(), "crypto".into(), "buy".into()],
..Default::default()
}).await?;
// Track BTC sell at peak
client.store_memory(agent_id, StoreMemoryRequest {
content: make_event_content(
NaiveDate::from_ymd_opt(2026, 3, 21).unwrap(),
"BTC hit $112,000. Sold 0.2 BTC (profit: $3,560). Remaining: 0.6 BTC."
),
memory_type: "episodic".into(),
importance: Some(0.92),
tags: vec!["event".into(), "trade".into(), "crypto".into(), "sell".into(), "profit".into()],
..Default::default()
}).await?;
// Temporal query
let events = client.recall(agent_id, RecallRequest {
query: "cryptocurrency trades and portfolio performance last 3 months".into(),
top_k: Some(20),
min_importance: Some(0.5),
..Default::default()
}).await?;
for event in &events.memories {
println!("{}", event.content);
}package main
import (
"context"
"fmt"
"time"
dakera "github.com/dakera-ai/dakera-go"
)
func trackPortfolioEvent(
ctx context.Context,
client *dakera.Client,
agentID string,
eventDate time.Time,
description string,
importance float64,
tags []string,
) {
dateStr := eventDate.Format("2006-01-02") // ISO 8601
content := fmt.Sprintf("On %s, %s", dateStr, description)
allTags := append([]string{"event"}, tags...)
client.StoreMemory(ctx, agentID, dakera.StoreMemoryRequest{
Content: content,
MemoryType: "episodic",
Importance: importance,
Tags: allTags,
})
}
func main() {
client := dakera.NewClient("http://localhost:3300", "dk-...")
ctx := context.Background()
agentID := "portfolio-alice"
trackPortfolioEvent(ctx, client, agentID,
time.Date(2026, 1, 15, 0, 0, 0, 0, time.UTC),
"bought 0.5 BTC at $94,200. Total BTC: 0.8 BTC.",
0.88, []string{"trade", "crypto", "buy"})
trackPortfolioEvent(ctx, client, agentID,
time.Date(2026, 3, 21, 0, 0, 0, 0, time.UTC),
"BTC hit $112,000. Sold 0.2 BTC (profit: $3,560). Remaining: 0.6 BTC.",
0.92, []string{"trade", "crypto", "sell", "profit"})
trackPortfolioEvent(ctx, client, agentID,
time.Date(2026, 4, 10, 0, 0, 0, 0, time.UTC),
"TSLA missed earnings by 18%. Sold 15 shares at $148 (loss: -$720).",
0.90, []string{"trade", "equity", "sell", "loss"})
// Temporal query
events, _ := client.Recall(ctx, agentID, dakera.RecallRequest{
Query: "cryptocurrency and equity trades last 3 months",
TopK: 20,
})
for _, e := range events.Memories {
fmt.Println(e.Content)
}
}Build an AI that actually understands time.
Temporal inference + hybrid retrieval in one API. No date filters to maintain.
Before & After: Event Store State
The left shows a naive approach with dates only in metadata (not searchable by BM25). The right shows the correct pattern with embedded ISO dates and event tags.
{
// Date is hidden in metadata only.
// BM25 cannot match it.
// "last 3 months" query fails.
"content": "Bought Bitcoin",
"metadata": {
"date": "2026-01-15",
"amount": 0.5
},
"tags": ["trade"]
// Query "January trades" returns
// random semantic matches instead
// of the actual Jan 15 event.
}
{
// ISO date in content = BM25 matches.
// Temporal inference resolves
// "last 3 months" against today.
"content": "On 2026-01-15, bought 0.5
BTC at $94,200. Total BTC: 0.8 BTC.",
"importance": 0.88,
"memory_type": "episodic",
"tags": ["event","trade","crypto","buy"],
// Query "crypto trades last quarter"
// returns this memory precisely.
// Also matches "January Bitcoin buy".
}
Real-World Example: Financial Assistant Tracking Portfolio Events
Scenario: Apex Finance AI is a personal financial assistant that helps retail investors track their portfolio. The core differentiator: users can ask natural language questions about their portfolio history and get accurate, time-anchored answers. The assistant must handle questions like "How did I do in Q1?", "What was my worst trade this year?", and "When did I first buy Bitcoin?"
A Typical User Session (May 2026)
User asks: "Can you summarize my portfolio activity in the first quarter of 2026?"
The agent fires a recall: query = "portfolio trades and events January February March 2026 Q1", top_k=30. Dakera's temporal inference resolves "Q1 2026" to the January–March window and returns 8 memories matching this period.
Response synthesized from recalled events: "In Q1 2026: You opened a BTC position (0.5 BTC at $94.2k on Jan 15), received an AAPL dividend ($23.40 on Feb 10), and made your best trade of the year — selling 0.2 BTC at $112k for a $3,560 profit on March 21. Net portfolio change: +$4,100."
No SQL queries. No date filters. No structured schema. All from natural language event memories with embedded dates and Dakera's temporal inference engine. The user rates the interaction 5 stars and refers two colleagues.
Embed the date in two formats for maximum BM25 coverage: ISO format ("2026-01-15") and human-readable ("January 15, 2026"). Users might query "January Bitcoin buy" (matches human-readable) or "2026-01 trades" (matches ISO). Example content: "On 2026-01-15 (January 15, 2026), bought 0.5 BTC at $94,200." The extra 20 characters is worth the retrieval precision gain.
Temporal Query Window: How Time-Bounded Retrieval Works
This diagram shows how temporal queries operate across a sliding event window, retrieving only events within a specific time range for time-accurate agent responses.
Performance Characteristics
Temporal queries perform identically to standard recall at scale — the temporal inference layer adds less than 2 ms overhead. At 10,000+ events per user, consider archiving events older than 2 years by reducing their importance to 0.2–0.3. They remain queryable via explicit date content but will not surface in general period queries.
Edge Cases & Developer Gotchas
The most common mistake is storing the event date only in the metadata field and relying on vector similarity for time-based retrieval. Vector similarity has no concept of when — "bought Bitcoin in January" and "bought Bitcoin in October" are nearly identical vectors. Solution: Always embed the ISO date ("2026-01-15") in the content string. BM25 will then match year-month queries against it.
"Last month" means different things on January 1st vs. January 31st. Dakera resolves relative expressions against the server timestamp at query time, which may not match the user's timezone. Solution: For financial applications where date precision matters, append the absolute date range to the query: "trades last month (2026-04-01 to 2026-04-30)." This anchors the BM25 matching precisely.
If you store every tick or every minute-level price alert, a query for "last week" may return 10,000 events. Even with top_k=20, the most relevant events may be buried under noise. Solution: Throttle storage: only store events meeting a significance threshold (price moved more than 5%, trade executed, threshold breached). Use importance scores to rank signal above noise.
Network failures may cause your event storage code to retry a store_memory call, creating duplicate memories ("bought BTC" stored twice). This corrupts temporal queries: "show my BTC trades" now shows duplicates, throwing off calculations. Solution: Implement idempotent storage: hash the event content + date, store the hash in your database, and check before calling store_memory.
If you pre-store scheduled events (a dividend payment due next month, a bond maturity date), these future dates will surface in "recent" queries because the BM25 matching will include the date string. Solution: Tag forward-looking events as scheduled and use a lower importance score (0.4–0.6). Alternatively, store scheduled events in a separate agent ID like portfolio-alice-schedule.
SDK Reference
| Operation | Python | TypeScript | Purpose |
|---|---|---|---|
| Store event | client.store_memory(agent_id, content, importance, memory_type, tags) |
client.storeMemory(agentId, {content, importance, memoryType, tags}) |
Persist a timestamped event |
| Temporal query | client.recall(agent_id, query, top_k, min_importance) |
client.recall(agentId, query, {top_k, min_importance}) |
Natural language time-window query |
| Keyword event search | client.search_memories(agent_id, query, top_k) |
client.searchMemories(agentId, query, {top_k}) |
BM25 search for specific dates/symbols |
| Bulk event queries | client.batch_recall(request) |
client.batchRecall(request) |
Fan out multiple period queries at once |
| Archive old event | client.update_importance(agent_id, memory_id, importance) |
client.updateImportance(agentId, request) |
Reduce importance to archive old events |
| Correct event error | client.update_memory(agent_id, memory_id, ...) |
client.updateMemory(agentId, memoryId, request) |
Fix an incorrectly recorded event |
Advanced Configuration
Batch Ingestion of Historical Events
When onboarding a new user with years of transaction history, use Batch Ingestion to import events efficiently:
from dakera import DakeraClient
client = DakeraClient(base_url="http://localhost:3300", api_key="dk-...")
# Import 2 years of trade history (from broker export)
historical_trades = load_trades_from_csv("trades_2024_2025.csv")
for batch in chunked(historical_trades, 50):
for trade in batch:
client.store_memory(
agent_id="portfolio-alice",
content=f"On {trade['date']}, {trade['description']}",
memory_type="episodic",
importance=0.70, # lower for historical data
tags=["event", "historical", trade["type"]]
)
Multi-Portfolio Batch Recall
Compare events across multiple portfolios or time periods simultaneously:
results = client.batch_recall({
"queries": [
{"agent_id": "portfolio-alice", "query": "Q1 2026 trades and performance", "top_k": 15},
{"agent_id": "portfolio-alice", "query": "Q2 2026 trades and performance", "top_k": 15},
{"agent_id": "portfolio-alice", "query": "biggest losses all time", "top_k": 5}
]
})
# Returns Q1, Q2, and top losses simultaneously
Event Importance Decay Schedule
Run a nightly job to automatically reduce importance on events older than 1 year:
# Nightly archival job
old_events = client.recall(
agent_id="portfolio-alice",
query="all portfolio events older than 1 year",
top_k=100,
min_importance=0.6
)
for event in old_events["memories"]:
if event_is_over_1_year(event):
client.update_importance(
agent_id="portfolio-alice",
memory_id=event["id"],
importance=0.30
)
When to Use This Pattern
- Financial assistants tracking trades, dividends, and price events
- Project management AIs tracking milestone and status changes over time
- Customer success tools tracking user lifecycle and usage events
- Personal health assistants tracking symptoms, medications, and appointments
- Any application where "when did X happen?" is a common query
- Audit trail systems requiring chronological event reconstruction
Build AI that answers "when did X happen?" perfectly
Temporal event tracking with natural language queries. No SQL schemas required.
Read the Quickstart → API Reference