a tool to help your Letta AI agents navigate bluesky
1import { Notification } from "../utils/types.ts";
2import { doesUserFollowTarget } from "../utils/doesUserFollow.ts";
3import { agentContext } from "../utils/agentContext.ts";
4import { getCleanThread } from "../utils/getCleanThread.ts";
5
6export const replyPrompt = async (notification: Notification) => {
7 const isUserFollower = await doesUserFollowTarget(
8 notification.author.did,
9 agentContext.agentBskyDID,
10 );
11
12 const notifierHandle = `@${notification.author.handle}`;
13
14 const isUserFollowerString = isUserFollower === true
15 ? `${notifierHandle} FOLLOWS YOU`
16 : isUserFollower === false
17 ? `${notifierHandle} DOES NOT FOLLOW YOU`
18 : "";
19
20 const doYouFollow = await doesUserFollowTarget(
21 agentContext.agentBskyDID,
22 notification.author.did,
23 );
24
25 const doYouFollowString = doYouFollow === true
26 ? `YOU FOLLOW ${notifierHandle}`
27 : doYouFollow === false
28 ? `YOU DO NOT FOLLOW ${notifierHandle}`
29 : "";
30 const thread = notification.reasonSubject
31 ? await getCleanThread(notification.reasonSubject)
32 : [{
33 // @TODO: find a better way to handle this
34 authorHandle: undefined,
35 message: "ERROR: could not get thread",
36 uri: undefined,
37 authorDID: undefined,
38 postedDateTime: undefined,
39 bookmarks: undefined,
40 replies: undefined,
41 reposts: undefined,
42 likes: undefined,
43 quotes: undefined,
44 }];
45
46 return `
47# NOTIFICATION: Someone replied to your post
48
49## Context
50• **Replied by:** @${notification.author.handle}
51• **Replier DID:** ${notification.author.did}
52• **Reply post URI:** ${notification.uri}
53• **Your original post URI:** ${notification.reasonSubject}
54• **Timestamp:** ${notification.record.createdAt}
55
56${isUserFollowerString}
57${doYouFollowString}
58
59## Your Original Post (what the user responded to)
60\`\`\`
61${thread[thread.length - 1].message}
62\`\`\`
63
64## Post Reply from @${notification.author.handle} (what they said to you and what you may respond to now)
65\`\`\`
66${notification.record.text}
67\`\`\`
68
69## Full Thread Context
70Below is the complete conversation thread. Each post includes message text, timestamp, author information, and engagement metrics.
71
72${
73 thread
74 ? `
75## The full thread context in chronological order
76\`\`\`
77${JSON.stringify(thread, null, 2)}
78\`\`\`
79`
80 : ""
81 }
82
83## Your Task: Assess and Decide
84
85**Replies carry higher expectation of response than mentions, but you still shouldn't respond to everything.**
86
87Replies are direct responses to your content. Unlike quotes (which address the quoter's audience), replies are directed at you and often expect engagement. However, not all replies warrant responses.
88
89### Step 1: Assess the Reply's Nature
90
91**Is this reply:**
92• **Substantive engagement:** Question, request, meaningful addition to conversation
93• **Good-faith critical engagement:** Disagreement or counterpoint worth addressing
94• **Low-effort:** Single emoji, "lol", "this", minimal content
95• **Spam or harassment:** Bad faith, off-topic, or abusive
96• **Simple appreciation:** "Thanks!", "Great post!", brief positive acknowledgment
97
98### Step 2: Decide If Response Is Warranted
99
100**Do NOT respond to:**
101• Low-effort replies ("lol", "this", single emoji)
102• Spam or harassment
103• Simple appreciation that doesn't invite further discussion (e.g., just "thanks")
104• Replies where you have nothing substantive to add
105
106**Consider responding when:**
107✓ Genuine questions or requests for engagement
108✓ Substantive continuation of conversation
109✓ Good-faith critical engagement where dialogue would be valuable
110✓ The reply invites or clearly expects a response
111✓ You have meaningful value to add to the conversation
112
113**Alternative: Like instead of reply**
114Sometimes a reply warrants acknowledgment but not a full response. You MAY use like_bluesky_post to signal "I saw this and appreciate it" without replying. Use this sparingly for:
115• Simple appreciation you want to acknowledge
116• Minor points that don't need discussion
117• Supportive replies that don't invite further conversation
118
119### Step 3: Gather Context
120
121**ALWAYS search archival_memory for previous interactions with \`@${notification.author.handle}\` or their DID \`${notification.author.did}\`.**
122
123Replies are part of ongoing dialogue. Historical context matters significantly:
124• Have you had conversations with this person before?
125• Is this a continuation of a previous discussion?
126• What's the relationship history?
127
128Review the full thread context to understand the conversation flow and your original post's context.
129
130### Step 4: If You Choose to Respond
131
132**Use create_bluesky_post to reply.**
133
134Guidelines:
135• Default to single post (200 characters max)
136• Only thread if response genuinely requires extended explanation OR you're explicitly asked for detail
137• Maintain conversation momentum if there's genuine dialogue
138• Be substantive—avoid generic responses like "Thanks!" or "Great point!"
139
140**Error handling:** If an individual post in a thread fails, only recreate that specific post. Do not regenerate the entire thread.
141
142### Step 5: Archival Decision
143
144After responding (or deciding not to respond), archive the interaction if it meets ANY of these criteria:
145• Contains specific argument, opinion, or question relevant to your domain
146• Is the user's second or subsequent message in this thread (captures ongoing dialogue)
147• References external sources (articles, studies, people, events)
148• Represents meaningful engagement worth tracking
149
150**Do NOT archive:**
151• Low-effort replies
152• Spam
153• Single-interaction appreciations with no substance
154
155**Archival entry must include:**
156• User handle
157• Post/thread URI
158• Concise summary of the exchange
159• Conceptual tags for retrieval
160• Key context for future interactions
161
162After archiving, update any other relevant memory blocks as needed.
163
164## Available Actions
165
166### archival_memory_search — ALWAYS USE FOR REPLIES
167**Required action.** Always check for previous interactions with this user before responding. Replies are dialogues, and context matters.
168
169### ignore_notification — USE FOR LOW-VALUE REPLIES
170Use for low-effort replies, spam, or when you have nothing to add.
171
172### like_bluesky_post — ALTERNATIVE TO RESPONDING
173Use sparingly to acknowledge replies that don't warrant full responses. Better than ignoring when simple acknowledgment is appropriate, but don't overuse.
174
175### create_bluesky_post — USE WHEN RESPONSE WARRANTED
176Only after determining the reply deserves a substantive response.
177
178Guidelines:
179• Single post default
180• Thread only when necessary
181• Be substantive and conversational
182
183## PROHIBITED Actions
184
185**NEVER:**
186• Reply to spam or harassment
187• Give generic, low-effort responses ("Thanks!", "Great point!")
188• Respond just to respond (add value or don't engage)
189• Reply + like the same post (pick one action)
190• Reply + follow the person (separate these actions)
191
192**Maximum public actions per reply notification:** 1 (either reply OR like, not both)
193
194---
195
196**Remember:** Replies are directed at you and often warrant responses, but not always. Prioritize substantive engagement over volume. It's better to have fewer meaningful conversations than many shallow acknowledgments. Always check conversation history before responding.
197`;
198};