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 classifies every recall query. A temporal one (it asks "when", or names a period such as "last week" or "in March 2023") has its date expressions parsed and is scored with temporal weighting, so memories whose event date falls near that period rank higher. The event date of a memory is read from its content when it is stored (and recorded as _dakera_content_date). For a hard window, recall also takes a since / until filter. The result is temporal precision without date columns or SQL.
Architecture: Event Timeline with Query Windows
Events are stored with ISO 8601 dates written in the memory content itself, not just in your own metadata. Dakera reads the event date from the content when the memory is stored, and the date string is also searchable by keyword (BM25). Temporal inference handles the query side, 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 put the date only in your own metadata. Include it in the content text itself: "On 2026-03-21, sold 0.2 BTC at $112,000." Dakera reads event dates from the content when it stores the memory (recorded as
_dakera_content_date, which you may also set yourself as an integer in Unix seconds), and the date string is searchable by keyword. -
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 major trade gets importance 0.92 and a portfolio rebalance 0.88; a minor dividend receipt is 0.65. 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 parses the date expressions and scores the results; add a
since/untilwindow when you need a hard cut-off. -
Archive old events with reduced importanceEvents older than 1 year should have their importance reduced to 0.3 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 portfolio events with the date at the start of the content
curl -X POST http://localhost:3000/v1/memory/store \
-H "Authorization: Bearer $DAKERA_API_KEY" \
-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.92,
"memory_type": "episodic",
"tags": ["event", "trade", "crypto", "buy"]
}'
curl -X POST http://localhost:3000/v1/memory/store \
-H "Authorization: Bearer $DAKERA_API_KEY" \
-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:3000/v1/memory/store \
-H "Authorization: Bearer $DAKERA_API_KEY" \
-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.92,
"memory_type": "episodic",
"tags": ["event", "trade", "equity", "sell", "loss"]
}'
# Temporal query: events in the last 3 months
curl -X POST http://localhost:3000/v1/memory/recall \
-H "Authorization: Bearer $DAKERA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"agent_id": "portfolio-alice", "query": "portfolio trades and events in the last 3 months", "top_k": 20, "min_importance": 0.5}'
# Targeted query: only losses this year, narrowed by tag
curl -X POST http://localhost:3000/v1/memory/recall \
-H "Authorization: Bearer $DAKERA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"agent_id": "portfolio-alice", "query": "losing trades and portfolio losses this year", "tags": ["loss"], "top_k": 10}'from datetime import date
from typing import Optional
from dakera import DakeraClient
client = DakeraClient(base_url="http://localhost:3000", 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):
"""Store an event with its ISO date at the start of the content,
where Dakera reads the event date and keyword search can match it."""
dt = event_date or date.today()
content = f"On {dt.isoformat()}, {event_description}"
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=["event", event_type.replace("_", "-")] + (tags or []),
)
def query_events(self, query: str, top_k: int = 20):
"""Natural language temporal query."""
return client.recall(agent_id=self.agent_id, query=query, top_k=top_k,
min_importance=0.5).memories
def get_recent_summary(self, period: str = "last 3 months") -> str:
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}."
return "\n".join([f"Portfolio events ({period}):"] + [f" - {e.content}" for e in events])
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("received AAPL dividend: $23.40 (10 shares x $2.34/share).",
event_type="dividend", event_date=date(2026, 2, 10), tags=["equity", "income"])
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"])
print(tracker.get_recent_summary("last 3 months"))
# User asks: "How did my crypto do this year?"
for e in tracker.query_events("cryptocurrency Bitcoin BTC trades performance this year"):
print(e.content)import { DakeraClient } from '@dakera-ai/dakera';
const client = new DakeraClient({ baseUrl: 'http://localhost:3000', apiKey: 'dk-...' });
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(description: string, eventType: string, eventDate = new Date(), tags: string[] = []) {
const dateStr = eventDate.toISOString().split('T')[0]; // YYYY-MM-DD
await client.storeMemory(this.agentId, {
content: `On ${dateStr}, ${description}`,
memory_type: 'episodic',
importance: IMPORTANCE_MAP[eventType] ?? 0.75,
tags: ['event', eventType.replace('_', '-'), ...tags],
});
}
async queryEvents(query: string, topK = 20) {
const { memories } = await client.recall(this.agentId, query, { top_k: topK, min_importance: 0.5 });
return 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('\n');
}
}
const tracker = new PortfolioEventTracker('alice');
await tracker.trackEvent('bought 0.5 BTC at $94,200. Total BTC: 0.8 BTC.', 'major_trade', new Date('2026-01-15'), ['crypto', 'buy']);
await tracker.trackEvent('BTC hit $112,000. Sold 0.2 BTC (profit: $3,560). Remaining: 0.6 BTC.', 'major_trade', new Date('2026-03-21'), ['crypto', 'sell', 'profit']);
await tracker.trackEvent('TSLA missed earnings by 18%. Sold 15 shares at $148 (loss: -$720).', 'major_trade', new Date('2026-04-10'), ['equity', 'sell', 'loss']);
console.log(await tracker.getPortfolioSummary('last 3 months'));use chrono::NaiveDate;
use dakera_client::{DakeraClient, RecallRequest, StoreMemoryRequest};
let client = DakeraClient::builder("http://localhost:3000").api_key("dk-...").build()?;
let agent = "portfolio-alice";
// The ISO date goes at the start of the content
fn event_content(date: NaiveDate, description: &str) -> String {
format!("On {}, {}", date.format("%Y-%m-%d"), description)
}
for (date, description, tags) in [
(NaiveDate::from_ymd_opt(2026, 1, 15).unwrap(),
"bought 0.5 BTC at $94,200. Total BTC position: 0.8 BTC.", vec!["trade", "crypto", "buy"]),
(NaiveDate::from_ymd_opt(2026, 3, 21).unwrap(),
"BTC hit $112,000. Sold 0.2 BTC (profit: $3,560). Remaining: 0.6 BTC.", vec!["trade", "crypto", "sell"]),
] {
client.store_memory(
StoreMemoryRequest::new(agent, event_content(date, description))
.with_importance(0.92)
.with_tags(tags.into_iter().map(String::from).collect()),
).await?;
}
// Temporal query
let events = client.recall(
RecallRequest::new(agent, "cryptocurrency trades and portfolio performance last 3 months").with_top_k(20),
).await?;
for event in &events.memories {
println!("{}", event.content);
}func trackPortfolioEvent(ctx context.Context, client *dakera.Client, agentID string,
eventDate time.Time, description string, importance float32, tags []string) error {
// The ISO date goes at the start of the content
content := fmt.Sprintf("On %s, %s", eventDate.Format("2006-01-02"), description)
_, err := client.StoreMemory(ctx, agentID, dakera.StoreMemoryRequest{
Content: content,
Importance: &importance,
Tags: append([]string{"event"}, tags...),
})
return err
}
func main() {
ctx := context.Background()
client := dakera.NewClientWithOptions(dakera.ClientOptions{
BaseURL: "http://localhost:3000",
APIKey: os.Getenv("DAKERA_API_KEY"),
})
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.92, []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.92, []string{"trade", "equity", "sell", "loss"})
// Temporal query
events, err := client.Recall(ctx, agentID, dakera.RecallRequest{
Query: "cryptocurrency and equity trades last 3 months",
TopK: 20,
})
if err != nil {
log.Fatal(err)
}
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 the date only in a metadata field of your own, which Dakera does not read as an event date. The right shows the correct pattern with the ISO date in the content and event tags.
{
// Date is in a metadata field of your own.
// Not read as the event date, not
// matched by keyword search.
"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: Dakera reads it
// as the event date at store time,
// and keyword search matches it.
"content": "On 2026-01-15, bought 0.5
BTC at $94,200. Total BTC: 0.8 BTC.",
"importance": 0.92,
"memory_type": "episodic",
"tags": ["event","trade","crypto","buy"],
// Query "crypto trades in January 2026"
// finds this memory by its date.
}
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. The query names the months, which Dakera's date parser reads, so the January to March 2026 events rank at the top; for a hard cut-off the agent can add a since / until window for the quarter.
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 structured schema. All from natural language event memories with embedded dates and Dakera's temporal inference. 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 a question about a period is matched against the event dates, so the answer is built from the events of that period.
Performance Characteristics
A temporal query is an ordinary recall: there is no separate index to maintain. As an agent's event log grows, archive events older than 1 year by reducing their importance to 0.3. They remain queryable by their dates but no longer crowd 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. Dakera then reads it as the event date, and keyword search matches year-month queries against it.
"Last month" means different things on January 1st vs. January 31st, and in different time zones. Solution: For financial applications where date precision matters, put the absolute dates in the query ("trades last month (2026-04-01 to 2026-04-30)") or set the window yourself with recall's since / until.
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), their dates are near "now" too and can surface in "recent" queries. Solution: Tag forward-looking events as scheduled, give them a lower importance score (0.4–0.6) and filter them out by tag where needed. 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, memory_type, 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 |
| Filtered event search | client.search_memories(agent_id, query, memory_type, top_k) |
POST /v1/memory/search |
Search with tag and type filters for specific symbols |
| List events by filter | client.batch_recall(BatchRecallRequest(...)) |
client.batchRecall({agent_id, filter}) |
List events by tag or importance, with no query |
| Archive old event | client.update_importance(agent_id, memory_ids, importance) |
POST /v1/memory/importance |
Reduce importance to archive old events |
| Correct event error | client.update_memory(agent_id, memory_id, ...) |
PUT /v1/memory/update/{id} |
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:3000", 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"]]
)
Several Periods at Once
Compare time periods by sending one recall per period in parallel:
from concurrent.futures import ThreadPoolExecutor
queries = {
"q1": "trades and performance January February March 2026",
"q2": "trades and performance April May June 2026",
"losses": "biggest losses all time",
}
with ThreadPoolExecutor() as pool:
futures = {k: pool.submit(client.recall, agent_id="portfolio-alice", query=q, top_k=15)
for k, q in queries.items()}
results = {k: f.result().memories for k, f in futures.items()}
# Returns Q1, Q2, and top losses side by side
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.5,
)
stale = [e.id for e in old_events.memories if event_is_over_1_year(e)] # your date check
if stale:
client.update_importance(agent_id="portfolio-alice", memory_ids=stale, importance=0.3)
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
Run this pattern against a real Dakera.
The Playground executes live API calls in your browser. No account, no install. Then run it on your own server in five minutes.