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
62 changes: 62 additions & 0 deletions .claude-flow/data/auto-memory-store.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
[
{
"id": "task-task_1783449649108_gdz3st",
"key": "task_1783449649108_gdz3st",
"content": "Task completed: task_1783449649108_gdz3st",
"namespace": "tasks",
"type": "task-outcome",
"metadata": {
"success": true,
"quality": 0.85
},
"createdAt": 1783449649579
},
{
"id": "task-task_1783449682752_ka3jge",
"key": "task_1783449682752_ka3jge",
"content": "Task completed: task_1783449682752_ka3jge",
"namespace": "tasks",
"type": "task-outcome",
"metadata": {
"success": true,
"quality": 0.85
},
"createdAt": 1783449683196
},
{
"id": "task-task_1783449683525_360381",
"key": "task_1783449683525_360381",
"content": "Task completed: task_1783449683525_360381",
"namespace": "tasks",
"type": "task-outcome",
"metadata": {
"success": true,
"quality": 0.85
},
"createdAt": 1783449684212
},
{
"id": "task-task_1783449747595_16svkj",
"key": "task_1783449747595_16svkj",
"content": "Task completed: task_1783449747595_16svkj",
"namespace": "tasks",
"type": "task-outcome",
"metadata": {
"success": true,
"quality": 0.85
},
"createdAt": 1783449748079
},
{
"id": "task-task_1783450000362_5snngx",
"key": "task_1783450000362_5snngx",
"content": "Task completed: task_1783450000362_5snngx",
"namespace": "tasks",
"type": "task-outcome",
"metadata": {
"success": true,
"quality": 0.85
},
"createdAt": 1783450000807
}
]
6 changes: 6 additions & 0 deletions .claude-flow/neural/stats.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"trajectoriesRecorded": 4,
"patternsLearned": 1,
"signalsProcessed": 0,
"lastAdaptation": 1783450000807
}
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,44 @@ Mutative actions (`store`, `update`, `delete`) prompt for confirmation unless `B

See [`docs/dakera_memory_tool.md`](docs/dakera_memory_tool.md) for the full action reference.

## Memory store

`DakeraMemoryStore` is a Strands [`MemoryStore`](https://strandsagents.com/) — a
`MemoryManager` extension point (Strands ≥ 1.45). Where the `dakera_memory` tool is
called explicitly by the model, a store plugs into the **agent loop** directly: the
manager searches it to recall context (injected into the prompt automatically) and,
when writable, writes new memories — either directly or via periodic **extraction**
from the conversation. Both share one Dakera server and agent namespace.

```python
from strands import Agent
from strands.memory import MemoryManager
from strands_dakera import DakeraMemoryStore

# Recall + write, distilling facts from the conversation every few turns.
store = DakeraMemoryStore(agent_id="alex", writable=True, extraction=True)
agent = Agent(memory_manager=MemoryManager(stores=[store]))

# The agent now recalls from and writes to Dakera without any explicit tool call.
agent("Remember that I prefer dark-mode dashboards.")
agent("How do I like my dashboards?") # recalls the stored preference
```

`DakeraMemoryStore` implements `search` (decay-weighted recall) and `add` (a client-side
write sink), so enabling `extraction` uses the manager's client-side `ModelExtractor` to
distill facts before storing them.

| Argument | Default | Description |
|---|---|---|
| `agent_id` | _(required)_ | Dakera agent namespace that owns the memories |
| `name` | `"dakera"` | Store identifier, used to target it from memory tools |
| `writable` | `True` | Whether the manager may write to the store |
| `extraction` | `None` | Automatic extraction (`bool` or `ExtractionConfig`) |
| `max_search_results` | `None` | Default result cap per search (falls back to 5) |
| `importance` | `None` | Default importance (0.0–1.0) applied to writes |
| `memory_type` | `"episodic"` | Default Dakera memory type for writes |
| `base_url` / `api_key` | env | Override `DAKERA_BASE_URL` / `DAKERA_API_KEY` |

## Development

The package lives under [`python/`](python/) (monorepo-style layout matching the
Expand Down
Binary file added agentdb.rvf
Binary file not shown.
Binary file added agentdb.rvf.lock
Binary file not shown.
62 changes: 62 additions & 0 deletions python/.claude-flow/data/auto-memory-store.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
[
{
"id": "task-task_1783449905604_0fqkri",
"key": "task_1783449905604_0fqkri",
"content": "Task completed: task_1783449905604_0fqkri",
"namespace": "tasks",
"type": "task-outcome",
"metadata": {
"success": true,
"quality": 0.85
},
"createdAt": 1783449906138
},
{
"id": "task-task_1783449911710_7k33m3",
"key": "task_1783449911710_7k33m3",
"content": "Task completed: task_1783449911710_7k33m3",
"namespace": "tasks",
"type": "task-outcome",
"metadata": {
"success": true,
"quality": 0.85
},
"createdAt": 1783449912371
},
{
"id": "task-task_1783449935947_cq3mgy",
"key": "task_1783449935947_cq3mgy",
"content": "Task completed: task_1783449935947_cq3mgy",
"namespace": "tasks",
"type": "task-outcome",
"metadata": {
"success": true,
"quality": 0.85
},
"createdAt": 1783449936390
},
{
"id": "task-task_1783449963076_vhlzn7",
"key": "task_1783449963076_vhlzn7",
"content": "Task completed: task_1783449963076_vhlzn7",
"namespace": "tasks",
"type": "task-outcome",
"metadata": {
"success": true,
"quality": 0.85
},
"createdAt": 1783449963759
},
{
"id": "task-task_1783449972951_pg1sde",
"key": "task_1783449972951_pg1sde",
"content": "Task completed: task_1783449972951_pg1sde",
"namespace": "tasks",
"type": "task-outcome",
"metadata": {
"success": true,
"quality": 0.85
},
"createdAt": 1783449973416
}
]
6 changes: 6 additions & 0 deletions python/.claude-flow/neural/stats.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"trajectoriesRecorded": 4,
"patternsLearned": 1,
"signalsProcessed": 0,
"lastAdaptation": 1783449973416
}
13 changes: 13 additions & 0 deletions python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,26 @@ See the [repository README](../README.md) for full usage. Quick start:
pip install strands-dakera
```

