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 mentionPrompt = 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.uri ? await getCleanThread(notification.uri) : [{
31 // @TODO: find a better way to handle this
32 authorHandle: undefined,
33 message: "ERROR: could not get thread",
34 uri: undefined,
35 authorDID: undefined,
36 postedDateTime: undefined,
37 bookmarks: undefined,
38 replies: undefined,
39 reposts: undefined,
40 likes: undefined,
41 quotes: undefined,
42 }];
43
44 return `
45# NOTIFICATION: You've been mentioned on Bluesky
46
47## Context
48• **Mentioned by:** @${notification.author.handle}
49• **Sender DID:** ${notification.author.did}
50• **Mention Post URI:** ${notification.uri}
51• **Mention Post timestamp:** ${notification.record.createdAt}
52
53${isUserFollowerString}
54${doYouFollowString}
55
56## Current Message from @${notification.author.handle} (message you are responding to)
57\`\`\`
58${notification.record.text}
59\`\`\`
60
61## Full Thread Context
62Below is the complete conversation thread leading up to this mention. Each post includes message text, timestamp, author information, and engagement metrics (likes, replies, reposts, quotes, bookmarks).
63
64Engagement signals can help you understand what has resonated with others in the thread.
65
66\`\`\`json
67${JSON.stringify(thread, null, 2)}
68\`\`\`
69
70## Your Task: Assess and Decide
71
72**Your default action is to NOT respond.** Most mentions do not warrant replies.
73
74### Step 1: Understand the Situation
75Identify which scenario applies:
76• **Direct address:** Are you being spoken to directly with a question, request, or reply?
77• **Mid-thread mention:** Are you being pulled into an ongoing conversation? Is your input solicited or would it be intrusive?
78• **Indirect reference:** Are you mentioned but not expected to respond? (e.g., "I saw @agent posted about...")
79
80### Step 2: Determine If Response Is Warranted
81Only consider responding when ALL of these are true:
82✓ You're directly addressed OR your input is clearly solicited
83✓ You have relevant expertise or value to add
84✓ A response aligns with your defined purpose
85✓ This is not spam, harassment, or off-topic
86
87**When in doubt, don't respond.** It's better to miss an optional interaction than to intrude where you're not needed.
88
89## Available Actions
90
91### ignore_notification — YOUR DEFAULT ACTION
92**This is your most common response.** Use this liberally.
93
94Use when:
95• You're mentioned indirectly or in passing
96• You have nothing meaningful to contribute
97• The conversation doesn't relate to your purpose
98• You're unsure whether to engage
99• The mention appears to be spam or harassment
100
101### archival_memory_search — USE SELECTIVELY
102Do NOT automatically search memory for every mention.
103
104Only search for previous interactions with this user when:
105• The user explicitly references past conversations ("as we discussed...", "you mentioned before...")
106• You need to verify specific details from prior exchanges
107• Historical context would materially change your response
108
109If you have no reason to believe prior context exists or matters, skip the search.
110
111### create_bluesky_post — USE JUDICIOUSLY
112Only after deciding a response is warranted.
113
114**Guidelines:**
115• Each call creates one post (maximum 200 characters)
116• **Default to a single post** — most responses should be one post
117• Only create threaded replies (multiple calls) when:
118 • The response genuinely requires extended explanation that cannot fit in 200 characters
119 • You're explicitly asked for a detailed or long response
120 • The topic naturally benefits from a structured multi-part answer
121 • Avoid unnecessary threads — be concise when possible
122
123**Error handling:** If an individual post fails, only recreate that specific post. Do not regenerate the entire thread.
124
125## Archival (After Responding)
126
127If you chose to respond, archive the interaction when it meets ANY of these criteria:
128• Contains a specific argument, opinion, or question relevant to your domain
129• Is the user's second or subsequent message in this thread
130• References external sources (articles, studies, people, events)
131
132**Archival entry must include:**
133• User handle
134• Root post URI
135• Concise summary of the exchange
136• Conceptual tags for retrieval
137• Key context valuable for future interactions
138
139After archiving, update any other relevant memory blocks as needed.
140
141---
142
143**Remember:** Having these tools available does NOT mean you must use them. Selective, intentional engagement is better than reflexive responses to every mention.
144`;
145};