NewRelease 0.12.3 is out: Engine v0.12.3, Dashboard 0.5.2, Helm 0.12.3, the four SDKs, CLI 0.8.2 and MCP 0.12.3Release 0.12.3 is outSee what's new →
Intermediate Retrieval

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.

⏱ ~25 min to implement 📦 Requires: Dakera v0.12+
Start Building →
Prerequisites
  • 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."

How Dakera's Temporal Inference Works

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.

Workflow of storing events and asking about time. When an event happens, such as a trade, dividend or alert, your agent writes a memory whose text starts with the ISO date, for example On 2026-03-21, plus tags and importance, and stores it with store_memory() as an episodic memory in portfolio-alice; Dakera reads the event date from the text and BM25 can match it. A question like last 3 months goes to recall(); temporal inference resolves it against the current time, and BM25 plus vector search match dates and meaning within that window, with importance as a secondary signal, returning the events in that window.Workflow of storing events and asking about time. When an event happens, such as a trade, dividend or alert, your agent writes a memory whose text starts with the ISO date, for example On 2026-03-21, plus tags and importance, and stores it with store_memory() as an episodic memory in portfolio-alice; Dakera reads the event date from the text and BM25 can match it. A question like last 3 months goes to recall(); temporal inference resolves it against the current time, and BM25 plus vector search match dates and meaning within that window, with importance as a secondary signal, returning the events in that window.
The date lives in the memory text, where Dakera reads it at store time, so a temporal question can be matched against it.Full size ↗Full size ↗

Step-by-Step Implementation

  1. Embed the ISO date in the memory content string
    Do 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.
  2. Classify event types with tags
    Tag 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.
  3. Assign importance based on event significance
    Not 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.
  4. Use semantic temporal queries for recall
    Write 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 / until window when you need a hard cut-off.
  5. Archive old events with reduced importance
    Events older than 1 year should have their importance reduced to 0.3 via update_importance so 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.

Get Started →

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.

Before: Date Only in Metadata
{
  // 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.
}
After: Date Embedded in Content
{
  // 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.

Pro Tip: Double-Write Dates for Robustness

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.

Workflow of a Q1 2026 question. The user asks about Q1 2026 and the agent calls recall() with top_k=30 and a query that names January, February and March 2026. Dakera reads those dates and matches the events inside that window: 2026-01-15 bought 0.5 BTC, 2026-02-10 AAPL dividend and 2026-03-21 sold 0.2 BTC. The 2026-04-10 TSLA sale falls after Q1 and is not matched. The agent builds the Q1 summary from the Q1 events only.Workflow of a Q1 2026 question. The user asks about Q1 2026 and the agent calls recall() with top_k=30 and a query that names January, February and March 2026. Dakera reads those dates and matches the events inside that window: 2026-01-15 bought 0.5 BTC, 2026-02-10 AAPL dividend and 2026-03-21 sold 0.2 BTC. The 2026-04-10 TSLA sale falls after Q1 and is not matched. The agent builds the Q1 summary from the Q1 events only.
Asked about Q1 2026, the query names its months and only the January to March events feed the answer.Full size ↗Full size ↗

Performance Characteristics

7
Languages the date parser reads in queries and content (en, de, fr, es, it, pt, nl)
since / until
Recall's time-window filter, for a hard cut-off
_dakera_content_date
The event date Dakera records, in Unix seconds

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

Gotcha 1: Dates Only in Your Own Metadata Are Not Event Dates

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.

Gotcha 2: Ambiguous Relative Time in Queries

"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.

Gotcha 3: High-Frequency Events Flood Recall

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.

Gotcha 4: Duplicate Events on Retry

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.

Gotcha 5: Future-Dated Events Confuse "Recent" Queries

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

Try it live

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.