# Strands Agents

> Give a Strands agent a memory of each of your users, with one hook provider that puts what is known in its system prompt before each request and saves the exchange after it.

Give an agent built with Strands Agents a memory of each of your users. One hook provider does it: before
each request it puts what is known about the user that bears on their message into the system prompt, and
after the agent has answered it saves the exchange.

## Install

```bash
pip install "strands-agents[anthropic]" geniffy
```

```bash
uv add "strands-agents[anthropic]" geniffy
```

Set `ANTHROPIC_API_KEY`, and `GENIFFY_API_KEY` from **API keys** in the Geniffy app. Any model provider Strands
supports works the same way, Amazon Bedrock included.

## Remember each user

```python
from geniffy import AsyncGeniffy
from strands import Agent
from strands.hooks import AfterInvocationEvent, BeforeInvocationEvent, HookProvider, HookRegistry
from strands.models.anthropic import AnthropicModel

geniffy = AsyncGeniffy()                          # reads GENIFFY_API_KEY
INSTRUCTIONS = "You are a helpful assistant."


def last_user_text(messages) -> str:
    for m in reversed(messages or []):
        if m.get("role") == "user":
            return " ".join(b["text"] for b in m.get("content") or [] if "text" in b).strip()
    return ""


class GeniffyMemory(HookProvider):
    """What is known about one user before each request; the exchange, saved after it."""

    def __init__(self, user_id: str) -> None:
        self.mem = geniffy.space(f"user_{user_id}")
        self.asked = ""

    def register_hooks(self, registry: HookRegistry, **kwargs) -> None:
        registry.add_callback(BeforeInvocationEvent, self.recall)
        registry.add_callback(AfterInvocationEvent, self.remember)

    async def recall(self, event: BeforeInvocationEvent) -> None:
        self.asked = last_user_text(event.messages)
        known = await self.mem.context(self.asked) if self.asked else ""
        event.agent.system_prompt = f"{INSTRUCTIONS}\n\n<memory>\n{known}\n</memory>" if known else INSTRUCTIONS

    async def remember(self, event: AfterInvocationEvent) -> None:
        reply = str(event.result or "").strip()
        if self.asked and reply:
            await self.mem.memories.add(messages=[{"role": "user", "content": self.asked},
                                                  {"role": "assistant", "content": reply}])


def agent_for(user_id: str) -> Agent:
    """One agent per user: an agent keeps its conversation, so it is never shared between users."""
    model = AnthropicModel(model_id="claude-opus-5-5", max_tokens=1024)
    return Agent(model=model, system_prompt=INSTRUCTIONS, hooks=[GeniffyMemory(user_id)], callback_handler=None)


async def chat(user_id: str, message: str) -> str:
    result = await agent_for(user_id).invoke_async(message)
    return str(result)
```

Call it with the user from your own sign-in:

```python
reply = await chat(user.id, "Who signs the Lumen renewal?")
```

The memory goes in on every request, rebuilt from what bears on that message, each line with where it came
from. When nothing is known, the block says so in one sentence, so the agent says it doesn't know instead of
guessing. In a running app, keep one agent per conversation rather than one per message, so it remembers the
conversation as well; each user still has an agent of their own.

Source: https://docs.geniffy.com/integrations/strands
