Application Memory
Application memory is entity state that builds up incrementally over time. It is not a separate system — it is entity state where the fields accumulate history rather than representing current workflow status.
What Application Memory Looks Like
Consider a user in a concert attendance application. After using the app for two years:
- Has attended 48 concerts
- Has seen Pearl Jam 7 times (most-attended artist)
- Has traveled 1,200 miles total to shows
- Favorite venue is The Stone Pony (most-visited)
- Most common genre is indie rock
- Typically attends on Friday and Saturday evenings
None of these values were set manually. Each was derived from events as they were committed to the ledger.
Defining Memory Fields
Memory fields are ordinary entity state fields. The difference is in how core rules update them — via accumulation operations (add, push, merge) rather than point-in-time set operations.
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: artist_play_counts
type: array
item_type: object
item_fields:
artist_id: { type: string, required: true }
play_count: { type: int, required: true }
default: []
- name: favorite_venue
type: string
default: ""
- name: venue_visit_counts
type: array
item_type: object
item_fields:
venue_id: { type: string, required: true }
visit_count: { type: int, required: true }
default: []Building Memory via Core Rules
Each time a CONCERT_ATTENDED event is processed, core rules fire and update the accumulating fields:
core:
rules:
- name: increment_concert_count
when: {}
then:
- op: add
path: /concert_count
value: 1
- name: record_concert
when: {}
then:
- op: push
path: /concerts_attended
value:
show_id: intent.show_id
venue: intent.venue
artist_id: intent.artist_id
attended_at: intent.attended_at
- name: accumulate_distance
when:
- path: intent.distance_miles
op: gt
value: 0
then:
- op: add
path: /total_distance_traveled
value: intent.distance_miles
- name: update_venue_visits
when: {}
then:
- op: find
path: /venue_visit_counts
where: "item.venue_id == intent.venue_id"
as: existing_venue
- op: if
expr: "existing_venue != null"
then:
- op: map
path: /venue_visit_counts
as: v
value: "v.venue_id == intent.venue_id ? { venue_id: v.venue_id, visit_count: v.visit_count + 1 } : v"
else:
- op: push
path: /venue_visit_counts
value:
venue_id: intent.venue_id
visit_count: 1There is no single op: upsert for arrays. “Find-or-append” on an array field is composed from find (locate an existing item), if (branch on whether it was found), and map or push (update or append). Verify the exact expression syntax for your compiler version — find’s where, if’s expr, and map’s value all take expression strings, but ternary support and the item/as-bound variable names should be confirmed against DSL: Operations before relying on this pattern.
These rules are deterministic: the same sequence of events always produces the same memory state.
Memory Is Just Accumulated Entity State
There is no separate “memory” API or storage system. Memory is entity state where:
- Fields have accumulator defaults (
0,[]) - Core rules use
add,push,merge,find/if/maprather than a singleupsert - The fields are designed to grow richer over time
The entity snapshot is the memory. Reading user memory is reading the entity snapshot:
const user = await causet.getEntity("user", userId);
console.log(user.concert_count); // 48
console.log(user.total_distance_traveled); // 1200
console.log(user.concerts_attended.length); // 48Projecting Memory for Efficient Queries
The full concerts_attended array on the user entity is useful for per-user queries, but it is not efficient for aggregate queries across all users (e.g., “which shows have the highest attendance?”).
Materialize specific memory fields into projection tables for efficient cross-entity queries:
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
play_count: BIGINT
derive:
user_id: event.entity_id
artist_id: event.payload.artist_id
aggregates:
CONCERT_ATTENDED:
play_count: { op: add, by: 1, floor: 0 }Now you can query which users have the highest affinity for a given artist:
SELECT user_id, play_count
FROM user_artist_affinity
WHERE artist_id = 'artist_pearl_jam'
ORDER BY play_count DESC
LIMIT 100;Memory Is Replayable
Because memory is built from deterministic core rules applied to ledger events, it can be rebuilt at any time:
- Add a new memory field → rebuild entity snapshots from ledger → new field backfilled
- Change accumulation logic → rebuild → all entities reflect new logic
- Auditor needs historical state → replay ledger up to timestamp T → state at T reconstructed
This is not true of manually maintained state, which has no replay mechanism.
Memory Powers Intelligent Features
Structured, event-sourced memory is a strong foundation for personalization and AI features:
- Recommendations: “You’ve seen Pearl Jam 7 times — there’s a show next Friday”
- Personalization: surface venues the user has visited before
- Context for AI: pass user memory as structured context to an LLM for grounded, non-hallucinated responses
- Behavioral scoring: calculate engagement scores from attendance frequency, distance traveled, and genre diversity
Each feature reads from the entity snapshot directly or from a materialized projection table. For unstructured text in LLM prompts, add vector memory on top of this structured state.
Privacy Considerations
User memory contains a detailed personal history. Consider:
- Which fields constitute PII under GDPR, CCPA, or your jurisdiction’s regulations
- Right-to-erasure: how to handle deletion of a user’s memory (cannot remove from append-only ledger without a separate redaction mechanism)
- Data minimization: only accumulate memory fields that serve a defined purpose
See the control plane documentation for data handling and compliance settings for your platform.