Skip to content

Commit 30aed2d

Browse files
zzstoatzzclaude
andauthored
feat: integrate learning-sdk into slackbot for persistent memory (#1249)
- add letta_api_key_secret_name setting and load LETTA_API_KEY env var - wrap agent run with learning() context for per-user memory - add agentic-learning dependency each slack user gets their own memory agent (slackbot-{user_id}) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2134a30 commit 30aed2d

4 files changed

Lines changed: 23 additions & 5 deletions

File tree

examples/slackbot/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ dependencies = [
4242
"raggy[tpuf] @ git+https://github.com/zzstoatzz/raggy.git",
4343
"pretty-mod",
4444
"claude-agent-sdk",
45+
"agentic-learning",
4546
]
4647

4748

examples/slackbot/src/slackbot/api.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from contextlib import asynccontextmanager
66
from typing import Any
77

8+
from agentic_learning import learning
89
from fastapi import FastAPI, HTTPException, Request
910
from prefect import Flow, State, flow, get_run_logger, task
1011
from prefect.blocks.notifications import SlackWebhook
@@ -100,11 +101,13 @@ async def run_agent(
100101
settings=decorator_settings,
101102
max_tool_calls=settings.max_tool_calls_per_turn,
102103
):
103-
result = await create_agent(model=settings.model_name).run(
104-
user_prompt=cleaned_message,
105-
message_history=conversation,
106-
deps=user_context,
107-
)
104+
# wrap agent run with learning context for persistent memory
105+
with learning(agent=f"slackbot-{user_context['user_id']}"):
106+
result = await create_agent(model=settings.model_name).run(
107+
user_prompt=cleaned_message,
108+
message_history=conversation,
109+
deps=user_context,
110+
)
108111
finally:
109112
_progress_message.reset(token)
110113
_tool_usage_counts.reset(counts_token)

examples/slackbot/src/slackbot/settings.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from typing import ClassVar, Literal
44

55
from prefect.blocks.system import Secret
6+
from prefect.exceptions import ObjectNotFound
67
from prefect.variables import Variable
78
from pydantic import Field, field_validator, model_validator
89
from pydantic_settings import BaseSettings, SettingsConfigDict
@@ -56,6 +57,10 @@ def validate_log_level(cls, v: str) -> str:
5657
default="anthropic-api-key",
5758
description="Name of the Prefect secret block containing Anthropic API key",
5859
)
60+
letta_api_key_secret_name: str = Field(
61+
default="letta-api-key",
62+
description="Name of the Prefect secret block containing Letta API key",
63+
)
5964

6065
vector_store_type: Literal["turbopuffer"] = Field(
6166
default="turbopuffer", description="Type of vector store to use"
@@ -94,6 +99,12 @@ def _apply_post_validation_defaults(self) -> "SlackbotSettings":
9499
os.environ["TURBOPUFFER_API_KEY"] = api_key
95100
except Exception:
96101
pass # If secret doesn't exist, turbopuffer will handle the error
102+
if not os.getenv("LETTA_API_KEY"):
103+
try:
104+
api_key = Secret.load(self.letta_api_key_secret_name, _sync=True).get() # type: ignore
105+
os.environ["LETTA_API_KEY"] = api_key
106+
except ObjectNotFound:
107+
pass # If secret doesn't exist, learning-sdk won't be used
97108
if not self.admin_slack_user_id:
98109
self.admin_slack_user_id = Variable.get("admin-slack-id", _sync=True)
99110
return self

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ extend-select = ["I"]
9696
[tool.ruff.lint.per-file-ignores]
9797
"__init__.py" = ["F401", "I001", "RUF013"]
9898

99+
[tool.uv]
100+
prerelease = "allow"
101+
99102
[tool.uv.sources]
100103
slackbot = { workspace = true }
101104

0 commit comments

Comments
 (0)