From 28d83d4086ce12422af20fcbf4045bd10d2c9576 Mon Sep 17 00:00:00 2001 From: Seth Juarez Date: Thu, 17 Sep 2026 22:42:19 -0700 Subject: [PATCH] docs: add agent memory concept Document Prompty's memory runtime/host boundary in Agentic Concepts and improve Mermaid contrast in dark mode across the docs site. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- web/astro.config.mjs | 5 +- .../docs/agentic-concepts/agent-loop.mdx | 12 +- .../content/docs/agentic-concepts/index.mdx | 15 +- .../content/docs/agentic-concepts/memory.mdx | 230 ++++++++++++++++++ web/src/styles/custom.css | 57 +++++ 5 files changed, 312 insertions(+), 7 deletions(-) create mode 100644 web/src/content/docs/agentic-concepts/memory.mdx diff --git a/web/astro.config.mjs b/web/astro.config.mjs index b02bbc88d..1d29dd6e3 100644 --- a/web/astro.config.mjs +++ b/web/astro.config.mjs @@ -10,8 +10,9 @@ export default defineConfig({ trailingSlash: "always", integrations: [ mermaid({ - theme: 'forest', - autoTheme: true + theme: "neutral", + autoTheme: true, + enableLog: false, }), starlight({ title: "Prompty", diff --git a/web/src/content/docs/agentic-concepts/agent-loop.mdx b/web/src/content/docs/agentic-concepts/agent-loop.mdx index 69970100f..c572ec906 100644 --- a/web/src/content/docs/agentic-concepts/agent-loop.mdx +++ b/web/src/content/docs/agentic-concepts/agent-loop.mdx @@ -178,7 +178,11 @@ The loop does not invent tool implementations. A `.prompty` file can declare tool schemas, but your application still supplies the functions or provider implementations that execute them. -The loop also does not automatically persist memory. Conversation history is -passed by the application, usually through a `kind: thread` input. See -[Conversation History](/core-concepts/conversation-history/) for the thread -pattern. +The loop also does not automatically choose a memory backend. Prompty defines +the portable memory store and deterministic recall/formatting behavior, but +your application still supplies the persistence hook and lifecycle policy. See +[Memory](/agentic-concepts/memory/) for the host/runtime boundary. + +Conversation history is passed by the application, usually through a +`kind: thread` input. See [Conversation History](/core-concepts/conversation-history/) +for the thread pattern. diff --git a/web/src/content/docs/agentic-concepts/index.mdx b/web/src/content/docs/agentic-concepts/index.mdx index 86b1f05c2..fe8f42f3c 100644 --- a/web/src/content/docs/agentic-concepts/index.mdx +++ b/web/src/content/docs/agentic-concepts/index.mdx @@ -2,7 +2,8 @@ title: Agentic Concepts description: > Understand Prompty's agentic runtime model: turns, tool-calling loops, - guardrails, context compaction, steering, cancellation, events, and retries. + guardrails, memory, context compaction, steering, cancellation, events, and + retries. sidebar: order: 1 --- @@ -16,6 +17,7 @@ tool-using agents. After reading it, you should be able to answer: - where the boundary is between a `.prompty` file and host runtime code; - how a single `turn()` differs from a full chat session; +- how memory is represented by Prompty and persisted by your host; - when messages are prepared, appended, trimmed, compacted, or steered; - where to enforce policy with guardrails instead of prompt instructions; - how to observe and stop a long-running tool loop. @@ -38,6 +40,7 @@ flowchart TD C -. "optional controls" .-> K["context budget + compaction"] C -. "optional controls" .-> L["guardrails"] C -. "optional controls" .-> M["steering"] + C -. "host-provided context" .-> N["memory store"] ``` The key distinction is: @@ -46,6 +49,9 @@ The key distinction is: - **`turn()`** runs one user turn and, if needed, loops internally for tool calls. - **Tool-loop iterations do not re-render the template.** They mutate the prepared message array by appending assistant/tool messages. +- **Memory is a host boundary.** Prompty defines the portable memory model and + deterministic behavior; your application decides where the memory store lives + and when it is loaded or saved. - **External user turns call `turn()` again.** If you pass conversation history as a thread input, that thread is expanded during the next `prepare()` call. @@ -54,6 +60,7 @@ The key distinction is: | Concept | What it controls | Start here | |---|---|---| | Agent loop | How tool calls are executed and fed back to the model | [Agent Loop](/agentic-concepts/agent-loop/) | +| Memory | Portable memory semantics, recall, formatting, and host persistence boundaries | [Memory](/agentic-concepts/memory/) | | Runtime controls | The per-iteration order for cancellation, steering, context, guardrails, model calls, and tools | [Runtime Controls](/agentic-concepts/runtime-controls/) | | Guardrails | Validate or rewrite inputs, outputs, and tool arguments | [Guardrails](/agentic-concepts/guardrails/) | | Context budget | Trim messages before model calls | [Context & Compaction](/agentic-concepts/context-compaction/) | @@ -76,10 +83,16 @@ available in the v2 runtimes: | C# | Yes | Yes | Yes | Yes | | Rust | Yes | Yes | Yes | Yes | +Memory helpers and conformance vectors are available across the generated model +and runtime surface. The host still owns persistence by implementing the runtime's +memory port or equivalent load/save adapter. + ## Related docs - [Agent tool calling guide](/how-to/agent-tool-calling/) for a hands-on walkthrough - [Tools](/core-concepts/tools/) for `.prompty` tool declarations - [Conversation history](/core-concepts/conversation-history/) for thread inputs +- [Memory reference](/reference/memorystore/) for the generated `MemoryStore` + contract - [Tracing](/core-concepts/tracing/) for observing runtime execution - [Agent loop specification](/specification/agent-loop/) for the lower-level contract diff --git a/web/src/content/docs/agentic-concepts/memory.mdx b/web/src/content/docs/agentic-concepts/memory.mdx new file mode 100644 index 000000000..1c13271dc --- /dev/null +++ b/web/src/content/docs/agentic-concepts/memory.mdx @@ -0,0 +1,230 @@ +--- +title: Memory +description: > + How Prompty represents memory, what behavior the runtimes provide, and where + your host application plugs in persistence. +sidebar: + order: 6 +--- + +import { Aside, Tabs, TabItem } from '@astrojs/starlight/components'; + +Prompty memory is a **portable runtime contract**, not a hosted database. The +runtimes know how to work with memory once a store is available; your +application decides where that store lives and when to load or save it. + +That split keeps memory behavior consistent across runtimes without forcing +every app into the same storage, privacy, or lifecycle model. + +## Mental model + +```mermaid +flowchart LR + A["Host application"] --> B["load MemoryStore"] + B --> C["Prompty turn()"] + C --> D["core memories injected into system prompt"] + C --> E["recall / remember / update / clear"] + E --> F["updated MemoryStore"] + F --> G["host saves store"] + + H["Storage choice\nfile, database, service"] -. owned by host .-> A +``` + +Prompty owns the portable behavior: + +- `MemoryStore`, `MemoryEntry`, and `MemoryCategory`; +- deterministic `recall` scoring and formatting; +- `remember`, `update`, `remove`, `clear`, and cap-based eviction; +- core-memory formatting for model-visible system context; +- conformance vectors that keep those semantics aligned across runtimes. + +Your host owns the integration: + +- persistence location: file, database, profile service, project store, or + tenant-scoped service; +- scoping: user, project, agent, session, organization, or some combination; +- lifecycle policy: when to remember, summarize, update, expire, or ask for + permission; +- privacy and security boundaries: consent, audit, tenant isolation, encryption, + and deletion; +- wiring the store into the turn loop through the runtime's memory port or + equivalent load/save adapter. + + + +## Categories + +Memory entries are tiered so runtimes agree on what each entry means. + +| Category | Meaning | Runtime behavior | +|---|---|---| +| `core` | Persistent facts or preferences that should be model-visible by default | Injected into the system prompt, boosted during recall, and deduplicated by tag set on write | +| `archival` | Summaries or older context that should be available through recall | Not injected by default; preferred for eviction when the store exceeds its cap | +| `insight` | Saved reflections or derived observations | Available through recall; not injected by default | + +Only `core` memories are formatted directly into the model-visible system prompt: + +```text +## Memory +- User prefers concise answers +- Deploys on Friday afternoons +``` + +Archival and insight memories stay out of the prompt unless your app recalls +them and chooses to include the recall results. + +## Runtime shape + + + + +```python +from prompty import MemoryEntry, MemoryStore, format_for_system_prompt, recall, remember + +store = MemoryStore.load({"entries": []}) +remember( + store, + MemoryEntry.load( + { + "content": "User prefers concise answers", + "category": "core", + "tags": ["preference", "tone"], + } + ), +) + +system_context = format_for_system_prompt(store) +results = recall(store, "tone", limit=3) +``` + + + + +```ts +import { + MemoryEntry, + MemoryStore, + formatForSystemPrompt, + recall, + remember, +} from "@prompty/core"; + +const store = MemoryStore.load({ entries: [] }); +remember( + store, + MemoryEntry.load({ + content: "User prefers concise answers", + category: "core", + tags: ["preference", "tone"], + }), +); + +const systemContext = formatForSystemPrompt(store); +const results = recall(store, "tone", 3); +``` + + + + +```csharp +using Prompty.Core; + +var store = MemoryStore.Load(new Dictionary { ["entries"] = new List() }); + +Memory.Remember( + store, + MemoryEntry.Load(new Dictionary + { + ["content"] = "User prefers concise answers", + ["category"] = "core", + ["tags"] = new[] { "preference", "tone" }, + }) +); + +var systemContext = Memory.FormatForSystemPrompt(store); +var results = Memory.Recall(store, "tone", limit: 3); +``` + + + + +```rust +use prompty::{MemoryCategory, MemoryEntry, MemoryStore}; + +let mut store = MemoryStore { entries: vec![] }; +store.remember( + MemoryEntry { + content: "User prefers concise answers".to_string(), + category: MemoryCategory::Core, + created_at: None, + tags: Some(vec!["preference".to_string(), "tone".to_string()]), + }, + 0, +); + +let system_context = store.format_for_system_prompt(); +let results = store.recall("tone", 3); +``` + + + + +## Persistence boundary + +Each flagship runtime exposes a memory port shape: load a whole `MemoryStore` +snapshot, let Prompty apply deterministic behavior, then save the updated +snapshot. A host can back that port with any storage system. + +```mermaid +sequenceDiagram + participant App + participant MemoryPort + participant Prompty + participant Model + + App->>MemoryPort: load scoped store + MemoryPort-->>App: MemoryStore + App->>Prompty: turn(agent, inputs, memory) + Prompty->>Prompty: format core memories + Prompty->>Model: messages + memory context + Model-->>Prompty: response / tool calls + Prompty-->>App: result + updated store + App->>MemoryPort: save updated store +``` + +Prompty deliberately does not decide when a model response should become a +memory. Many apps should ask for user permission, apply policy, or summarize +conversation history before writing. Those choices depend on the host's product +requirements, not the `.prompty` file. + +## Conformance + +Memory is covered by shared vectors, so flagship runtimes benefit equally from +the same semantics. The vectors assert: + +- recall ranking by weighted lexical score; +- tag matches and core-memory boosts; +- empty-query behavior and result limits; +- stable tie ordering; +- core deduplication by tags; +- archival-first eviction; +- clear, update, and out-of-range behavior; +- system-prompt formatting; +- snapshot round-trip behavior; +- model-visible memory context in a turn. + +If a runtime changes any of those behaviors, its generated conformance suite +goes red. + +## Related docs + +- [Agent Loop](/agentic-concepts/agent-loop/) for where memory context appears + in a turn +- [Context & Compaction](/agentic-concepts/context-compaction/) for trimming and + summarizing conversation history +- [MemoryStore reference](/reference/memorystore/) for the generated store shape +- [MemoryEntry reference](/reference/memoryentry/) for entry fields and category + metadata diff --git a/web/src/styles/custom.css b/web/src/styles/custom.css index c91449b9c..4c1008ced 100644 --- a/web/src/styles/custom.css +++ b/web/src/styles/custom.css @@ -57,3 +57,60 @@ starlight-tabs .tablist-wrapper { overflow-y: hidden; } + +/* Improve Mermaid contrast in Starlight dark mode. */ +:root[data-theme="dark"] .mermaid { + background: var(--sl-color-bg-nav); + border: 1px solid var(--sl-color-gray-5); + border-radius: 0.5rem; + padding: 1rem; +} + +:root[data-theme="dark"] .mermaid svg { + color: var(--sl-color-white); +} + +:root[data-theme="dark"] .mermaid .node rect, +:root[data-theme="dark"] .mermaid .node circle, +:root[data-theme="dark"] .mermaid .node ellipse, +:root[data-theme="dark"] .mermaid .node polygon, +:root[data-theme="dark"] .mermaid .node path, +:root[data-theme="dark"] .mermaid rect.actor, +:root[data-theme="dark"] .mermaid .actor.actor-top, +:root[data-theme="dark"] .mermaid .actor.actor-bottom { + fill: var(--sl-color-gray-6) !important; + stroke: var(--prompty-blue) !important; +} + +:root[data-theme="dark"] .mermaid .edgeLabel, +:root[data-theme="dark"] .mermaid .labelBkg, +:root[data-theme="dark"] .mermaid .labelBox { + background-color: var(--sl-color-bg-nav) !important; + fill: var(--sl-color-bg-nav) !important; +} + +:root[data-theme="dark"] .mermaid text, +:root[data-theme="dark"] .mermaid tspan, +:root[data-theme="dark"] .mermaid .label, +:root[data-theme="dark"] .mermaid .nodeLabel, +:root[data-theme="dark"] .mermaid .edgeLabel, +:root[data-theme="dark"] .mermaid .messageText, +:root[data-theme="dark"] .mermaid .actor, +:root[data-theme="dark"] .mermaid .actor tspan, +:root[data-theme="dark"] .mermaid .label tspan, +:root[data-theme="dark"] .mermaid .nodeLabel tspan, +:root[data-theme="dark"] .mermaid .edgeLabel tspan { + color: var(--sl-color-white) !important; + fill: var(--sl-color-white) !important; +} + +:root[data-theme="dark"] .mermaid .flowchart-link, +:root[data-theme="dark"] .mermaid .messageLine0, +:root[data-theme="dark"] .mermaid .messageLine1 { + stroke: var(--prompty-blue) !important; +} + +:root[data-theme="dark"] .mermaid marker path { + fill: var(--prompty-blue) !important; + stroke: var(--prompty-blue) !important; +}