mirror of
https://github.com/strands-agents/harness-sdk.git
synced 2026-10-02 02:44:48 +08:00
docs: add memory manager docs (#2758)
This commit is contained in:
@@ -15,6 +15,9 @@ One concept, one term. Never vary for stylistic reasons. This file is the canoni
|
||||
| Configuring which model to use | model provider | model backend, LLM provider, inference provider |
|
||||
| Maintaining conversation across turns (user-facing concept) | session management | context persistence, memory, conversation history |
|
||||
| The SDK class that implements session management | conversation manager | session handler, context manager (note: `ConversationManager` is the API class name; "session management" is the user-facing concept in docs) |
|
||||
| Durable knowledge an agent recalls across sessions | memory (or long-term memory) | knowledge store, recall, persistent memory |
|
||||
| The SDK class that implements memory management | memory manager | knowledge store, recall, memory store, knowledge base |
|
||||
| The SDK class that implements the backend containing memories | memory store | knowledge store, recall |
|
||||
| Controlling agent behavior at runtime | hooks | middleware, interceptors, callbacks (hooks is Strands-specific) |
|
||||
| Multiple agents working together | multi-agent | multi-agent system, agent orchestration, agent coordination |
|
||||
| Agent-to-agent communication pattern | agents as tools | agent chaining, agent delegation, nested agents |
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
# - sidebar: Hierarchical sidebar structure (slugs only for files - labels from frontmatter)
|
||||
# - github: GitHub dropdown sections
|
||||
#
|
||||
# NOTE: Badges (like "new", "community") should come from page frontmatter only,
|
||||
# not from this configuration file.
|
||||
# NOTE: Page (leaf) badges come from page frontmatter, not this file. Group labels
|
||||
# have no frontmatter, so a group may declare a `badge` here (text + variant) to
|
||||
# surface status on a collapsed section.
|
||||
|
||||
navbar:
|
||||
- label: Home
|
||||
@@ -74,6 +75,15 @@ sidebar:
|
||||
- docs/user-guide/concepts/agents/hooks
|
||||
- docs/user-guide/concepts/agents/conversation-management
|
||||
- docs/user-guide/concepts/context-management
|
||||
- label: Memory
|
||||
collapsed: true
|
||||
badge:
|
||||
text: New
|
||||
variant: tip
|
||||
items:
|
||||
- label: "Overview"
|
||||
slug: docs/user-guide/concepts/memory/overview
|
||||
- docs/user-guide/concepts/memory/bedrock-knowledge-base
|
||||
- docs/user-guide/concepts/agents/retry-strategies
|
||||
- docs/user-guide/concepts/interrupts
|
||||
- label: Tools
|
||||
@@ -88,6 +98,7 @@ sidebar:
|
||||
- docs/user-guide/concepts/plugins/skills
|
||||
- docs/user-guide/concepts/plugins/steering
|
||||
- docs/user-guide/concepts/plugins/context-offloader
|
||||
- docs/user-guide/concepts/plugins/context-injector
|
||||
- docs/user-guide/concepts/plugins/goal-loop
|
||||
- label: Interventions
|
||||
items:
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
- hooks
|
||||
- interventions
|
||||
- local-models
|
||||
- memory
|
||||
- multi-agent
|
||||
- multimodal-evaluation
|
||||
- prompt-authoring
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
---
|
||||
title: Bedrock Knowledge Base Store
|
||||
description: "Back agent memory with Amazon Bedrock Knowledge Bases: semantic search over a managed vector store and document ingestion for CUSTOM and S3 data sources."
|
||||
tags: [memory, bedrock, aws]
|
||||
sidebar:
|
||||
label: "Bedrock Knowledge Base"
|
||||
badge:
|
||||
text: New
|
||||
variant: tip
|
||||
---
|
||||
|
||||
`BedrockKnowledgeBaseStore` is a [`MemoryStore`](./overview#stores) backed by [Amazon Bedrock Knowledge Bases](https://docs.aws.amazon.com/bedrock/latest/userguide/knowledge-base.html).
|
||||
|
||||
Connect the store to a knowledge base you have already set up. Configure your store with its knowledge base ID and data source (see [Data Source Types and Writability](#data-source-types-and-writability)). A store with only a knowledge base ID is read-only. The store reaches Bedrock with the standard AWS credential chain, the same as the [Amazon Bedrock model provider](../model-providers/amazon-bedrock).
|
||||
|
||||
<Tabs>
|
||||
<Tab label="Python">
|
||||
|
||||
```python
|
||||
from strands import Agent
|
||||
from strands.memory import MemoryManager
|
||||
from strands.vended_memory_stores import BedrockKnowledgeBaseStore
|
||||
|
||||
store = BedrockKnowledgeBaseStore(
|
||||
name="docs",
|
||||
description="Company documentation and policies.",
|
||||
config={"knowledge_base_id": "KB123"},
|
||||
)
|
||||
|
||||
agent = Agent(memory_manager=MemoryManager(stores=[store]))
|
||||
```
|
||||
</Tab>
|
||||
<Tab label="TypeScript">
|
||||
|
||||
```typescript
|
||||
--8<-- "user-guide/concepts/memory/bedrock-knowledge-base_imports.ts:read_only_imports"
|
||||
|
||||
--8<-- "user-guide/concepts/memory/bedrock-knowledge-base.ts:read_only"
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
To enable writing memories through the [`add_memory` tool](./overview#memory-tools) or [automatic extraction](./overview#automatic-extraction), mark the store writable and point it at a data source that accepts ingestion:
|
||||
|
||||
<Tabs>
|
||||
<Tab label="Python">
|
||||
|
||||
```python
|
||||
from strands.vended_memory_stores import BedrockKnowledgeBaseStore
|
||||
|
||||
store = BedrockKnowledgeBaseStore(
|
||||
name="preferences",
|
||||
description="User preferences and stable facts.",
|
||||
writable=True,
|
||||
config={
|
||||
"knowledge_base_id": "KB123",
|
||||
"data_source_type": "CUSTOM",
|
||||
"data_source_id": "DS456",
|
||||
},
|
||||
)
|
||||
```
|
||||
</Tab>
|
||||
<Tab label="TypeScript">
|
||||
|
||||
```typescript
|
||||
--8<-- "user-guide/concepts/memory/bedrock-knowledge-base_imports.ts:writable_custom_imports"
|
||||
|
||||
--8<-- "user-guide/concepts/memory/bedrock-knowledge-base.ts:writable_custom"
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Configuration
|
||||
|
||||
### Scope and namespaces
|
||||
|
||||
`scope` isolates documents within a single knowledge base. It is stamped on every write (under <Syntax py="scope_metadata_key" ts="scopeMetadataKey" />, default `'namespace'`) and applied as a metadata filter on search. Unlike `name`, scope is not a routing identity, so it never affects which store the `MemoryManager` targets. For per-tenant isolation, construct one store per scope; since they share a connection, this is cheap.
|
||||
|
||||
### Store config
|
||||
|
||||
The outer `BedrockKnowledgeBaseStoreConfig` carries the per-store identity and behavior, plus the [shared `MemoryStore` fields](./overview#stores) (`name`, `description`, <Syntax py="max_search_results" ts="maxSearchResults" />, `writable`, `extraction`):
|
||||
|
||||
| Field | Purpose |
|
||||
|-------|---------|
|
||||
| `config` | The knowledge base connection (see below). Reuse one across stores that differ only by `scope`. |
|
||||
| `scope` | Logical namespace isolating documents. Applied as a metadata filter on search and stamped on writes. |
|
||||
| `filter` | Explicit retrieval filter; overrides the auto-generated scope filter on search. |
|
||||
|
||||
### Connection config
|
||||
|
||||
The inner `BedrockKnowledgeBaseConfig` is the reusable connection: which knowledge base, which data source, and the clients used to reach them:
|
||||
|
||||
| Field | Purpose |
|
||||
|-------|---------|
|
||||
| <Syntax py="knowledge_base_id" ts="knowledgeBaseId" /> | The Bedrock Knowledge Base to query and ingest into. Required. |
|
||||
| <Syntax py="data_source_type" ts="dataSourceType" /> | `'CUSTOM'`, `'S3'`, or `'OTHER'`. Governs whether and how the store can be written to. |
|
||||
| <Syntax py="data_source_id" ts="dataSourceId" /> | The data source to ingest into. Required for writes. |
|
||||
| `s3` | S3 ingestion settings (`bucket`, `prefix`). Required when the data source type is `'S3'`. |
|
||||
| <Syntax py="scope_metadata_key" ts="scopeMetadataKey" /> | Metadata attribute key used for scope filtering. Defaults to `'namespace'`. |
|
||||
| <Syntax py="runtime_client / agent_client" ts="runtimeClient / agentClient" /> | Pre-constructed AWS clients. When omitted, default clients are constructed using the standard credential chain (the agent client lazily, on first write). |
|
||||
|
||||
Because the connection is a separate object, you build it once and vary only `name` and `scope` per store. This is the cheap way to give each tenant an isolated namespace over a single knowledge base:
|
||||
|
||||
<Tabs>
|
||||
<Tab label="Python">
|
||||
|
||||
```python
|
||||
from strands.vended_memory_stores import BedrockKnowledgeBaseStore
|
||||
|
||||
# Build the connection once, vary only name and scope per store.
|
||||
connection = {
|
||||
"knowledge_base_id": "KB123",
|
||||
"data_source_type": "CUSTOM",
|
||||
"data_source_id": "DS456",
|
||||
}
|
||||
|
||||
alice = BedrockKnowledgeBaseStore(name="alice", writable=True, scope="user-alice", config=connection)
|
||||
bob = BedrockKnowledgeBaseStore(name="bob", writable=True, scope="user-bob", config=connection)
|
||||
```
|
||||
</Tab>
|
||||
<Tab label="TypeScript">
|
||||
|
||||
```typescript
|
||||
--8<-- "user-guide/concepts/memory/bedrock-knowledge-base_imports.ts:scoped_stores_imports"
|
||||
|
||||
--8<-- "user-guide/concepts/memory/bedrock-knowledge-base.ts:scoped_stores"
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Data Source Types and Writability
|
||||
|
||||
Only data sources that accept direct ingestion can be written to. A store is writable only when you opt in *and* the backend supports ingestion: that is, the store is marked writable and the data source type is `'CUSTOM'` or `'S3'`.
|
||||
|
||||
| Data source type | Writable | How writes work |
|
||||
|------------------|----------|-----------------|
|
||||
| `CUSTOM` | Yes | Ingests the content as inline text, with scope and metadata attached as inline attributes. |
|
||||
| `S3` | Yes | Uploads the content to the configured `s3` bucket and ingests that object. Requires an `s3` config. |
|
||||
| `OTHER` | No | External backends such as Confluence, SharePoint, Salesforce, Web, or SQL/Redshift that sync from their own source or are query-only. Read-only. |
|
||||
| omitted | No | Read-only. |
|
||||
|
||||
A writable `CUSTOM` store needs a data source ID. A writable `S3` store needs a data source ID and an `s3` config:
|
||||
|
||||
<Tabs>
|
||||
<Tab label="Python">
|
||||
|
||||
```python
|
||||
from strands.vended_memory_stores import BedrockKnowledgeBaseStore
|
||||
|
||||
store = BedrockKnowledgeBaseStore(
|
||||
name="preferences",
|
||||
writable=True,
|
||||
config={
|
||||
"knowledge_base_id": "KB123",
|
||||
"data_source_type": "S3",
|
||||
"data_source_id": "DS789",
|
||||
"s3": {"bucket": "my-agent-memories", "prefix": "memories/"},
|
||||
},
|
||||
)
|
||||
```
|
||||
</Tab>
|
||||
<Tab label="TypeScript">
|
||||
|
||||
```typescript
|
||||
--8<-- "user-guide/concepts/memory/bedrock-knowledge-base_imports.ts:s3_store_imports"
|
||||
|
||||
--8<-- "user-guide/concepts/memory/bedrock-knowledge-base.ts:s3_store"
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### How S3 writes work
|
||||
|
||||
An S3 document can't carry metadata inline, so when a scope or metadata are present a single write produces two objects: the content as a `.txt` object, and a `<object-key>.metadata.json` sidecar beside it (Bedrock's convention for attaching attributes to an S3 object). The uploads are not transactional, so if a later step fails, any already-uploaded objects remain in the bucket un-ingested, where a future sync may pick them up or you can clean them up out of band.
|
||||
|
||||
A write ingests its object directly, regardless of where the data source is configured to scan. A later data-source sync reconciles the index to the scanned location, so a memory written outside that location is treated as deleted and removed. If you run periodic syncs against this data source, upload to the bucket and prefix it scans (the `s3` config) so directly-ingested memories survive them.
|
||||
|
||||
## Search and Ingestion
|
||||
|
||||
`search` runs the Bedrock Retrieve API and returns entries ordered by relevance, while `add` ingests new content and returns its document id:
|
||||
|
||||
<Tabs>
|
||||
<Tab label="Python">
|
||||
|
||||
```python
|
||||
from strands.memory.types import SearchOptions
|
||||
from strands.vended_memory_stores import BedrockKnowledgeBaseStore
|
||||
|
||||
store = BedrockKnowledgeBaseStore(
|
||||
name="preferences",
|
||||
writable=True,
|
||||
config={
|
||||
"knowledge_base_id": "KB123",
|
||||
"data_source_type": "CUSTOM",
|
||||
"data_source_id": "DS456",
|
||||
},
|
||||
)
|
||||
|
||||
results = await store.search("what are my preferences?", SearchOptions(max_search_results=5))
|
||||
for entry in results:
|
||||
print(entry.content, entry.metadata.get("_relevance_score"))
|
||||
|
||||
# add returns the new document's id (a UUID for CUSTOM, an s3:// URI for S3)
|
||||
result = await store.add("User prefers aisle seats", {"category": "travel"})
|
||||
print(result.document_id)
|
||||
```
|
||||
</Tab>
|
||||
<Tab label="TypeScript">
|
||||
|
||||
```typescript
|
||||
--8<-- "user-guide/concepts/memory/bedrock-knowledge-base_imports.ts:search_imports"
|
||||
|
||||
--8<-- "user-guide/concepts/memory/bedrock-knowledge-base.ts:search"
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
The search result cap defaults to `10` when neither the call nor the store sets one. This `10` default applies only to direct calls on the store. Reached through a `MemoryManager` tool or its search method, the manager supplies its own per-call cap instead; set <Syntax py="max_search_results" ts="maxSearchResults" /> on the store to pin a value across both paths.
|
||||
|
||||
Each entry's `metadata` carries the document's own attributes plus two reserved synthetic keys: <Syntax py="_relevance_score" ts="_relevanceScore" /> (the Bedrock relevance score) and <Syntax py="_source_location" ts="_sourceLocation" /> (the retrieval location). When `scope` is set, search filters to that namespace automatically; an explicit `filter` overrides the scope-derived one. The asymmetry is deliberate: an explicit `filter` affects search only, while writes always scope by `scope`.
|
||||
|
||||
The document id from `add` is the generated UUID for a `CUSTOM` document, or the `s3://` URI of the uploaded object for `S3`. Writes require a data source ID (and an `s3` config for S3 data sources); a write against a missing or read-only configuration raises.
|
||||
|
||||
Ingestion is eventually consistent: a successful write does not mean the content is immediately searchable.
|
||||
|
||||
## Extraction
|
||||
|
||||
Enable [automatic extraction](./overview#automatic-extraction) on a writable store to capture memories from the conversation. By default it runs every 5 turns, distilling facts client-side with a `ModelExtractor` that uses the agent's own model:
|
||||
|
||||
<Tabs>
|
||||
<Tab label="Python">
|
||||
|
||||
```python
|
||||
from strands.vended_memory_stores import BedrockKnowledgeBaseStore
|
||||
|
||||
store = BedrockKnowledgeBaseStore(
|
||||
name="preferences",
|
||||
writable=True,
|
||||
extraction=True,
|
||||
config={
|
||||
"knowledge_base_id": "KB123",
|
||||
"data_source_type": "CUSTOM",
|
||||
"data_source_id": "DS456",
|
||||
},
|
||||
)
|
||||
```
|
||||
</Tab>
|
||||
<Tab label="TypeScript">
|
||||
|
||||
```typescript
|
||||
--8<-- "user-guide/concepts/memory/bedrock-knowledge-base_imports.ts:extraction_imports"
|
||||
|
||||
--8<-- "user-guide/concepts/memory/bedrock-knowledge-base.ts:extraction"
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
To change the cadence, swap the extractor, or extract server-side, see [Automatic Extraction](./overview#automatic-extraction) on the Memory page.
|
||||
|
||||
## Required IAM Permissions
|
||||
|
||||
The credentials the store uses must allow the Bedrock operations it calls, plus S3 writes when using an S3 data source:
|
||||
|
||||
- `bedrock:Retrieve` - for search.
|
||||
- `bedrock:IngestKnowledgeBaseDocuments` - for writes (`CUSTOM` and `S3`).
|
||||
- `s3:PutObject` - for writes to an `S3` data source, on the configured bucket and prefix.
|
||||
|
||||
Here is a sample IAM policy for a writable store backed by a `CUSTOM` data source:
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"bedrock:Retrieve",
|
||||
"bedrock:IngestKnowledgeBaseDocuments"
|
||||
],
|
||||
"Resource": "arn:aws:bedrock:*:*:knowledge-base/KB123"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
For an `S3` data source, add `s3:PutObject` on the bucket and prefix the store uploads to:
|
||||
|
||||
```json
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "s3:PutObject",
|
||||
"Resource": "arn:aws:s3:::my-agent-memories/memories/*"
|
||||
}
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [Memory](./overview) - the `MemoryManager` concept this store plugs into, including the tools, extraction, and injection it enables.
|
||||
- [Amazon Bedrock](../model-providers/amazon-bedrock) - credential and region setup shared with the Bedrock model provider.
|
||||
@@ -0,0 +1,149 @@
|
||||
import { Agent, BedrockModel } from '@strands-agents/sdk'
|
||||
import {
|
||||
BedrockKnowledgeBaseStore,
|
||||
type BedrockKnowledgeBaseConfig,
|
||||
} from '@strands-agents/sdk/vended-memory-stores/bedrock-knowledge-base'
|
||||
|
||||
// =====================
|
||||
// Read-only store
|
||||
// =====================
|
||||
|
||||
function readOnly() {
|
||||
// --8<-- [start:read_only]
|
||||
const store = new BedrockKnowledgeBaseStore({
|
||||
name: 'docs',
|
||||
description: 'Company documentation and policies.',
|
||||
config: { knowledgeBaseId: 'KB123' },
|
||||
})
|
||||
|
||||
const agent = new Agent({
|
||||
model: new BedrockModel(),
|
||||
memoryManager: { stores: [store] },
|
||||
})
|
||||
// --8<-- [end:read_only]
|
||||
|
||||
void agent
|
||||
}
|
||||
void readOnly
|
||||
|
||||
// =====================
|
||||
// Writable CUSTOM store
|
||||
// =====================
|
||||
|
||||
function writableCustom() {
|
||||
// --8<-- [start:writable_custom]
|
||||
const store = new BedrockKnowledgeBaseStore({
|
||||
name: 'preferences',
|
||||
description: 'User preferences and stable facts.',
|
||||
writable: true,
|
||||
config: {
|
||||
knowledgeBaseId: 'KB123',
|
||||
dataSourceType: 'CUSTOM',
|
||||
dataSourceId: 'DS456',
|
||||
},
|
||||
})
|
||||
// --8<-- [end:writable_custom]
|
||||
|
||||
void store
|
||||
}
|
||||
void writableCustom
|
||||
|
||||
// =====================
|
||||
// Reuse one connection across scoped stores
|
||||
// =====================
|
||||
|
||||
function scopedStores() {
|
||||
// --8<-- [start:scoped_stores]
|
||||
// Build the connection once, vary only name and scope per store.
|
||||
const connection: BedrockKnowledgeBaseConfig = {
|
||||
knowledgeBaseId: 'KB123',
|
||||
dataSourceType: 'CUSTOM',
|
||||
dataSourceId: 'DS456',
|
||||
}
|
||||
|
||||
const alice = new BedrockKnowledgeBaseStore({
|
||||
name: 'alice',
|
||||
writable: true,
|
||||
scope: 'user-alice',
|
||||
config: connection,
|
||||
})
|
||||
|
||||
const bob = new BedrockKnowledgeBaseStore({
|
||||
name: 'bob',
|
||||
writable: true,
|
||||
scope: 'user-bob',
|
||||
config: connection,
|
||||
})
|
||||
// --8<-- [end:scoped_stores]
|
||||
|
||||
void alice
|
||||
void bob
|
||||
}
|
||||
void scopedStores
|
||||
|
||||
// =====================
|
||||
// S3 data source
|
||||
// =====================
|
||||
|
||||
function s3Store() {
|
||||
// --8<-- [start:s3_store]
|
||||
const store = new BedrockKnowledgeBaseStore({
|
||||
name: 'preferences',
|
||||
writable: true,
|
||||
config: {
|
||||
knowledgeBaseId: 'KB123',
|
||||
dataSourceType: 'S3',
|
||||
dataSourceId: 'DS789',
|
||||
s3: { bucket: 'my-agent-memories', prefix: 'memories/' },
|
||||
},
|
||||
})
|
||||
// --8<-- [end:s3_store]
|
||||
|
||||
void store
|
||||
}
|
||||
void s3Store
|
||||
|
||||
// =====================
|
||||
// Search and add
|
||||
// =====================
|
||||
|
||||
async function searchAndAdd() {
|
||||
// --8<-- [start:search]
|
||||
const store = new BedrockKnowledgeBaseStore({
|
||||
name: 'preferences',
|
||||
writable: true,
|
||||
config: { knowledgeBaseId: 'KB123', dataSourceType: 'CUSTOM', dataSourceId: 'DS456' },
|
||||
})
|
||||
|
||||
const results = await store.search('what are my preferences?', { maxSearchResults: 5 })
|
||||
for (const entry of results) {
|
||||
console.log(entry.content, entry.metadata?._relevanceScore)
|
||||
}
|
||||
|
||||
// add returns the new document's id (a UUID for CUSTOM, an s3:// URI for S3)
|
||||
const { documentId } = await store.add('User prefers aisle seats', {
|
||||
category: 'travel',
|
||||
})
|
||||
// --8<-- [end:search]
|
||||
|
||||
void documentId
|
||||
}
|
||||
void searchAndAdd
|
||||
|
||||
// =====================
|
||||
// Extraction
|
||||
// =====================
|
||||
|
||||
function extraction() {
|
||||
// --8<-- [start:extraction]
|
||||
const store = new BedrockKnowledgeBaseStore({
|
||||
name: 'preferences',
|
||||
writable: true,
|
||||
extraction: true,
|
||||
config: { knowledgeBaseId: 'KB123', dataSourceType: 'CUSTOM', dataSourceId: 'DS456' },
|
||||
})
|
||||
// --8<-- [end:extraction]
|
||||
|
||||
void store
|
||||
}
|
||||
void extraction
|
||||
@@ -0,0 +1,29 @@
|
||||
// @ts-nocheck
|
||||
|
||||
// --8<-- [start:read_only_imports]
|
||||
import { Agent, BedrockModel } from '@strands-agents/sdk'
|
||||
import { BedrockKnowledgeBaseStore } from '@strands-agents/sdk/vended-memory-stores/bedrock-knowledge-base'
|
||||
// --8<-- [end:read_only_imports]
|
||||
|
||||
// --8<-- [start:writable_custom_imports]
|
||||
import { BedrockKnowledgeBaseStore } from '@strands-agents/sdk/vended-memory-stores/bedrock-knowledge-base'
|
||||
// --8<-- [end:writable_custom_imports]
|
||||
|
||||
// --8<-- [start:scoped_stores_imports]
|
||||
import {
|
||||
BedrockKnowledgeBaseStore,
|
||||
type BedrockKnowledgeBaseConfig,
|
||||
} from '@strands-agents/sdk/vended-memory-stores/bedrock-knowledge-base'
|
||||
// --8<-- [end:scoped_stores_imports]
|
||||
|
||||
// --8<-- [start:s3_store_imports]
|
||||
import { BedrockKnowledgeBaseStore } from '@strands-agents/sdk/vended-memory-stores/bedrock-knowledge-base'
|
||||
// --8<-- [end:s3_store_imports]
|
||||
|
||||
// --8<-- [start:search_imports]
|
||||
import { BedrockKnowledgeBaseStore } from '@strands-agents/sdk/vended-memory-stores/bedrock-knowledge-base'
|
||||
// --8<-- [end:search_imports]
|
||||
|
||||
// --8<-- [start:extraction_imports]
|
||||
import { BedrockKnowledgeBaseStore } from '@strands-agents/sdk/vended-memory-stores/bedrock-knowledge-base'
|
||||
// --8<-- [end:extraction_imports]
|
||||
@@ -0,0 +1,501 @@
|
||||
---
|
||||
title: Memory
|
||||
description: "Give agents long-term memory across sessions: store facts to configurable backends, recall them via tools or injection, and extract them automatically."
|
||||
tags: [memory]
|
||||
sidebar:
|
||||
badge:
|
||||
text: New
|
||||
variant: tip
|
||||
---
|
||||
|
||||
By default a Strands agent starts every conversation from zero: it cannot recall a user's preferences, past decisions, or anything it learned in an earlier session. The `MemoryManager` gives an agent long-term memory that persists across sessions.
|
||||
|
||||
It works through **memory stores**, the backends that hold the memories. A store can be a vector database, a managed service like [Amazon Bedrock Knowledge Bases](./bedrock-knowledge-base), or [your own implementation](#custom-stores). The manager handles three jobs across the stores you give it:
|
||||
|
||||
1. **Recall** - the agent searches stored knowledge on demand through a tool.
|
||||
2. **Injection** - the manager folds relevant knowledge into the prompt automatically, before the model runs.
|
||||
3. **Extraction** - turning conversation messages into memories and writing them to stores.
|
||||
|
||||
Recall and injection are enabled by default when you attach a store. Extraction and fact storage through tools is opt-in.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Attach a memory manager to an agent through the <Syntax py="memory_manager" ts="memoryManager" /> parameter. The examples below use a `store`, a `MemoryStore` you provide; see [Bedrock Knowledge Base](./bedrock-knowledge-base) for a managed backend or [Custom Stores](#custom-stores) to create your own.
|
||||
|
||||
<Tabs>
|
||||
<Tab label="Python">
|
||||
|
||||
```python
|
||||
from strands import Agent
|
||||
from strands.memory import MemoryManager
|
||||
|
||||
agent = Agent(memory_manager=MemoryManager(stores=[store]))
|
||||
```
|
||||
</Tab>
|
||||
<Tab label="TypeScript">
|
||||
|
||||
```typescript
|
||||
--8<-- "user-guide/concepts/memory/overview_imports.ts:getting_started_imports"
|
||||
|
||||
--8<-- "user-guide/concepts/memory/overview.ts:getting_started"
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
With no further configuration, reading through recall and injection is enabled. Writing is opt-in, and comes in two modes:
|
||||
|
||||
- **The `add_memory` tool** lets the agent decide what to save. Enable it on the manager with <Syntax py="add_tool_config=True" ts="addToolConfig: true" />.
|
||||
- **Automatic extraction** captures memories from the conversation without tool call. Enable it on a writable store, where it runs every 5 turns by default using your agent's model.
|
||||
|
||||
<Tabs>
|
||||
<Tab label="Python">
|
||||
|
||||
```python
|
||||
from strands import Agent
|
||||
from strands.memory import MemoryManager
|
||||
from strands.vended_memory_stores import BedrockKnowledgeBaseStore
|
||||
|
||||
store = BedrockKnowledgeBaseStore(
|
||||
name="preferences",
|
||||
writable=True,
|
||||
extraction=True, # capture memories from the conversation, every 5 turns
|
||||
config={"knowledge_base_id": "KB123", "data_source_type": "CUSTOM", "data_source_id": "DS456"},
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
memory_manager=MemoryManager(
|
||||
stores=[store],
|
||||
add_tool_config=True, # let the agent save memories itself
|
||||
),
|
||||
)
|
||||
```
|
||||
</Tab>
|
||||
<Tab label="TypeScript">
|
||||
|
||||
```typescript
|
||||
--8<-- "user-guide/concepts/memory/overview_imports.ts:turn_on_writes_imports"
|
||||
|
||||
--8<-- "user-guide/concepts/memory/overview.ts:turn_on_writes"
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
|
||||
## Stores
|
||||
|
||||
A manager can own several stores at once, which keeps multi-tenancy out of your application code. A single agent can query personal, team, and organization knowledge together, with each store scoped to its own tenant:
|
||||
|
||||
<Tabs>
|
||||
<Tab label="Python">
|
||||
|
||||
```python
|
||||
from strands import Agent
|
||||
from strands.memory import MemoryManager
|
||||
|
||||
# personal and team are two MemoryStore instances, each scoped to its own tenant.
|
||||
agent = Agent(memory_manager=MemoryManager(stores=[personal, team]))
|
||||
```
|
||||
</Tab>
|
||||
<Tab label="TypeScript">
|
||||
|
||||
```typescript
|
||||
--8<-- "user-guide/concepts/memory/overview_imports.ts:multi_store_imports"
|
||||
|
||||
--8<-- "user-guide/concepts/memory/overview.ts:multi_store"
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
Each store carries its own identity and behavior:
|
||||
|
||||
| Field | Purpose |
|
||||
|-------|---------|
|
||||
| `name` | Unique identifier, used to target the store from tools and the programmatic API. |
|
||||
| `description` | Human-readable summary, surfaced in the memory tool descriptions so the model knows what each store holds. |
|
||||
| <Syntax py="max_search_results" ts="maxSearchResults" /> | Default result cap per search when a caller does not pass one. The manager falls back to `3` if neither is set. |
|
||||
| `writable` | Whether the store accepts writes. |
|
||||
|
||||
The manager attaches each store's `name` to its results, so the model and your code can tell which store produced each entry and target follow-up queries.
|
||||
|
||||
## Memory Tools
|
||||
|
||||
The manager can register two tools the agent can call during the loop, both configurable. `search_memory` is registered for you; `add_memory` is opt-in:
|
||||
|
||||
<Tabs>
|
||||
<Tab label="Python">
|
||||
|
||||
```python
|
||||
from strands import Agent
|
||||
from strands.memory import MemoryManager
|
||||
from strands.memory.types import MemoryAddToolConfig, MemoryToolConfig
|
||||
|
||||
agent = Agent(
|
||||
memory_manager=MemoryManager(
|
||||
stores=[store],
|
||||
search_tool_config=MemoryToolConfig(
|
||||
name="recall",
|
||||
description="Look up what you remember about the user.",
|
||||
),
|
||||
# opt in, and return as soon as writes dispatch instead of awaiting them
|
||||
add_tool_config=MemoryAddToolConfig(wait_for_writes=False),
|
||||
),
|
||||
)
|
||||
```
|
||||
</Tab>
|
||||
<Tab label="TypeScript">
|
||||
|
||||
```typescript
|
||||
--8<-- "user-guide/concepts/memory/overview_imports.ts:search_tool_config_imports"
|
||||
|
||||
--8<-- "user-guide/concepts/memory/overview.ts:search_tool_config"
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
`search_memory` lets the agent recall knowledge on demand. Rename or re-describe it through its config, or turn it off. When the manager owns multiple stores, their names and descriptions are folded into the tool description so the model can target a specific store by name or search them all.
|
||||
|
||||
`add_memory` lets the agent write new memories. Enable it to allow writes to your writable stores, or pass a config to scope it to specific ones. By default it waits for writes so it can report failures back to the model. The fire-and-forget option returns as soon as writes are dispatched, so a slow backend never blocks the agent loop. This tool can only targets stores implementing <Syntax py="add" ts="add" />.
|
||||
|
||||
## Context Injection
|
||||
|
||||
Injection searches memory before a model call and folds the top results into the prompt, so relevant knowledge is present on every turn. It is **on by default**: the manager injects on a fresh user turn, retrieves up to 5 entries, derives the query adaptively from the latest user message, and renders the results as a `<memory>` block. Turn it off by disabling the injection config.
|
||||
|
||||
The injected text is **ephemeral by design**: it augments the model input for a single call and never persists into the durable conversation or session.
|
||||
|
||||
Customize retrieval, timing, and formatting with a config object:
|
||||
|
||||
<Tabs>
|
||||
<Tab label="Python">
|
||||
|
||||
```python
|
||||
from strands import Agent
|
||||
from strands.memory import MemoryManager
|
||||
from strands.memory.types import MemoryInjectionConfig
|
||||
|
||||
agent = Agent(
|
||||
memory_manager=MemoryManager(
|
||||
stores=[store],
|
||||
injection=MemoryInjectionConfig(
|
||||
trigger="everyTurn", # inject before every model call
|
||||
max_entries=3,
|
||||
format=lambda context: "\n".join(f"- {entry.content}" for entry in context.entries),
|
||||
),
|
||||
),
|
||||
)
|
||||
```
|
||||
</Tab>
|
||||
<Tab label="TypeScript">
|
||||
|
||||
```typescript
|
||||
--8<-- "user-guide/concepts/memory/overview_imports.ts:injection_custom_imports"
|
||||
|
||||
--8<-- "user-guide/concepts/memory/overview.ts:injection_custom"
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
- `trigger` accepts `'userTurn'` (the default, inject only on a fresh user ask), `'everyTurn'` (inject before every model call, for autonomous agents), or a predicate: a function that receives the injection context and returns whether to inject this call.
|
||||
- <Syntax py="max_entries" ts="maxEntries" /> caps how many entries are retrieved and injected.
|
||||
- `query` overrides the adaptive default with your own query logic. Return an empty value to skip injection for this call.
|
||||
- `format` renders the retrieved entries. The default emits an escaped `<memory>` block; a custom formatter that emits markup owns its own escaping.
|
||||
|
||||
Injection fails open: if the search fails or a callback throws, the manager logs it and proceeds with the model call uninjected. The agent runs without the memory context rather than erroring, so a backend outage degrades silently.
|
||||
|
||||
### The injection engine is generic
|
||||
|
||||
Memory injection is built on a reusable engine. For non-memory context (a clock, a sandbox descriptor, a fixed reminder), the same mechanism is exposed as the [`ContextInjector`](../plugins/context-injector) vended plugin: supply a render callback and it folds the result into the model input the same way.
|
||||
|
||||
## Automatic Extraction
|
||||
|
||||
Extraction captures memories from the conversation automatically, instead of relying on the agent to call the `add_memory` tool. Enable it on a writable store:
|
||||
|
||||
<Tabs>
|
||||
<Tab label="Python">
|
||||
|
||||
```python
|
||||
from strands.vended_memory_stores import BedrockKnowledgeBaseStore
|
||||
|
||||
# Set extraction on the store at construction; True uses the defaults.
|
||||
store = BedrockKnowledgeBaseStore(
|
||||
name="preferences",
|
||||
writable=True,
|
||||
extraction=True,
|
||||
config={"knowledge_base_id": "KB123", "data_source_type": "CUSTOM", "data_source_id": "DS456"},
|
||||
)
|
||||
```
|
||||
</Tab>
|
||||
<Tab label="TypeScript">
|
||||
|
||||
```typescript
|
||||
--8<-- "user-guide/concepts/memory/overview_imports.ts:extraction_defaults_imports"
|
||||
|
||||
--8<-- "user-guide/concepts/memory/overview.ts:extraction_defaults"
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
With defaults, extraction runs every 5 turns. For a store that implements only <Syntax py="add" ts="add" /> (like Bedrock Knowledge Bases), it uses a `ModelExtractor` to distill facts from the conversation; a store that implements <Syntax py="add_messages" ts="addMessages" /> extracts server-side instead, covered under [Custom Stores](#custom-stores).
|
||||
|
||||
### Triggers and extractors
|
||||
|
||||
An extraction config has two parts. A **trigger** decides *when* extraction runs; an **extractor** decides *how* messages become entries:
|
||||
|
||||
<Tabs>
|
||||
<Tab label="Python">
|
||||
|
||||
```python
|
||||
from strands.models import BedrockModel
|
||||
from strands.memory.extraction.triggers import InvocationTrigger
|
||||
from strands.memory.extraction.types import ExtractionConfig
|
||||
from strands.memory.extraction.model_extractor import ModelExtractor
|
||||
from strands.vended_memory_stores import BedrockKnowledgeBaseStore
|
||||
|
||||
store = BedrockKnowledgeBaseStore(
|
||||
name="preferences",
|
||||
writable=True,
|
||||
extraction=ExtractionConfig(
|
||||
trigger=InvocationTrigger(), # after every turn, not every 5
|
||||
extractor=ModelExtractor(
|
||||
model=BedrockModel(model_id="us.anthropic.claude-haiku-4-5-20251001-v1:0"), # cheaper than the agent's
|
||||
system_prompt="Extract durable user preferences as discrete facts.",
|
||||
),
|
||||
),
|
||||
config={"knowledge_base_id": "KB123", "data_source_type": "CUSTOM", "data_source_id": "DS456"},
|
||||
)
|
||||
```
|
||||
</Tab>
|
||||
<Tab label="TypeScript">
|
||||
|
||||
```typescript
|
||||
--8<-- "user-guide/concepts/memory/overview_imports.ts:extraction_custom_imports"
|
||||
|
||||
--8<-- "user-guide/concepts/memory/overview.ts:extraction_custom"
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
The `ModelExtractor` distills messages into discrete facts with a model call. It uses the agent's own model by default. Pass a cheaper model to cut cost, or a system prompt to steer what information you want to save as memories. Some backends extract server-side instead: a store that implements the <Syntax py="add_messages" ts="addMessages" /> sink receives the raw message batch with no model call, so for those you omit the extractor.
|
||||
|
||||
Two triggers ship with the SDK. `InvocationTrigger` runs after every turn, `IntervalTrigger` runs every N turns. For a custom trigger, extend `ExtractionTrigger`. A trigger registers a hook on the agent and calls `fire()` when extraction should run. Tying it to agent state can let a tool decide the moment, rather than extracting on a turn cadence:
|
||||
|
||||
<Tabs>
|
||||
<Tab label="Python">
|
||||
|
||||
```python
|
||||
from strands.memory.extraction.types import ExtractionConfig, ExtractionTrigger, ExtractionTriggerContext
|
||||
from strands.hooks import AfterInvocationEvent
|
||||
from strands.vended_memory_stores import BedrockKnowledgeBaseStore
|
||||
|
||||
|
||||
class CustomTrigger(ExtractionTrigger):
|
||||
name = "custom-trigger"
|
||||
|
||||
def attach(self, context: ExtractionTriggerContext) -> None:
|
||||
# Extract only after a tool has flagged extraction.
|
||||
def maybe_fire(event: AfterInvocationEvent) -> None:
|
||||
if context.agent.state.get("extract"):
|
||||
context.fire()
|
||||
|
||||
context.agent.add_hook(maybe_fire, AfterInvocationEvent)
|
||||
|
||||
|
||||
store = BedrockKnowledgeBaseStore(
|
||||
name="preferences",
|
||||
writable=True,
|
||||
extraction=ExtractionConfig(trigger=CustomTrigger()),
|
||||
config={"knowledge_base_id": "KB123", "data_source_type": "CUSTOM", "data_source_id": "DS456"},
|
||||
)
|
||||
```
|
||||
</Tab>
|
||||
<Tab label="TypeScript">
|
||||
|
||||
```typescript
|
||||
--8<-- "user-guide/concepts/memory/overview_imports.ts:custom_trigger_imports"
|
||||
|
||||
--8<-- "user-guide/concepts/memory/overview.ts:custom_trigger"
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
`fire()` runs the save in the background and returns immediately, so a trigger never blocks the agent loop. A trigger that never fires never extracts; for a guaranteed final write regardless of triggers, use the manager's flush method.
|
||||
|
||||
Extraction is at-least-once: a failed batch is retried, so the same entry may be written more than once. A store used with extraction should tolerate duplicate writes (the manager tracks a high-water mark per store, so a successful batch is never re-extracted).
|
||||
|
||||
### Flushing pending writes
|
||||
|
||||
Extraction writes run in the background and are not awaited by the agent loop, so the most recent turn may not be saved yet when the agent responds. The manager's flush method closes that gap. It forces every store to save its buffered messages, even a store whose trigger has not fired this turn or one currently backed off, and awaits all of those writes (including any that start while it waits). Awaiting it as part of a graceful shutdown guarantees nothing in the buffer is lost.
|
||||
|
||||
When to call it differs by SDK:
|
||||
|
||||
<Tabs>
|
||||
<Tab label="Python">
|
||||
|
||||
Whether you need to flush manually depends on how you call the agent:
|
||||
|
||||
- **`agent(...)`** (the synchronous call) runs each invocation in its own event loop. Closing that loop would cancel in-flight background saves, so this path awaits a flush after every invocation. Writes already persist by the time the call returns, and you never flush manually.
|
||||
- **`agent.invoke_async(...)`** and **`agent.stream_async(...)`** share your own long-lived event loop and do not flush. Extraction stays on its trigger cadence, so flush yourself at a shutdown boundary before the loop closes:
|
||||
|
||||
```python
|
||||
# After driving the agent with invoke_async / stream_async, before the loop closes.
|
||||
await memory_manager.flush()
|
||||
```
|
||||
</Tab>
|
||||
<Tab label="TypeScript">
|
||||
|
||||
The agent loop never flushes for you, on any call path. Await `flush()` as part of a graceful shutdown and every outstanding write lands before the process exits; skip it and the last turns' background writes are dropped:
|
||||
|
||||
```typescript
|
||||
--8<-- "user-guide/concepts/memory/overview.ts:flush"
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
This protects a graceful shutdown. A process killed without one (crash, `SIGKILL`, hard timeout) can still lose the last unsaved turn, since the flush never runs; a more frequent trigger narrows that window. Do not call `flush()` after every turn alongside a periodic trigger, since that forces a save each time and defeats the trigger's schedule.
|
||||
|
||||
## Programmatic Access
|
||||
|
||||
You can search and write directly on the memory manager, outside the agent loop. Both methods target all relevant stores by default, or a subset by name:
|
||||
|
||||
<Tabs>
|
||||
<Tab label="Python">
|
||||
|
||||
```python
|
||||
from strands.memory.types import MemoryAddOptions, MemorySearchOptions
|
||||
|
||||
# Search every store, or a subset by name.
|
||||
all_results = await memory_manager.search("travel plans")
|
||||
scoped = await memory_manager.search(
|
||||
"travel plans",
|
||||
MemorySearchOptions(stores=["personal"], max_search_results=5),
|
||||
)
|
||||
|
||||
# Write to writable stores, with metadata.
|
||||
await memory_manager.add(
|
||||
"Prefers aisle seats",
|
||||
MemoryAddOptions(stores=["personal"], metadata={"category": "travel"}),
|
||||
)
|
||||
```
|
||||
</Tab>
|
||||
<Tab label="TypeScript">
|
||||
|
||||
```typescript
|
||||
--8<-- "user-guide/concepts/memory/overview.ts:programmatic"
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
Partial failures are handled per method. `search` logs and skips any store that fails, returning whatever the rest produced. `add` validates the target stores first, then raises an aggregate error if any write fails, so a failed write is never silent.
|
||||
|
||||
## Custom Stores
|
||||
|
||||
Use the memory manager with any backend by implementing the `MemoryStore` interface. Only `search` is required; add an `add` method to make the store writable, and a tool method to expose backend-native tools alongside the manager's:
|
||||
|
||||
<Tabs>
|
||||
<Tab label="Python">
|
||||
|
||||
A store implements the config attributes (`name`, `writable`, and so on) plus an async `search`, and optionally `add`, `add_messages`, and `get_tools`:
|
||||
|
||||
```python
|
||||
from strands.memory.types import MemoryEntry, SearchOptions
|
||||
|
||||
|
||||
class InMemoryStore:
|
||||
name = "preferences"
|
||||
description = "User preferences and stable facts."
|
||||
max_search_results = None
|
||||
writable = True
|
||||
extraction = None
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._entries: list[str] = []
|
||||
|
||||
async def search(self, query: str, options: SearchOptions | None = None) -> list[MemoryEntry]:
|
||||
limit = (options and options.max_search_results) or 3
|
||||
matches = [content for content in self._entries if query in content]
|
||||
return [MemoryEntry(content=content) for content in matches[:limit]]
|
||||
|
||||
async def add(self, content: str, metadata: dict | None = None) -> None:
|
||||
self._entries.append(content)
|
||||
|
||||
|
||||
store = InMemoryStore()
|
||||
```
|
||||
</Tab>
|
||||
<Tab label="TypeScript">
|
||||
|
||||
A store implements `search`, and optionally `add`, `addMessages`, and `getTools`:
|
||||
|
||||
```typescript
|
||||
--8<-- "user-guide/concepts/memory/overview_imports.ts:custom_store_imports"
|
||||
|
||||
--8<-- "user-guide/concepts/memory/overview.ts:custom_store"
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
A store exposes two write paths, and which ones it implements decides how it can be written to:
|
||||
|
||||
- `add` takes a single piece of content. It backs the `add_memory` tool, the programmatic `add` method, and extraction that distills facts client-side with a `ModelExtractor`.
|
||||
- <Syntax py="add_messages" ts="addMessages" /> takes a batch of raw conversation messages. It backs **server-side extraction**: the manager hands the filtered message batch straight to this method with no client-side model call, so the backend does the distillation itself. The batch preserves the conversation's role structure.
|
||||
|
||||
A store can implement either path or both. The following store extracts server-side, delegating to `my_backend`, a stand-in for your managed backend's client:
|
||||
|
||||
<Tabs>
|
||||
<Tab label="Python">
|
||||
|
||||
```python
|
||||
from strands.memory.types import AddMessagesContext, MemoryEntry, SearchOptions
|
||||
from strands.types.content import Message
|
||||
|
||||
|
||||
class ServerSideStore:
|
||||
name = "preferences"
|
||||
description = "User preferences and stable facts."
|
||||
max_search_results = None
|
||||
writable = True
|
||||
extraction = True # extract every 5 turns; no extractor, so add_messages is used
|
||||
|
||||
async def search(self, query: str, options: SearchOptions | None = None) -> list[MemoryEntry]:
|
||||
return await my_backend.retrieve(query, options and options.max_search_results)
|
||||
|
||||
# The manager hands the raw message batch here; the backend extracts server-side.
|
||||
async def add_messages(
|
||||
self, messages: list[Message], context: AddMessagesContext | None = None
|
||||
) -> None:
|
||||
await my_backend.ingest_conversation(messages)
|
||||
```
|
||||
</Tab>
|
||||
<Tab label="TypeScript">
|
||||
|
||||
```typescript
|
||||
--8<-- "user-guide/concepts/memory/overview_imports.ts:server_side_store_imports"
|
||||
|
||||
--8<-- "user-guide/concepts/memory/overview.ts:server_side_store"
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
For a reference implementation backed by a managed service, see the [Bedrock Knowledge Base store](./bedrock-knowledge-base), which extracts client-side.
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Choose how the agent reads.** The `search_memory` tool lets the model pull knowledge when it judges it needs it; injection guarantees relevant context on every user turn. They compose: enable both for a guaranteed baseline plus on-demand depth.
|
||||
- **Match extraction cadence to cost.** An every-turn trigger with a model extractor means a model call per turn. That is a token cost, not a latency cost, since extraction runs in the background; an interval trigger lowers it, and turns skipped between runs are still captured when the trigger next fires.
|
||||
- **Scope stores for multi-tenancy.** Give each tenant its own scoped store rather than mixing knowledge in one (see [Bedrock Knowledge Base](./bedrock-knowledge-base)).
|
||||
- **Give the agent context.** Add a meaningful description to each store, so the agent knows what type of knowledge each store contains.
|
||||
- **Tolerate duplicate writes** in custom stores used with extraction, since failed batches are retried.
|
||||
|
||||
## How Memory Relates to Other Strands Constructs
|
||||
|
||||
Three SDK features manage different kinds of state; memory is the one that crosses sessions:
|
||||
|
||||
- [Session management](../agents/session-management) persists the full conversation so an agent can resume where it left off.
|
||||
- [Conversation management](../agents/conversation-management) keeps the conversation within the model's context window during a session.
|
||||
- **Memory** carries durable knowledge *across* sessions, without replaying past conversations.
|
||||
|
||||
## Related
|
||||
|
||||
- [Bedrock Knowledge Base store](./bedrock-knowledge-base) - the vended `MemoryStore` backed by Amazon Bedrock Knowledge Bases.
|
||||
- [Context Injector](../plugins/context-injector) - the generic injection plugin that memory injection builds on.
|
||||
- [Session management](../agents/session-management) - persist the conversation itself across restarts.
|
||||
- [Conversation management](../agents/conversation-management) - keep a session within the model's context window.
|
||||
@@ -0,0 +1,331 @@
|
||||
import {
|
||||
Agent,
|
||||
MemoryManager,
|
||||
InvocationTrigger,
|
||||
ModelExtractor,
|
||||
BedrockModel,
|
||||
ExtractionTrigger,
|
||||
AfterInvocationEvent,
|
||||
} from '@strands-agents/sdk'
|
||||
import type {
|
||||
MemoryStore,
|
||||
MemoryEntry,
|
||||
SearchOptions,
|
||||
MessageData,
|
||||
AddMessagesContext,
|
||||
ExtractionTriggerContext,
|
||||
} from '@strands-agents/sdk'
|
||||
import {
|
||||
BedrockKnowledgeBaseStore,
|
||||
type BedrockKnowledgeBaseConfig,
|
||||
} from '@strands-agents/sdk/vended-memory-stores/bedrock-knowledge-base'
|
||||
|
||||
// Stand-in for the reader's own managed backend, used by the server-side store example.
|
||||
declare const myBackend: {
|
||||
retrieve(query: string, limit?: number): Promise<MemoryEntry[]>
|
||||
ingestConversation(messages: MessageData[]): Promise<void>
|
||||
}
|
||||
|
||||
// =====================
|
||||
// Getting Started (minimal happy path)
|
||||
// =====================
|
||||
|
||||
function gettingStarted() {
|
||||
// --8<-- [start:getting_started]
|
||||
const store = new BedrockKnowledgeBaseStore({
|
||||
name: 'preferences',
|
||||
description: 'User preferences and stable facts about the user.',
|
||||
writable: true,
|
||||
config: { knowledgeBaseId: 'KB123', dataSourceType: 'CUSTOM', dataSourceId: 'DS456' },
|
||||
})
|
||||
|
||||
const agent = new Agent({
|
||||
model: new BedrockModel(),
|
||||
memoryManager: { stores: [store] },
|
||||
})
|
||||
// --8<-- [end:getting_started]
|
||||
|
||||
void agent
|
||||
}
|
||||
void gettingStarted
|
||||
|
||||
// =====================
|
||||
// Turn on writes: add_memory tool + extraction, both with defaults
|
||||
// =====================
|
||||
|
||||
function turnOnWrites() {
|
||||
// --8<-- [start:turn_on_writes]
|
||||
const store = new BedrockKnowledgeBaseStore({
|
||||
name: 'preferences',
|
||||
writable: true,
|
||||
extraction: true, // capture memories from the conversation, every 5 turns
|
||||
config: { knowledgeBaseId: 'KB123', dataSourceType: 'CUSTOM', dataSourceId: 'DS456' },
|
||||
})
|
||||
|
||||
const agent = new Agent({
|
||||
model: new BedrockModel(),
|
||||
memoryManager: {
|
||||
stores: [store],
|
||||
addToolConfig: true, // let the agent save memories itself
|
||||
},
|
||||
})
|
||||
// --8<-- [end:turn_on_writes]
|
||||
|
||||
void agent
|
||||
}
|
||||
void turnOnWrites
|
||||
|
||||
// =====================
|
||||
// Multiple stores
|
||||
// =====================
|
||||
|
||||
function multiStore() {
|
||||
// --8<-- [start:multi_store]
|
||||
// Build the connection once, vary only name and scope per store.
|
||||
const connection: BedrockKnowledgeBaseConfig = {
|
||||
knowledgeBaseId: 'KB123',
|
||||
dataSourceType: 'CUSTOM',
|
||||
dataSourceId: 'DS456',
|
||||
}
|
||||
|
||||
const personal = new BedrockKnowledgeBaseStore({
|
||||
name: 'personal',
|
||||
description: 'Knowledge specific to this user.',
|
||||
writable: true,
|
||||
scope: 'user-abc',
|
||||
config: connection,
|
||||
})
|
||||
|
||||
const team = new BedrockKnowledgeBaseStore({
|
||||
name: 'team',
|
||||
description: 'Shared team knowledge.',
|
||||
scope: 'team-xyz',
|
||||
config: connection,
|
||||
})
|
||||
|
||||
const agent = new Agent({
|
||||
model: new BedrockModel(),
|
||||
memoryManager: { stores: [personal, team] },
|
||||
})
|
||||
// --8<-- [end:multi_store]
|
||||
|
||||
void agent
|
||||
}
|
||||
void multiStore
|
||||
|
||||
// =====================
|
||||
// Search tool configuration
|
||||
// =====================
|
||||
|
||||
function searchToolConfig() {
|
||||
// --8<-- [start:search_tool_config]
|
||||
const store = new BedrockKnowledgeBaseStore({
|
||||
name: 'preferences',
|
||||
config: { knowledgeBaseId: 'KB123' },
|
||||
})
|
||||
|
||||
const agent = new Agent({
|
||||
model: new BedrockModel(),
|
||||
memoryManager: {
|
||||
stores: [store],
|
||||
searchToolConfig: {
|
||||
name: 'recall',
|
||||
description: 'Look up what you remember about the user.',
|
||||
},
|
||||
// add_memory: opt in, and return as soon as writes dispatch instead of awaiting them
|
||||
addToolConfig: { waitForWrites: false },
|
||||
},
|
||||
})
|
||||
// --8<-- [end:search_tool_config]
|
||||
|
||||
void agent
|
||||
}
|
||||
void searchToolConfig
|
||||
|
||||
// =====================
|
||||
// Programmatic search and add
|
||||
// =====================
|
||||
|
||||
async function programmatic(memoryManager: MemoryManager) {
|
||||
// --8<-- [start:programmatic]
|
||||
// Search every store, or a subset by name.
|
||||
const all = await memoryManager.search('travel plans')
|
||||
const scoped = await memoryManager.search('travel plans', {
|
||||
stores: ['personal'],
|
||||
maxSearchResults: 5,
|
||||
})
|
||||
|
||||
// Write to writable stores, with metadata.
|
||||
await memoryManager.add('Prefers aisle seats', {
|
||||
stores: ['personal'],
|
||||
metadata: { category: 'travel' },
|
||||
})
|
||||
// --8<-- [end:programmatic]
|
||||
|
||||
void all
|
||||
void scoped
|
||||
}
|
||||
void programmatic
|
||||
|
||||
// =====================
|
||||
// Flush pending writes at shutdown
|
||||
// =====================
|
||||
|
||||
async function flushExample(memoryManager: MemoryManager) {
|
||||
// --8<-- [start:flush]
|
||||
// At a shutdown boundary you control, before the process exits.
|
||||
process.on('beforeExit', async () => {
|
||||
await memoryManager.flush()
|
||||
})
|
||||
// --8<-- [end:flush]
|
||||
}
|
||||
void flushExample
|
||||
|
||||
// =====================
|
||||
// Extraction: enable with defaults
|
||||
// =====================
|
||||
|
||||
function extractionDefaults() {
|
||||
// --8<-- [start:extraction_defaults]
|
||||
const store = new BedrockKnowledgeBaseStore({
|
||||
name: 'preferences',
|
||||
writable: true,
|
||||
extraction: true, // extract every 5 turns with a ModelExtractor
|
||||
config: { knowledgeBaseId: 'KB123', dataSourceType: 'CUSTOM', dataSourceId: 'DS456' },
|
||||
})
|
||||
// --8<-- [end:extraction_defaults]
|
||||
|
||||
void store
|
||||
}
|
||||
void extractionDefaults
|
||||
|
||||
// =====================
|
||||
// Extraction: custom trigger and extractor
|
||||
// =====================
|
||||
|
||||
function extractionCustom() {
|
||||
// --8<-- [start:extraction_custom]
|
||||
const store = new BedrockKnowledgeBaseStore({
|
||||
name: 'preferences',
|
||||
writable: true,
|
||||
extraction: {
|
||||
trigger: new InvocationTrigger(), // after every turn, not every 5
|
||||
extractor: new ModelExtractor({
|
||||
model: new BedrockModel(), // a cheaper model than the agent's to cut cost
|
||||
systemPrompt: 'Extract durable user preferences as discrete facts.',
|
||||
}),
|
||||
},
|
||||
config: { knowledgeBaseId: 'KB123', dataSourceType: 'CUSTOM', dataSourceId: 'DS456' },
|
||||
})
|
||||
// --8<-- [end:extraction_custom]
|
||||
|
||||
void store
|
||||
}
|
||||
void extractionCustom
|
||||
|
||||
// =====================
|
||||
// Extraction: custom trigger
|
||||
// =====================
|
||||
|
||||
// --8<-- [start:custom_trigger]
|
||||
// Extract only after a tool has flagged extraction
|
||||
class CustomTrigger extends ExtractionTrigger {
|
||||
readonly name = 'custom-trigger'
|
||||
|
||||
attach(context: ExtractionTriggerContext): void {
|
||||
context.agent.addHook(AfterInvocationEvent, () => {
|
||||
if (context.agent.appState.get('extract')) {
|
||||
context.fire()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
// --8<-- [end:custom_trigger]
|
||||
|
||||
void CustomTrigger
|
||||
|
||||
// =====================
|
||||
// Injection: customized
|
||||
// =====================
|
||||
|
||||
function injectionCustom() {
|
||||
// --8<-- [start:injection_custom]
|
||||
const store = new BedrockKnowledgeBaseStore({
|
||||
name: 'preferences',
|
||||
config: { knowledgeBaseId: 'KB123' },
|
||||
})
|
||||
|
||||
const agent = new Agent({
|
||||
model: new BedrockModel(),
|
||||
memoryManager: {
|
||||
stores: [store],
|
||||
injection: {
|
||||
// 'userTurn' (default), 'everyTurn', or a predicate over the conversation
|
||||
trigger: ({ messages }) => messages.length >= 4,
|
||||
maxEntries: 3,
|
||||
query: ({ messages }: { messages: MessageData[] }) => {
|
||||
const block = messages.at(-1)?.content[0]
|
||||
return block && 'text' in block ? block.text : undefined
|
||||
},
|
||||
format: ({ entries }) => entries.map((entry) => `- ${entry.content}`).join('\n'),
|
||||
},
|
||||
},
|
||||
})
|
||||
// --8<-- [end:injection_custom]
|
||||
|
||||
void agent
|
||||
}
|
||||
void injectionCustom
|
||||
|
||||
// =====================
|
||||
// Custom store
|
||||
// =====================
|
||||
|
||||
// --8<-- [start:custom_store]
|
||||
class InMemoryStore implements MemoryStore {
|
||||
readonly name = 'preferences'
|
||||
readonly writable = true
|
||||
private readonly _entries: string[] = []
|
||||
|
||||
async search(query: string, options?: SearchOptions): Promise<MemoryEntry[]> {
|
||||
const limit = options?.maxSearchResults ?? 3
|
||||
return this._entries
|
||||
.filter((content) => content.includes(query))
|
||||
.slice(0, limit)
|
||||
.map((content) => ({ content }))
|
||||
}
|
||||
|
||||
async add(content: string): Promise<void> {
|
||||
this._entries.push(content)
|
||||
}
|
||||
}
|
||||
// --8<-- [end:custom_store]
|
||||
|
||||
void InMemoryStore
|
||||
|
||||
// =====================
|
||||
// Server-side extraction store (addMessages sink)
|
||||
// =====================
|
||||
|
||||
// --8<-- [start:server_side_store]
|
||||
class ServerSideStore implements MemoryStore {
|
||||
readonly name = 'preferences'
|
||||
readonly writable = true
|
||||
// Extract every 5 turns; no extractor, so the manager calls addMessages.
|
||||
readonly extraction = true
|
||||
|
||||
async search(query: string, options?: SearchOptions): Promise<MemoryEntry[]> {
|
||||
return myBackend.retrieve(query, options?.maxSearchResults)
|
||||
}
|
||||
|
||||
// The manager hands the raw message batch here; the backend extracts server-side.
|
||||
async addMessages(
|
||||
messages: MessageData[],
|
||||
context?: AddMessagesContext
|
||||
): Promise<void> {
|
||||
await myBackend.ingestConversation(messages)
|
||||
}
|
||||
}
|
||||
// --8<-- [end:server_side_store]
|
||||
|
||||
void ServerSideStore
|
||||
@@ -0,0 +1,57 @@
|
||||
// @ts-nocheck
|
||||
|
||||
// --8<-- [start:getting_started_imports]
|
||||
import { Agent, BedrockModel } from '@strands-agents/sdk'
|
||||
import { BedrockKnowledgeBaseStore } from '@strands-agents/sdk/vended-memory-stores/bedrock-knowledge-base'
|
||||
// --8<-- [end:getting_started_imports]
|
||||
|
||||
// --8<-- [start:turn_on_writes_imports]
|
||||
import { Agent, BedrockModel } from '@strands-agents/sdk'
|
||||
import { BedrockKnowledgeBaseStore } from '@strands-agents/sdk/vended-memory-stores/bedrock-knowledge-base'
|
||||
// --8<-- [end:turn_on_writes_imports]
|
||||
|
||||
// --8<-- [start:multi_store_imports]
|
||||
import { Agent, BedrockModel } from '@strands-agents/sdk'
|
||||
import {
|
||||
BedrockKnowledgeBaseStore,
|
||||
type BedrockKnowledgeBaseConfig,
|
||||
} from '@strands-agents/sdk/vended-memory-stores/bedrock-knowledge-base'
|
||||
// --8<-- [end:multi_store_imports]
|
||||
|
||||
// --8<-- [start:search_tool_config_imports]
|
||||
import { Agent, BedrockModel } from '@strands-agents/sdk'
|
||||
import { BedrockKnowledgeBaseStore } from '@strands-agents/sdk/vended-memory-stores/bedrock-knowledge-base'
|
||||
// --8<-- [end:search_tool_config_imports]
|
||||
|
||||
// --8<-- [start:extraction_defaults_imports]
|
||||
import { BedrockKnowledgeBaseStore } from '@strands-agents/sdk/vended-memory-stores/bedrock-knowledge-base'
|
||||
// --8<-- [end:extraction_defaults_imports]
|
||||
|
||||
// --8<-- [start:extraction_custom_imports]
|
||||
import { InvocationTrigger, ModelExtractor, BedrockModel } from '@strands-agents/sdk'
|
||||
import { BedrockKnowledgeBaseStore } from '@strands-agents/sdk/vended-memory-stores/bedrock-knowledge-base'
|
||||
// --8<-- [end:extraction_custom_imports]
|
||||
|
||||
// --8<-- [start:custom_trigger_imports]
|
||||
import { ExtractionTrigger, AfterInvocationEvent } from '@strands-agents/sdk'
|
||||
import type { ExtractionTriggerContext } from '@strands-agents/sdk'
|
||||
// --8<-- [end:custom_trigger_imports]
|
||||
|
||||
// --8<-- [start:injection_custom_imports]
|
||||
import { Agent, BedrockModel, type MessageData } from '@strands-agents/sdk'
|
||||
import { BedrockKnowledgeBaseStore } from '@strands-agents/sdk/vended-memory-stores/bedrock-knowledge-base'
|
||||
// --8<-- [end:injection_custom_imports]
|
||||
|
||||
// --8<-- [start:custom_store_imports]
|
||||
import type { MemoryStore, MemoryEntry, SearchOptions } from '@strands-agents/sdk'
|
||||
// --8<-- [end:custom_store_imports]
|
||||
|
||||
// --8<-- [start:server_side_store_imports]
|
||||
import type {
|
||||
MemoryStore,
|
||||
MemoryEntry,
|
||||
SearchOptions,
|
||||
MessageData,
|
||||
AddMessagesContext,
|
||||
} from '@strands-agents/sdk'
|
||||
// --8<-- [end:server_side_store_imports]
|
||||
@@ -0,0 +1,120 @@
|
||||
---
|
||||
title: Context Injector
|
||||
description: "Fold real-time text into the model input before each call with the ContextInjector plugin: a clock, environment facts, or a lookup, not stored in history."
|
||||
tags: [token-management]
|
||||
sidebar:
|
||||
badge:
|
||||
text: New
|
||||
variant: tip
|
||||
---
|
||||
|
||||
The `ContextInjector` plugin folds real-time text into the model input before each call. The text is added to that single call's input only: it is never written to the conversation history, so it is not persisted or replayed on later turns. Use it for context the agent should always have but that does not belong in the stored history: the current time, a sandbox descriptor, environment facts, or a retrieval lookup.
|
||||
|
||||
## How It Works
|
||||
|
||||
A `ContextInjector` has two parts. The callback function decides **what** text to fold into the next model call's input. The trigger decides **when** the callback is called. The injected text is **ephemeral by design**: it augments the model input for that one call and never persists into the durable conversation or session.
|
||||
|
||||
This is the same injection mechanism that powers [memory context injection](../memory/overview#context-injection). `ContextInjector` is the generic surface for any consumer.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Pass a `ContextInjector` to your agent's `plugins` list with a callback that renders the text to inject. By default it injects only on a fresh user turn, the common case for chat agents:
|
||||
|
||||
<Tabs>
|
||||
<Tab label="Python">
|
||||
|
||||
```python
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from strands import Agent
|
||||
from strands.vended_plugins.context_injector import ContextInjector
|
||||
|
||||
agent = Agent(
|
||||
plugins=[
|
||||
ContextInjector(lambda context: f"<now>{datetime.now(timezone.utc).isoformat()}</now>"),
|
||||
],
|
||||
)
|
||||
```
|
||||
</Tab>
|
||||
<Tab label="TypeScript">
|
||||
|
||||
```typescript
|
||||
--8<-- "user-guide/concepts/plugins/context-injector_imports.ts:getting_started_imports"
|
||||
|
||||
--8<-- "user-guide/concepts/plugins/context-injector.ts:getting_started"
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## When to Inject
|
||||
|
||||
The trigger controls when the callback runs:
|
||||
|
||||
- `'userTurn'` (the default) injects only when the latest message is a fresh user ask, a user message carrying no tool result. This is the common case for chat agents.
|
||||
- `'everyTurn'` injects before every model call, including mid-task tool-result turns. Use it for autonomous agents that should consult the injected context at each step.
|
||||
|
||||
<Tabs>
|
||||
<Tab label="Python">
|
||||
|
||||
```python
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from strands.vended_plugins.context_injector import ContextInjector
|
||||
|
||||
clock = ContextInjector(
|
||||
lambda context: f"<now>{datetime.now(timezone.utc).isoformat()}</now>",
|
||||
name="clock",
|
||||
trigger="everyTurn", # inject before every model call, not just fresh user asks
|
||||
)
|
||||
```
|
||||
</Tab>
|
||||
<Tab label="TypeScript">
|
||||
|
||||
```typescript
|
||||
--8<-- "user-guide/concepts/plugins/context-injector_imports.ts:every_turn_imports"
|
||||
|
||||
--8<-- "user-guide/concepts/plugins/context-injector.ts:every_turn"
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
For finer control, pass a predicate: a function that receives the injection context and returns whether to inject this call. The context carries the current messages, durable state shared across calls via <Syntax py="context.state" ts="context.appState" />, and the agent. A predicate that throws fails open, so the model call still proceeds:
|
||||
|
||||
<Tabs>
|
||||
<Tab label="Python">
|
||||
|
||||
```python
|
||||
from strands.vended_plugins.context_injector import ContextInjector
|
||||
|
||||
injector = ContextInjector(
|
||||
lambda context: f"<context>{len(context.messages)} turns so far</context>",
|
||||
# Inject only when a tool stashed a flag in agent state last turn.
|
||||
trigger=lambda context: context.state.get("recall_enabled") is True,
|
||||
)
|
||||
```
|
||||
</Tab>
|
||||
<Tab label="TypeScript">
|
||||
|
||||
```typescript
|
||||
--8<-- "user-guide/concepts/plugins/context-injector_imports.ts:predicate_imports"
|
||||
|
||||
--8<-- "user-guide/concepts/plugins/context-injector.ts:predicate"
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Configuration
|
||||
|
||||
| Field | Purpose |
|
||||
|-------|---------|
|
||||
| <Syntax py="render_content" ts="renderContent" /> | Returns the text to inject for this call, or <Syntax py="None / empty string" ts="undefined / empty string" /> to skip. Required, and the only positional argument. |
|
||||
| `trigger` | `'userTurn'` (default), `'everyTurn'`, or a predicate over the injection context. |
|
||||
| `name` | Plugin name for logging and duplicate detection. Defaults to `'strands:context-injector'`. Set a distinct name when registering more than one. |
|
||||
|
||||
:::caution[Injected text reaches the model verbatim]
|
||||
The rendered text is a prompt-injection surface. If it interpolates attacker-influenced data (tool output, user-derived state), escape it yourself before returning. A callback that throws fails open: injection is skipped and the model call proceeds.
|
||||
:::
|
||||
|
||||
## Related
|
||||
|
||||
- [Memory](../memory/overview#context-injection) builds on this engine to inject retrieved knowledge before a model call.
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Agent } from '@strands-agents/sdk'
|
||||
import { ContextInjector } from '@strands-agents/sdk/vended-plugins/context-injector'
|
||||
import type { InjectionContext } from '@strands-agents/sdk/vended-plugins/context-injector'
|
||||
|
||||
// =====================
|
||||
// Getting Started
|
||||
// =====================
|
||||
|
||||
function gettingStarted() {
|
||||
// --8<-- [start:getting_started]
|
||||
const agent = new Agent({
|
||||
plugins: [
|
||||
new ContextInjector({
|
||||
renderContent: async () => `<now>${new Date().toISOString()}</now>`,
|
||||
}),
|
||||
],
|
||||
})
|
||||
// --8<-- [end:getting_started]
|
||||
|
||||
void agent
|
||||
}
|
||||
void gettingStarted
|
||||
|
||||
// =====================
|
||||
// Inject before every turn
|
||||
// =====================
|
||||
|
||||
function everyTurn() {
|
||||
// --8<-- [start:every_turn]
|
||||
const clock = new ContextInjector({
|
||||
name: 'clock',
|
||||
trigger: 'everyTurn', // inject before every model call, not just fresh user asks
|
||||
renderContent: async () => `<now>${new Date().toISOString()}</now>`,
|
||||
})
|
||||
// --8<-- [end:every_turn]
|
||||
|
||||
void clock
|
||||
}
|
||||
void everyTurn
|
||||
|
||||
// =====================
|
||||
// Predicate trigger reading app state
|
||||
// =====================
|
||||
|
||||
function predicate() {
|
||||
// --8<-- [start:predicate]
|
||||
const injector = new ContextInjector({
|
||||
// Inject only when a tool stashed a flag in app state last turn.
|
||||
trigger: ({ appState }: InjectionContext) => appState.get('recallEnabled') === true,
|
||||
renderContent: async ({ messages }) =>
|
||||
`<context>${messages.length} turns so far</context>`,
|
||||
})
|
||||
// --8<-- [end:predicate]
|
||||
|
||||
void injector
|
||||
}
|
||||
void predicate
|
||||
@@ -0,0 +1,15 @@
|
||||
// @ts-nocheck
|
||||
|
||||
// --8<-- [start:getting_started_imports]
|
||||
import { Agent } from '@strands-agents/sdk'
|
||||
import { ContextInjector } from '@strands-agents/sdk/vended-plugins/context-injector'
|
||||
// --8<-- [end:getting_started_imports]
|
||||
|
||||
// --8<-- [start:every_turn_imports]
|
||||
import { ContextInjector } from '@strands-agents/sdk/vended-plugins/context-injector'
|
||||
// --8<-- [end:every_turn_imports]
|
||||
|
||||
// --8<-- [start:predicate_imports]
|
||||
import { ContextInjector } from '@strands-agents/sdk/vended-plugins/context-injector'
|
||||
import type { InjectionContext } from '@strands-agents/sdk/vended-plugins/context-injector'
|
||||
// --8<-- [end:predicate_imports]
|
||||
@@ -11,6 +11,7 @@ The Strands SDK provides built-in plugins that you can use out of the box:
|
||||
- **[Skills](./skills.mdx)** - On-demand, modular instructions that agents discover and activate at runtime following the [Agent Skills specification](https://agentskills.io/specification)
|
||||
- **[Steering](./steering)** - Modular prompting for complex agent tasks through context-aware guidance
|
||||
- **[Context Offloader](./context-offloader)** - Proactively offloads oversized tool results to storage, replacing them with previews and providing a built-in retrieval tool
|
||||
- **[Context Injector](./context-injector)** - Folds real-time text (a clock, environment facts, a lookup) into the model input before each call, without persisting it to history
|
||||
|
||||
You can also build and distribute your own plugins to extend agent functionality. See [Get Featured](/docs/community/get-featured) to share your plugins with the community.
|
||||
|
||||
@@ -302,4 +303,5 @@ class AsyncConfigPlugin(Plugin):
|
||||
- [Hooks](../agents/hooks.mdx) - Learn about the underlying hook system
|
||||
- [Steering](./steering) - Explore the built-in steering plugin
|
||||
- [Context Offloader](./context-offloader) - Manage large tool results proactively
|
||||
- [Context Injector](./context-injector) - Inject real-time context into the model input
|
||||
- [Get Featured](/docs/community/get-featured) - Share your plugins with the community
|
||||
|
||||
+6
-1
@@ -2,11 +2,14 @@ import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import yaml from 'js-yaml'
|
||||
|
||||
// A badge rendered next to a sidebar label. A bare string is shorthand for the default variant.
|
||||
type SidebarBadge = string | { text: string; variant?: 'note' | 'tip' | 'caution' | 'danger' | 'success' | 'default' }
|
||||
|
||||
// Starlight sidebar item types
|
||||
export type StarlightSidebarItem =
|
||||
| { slug: string; label?: string; attrs?: Record<string, string> } // Internal link
|
||||
| { label: string; link: string; attrs?: Record<string, string> } // External link
|
||||
| { label: string; items: StarlightSidebarItem[]; collapsed?: boolean } // Group
|
||||
| { label: string; items: StarlightSidebarItem[]; collapsed?: boolean; badge?: SidebarBadge } // Group
|
||||
|
||||
// Navigation config types
|
||||
interface NavConfigItem {
|
||||
@@ -14,6 +17,7 @@ interface NavConfigItem {
|
||||
items?: NavConfigItem[]
|
||||
slug?: string // For labeled leaf items "Adding Tools"
|
||||
collapsed?: boolean // Explicit collapse state for groups (overrides auto-collapse)
|
||||
badge?: SidebarBadge // Badge on a group label (leaf badges come from page frontmatter)
|
||||
}
|
||||
type NavConfigEntry = string | NavConfigItem
|
||||
|
||||
@@ -86,6 +90,7 @@ function convertConfigItem(item: NavConfigEntry, ctx: ConvertContext): Starlight
|
||||
label: item.label,
|
||||
items: children,
|
||||
...(typeof item.collapsed === 'boolean' && { collapsed: item.collapsed }),
|
||||
...(item.badge !== undefined && { badge: item.badge }),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user