As an explicit tool:

```python
from strands import Agent
from strands_dakera import dakera_memory

agent = Agent(tools=[dakera_memory])
```

Or as a `MemoryStore` that plugs into the agent loop (Strands ≥ 1.45):

```python
from strands import Agent
from strands.memory import MemoryManager
from strands_dakera import DakeraMemoryStore

store = DakeraMemoryStore(agent_id="alex", writable=True, extraction=True)
agent = Agent(memory_manager=MemoryManager(stores=[store]))
```

## Local development

```bash
Expand Down
Binary file added python/agentdb.rvf
Binary file not shown.
Binary file added python/agentdb.rvf.lock
Binary file not shown.
4 changes: 3 additions & 1 deletion python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ classifiers = [
"Topic :: Scientific/Engineering :: Artificial Intelligence",
]

# strands-agents>=1.45.0: first release shipping the `strands.memory` module
# (MemoryStore / MemoryManager) that DakeraMemoryStore implements.
dependencies = [
"strands-agents>=1.0.0",
"strands-agents>=1.45.0",
"dakera>=0.12.0",
"rich>=13.0.0",
]
Expand Down
16 changes: 14 additions & 2 deletions python/src/strands_dakera/__init__.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
"""Strands Dakera — persistent, decay-weighted memory for Strands agents.

Backed by a self-hosted `Dakera <https://github.com/dakera-ai/dakera-deploy>`_
memory server. Exposes the ``dakera_memory`` tool for store / retrieve / get /
update / delete operations with importance-weighted, decay-aware recall.
memory server. Two integration points, sharing one server:

- :func:`dakera_memory` — a tool the model calls explicitly for store / retrieve
/ get / update / delete operations.
- :class:`DakeraMemoryStore` — a ``MemoryStore`` that plugs into the agent loop
via a ``MemoryManager``, with automatic memory injection and extraction.

Both offer importance-weighted, decay-aware recall.
"""

from strands_dakera.memory import (
TOOL_SPEC,
DakeraServiceClient,
dakera_memory,
)
from strands_dakera.store import (
DakeraMemoryStore,
DakeraMemoryStoreConfig,
)

__all__ = [
"dakera_memory",
"DakeraServiceClient",
"DakeraMemoryStore",
"DakeraMemoryStoreConfig",
"TOOL_SPEC",
]

Expand Down
13 changes: 8 additions & 5 deletions python/src/strands_dakera/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,13 @@
class DakeraServiceClient:
"""Thin wrapper around the Dakera Python SDK for the memory tool."""

def __init__(self) -> None:
"""Initialize the Dakera client from environment configuration.
def __init__(self, base_url: str | None = None, api_key: str | None = None) -> None:
"""Initialize the Dakera client.

Reads DAKERA_BASE_URL (default http://localhost:3000) and DAKERA_API_KEY.
Args:
base_url: Dakera server URL. Falls back to ``$DAKERA_BASE_URL``,
then ``http://localhost:3000``.
api_key: Dakera API key. Falls back to ``$DAKERA_API_KEY``.
"""
try:
from dakera import DakeraClient
Expand All @@ -152,8 +155,8 @@ def __init__(self) -> None:
"Install it with: pip install 'strands-dakera'"
) from err

base_url = os.environ.get("DAKERA_BASE_URL", "http://localhost:3000")
api_key = os.environ.get("DAKERA_API_KEY")
base_url = base_url or os.environ.get("DAKERA_BASE_URL", "http://localhost:3000")
api_key = api_key or os.environ.get("DAKERA_API_KEY")
self.client = DakeraClient(base_url=base_url, api_key=api_key)

def store_memory(
Expand Down
Loading
Loading