Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ You can keep both subprojects in the same repo if your package ships SDKs in bot

## What's in each template

Both templates expose the same seven Strands extension points so you can pick whichever fits your use case:
Both templates expose the same eight Strands extension points so you can pick whichever fits your use case:

| Component | Purpose |
|----------------------|--------------------------------------------------------|
Expand All @@ -45,6 +45,7 @@ Both templates expose the same seven Strands extension points so you can pick wh
| Session manager | Persist conversations across restarts |
| Conversation manager | Control context window and message history |
| Memory store | Give agents cross-session knowledge via a search backend |
| Storage | Persist raw bytes under string keys for sessions, offloading, etc. |

The interactive setup script in each subdirectory removes the components you don't select, renames everything to your package name, and wires up `pyproject.toml` / `package.json` accordingly.

Expand Down
8 changes: 8 additions & 0 deletions python/setup_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@
"test_files": ["test_memory_store.py"],
"exports": ["TemplateMemoryStore"],
},
"storage": {
"name": "Storage",
"description": "Persist raw bytes under string keys for sessions, offloading, etc.",
"files": ["storage.py"],
"test_files": ["test_storage.py"],
"exports": ["TemplateStorage"],
},
}


Expand Down Expand Up @@ -437,6 +444,7 @@ def main() -> None:
"TemplateSessionManager": f"{pascal_name}SessionManager",
"TemplateConversationManager": f"{pascal_name}ConversationManager",
"TemplateMemoryStore": f"{pascal_name}MemoryStore",
"TemplateStorage": f"{pascal_name}Storage",
# Function names
"template_tool": f"{snake_name}_tool",
# Plugin name
Expand Down
2 changes: 2 additions & 0 deletions python/src/strands_template/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from strands_template.model import TemplateModel
from strands_template.plugin import TemplatePlugin
from strands_template.session_manager import TemplateSessionManager
from strands_template.storage import TemplateStorage
from strands_template.tool import template_tool

__all__ = [
Expand All @@ -16,4 +17,5 @@
"TemplateModel",
"TemplatePlugin",
"TemplateSessionManager",
"TemplateStorage",
]
72 changes: 72 additions & 0 deletions python/src/strands_template/storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""Storage Implementation."""

import builtins
import logging

logger = logging.getLogger(__name__)


class TemplateStorage:
"""Template storage backend implementation.

Storage backends persist raw bytes under string keys. The SDK uses the
Storage interface for session snapshots, context offloading, and any
construct that needs durable key-value persistence.

Example:
```python
from strands import Agent
from strands.session import SessionManager
from strands_template import TemplateStorage

storage = TemplateStorage()
agent = Agent(session_manager=SessionManager(storage=storage))
```
"""

def __init__(self) -> None:
"""Initialize the storage backend."""
# TODO: Add backend-specific constructor arguments.

async def write(self, key: str, data: bytes) -> None:
"""Store data under key, overwriting any existing value.

Args:
key: Opaque, '/'-separated key identifying the value.
data: Raw bytes to persist.
"""
# TODO: Persist the data to your backend.
raise NotImplementedError

async def read(self, key: str) -> bytes | None:
"""Retrieve the bytes previously stored under key.

Args:
key: The key to read.

Returns:
The stored bytes, or None if no value exists for key.
"""
# TODO: Read from your backend, return None if not found.
raise NotImplementedError

async def delete(self, key: str) -> None:
"""Delete the value stored under key. A no-op if the key does not exist.

Args:
key: The key to delete.
"""
# TODO: Delete from your backend.
raise NotImplementedError

async def list(self, query: str = "") -> builtins.list[str]:
"""List keys matching the given prefix query.

Args:
query: A prefix string to filter keys. Empty string matches all.

Returns:
Matching keys sorted ascending.
"""
# TODO: Query your backend for keys matching the prefix.
raise NotImplementedError
17 changes: 17 additions & 0 deletions python/tests/test_storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Tests for TemplateStorage."""

from strands.storage import Storage

from strands_template import TemplateStorage


def test_template_storage_init():
"""Test initialization."""
storage = TemplateStorage()
assert isinstance(storage, TemplateStorage)


