Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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 strands-py/src/strands/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""A framework for building, deploying, and managing AI agents."""

from . import agent, models, telemetry, types
from . import agent, models, storage, telemetry, types
from .agent.agent import Agent
from .agent.base import AgentBase
from .event_loop._retry import ModelRetryStrategy
Expand Down Expand Up @@ -32,6 +32,7 @@
"SandboxTimeoutError",
"Skill",
"Snapshot",
"storage",
"tool",
"ToolContext",
"types",
Expand Down
26 changes: 26 additions & 0 deletions strands-py/src/strands/storage/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Unified storage module.

Provides the Storage interface and shipped implementations for persisting
raw bytes under string keys. All SDK subsystems that need persistence
consume this interface.

Example:
```python
from strands.storage import LocalFileStorage, InMemoryStorage

storage = LocalFileStorage("./.strands/")
await storage.write("sessions/abc/snapshot.json", data)
```
"""

from .in_memory_storage import InMemoryStorage
from .local_file_storage import LocalFileStorage
from .s3_storage import S3Storage
from .storage import Storage

__all__ = [
"InMemoryStorage",
"LocalFileStorage",
"S3Storage",
"Storage",
]
107 changes: 107 additions & 0 deletions strands-py/src/strands/storage/in_memory_storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"""In-memory storage implementation."""

from __future__ import annotations

import builtins
import threading

from .storage import _NamespacedStorage, _normalize_key, _normalize_prefix


class InMemoryStorage:
"""Map-backed storage for testing and short-lived processes.

Content does not survive process restarts. The store is unbounded — consumers
manage eviction themselves.

Example:
```python
from strands.storage import InMemoryStorage

storage = InMemoryStorage()
await storage.write("sessions/abc/state.json", b'{"messages": []}')
data = await storage.read("sessions/abc/state.json")
```
"""

def __init__(self) -> None:
"""Initialize an empty in-memory store."""
self._store: dict[str, bytes] = {}
self._lock = threading.Lock()

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.

Raises:
StorageError: If the key is invalid.
"""
normalized = _normalize_key(key)
with self._lock:
self._store[normalized] = bytes(data)

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.

Raises:
StorageError: If the key is invalid.
"""
normalized = _normalize_key(key)
with self._lock:
value = self._store.get(normalized)
return value

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.

Raises:
StorageError: If the key is invalid.
"""
normalized = _normalize_key(key)
with self._lock:
self._store.pop(normalized, None)

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

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

Returns:
Matching keys sorted ascending.

Raises:
StorageError: If the prefix is invalid.
"""
prefix = _normalize_prefix(query)
with self._lock:
keys = sorted(k for k in self._store if k.startswith(prefix))
return keys

def namespace(self, prefix: str) -> _NamespacedStorage:
"""Return a view of this storage with all keys prefixed.

Args:
prefix: Prefix to prepend to all keys.

Returns:
A namespaced storage view.
"""
return _NamespacedStorage(self, prefix)

def clear(self) -> None:
"""Remove all stored entries."""
with self._lock:
self._store.clear()
Loading
Loading