Type it in, or summarise everything.
- A human keeps it current, until the week gets busy.
- Summarise everything, and an opened email sits beside a contract.
- No provenance. You cannot tell a fact from a guess.
worked example · crm
Avery takes ownership of a migration, asks for written updates, and gets a new role. Those ordinary CRM events become the current profile on the right—while a low-value email open stays out. Every line can be traced back to the event that supports it.
Simulation · example walkthrough. Avery Chen, the CRM events, and the derived profile are fictional examples that show the workflow step by step. No live CRM or customer record is being changed.
postgres + one worker · 5 keys · every slot cited
00 · the problem
Because keeping it current is somebody’s unpaid second job. The emails, tickets and calls all arrive on their own; turning them into what is true about this account right now is manual, so it happens late, partially, or never — and the rep joins the call reading a field someone typed two quarters ago.
Your app reports what happened. It does not decide what the profile should say.
“Avery now owns the Atlas migration and committed to an August launch.”
Nobody edits a field. The commitment appears, with the event behind it.
commitments — Atlas migration launches in August
A marketing email gets opened. The profile does not change.
“Opened the release-notes email.”Still searchable, still on file — just not something a rep should act on.
Append the event, read the profile. That is the whole integration.
await memseek.records.ingest(collection="crm_events", entity="contact:avery-chen", text=note) doc = await memseek.document(entity="contact:avery-chen") doc["beliefs"] # each one with its citations
goals, so nothing is invented to fill it.New to Memseek? You write one small configuration file describing what your application should remember. After that your application only appends what happened; Memseek does the deriving, keeps every conclusion linked to the evidence underneath it, and hands your agent a bounded briefing instead of a pile of text. Everything below is that file and what it produces — how it works.
The application reports that Avery owns the Atlas migration and committed to August. It appends exactly what happened; it does not decide which profile field should change.
name: crm_events
mode: event
schema:
required: [text, source, event_kind]
required_processors:
- crm_embedding
- importancemode: eventAppend a new fact to history. Never overwrite the event that later conclusions must prove themselves against.
requiredForce every write to say what happened, where it came from, and what kind of event it is.
importanceScore salience after the write so low-value interactions can remain searchable without becoming durable profile knowledge.
One immutable crm_events record holding the sentence as it arrived — “Avery now owns the Atlas migration and committed to an August launch.” The current profile is still untouched.
The YAML makes the maintenance rule explicit: consider only new CRM events, read the five current slots, and emit no more than those same five keys. The prompt explains the semantic boundary between a commitment, a preference, a role, an open thread, and a goal.
trigger:
accumulator:
metric: importance
threshold: 1
sources:
new_crm_events:
kind: changes
collections: [crm_events]
current_profile:
kind: current
collections: [user_profiles]
emit:
collection: user_profiles
type: profile
keys: [role, commitments,
preferences, open_threads, goals]trigger.accumulatorRun when enough meaningful evidence accumulates. The example uses a deliberately low threshold so each seeded case step is visible.
kind: changesGive the model the new evidence that caused this run—not an unbounded CRM history.
kind: currentAlso show the present profile so list-like slots can preserve still-valid claims and replace only what new evidence supersedes.
keysThese are the only legal outputs. The model cannot invent a “personality” or “hobbies” field.
A new current version of commitments — “Atlas migration launches in August” — citing the Salesforce event. The prior version remains in history.
The same rule handles Avery’s written-update preference and promotion. The product login remains in the event log but supports no action-worthy slot, so the correct output is silence. A second derivation then rewrites one summary from the complete current slot set.
Avery owns Atlas and committed to an August launch.
Send a concise written recap the day before every call.
Avery was promoted to SVP Product; platform is now in scope.
Opened the release-notes email.
Atlas migration launches in August.
Concise written updates the day before any call.
SVP Product at Acme Cloud — product and platform.
The event stays searchable in history, but it supports none of the five durable profile slots.
Visible effect: 4 events remain in history; only 3 change current profile state.
trigger:
changed:
collections: [user_profiles]
types: [profile]
sources:
changed_slots:
kind: changes
current_slots:
kind: current
emit:
collection: user_profiles
type: summary
keys: [summary]changed_slotsExplains why the summary must be regenerated and advances a cursor so settled profiles do not loop forever.
current_slotsBuild the sentence from the whole live profile, not only the field that changed most recently.
type: summaryKeep the presentation layer separate from the durable slot records it cites.
Three slots now hold cited values: role “SVP Product at Acme Cloud — product and platform”, commitments “Atlas migration launches in August · SSO before the renewal signature”, preferences “Concise written updates the day before any call”. goals stays absent, and one summary record points at the slots it synthesized.
01 · the write
One call per event, replay-safe by dedupe_key. The events are immutable: nothing here is ever edited or deleted, so the profile can always be rebuilt from them.
Declared as a required processor, so a record is not ready until it has run.
One number per event: should this change a durable profile? It is what the accumulator sums.
Optional structured enrichment; churn_risk is promoted into scores for ranking.
02 · the derivation
Sources bound what the run may see. Limits bound what it may spend. The emission bounds which keys it may touch — and the schema makes citations mandatory, so an uncited belief is rejected rather than stored.
importance crosses the threshold, so the run fires itself. The importance-2 interaction above never trips it alone.name: crm_profile trigger: accumulator: {metric: importance, threshold: 6} cooldown_s: 1 sources: new_crm_events: {kind: changes, collections: [crm_events]} current_profile: {kind: current, keys: [role, commitments, …]} account_playbook: {kind: record, key: playbook} commitment_history: {kind: view, view: crm_history} model: strong limits: max_llm_calls: 2 max_visible_records: 100 max_total_tokens: 20000 max_wall_s: 60 tasks: - id: result use: llm with: output_schema: # citations are schema, not etiquette citations: {type: array, minItems: 1, items: {format: uuid}} emit: collection: user_profiles keys: [role, commitments, preferences, open_threads, goals]
03 · the glass box
A belief names the run that wrote it; the run names the events it fused; each event is an immutable record a human or a system actually produced. The descent bottoms out — there is no layer below it that only the model saw.
Atlas migration launches in August · SSO before the renewal signature
2 LLM calls · 41 visible records · 9.4s wall · keyed divergence on 2 of 5 keys
"Avery now owns the Atlas migration and committed to an August launch."
"SSO has to ship before they sign the renewal."
04 · the reviewed path
The incremental run maintains the profile on its own. A full reconstruction from the complete corpus is a different, explicitly-enqueued derivation — and it stages a draft instead of activating, because its emission declares review: required.
Promotion re-reads the heads captured before the task ran. If the corpus moved underneath the rebuild, activation is rejected as stale rather than clobbering newer evidence.
The reviewed artifact names the exact key set a candidate must cover, so a partial rebuild cannot be promoted over a full profile.
There is nothing to undo: an un-promoted candidate remains in the audit trail and live memory was never touched.
05 · from your app
The application never triggers enrichment, never schedules the derivation, and never assembles the prompt. It appends events and reads a document.
Versioned definitions into a workspace. Re-publishing is how the memory design changes.
The worker owns everything after the write.
Current keyed state, plus whether the derivation is caught up.
A token-budgeted block with a hashed input manifest — replayable.
async with MemseekClient(BASE_URL, API_KEY) as memseek: await memseek.catalog.publish( package="crm_user_profile@2.0.0", directory="examples/crm_profile_catalog", ) # append an event — that is the whole write path await memseek.records.ingest( collection="crm_events", type="crm_event", entity="contact:avery-chen", text="SSO has to ship before they sign the renewal.", content={"source": "support", "event_kind": "commitment", "account_id": "acme-cloud"}, dedupe_key="support:evt:5512", ) # read what is true now, and how fresh it is doc = await memseek.document( entity="contact:avery-chen", collections="user_profiles", ) for belief in doc["beliefs"]: print(belief["key"], belief["text"], belief["citations"]) doc["freshness"] # {"crm_profile": {"stale": false, …}} # or hand the agent a deterministic prompt block brief = await memseek.render_artifact( "crm_profile_brief", entity="contact:avery-chen", query="commitments and launch dates", ) brief["manifest"]["rendered_sha256"] # same inputs → same bytes
The catalog, the five keys, the processors, and the SDK calls above are the checked-in examples/crm_profile_catalog and examples/sdk_crm_profile.py. Model-written text — the profile wording, the divergence — is representative of a real provider run rather than a captured transcript, because that text varies by model.
start building
The walkthrough seeds its own disposable workspace, waits while the service derives the profile from nothing, then hands you a prompt so you can type a CRM note and watch it fold in.