def test_satisfies_storage_protocol():
"""TemplateStorage satisfies the Storage protocol."""
storage = TemplateStorage()
assert isinstance(storage, Storage)
22 changes: 11 additions & 11 deletions typescript/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"zod": "^4.1.12"
},
"devDependencies": {
"@strands-agents/sdk": ">=1.0.0 <2.0.0",
"@strands-agents/sdk": "^1.10.0",
"@types/node": "^22.0.0",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
Expand Down
9 changes: 9 additions & 0 deletions typescript/setup-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ const COMPONENTS: Record<string, ComponentSpec> = {
testFiles: ['memory-store.test.ts'],
exports: ['TemplateMemoryStore', 'TemplateMemoryStoreConfig'],
},
storage: {
name: 'Storage',
description: 'Persist raw bytes under string keys for sessions, offloading, etc.',
files: ['storage.ts'],
testFiles: ['storage.test.ts'],
exports: ['TemplateStorage', 'TemplateStorageConfig'],
},
}

const SRC_DIR = 'src'
Expand Down Expand Up @@ -304,6 +311,8 @@ function buildReplacements(info: ProjectInfo): Record<string, string> {
TemplateConversationManager: `${pascalName}ConversationManager`,
TemplateMemoryStoreConfig: `${pascalName}MemoryStoreConfig`,
TemplateMemoryStore: `${pascalName}MemoryStore`,
TemplateStorageConfig: `${pascalName}StorageConfig`,
TemplateStorage: `${pascalName}Storage`,
// Tool function name
templateTool: `${camelName}Tool`,
template_tool: `${camelName.replace(/([A-Z])/g, '_$1').toLowerCase()}_tool`,
Expand Down
1 change: 1 addition & 0 deletions typescript/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export { TemplateIntervention } from './intervention.js'
export { TemplateSnapshotStorage, type TemplateSnapshotStorageConfig } from './session-manager.js'
export { TemplateConversationManager } from './conversation-manager.js'
export { TemplateMemoryStore, type TemplateMemoryStoreConfig } from './memory-store.js'
export { TemplateStorage, type TemplateStorageConfig } from './storage.js'
51 changes: 51 additions & 0 deletions typescript/src/storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Storage Implementation.
*
* Storage backends persist raw bytes under string keys. The SDK uses the
* {@link Storage} interface for session snapshots, context offloading, and any
* construct that needs durable key-value persistence.
*
* @example
* ```ts
* import { Agent, SessionManager } from '@strands-agents/sdk'
* import { TemplateStorage } from 'strands-template'
*
* const storage = new TemplateStorage({})
* const session = new SessionManager({
* sessionId: 'abc',
* storage: { snapshot: storage },
* })
* const agent = new Agent({ sessionManager: session })
* ```
*/

import type { Storage } from '@strands-agents/sdk'

// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface TemplateStorageConfig {
// TODO: Add backend-specific fields your storage needs.
}

export class TemplateStorage implements Storage {
constructor(private readonly _config: TemplateStorageConfig) {}

async write(_key: string, _data: Uint8Array): Promise<void> {
// TODO: Persist the data to your backend.
throw new Error('Not implemented')
}

async read(_key: string): Promise<Uint8Array | null> {
// TODO: Read from your backend, return null if not found.
throw new Error('Not implemented')
}

async delete(_key: string): Promise<void> {
// TODO: Delete from your backend. No-op if key does not exist.
throw new Error('Not implemented')
}

async list(_query: string): Promise<string[]> {
// TODO: Return keys matching the prefix, sorted ascending.
throw new Error('Not implemented')
}
}
15 changes: 15 additions & 0 deletions typescript/test/storage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { describe, it, expect } from 'vitest'
import type { Storage } from '@strands-agents/sdk'
import { TemplateStorage } from '../src/storage.js'

describe('TemplateStorage', () => {
it('satisfies the Storage interface', () => {
const storage: Storage = new TemplateStorage({})
expect(typeof storage.write).toBe('function')
expect(typeof storage.read).toBe('function')
expect(typeof storage.delete).toBe('function')
expect(typeof storage.list).toBe('function')
})

// TODO: Add round-trip write/read tests once the storage backend is implemented.
})
Loading