Skip to content

Commit 36da4c1

Browse files
zzstoatzzclaude
andauthored
chore: consolidate slackbot memory on letta learning-sdk (#1250)
* chore: consolidate slackbot memory on letta learning-sdk remove home-rolled TurboPuffer-based user facts system in favor of letta's learning-sdk which was integrated in the previous PR. the learning-sdk automatically extracts and injects memories without needing explicit tool calls. removed: - store_facts_about_user and delete_facts_about_user agent tools - user_notes field from UserContext - user_facts_namespace_prefix setting - store_user_facts and user_facts_asset from assets.py - TurboPuffer imports for user facts (kept for doc search) note: TurboPuffer is still used for documentation search (prefect-2, prefect-3, marvin namespaces) - only user facts storage was removed. also adds examples/agentic_learning_demo.py showing standalone usage. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: restore system prompt to agent constructor the previous commit accidentally removed the system prompt entirely when removing the dynamic user_notes injection. this restores the DEFAULT_SYSTEM_PROMPT by passing it directly to the Agent constructor. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 30aed2d commit 36da4c1

8 files changed

Lines changed: 69 additions & 124 deletions

File tree

examples/agentic_learning_demo.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# /// script
2+
# requires-python = ">=3.10"
3+
# dependencies = [
4+
# "agentic-learning",
5+
# "pydantic-ai",
6+
# "pydantic-settings",
7+
# ]
8+
# [tool.uv]
9+
# prerelease = "allow"
10+
# ///
11+
"""
12+
agentic learning SDK demo
13+
14+
this example shows how to use the agentic-learning SDK with pydantic-ai.
15+
the SDK automatically captures conversations and manages persistent memory.
16+
17+
prerequisites:
18+
LETTA_API_KEY and ANTHROPIC_API_KEY in .env
19+
20+
usage:
21+
uv run examples/agentic_learning_demo.py
22+
"""
23+
24+
import os
25+
26+
from pydantic_settings import BaseSettings, SettingsConfigDict
27+
28+
29+
class Settings(BaseSettings):
30+
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
31+
32+
anthropic_api_key: str
33+
letta_api_key: str
34+
35+
36+
settings = Settings()
37+
os.environ["LETTA_API_KEY"] = settings.letta_api_key
38+
os.environ["ANTHROPIC_API_KEY"] = settings.anthropic_api_key
39+
40+
from agentic_learning import learning # noqa: E402
41+
from pydantic_ai import Agent # noqa: E402
42+
43+
agent = Agent("anthropic:claude-sonnet-4-20250514")
44+
45+
46+
def ask(message: str):
47+
print(f"user: {message}\n")
48+
49+
with learning(agent="pydantic-ai-demo"):
50+
result = agent.run_sync(message)
51+
print(f"assistant: {result.output}\n")
52+
53+
54+
if __name__ == "__main__":
55+
while True:
56+
message = input("you: ")
57+
if message.lower() in ("quit", "exit", "q"):
58+
break
59+
ask(message)

examples/slackbot/src/slackbot/_internal/templates.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@
5151
- **Assume Prefect 3.x:** Unless the user specifies otherwise, assume the user is using Prefect 3.x. You can mention this assumption IF RELEVANT (e.g., "In Prefect 3.x, you would...").
5252
- **Code is King:** When providing code examples, ensure they are complete and correct. Use your `verify_import_statements` tool's output to guide you.
5353
- **Honesty Over Invention:** If your tools don't find a clear answer, say so. It's better to admit a knowledge gap than to provide incorrect information.
54-
- **Stay on Topic:** Only reference notes you've stored about the user if they are directly relevant to the current question.
5554
- **Proportionality:** If asked a simple question, you don't need to do a bunch of work. Just answer the question once you find it. However, feel free to dig into broad questions.
5655
5756
## CRITICAL - Removed/Deprecated Features
@@ -63,18 +62,17 @@
6362
If a user explicitly mentions using Prefect 2.x, that's fine, but recommend upgrading to 3.x or using workers in 2.x.
6463
6564
## Tool Usage Protocol
66-
You have a suite of tools to gather and store information. Use them methodically.
65+
You have a suite of tools to gather information. Use them methodically.
6766
6867
1. **For Technical/Conceptual Questions:** Use `research_prefect_topic`. It delegates to a specialized agent that will do comprehensive research for you.
6968
2. **For Bugs or Error Reports:** Use `read_github_issues` to find existing discussions or solutions.
7069
3. **For Community Discussions:** Use `search_github_discussions` to find existing GitHub discussions on topics.
71-
4. **For Remembering User Details:** When a user shares information about their goals, environment, or preferences, use `store_facts_about_user` to save these details for future interactions.
72-
5. **For Checking the Work of the Research Agent:** Use `explore_module_offerings` and `display_callable_signature` to verify specific syntax recommendations.
73-
6. **For CLI Commands:** use `check_cli_command` with --help before suggesting any Prefect CLI command to verify it exists and has the correct syntax. This prevents suggesting non-existent commands.
70+
4. **For Checking the Work of the Research Agent:** Use `explore_module_offerings` and `display_callable_signature` to verify specific syntax recommendations.
71+
5. **For CLI Commands:** use `check_cli_command` with --help before suggesting any Prefect CLI command to verify it exists and has the correct syntax. This prevents suggesting non-existent commands.
7472
- **IMPORTANT:** When checking commands that require optional dependencies (e.g., AWS, Docker, Kubernetes integrations), use the `uv run --with 'prefect[<extra>]'` syntax.
7573
- Examples: `uv run --with 'prefect[aws]'`, `uv run --with 'prefect[docker]'`, `uv run --with 'prefect[kubernetes]'`
7674
- This ensures the command runs with the necessary dependencies installed.
77-
7. **For Creating GitHub Discussions (USE SPARINGLY):** Use `create_discussion_and_notify` only when:
75+
6. **For Creating GitHub Discussions (USE SPARINGLY):** Use `create_discussion_and_notify` only when:
7876
- The thread contains valuable insights, solutions, or patterns not documented elsewhere
7977
- You've searched both issues and discussions and found no existing coverage of the topic
8078
- The conversation would clearly benefit the broader Prefect community

examples/slackbot/src/slackbot/api.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,6 @@ async def handle_message(payload: SlackPayload, db: Database):
250250

251251
user_context = build_user_context(
252252
user_id=event.user,
253-
user_question=cleaned_message,
254253
thread_ts=thread_ts,
255254
workspace_name=await get_workspace_domain(),
256255
channel_id=event.channel or "unknown",

examples/slackbot/src/slackbot/assets.py

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,9 @@
44

55
from prefect.assets import Asset, AssetProperties, add_asset_metadata, materialize
66
from pydantic import BaseModel
7-
from pydantic_ai import RunContext
87
from pydantic_ai.messages import ModelMessage, SystemPromptPart
9-
from raggy.documents import Document
10-
from raggy.vectorstores.tpuf import TurboPuffer
118

129
from marvin import cast_async
13-
from slackbot.settings import settings
1410
from slackbot.slack import get_channel_name
1511
from slackbot.types import UserContext
1612

@@ -74,54 +70,6 @@ def thread_summary_asset(
7470
)
7571

7672

77-
def user_facts_asset(user_context: UserContext) -> Asset:
78-
user_id = user_context["user_id"]
79-
workspace_name = user_context["workspace_name"]
80-
bot_id = user_context["bot_id"]
81-
return Asset(
82-
key=f"slack://{workspace_name}/bot/{bot_id}/facts/{user_id}",
83-
properties=AssetProperties(
84-
name=f"User Facts {user_id}",
85-
description=f"Facts learned about user {user_id} by bot {bot_id}",
86-
owners=["slackbot"],
87-
),
88-
)
89-
90-
91-
async def store_user_facts(ctx: RunContext[UserContext], facts: list[str]) -> str:
92-
"""Store facts extracted from a Slack thread using context for namespacing."""
93-
94-
with TurboPuffer(
95-
namespace=f"{settings.user_facts_namespace_prefix}{ctx.deps['user_id']}"
96-
) as tpuf:
97-
tpuf.upsert(documents=[Document(text=fact) for fact in facts])
98-
99-
user_facts = user_facts_asset(ctx.deps)
100-
101-
slack_thread = await slack_thread_asset(ctx.deps)
102-
slackbot = slackbot_asset(ctx.deps)
103-
104-
@materialize(user_facts, asset_deps=[slack_thread, slackbot])
105-
async def materialize_user_facts():
106-
add_asset_metadata(
107-
user_facts,
108-
{
109-
"user_id": ctx.deps["user_id"],
110-
"fact_count": len(facts),
111-
"timestamp": datetime.now().isoformat(),
112-
"namespace": f"{settings.user_facts_namespace_prefix}{ctx.deps['user_id']}",
113-
"thread_ts": ctx.deps["thread_ts"],
114-
"workspace_name": ctx.deps["workspace_name"],
115-
"channel_id": ctx.deps["channel_id"],
116-
"bot_id": ctx.deps["bot_id"],
117-
"facts": facts,
118-
},
119-
)
120-
return f"Stored {len(facts)} facts about user {ctx.deps['user_id']} from thread {ctx.deps['thread_ts']}"
121-
122-
return await materialize_user_facts()
123-
124-
12573
async def summarize_thread(
12674
user_context: UserContext, conversation: list[ModelMessage]
12775
) -> ThreadSummary:

examples/slackbot/src/slackbot/core.py

Lines changed: 1 addition & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,8 @@
1717
from pydantic_ai.models.anthropic import AnthropicModel
1818
from pydantic_ai.providers import Provider
1919
from pydantic_ai.settings import ModelSettings
20-
from raggy.vectorstores.tpuf import TurboPuffer, query_namespace
21-
from turbopuffer import NotFoundError
2220

2321
from slackbot._internal.templates import DEFAULT_SYSTEM_PROMPT
24-
from slackbot.assets import store_user_facts
2522
from slackbot.github import (
2623
GitHubAuthError,
2724
GitHubError,
@@ -136,23 +133,13 @@ def _insert():
136133
@task(task_run_name="build user context for {user_id}")
137134
def build_user_context(
138135
user_id: str,
139-
user_question: str,
140136
thread_ts: str,
141137
workspace_name: str,
142138
channel_id: str,
143139
bot_id: str,
144140
) -> UserContext:
145-
try:
146-
user_notes = query_namespace(
147-
query_text=user_question,
148-
namespace=f"{settings.user_facts_namespace_prefix}{user_id}",
149-
top_k=5,
150-
)
151-
except NotFoundError:
152-
user_notes = "<No notes found>"
153141
return UserContext(
154142
user_id=user_id,
155-
user_notes=user_notes,
156143
thread_ts=thread_ts,
157144
workspace_name=workspace_name,
158145
channel_id=channel_id,
@@ -177,6 +164,7 @@ def create_agent(
177164
UserContext, str
178165
](
179166
model=ai_model,
167+
system_prompt=DEFAULT_SYSTEM_PROMPT,
180168
model_settings=ModelSettings(temperature=settings.temperature),
181169
tools=[
182170
research_prefect_topic, # Tool for researching Prefect topics
@@ -189,42 +177,6 @@ def create_agent(
189177
deps_type=UserContext,
190178
)
191179

192-
@agent.system_prompt
193-
def personality_and_maybe_notes(ctx: RunContext[UserContext]) -> str:
194-
system_prompt = DEFAULT_SYSTEM_PROMPT + (
195-
f"\n\nUser notes: {ctx.deps['user_notes']}"
196-
if ctx.deps["user_notes"]
197-
else ""
198-
)
199-
print(f"System prompt: {system_prompt}")
200-
return system_prompt
201-
202-
@agent.tool
203-
async def store_facts_about_user(
204-
ctx: RunContext[UserContext], facts: list[str]
205-
) -> str:
206-
"""Store facts about the user that are useful for answering their questions."""
207-
print(f"Storing {len(facts)} facts about user {ctx.deps['user_id']}")
208-
# This creates an asset dependency: USER_FACTS depends on SLACK_MESSAGES
209-
message = await store_user_facts(ctx, facts)
210-
print(message)
211-
return message
212-
213-
@agent.tool
214-
def delete_facts_about_user(ctx: RunContext[UserContext], related_to: str) -> str:
215-
"""Delete facts about the user related to a specific topic."""
216-
print(f"forgetting stuff about {ctx.deps['user_id']} related to {related_to}")
217-
user_id = ctx.deps["user_id"]
218-
with TurboPuffer(
219-
namespace=f"{settings.user_facts_namespace_prefix}{user_id}"
220-
) as tpuf:
221-
vector_result = tpuf.query(related_to)
222-
ids = [str(v.id) for v in vector_result.rows or []]
223-
tpuf.delete(ids)
224-
message = f"Deleted {len(ids)} facts about user {user_id}"
225-
print(message)
226-
return message
227-
228180
@agent.tool
229181
async def create_discussion_and_notify(
230182
ctx: RunContext[UserContext],

examples/slackbot/src/slackbot/prompts.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
- **Assume Prefect 3.x:** Unless the user specifies otherwise, assume the user is using Prefect 3.x. You can mention this assumption IF RELEVANT (e.g., "In Prefect 3.x, you would...").
3131
- **Code is King:** When providing code examples, ensure they are complete and correct. Use your `verify_import_statements` tool's output to guide you.
3232
- **Honesty Over Invention:** If your tools don't find a clear answer, say so. It's better to admit a knowledge gap than to provide incorrect information.
33-
- **Stay on Topic:** Only reference notes you've stored about the user if they are directly relevant to the current question.
3433
- **Proportionality:** If asked a simple question, you don't need to do a bunch of work. Just answer the question once you find it. However, feel free to dig into broad questions.
3534
3635
## CRITICAL - Removed/Deprecated Features
@@ -42,18 +41,17 @@
4241
If a user explicitly mentions using Prefect 2.x, that's fine, but recommend upgrading to 3.x or using workers in 2.x.
4342
4443
## Tool Usage Protocol
45-
You have a suite of tools to gather and store information. Use them methodically.
44+
You have a suite of tools to gather information. Use them methodically.
4645
4746
1. **For Technical/Conceptual Questions:** Use `research_prefect_topic`. It delegates to a specialized agent that will do comprehensive research for you.
4847
2. **For Bugs or Error Reports:** Use `read_github_issues` to find existing discussions or solutions.
4948
3. **For Community Discussions:** Use `search_github_discussions` to find existing GitHub discussions on topics.
50-
4. **For Remembering User Details:** When a user shares information about their goals, environment, or preferences, use `store_facts_about_user` to save these details for future interactions.
51-
5. **For Checking the Work of the Research Agent:** Use `explore_module_offerings` and `display_callable_signature` to verify specific syntax recommendations.
52-
6. **For CLI Commands:** use `check_cli_command` with --help before suggesting any Prefect CLI command to verify it exists and has the correct syntax. This prevents suggesting non-existent commands.
49+
4. **For Checking the Work of the Research Agent:** Use `explore_module_offerings` and `display_callable_signature` to verify specific syntax recommendations.
50+
5. **For CLI Commands:** use `check_cli_command` with --help before suggesting any Prefect CLI command to verify it exists and has the correct syntax. This prevents suggesting non-existent commands.
5351
- **IMPORTANT:** When checking commands that require optional dependencies (e.g., AWS, Docker, Kubernetes integrations), use the `uv run --with 'prefect[<extra>]'` syntax.
5452
- Examples: `uv run --with 'prefect[aws]'`, `uv run --with 'prefect[docker]'`, `uv run --with 'prefect[kubernetes]'`
5553
- This ensures the command runs with the necessary dependencies installed.
56-
7. **For Creating GitHub Discussions (USE SPARINGLY):** Use `create_discussion_and_notify` only when:
54+
6. **For Creating GitHub Discussions (USE SPARINGLY):** Use `create_discussion_and_notify` only when:
5755
- The thread contains valuable insights, solutions, or patterns not documented elsewhere
5856
- You've searched both issues and discussions and found no existing coverage of the topic
5957
- The conversation would clearly benefit the broader Prefect community

examples/slackbot/src/slackbot/settings.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import os
22
from pathlib import Path
3-
from typing import ClassVar, Literal
3+
from typing import ClassVar
44

55
from prefect.blocks.system import Secret
66
from prefect.exceptions import ObjectNotFound
@@ -62,14 +62,6 @@ def validate_log_level(cls, v: str) -> str:
6262
description="Name of the Prefect secret block containing Letta API key",
6363
)
6464

65-
vector_store_type: Literal["turbopuffer"] = Field(
66-
default="turbopuffer", description="Type of vector store to use"
67-
)
68-
user_facts_namespace_prefix: str = Field(
69-
default="user-facts-",
70-
description="Prefix for user facts namespaces in vector store",
71-
)
72-
7365
# Development settings
7466
test_mode: bool = Field(
7567
default=False, description="Enable test mode with auto-reload"

examples/slackbot/src/slackbot/types.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
class UserContext(TypedDict):
55
user_id: str
6-
user_notes: str
76
thread_ts: str
87
workspace_name: str
98
channel_id: str

0 commit comments

Comments
 (0)