StateUser Memory

User Memory

User memory is the application of Causet’s event-sourced state model to user-level history. Every action a user takes generates an event. Core rules accumulate those events into a structured, queryable memory on the user entity.

The Concert App Example

In the Jamlet concert discovery application, a user’s memory builds from every concert they attend, every show they like, and every artist they follow:

state:
  user:
    entity_key: user_id
    fields:
      - name: concert_count
        type: int
        default: 0
      - name: concerts_attended
        type: array
        item_type: object
        item_fields:
          show_id: { type: string, required: true }
          venue: { type: string, required: true }
          artist_id: { type: string, required: true }
          attended_at: { type: datetime, required: true }
        default: []
      - name: total_distance_traveled
        type: number
        default: 0
      - name: liked_show_ids
        type: array
        item_type: string
        default: []
      - name: followed_artist_ids
        type: array
        item_type: string
        default: []
      - name: favorite_venue
        type: string
        default: ""
      - name: preferred_genres
        type: array
        item_type: string
        default: []

Building User Memory via Core Rules

Recording Concert Attendance

core:
  rules:
    - name: record_concert
      when: {}
      then:
        - op: add
          path: /concert_count
          value: 1
        - op: push
          path: /concerts_attended
          value:
            show_id: intent.show_id
            venue: intent.venue
            artist_id: intent.artist_id
            attended_at: intent.attended_at
        - op: add
          path: /total_distance_traveled
          value: intent.distance_miles

This rule fires on every CONCERT_ATTENDED intent. After 48 concerts, the user has concert_count: 48, a full history in concerts_attended, and their cumulative travel distance.

Recording Liked Shows

core:
  rules:
    - name: record_show_like
      when:
        - path: /liked_show_ids
          op: not_contains
          value: intent.show_id
      then:
        - op: push
          path: /liked_show_ids
          value: intent.show_id

The not_contains guard prevents duplicate entries if a SHOW_LIKED intent is submitted more than once for the same show.

Recording Artist Follows

core:
  rules:
    - name: record_artist_follow
      when:
        - path: /followed_artist_ids
          op: not_contains
          value: intent.artist_id
      then:
        - op: push
          path: /followed_artist_ids
          value: intent.artist_id

Querying User Memory

Entity Snapshot (per-user)

Access a specific user’s full memory via the entity snapshot:

const user = await causet.getEntity("user", "user_abc123");
 
console.log(`Concerts attended: ${user.concert_count}`);
console.log(`Total distance: ${user.total_distance_traveled} miles`);
console.log(`Favorite venue: ${user.favorite_venue}`);
console.log(`Artists followed: ${user.followed_artist_ids.length}`);

CLI Entity Inspection

Useful for debugging and support tooling:

causet inspect entity user_abc123 --fork main --stream user

HTTP Entity Endpoint

The causet-query-service exposes an HTTP endpoint for entity queries:

GET /v1/entities/user/user_abc123

Projecting User Memory for Aggregate Queries

Per-user memory is efficient for single-user reads. For aggregate queries across users, materialize the relevant fields into projection tables.

Artist Affinity Projection

projections:
  user_artist_affinity:
    source_events: [CONCERT_ATTENDED]
    target:
      table: user_artist_affinity
      primary_key: [user_id, artist_id]
    fields:
      user_id: TEXT
      artist_id: TEXT
      concert_count: BIGINT
    derive:
      user_id: event.entity_id
      artist_id: event.payload.artist_id
    aggregates:
      CONCERT_ATTENDED:
        concert_count: { op: add, by: 1, floor: 0 }

Query: all users who have seen Pearl Jam more than 3 times:

SELECT user_id, concert_count
FROM user_artist_affinity
WHERE artist_id = 'artist_pearl_jam'
  AND concert_count > 3
ORDER BY concert_count DESC;

Venue Affinity Projection

projections:
  user_venue_affinity:
    source_events: [CONCERT_ATTENDED]
    target:
      table: user_venue_affinity
      primary_key: [user_id, venue]
    fields:
      user_id: TEXT
      venue: TEXT
      visit_count: BIGINT
    derive:
      user_id: event.entity_id
      venue: event.payload.venue
    aggregates:
      CONCERT_ATTENDED:
        visit_count: { op: add, by: 1, floor: 0 }

User Memory for AI Features

User memory provides structured, auditable context for AI-driven features. Unlike raw event logs, entity state is already aggregated and typed — it can be passed directly to an LLM or recommendation model.

Example: generating a personalized concert recommendation prompt using user memory:

const user = await causet.getEntity("user", userId);
 
const context = {
  concerts_attended: user.concert_count,
  top_artists: user.concerts_attended
    .reduce((acc, c) => {
      acc[c.artist_id] = (acc[c.artist_id] || 0) + 1;
      return acc;
    }, {} as Record<string, number>),
  total_distance_miles: user.total_distance_traveled,
  favorite_venue: user.favorite_venue,
};
 
const prompt = `
  User has attended ${context.concerts_attended} concerts.
  Most-seen artists: ${Object.entries(context.top_artists)
    .sort(([, a], [, b]) => b - a)
    .slice(0, 3)
    .map(([id, count]) => `${id} (${count}x)`)
    .join(", ")}.
  Favorite venue: ${context.favorite_venue}.
  Recommend shows that match this profile.
`;

The key advantage: this context is grounded in the ledger. It is not synthesized or hallucinated — every data point corresponds to a committed event with an audit trail.

Privacy and PII

User memory is personal data. Each field in the user state definition that tracks individual behavior is potentially PII under GDPR, CCPA, or equivalent regulations.

Considerations:

  • concerts_attended contains location data (venue) and behavioral history
  • total_distance_traveled reveals mobility patterns
  • followed_artist_ids reveals cultural preferences

The append-only ledger makes hard deletion complex. If you need to support right-to-erasure:

  • Define a USER_MEMORY_CLEARED event that resets all memory fields to their defaults
  • Implement a projection that suppresses deleted users from query results
  • Consult your legal team on whether ledger retention requires a separate redaction mechanism

See Security: PII and Multitenancy for Causet’s tenant isolation and data handling model.