Request context.
getContext() is the read primitive for a single moment in your app. You give it an intent (a label for the moment, e.g. opening the app) and an optional query (free text describing what you're looking for), and you get back a permission-filtered, relevance-scored context bundle: the slice of the user's memory that matters right now. You can act on it with or without your own model.
context:read:app.session.start and memory:read:note.* (see Connect an app). A scope is one permission. Examples use Margin, a reading companion.The bundle, field by field (read this before the code)
Every getContext() call returns one bundle with these fields:
| Field | What it is |
|---|---|
identity | The light profile: just aiId (a stable handle for this user's memory) and an optional name (display name, only if shared via identity:read). Nothing else — see the callout below. |
memories | An array of relevant memories, already filtered to your read scopes and scored. Each has id, type, scope, content, importance, occurredAt, and an optional sourceApp (which app wrote it). |
relationships | Typed links between memories, drawn from the memory graph — each a { subject, relation, object } triple (e.g. “essay relatesTo distributed-systems”). They tell you how facts connect. |
suggestions | A ready-to-use hint: adaptation (a one-line, human-readable nudge for this moment) and optional tags (short topic labels you can branch on). |
generatedAt | ISO timestamp of when the bundle was assembled. |
const ctx = await pambase.getContext({intent: "app.session.start",query: "recent reading interests", // optional; informs the semantic matchlimit: 10, // optional; default 10});// Shape:{identity: {aiId: "ai_8f3c1b2d",name: "Sam" // optional display name, only if shared (identity:read)},memories: [{ id, type, scope, content, importance, occurredAt, sourceApp }],relationships: [{ subject, relation, object }],suggestions: {adaptation: "Reader has been deep on distributed systems — surface related long-form.",tags: ["distributed-systems", "long-form", "infrastructure"]},generatedAt: "2026-05-22T18:04:11.002Z"}
identity is only { aiId, name }. Never expect or render an archetype, personality, mood, or voice — users don't author personas, so those don't exist here. Your app brings the persona; PAMbase brings the person. See Identity.Pick an intent that means something
The intent string biases retrieval toward the right kind of memory for the moment. Use a descriptive one tied to where the call happens:
| Intent | When Margin uses it |
|---|---|
app.session.start | The user opens Margin — build the home screen. |
app.companion.turn | Each turn of Margin's recommendation chat. |
Declare every intent in your manifest's contextIntents — the API rejects an undeclared intent. The standard set is in the Catalog.
Pattern A — adapt with no LLM
You don't need a model to personalize. Branch on suggestions.tags and the structured memories, and render suggestions.adaptation verbatim if you want a human-readable nudge. This is the cheapest, fastest path. Here Margin builds its home screen:
const ctx = await pambase.getContext({intent: "app.session.start",query: "recent reading interests",});// Drive the home screen straight from tags + memories — no model call.const onSystems = ctx.suggestions.tags?.includes("distributed-systems")|| ctx.memories.some(m => m.scope.startsWith("note") && /systems|consensus|rate limit/i.test(m.content));if (onSystems) {renderShelf({heading: "Because you've been reading about distributed systems…",items: recommendFrom("distributed-systems"),});} else {renderShelf({ heading: "Pick up where you left off", items: recentReads() });}// Or just show the adaptation hint as-is:showBanner(ctx.suggestions.adaptation);
Expected result: a returning reader lands on a shelf titled by their real interests — assembled from memory alone, with no model and no cost.
Pattern B — RAG with your own model
Hosting your own model? The memories list is already permission-filtered and scored, so feed it straight in as retrieval context (this is RAG — retrieval-augmented generation). The persona and instructions are yours; PAMbase only supplies the grounding facts.
const ctx = await pambase.getContext({intent: "app.companion.turn",query: "What should I read next?",});const memoryBlock = ctx.memories.map(m => `- [${m.scope}] ${m.content} (importance ${m.importance})`).join("\n");const system = [marginPersona, // YOUR app's persona, not PAMbase'sctx.identity.name && `The reader's name is ${ctx.identity.name}.`,`What you remember about the reader:\n${memoryBlock}`,].filter(Boolean).join("\n\n");const reply = await myReadingModel.chat({ system, messages });
Expected result: your model recommends a next read grounded in the user's saved highlights and observed taste, in Margin's own voice.
Empty results?
A new user, a missing read scope, or the wrong scope filter all yield few or no memories. Confirm what this user actually granted (and how much data is in each scope) with listUserScopes() before assuming the store is empty.
What to expect next
- Memory — how memories are typed, scoped, and scored.
- Vector graph — where
relationshipscome from. - Host the AI — let PAMbase build the bundle and run the model.