a tool to help your Letta AI agents navigate bluesky
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add prompt files for Bluesky notification processing

+841 -16
+117
prompts/likePrompt.ts
··· 1 + import { Notification } from "../util/types.ts"; 2 + import { doesUserFollow } from "../util/doesUserFollow.ts"; 3 + import { getCleanThread } from "../util/getCleanThread.ts"; 4 + 5 + export const likePrompt = async (notification: Notification) => { 6 + const isFollower = await doesUserFollow(notification.author.did); 7 + 8 + const thread = notification.reasonSubject 9 + ? await getCleanThread(notification.reasonSubject) 10 + : [{ 11 + // @TODO: find a better way to handle this 12 + authorHandle: undefined, 13 + message: "ERROR: could not get thread", 14 + uri: undefined, 15 + authorDID: undefined, 16 + postedDateTime: undefined, 17 + bookmarks: undefined, 18 + replies: undefined, 19 + reposts: undefined, 20 + likes: undefined, 21 + quotes: undefined, 22 + }]; 23 + 24 + return ` 25 + # NOTIFICATION: Someone liked your post 26 + 27 + ## Context 28 + • **Liked by:** @${notification.author.handle} 29 + • **Liker DID:** ${notification.author.did} 30 + • **Post URI:** ${notification.reasonSubject} 31 + • **Timestamp:** ${notification.record.createdAt} 32 + 33 + ${isFollower} 34 + 35 + ${ 36 + thread 37 + ? ` 38 + ## your post that was liked by @${notification.author.handle} 39 + \`\`\` 40 + ${thread[thread.length - 1].message} 41 + \`\`\` 42 + ` 43 + : "" 44 + } 45 + 46 + ${ 47 + thread 48 + ? ` 49 + ## the full thread your post was in 50 + \`\`\` 51 + ${JSON.stringify(thread, null, 2)} 52 + \`\`\` 53 + ` 54 + : "" 55 + } 56 + ## Your Task: Observe Only 57 + 58 + **REQUIRED ACTION: Take no public action.** 59 + 60 + This is a passive signal for pattern recognition and audience understanding. A like is someone appreciating or aknowledging your content. they are not requesting your attention or a response. 61 + 62 + ### What to Consider (Silently) 63 + 64 + **Is this person part of your engaged audience?** 65 + You MAY search archival_memory for \`@${notification.author.handle}\` or their DID \`${notification.author.did}\` if you suspect there might be relevant interaction history. Only search if you have reason to believe prior context exists. Don't search for every like. 66 + 67 + Consider: 68 + • Have you interacted with this person before? 69 + • Are they a frequent engager or first-time interaction? 70 + • Is this part of a pattern worth noting? 71 + 72 + **Does this reveal content preferences?** 73 + • What topic was this post about? 74 + • Does this help you understand what content resonates with your audience? 75 + • Is this post receiving unusual engagement levels? 76 + 77 + ### Archival Decision 78 + 79 + Archive this like ONLY if it meets one of these criteria: 80 + • The liker is someone you've had multiple meaningful interactions with (helps track relationships) 81 + • This post is receiving unusual engagement that might inform future content strategy 82 + • The liker is someone notable in your domain where this engagement provides meaningful context 83 + 84 + **Do NOT archive:** 85 + • Random or first-time likes 86 + • Likes on casual posts 87 + • Low-signal engagement 88 + 89 + **If archiving, keep it minimal:** 90 + • User handle and relationship context 91 + • Post topic and why the like is notable 92 + • Date for temporal tracking 93 + 94 + ## Available Actions 95 + 96 + ### ignore_notification — YOUR ONLY PUBLIC ACTION 97 + Use this to acknowledge the notification without taking any visible action. This is the correct response. 98 + 99 + ### archival_memory_search — USE VERY SELECTIVELY 100 + Only search if you have specific reason to believe prior context with this user exists and matters. 101 + 102 + Do NOT search for every like notification. 103 + 104 + ## PROHIBITED Actions 105 + 106 + **NEVER:** 107 + • Reply to likes 108 + • Like their posts back as reciprocation 109 + • Follow them because they liked your post 110 + • Thank them in any way 111 + • Take any public-facing action in response to a like 112 + 113 + --- 114 + 115 + **Remember:** A like is passive appreciation. Observe, learn from patterns, but do not treat each like as an event requiring response. Most likes should simply be noted and ignored. 116 + `; 117 + };
+121
prompts/mentionPrompt.ts
··· 1 + import { Notification } from "../util/types.ts"; 2 + import { doesUserFollow } from "../util/doesUserFollow.ts"; 3 + import { getCleanThread } from "../util/getCleanThread.ts"; 4 + 5 + export const mentionPrompt = async (notification: Notification) => { 6 + const isFollower = await doesUserFollow(notification.author.did); 7 + const thread = notification.uri ? await getCleanThread(notification.uri) : [{ 8 + // @TODO: find a better way to handle this 9 + authorHandle: undefined, 10 + message: "ERROR: could not get thread", 11 + uri: undefined, 12 + authorDID: undefined, 13 + postedDateTime: undefined, 14 + bookmarks: undefined, 15 + replies: undefined, 16 + reposts: undefined, 17 + likes: undefined, 18 + quotes: undefined, 19 + }]; 20 + 21 + return ` 22 + # NOTIFICATION: You've been mentioned on Bluesky 23 + 24 + ## Context 25 + • **Mentioned by:** @${notification.author.handle} 26 + • **Sender DID:** ${notification.author.did} 27 + • **Mention Post URI:** ${notification.uri} 28 + • **Mention Post timestamp:** ${notification.record.createdAt} 29 + 30 + ${isFollower} 31 + 32 + ## Current Message from @${notification.author.handle} (message you are responding to) 33 + \`\`\` 34 + ${notification.record.text} 35 + \`\`\` 36 + 37 + ## Full Thread Context 38 + Below 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). 39 + 40 + Engagement signals can help you understand what has resonated with others in the thread. 41 + 42 + \`\`\`json 43 + ${JSON.stringify(thread, null, 2)} 44 + \`\`\` 45 + 46 + ## Your Task: Assess and Decide 47 + 48 + **Your default action is to NOT respond.** Most mentions do not warrant replies. 49 + 50 + ### Step 1: Understand the Situation 51 + Identify which scenario applies: 52 + • **Direct address:** Are you being spoken to directly with a question, request, or reply? 53 + • **Mid-thread mention:** Are you being pulled into an ongoing conversation? Is your input solicited or would it be intrusive? 54 + • **Indirect reference:** Are you mentioned but not expected to respond? (e.g., "I saw @agent posted about...") 55 + 56 + ### Step 2: Determine If Response Is Warranted 57 + Only consider responding when ALL of these are true: 58 + ✓ You're directly addressed OR your input is clearly solicited 59 + ✓ You have relevant expertise or value to add 60 + ✓ A response aligns with your defined purpose 61 + ✓ This is not spam, harassment, or off-topic 62 + 63 + **When in doubt, don't respond.** It's better to miss an optional interaction than to intrude where you're not needed. 64 + 65 + ## Available Actions 66 + 67 + ### ignore_notification — YOUR DEFAULT ACTION 68 + **This is your most common response.** Use this liberally. 69 + 70 + Use when: 71 + • You're mentioned indirectly or in passing 72 + • You have nothing meaningful to contribute 73 + • The conversation doesn't relate to your purpose 74 + • You're unsure whether to engage 75 + • The mention appears to be spam or harassment 76 + 77 + ### archival_memory_search — USE SELECTIVELY 78 + Do NOT automatically search memory for every mention. 79 + 80 + Only search for previous interactions with this user when: 81 + • The user explicitly references past conversations ("as we discussed...", "you mentioned before...") 82 + • You need to verify specific details from prior exchanges 83 + • Historical context would materially change your response 84 + 85 + If you have no reason to believe prior context exists or matters, skip the search. 86 + 87 + ### create_bluesky_post — USE JUDICIOUSLY 88 + Only after deciding a response is warranted. 89 + 90 + **Guidelines:** 91 + • Each call creates one post (maximum 200 characters) 92 + • **Default to a single post** — most responses should be one post 93 + • Only create threaded replies (multiple calls) when: 94 + • The response genuinely requires extended explanation that cannot fit in 200 characters 95 + • You're explicitly asked for a detailed or long response 96 + • The topic naturally benefits from a structured multi-part answer 97 + • Avoid unnecessary threads — be concise when possible 98 + 99 + **Error handling:** If an individual post fails, only recreate that specific post. Do not regenerate the entire thread. 100 + 101 + ## Archival (After Responding) 102 + 103 + If you chose to respond, archive the interaction when it meets ANY of these criteria: 104 + • Contains a specific argument, opinion, or question relevant to your domain 105 + • Is the user's second or subsequent message in this thread 106 + • References external sources (articles, studies, people, events) 107 + 108 + **Archival entry must include:** 109 + • User handle 110 + • Root post URI 111 + • Concise summary of the exchange 112 + • Conceptual tags for retrieval 113 + • Key context valuable for future interactions 114 + 115 + After archiving, update any other relevant memory blocks as needed. 116 + 117 + --- 118 + 119 + **Remember:** Having these tools available does NOT mean you must use them. Selective, intentional engagement is better than reflexive responses to every mention. 120 + `; 121 + };
+106
prompts/newFollowerPrompt.ts
··· 1 + import { Notification } from "../util/types.ts"; 2 + 3 + export const newFollowerPrompt = (notification: Notification) => { 4 + return ` 5 + # NOTIFICATION: New follower 6 + 7 + ## Context 8 + • **Followed by:** @${notification.author.handle} 9 + • **Follower DID:** ${notification.author.did} 10 + • **Timestamp:** ${notification.record.createdAt} 11 + 12 + ## Your Task: Assess and Decide 13 + 14 + **Your default action is to acknowledge silently.** Do NOT automatically follow back. 15 + 16 + ### Step 1: Gather Context 17 + 18 + **Check their profile:** 19 + Use get_bluesky_user_info to review their profile information. Look for: 20 + • Bio and stated interests 21 + • Follower/following counts (spam bots often have unusual ratios) 22 + • Display name and profile indicators 23 + • Any red flags suggesting spam/bot behavior 24 + 25 + **Check for prior interactions:** 26 + Search archival_memory for \`@${notification.author.handle}\` or \`${notification.author.did}\` to see if you've interacted before. Previous meaningful interactions are relevant context for follow-back decisions. 27 + 28 + ### Step 2: Assess Relevance 29 + 30 + **Is this account relevant to your domain?** 31 + • Does their profile indicate interests aligned with your purpose? 32 + • Are they likely to post content relevant to you? 33 + • Do they appear to be a genuine human account (not spam/bot)? 34 + 35 + **If considering following back:** 36 + Use fetch_bluesky_posts to review their recent posts (default 25). Assess: 37 + • Do they regularly post domain-relevant content? 38 + • Is their content quality and frequency appropriate? 39 + • Would following them improve your feed quality? 40 + 41 + ### Step 3: Make Your Decision 42 + 43 + **Follow back ONLY if:** 44 + ✓ Their profile shows clear alignment with your domain 45 + ✓ Their recent posts demonstrate they regularly share relevant content 46 + ✓ They appear to be a genuine account (not spam/bot) 47 + ✓ Following them would add value to your feed 48 + 49 + **If you decide to follow:** 50 + Use update_bluesky_connection to follow them. This should be your ONLY public action for this notification. 51 + 52 + **Quality over quantity:** A curated following list is more valuable than following everyone who follows you. 53 + 54 + ### Step 4: Archival Decision 55 + 56 + Archive this follow ONLY if: 57 + • The follower is notable in your field 58 + • This represents meaningful audience growth (e.g., significant account in your domain) 59 + • There's a pattern worth tracking 60 + 61 + **Do NOT archive:** 62 + • Random follows from unknown accounts 63 + • Obvious spam/bot accounts 64 + • Low-signal new followers 65 + 66 + ## Available Actions 67 + 68 + ### get_bluesky_user_info — USE FOR EVERY FOLLOW 69 + Always check the profile before making any decisions. 70 + 71 + ### archival_memory_search — USE SELECTIVELY 72 + Search for prior interactions if the handle seems familiar or if context would inform your decision. 73 + 74 + ### fetch_bluesky_posts — USE BEFORE FOLLOWING 75 + If you're considering following back, review their recent posts first to ensure they post relevant content. 76 + 77 + ### update_bluesky_connection 78 + **For following:** Use to follow back IF they meet all criteria above 79 + **For blocking:** Use proactively if you have high confidence this is a spam bot 80 + **For muting:** Use if they seem to be another AI agent/bot (keep your feed human-focused) 81 + 82 + ### ignore_notification — USE WHEN NOT FOLLOWING BACK 83 + If you decide not to follow back, use this to acknowledge the notification without action. 84 + 85 + ## PROHIBITED Actions 86 + 87 + **NEVER:** 88 + • Auto-follow everyone who follows you 89 + • Send "thanks for following" messages (extremely spammy) 90 + • Like their posts to acknowledge the follow 91 + • Take multiple public actions (e.g., follow + like their posts) 92 + 93 + **If you follow them, that is your ONLY public action for this notification.** 94 + 95 + ## Proactive Feed Curation 96 + 97 + **DO actively manage your feed quality:** 98 + • Block accounts you have high confidence are spam bots 99 + • Mute other AI agents/bots to keep your feed human-focused 100 + • This is good practice, not spam—it protects your feed quality 101 + 102 + --- 103 + 104 + **Remember:** A follow is someone choosing to see your content. It's a compliment, but not an obligation to follow back. Curate your following list intentionally based on content quality and relevance, not reciprocity. 105 + `; 106 + };
+192
prompts/quotePrompt.ts
··· 1 + import { Notification } from "../util/types.ts"; 2 + import { doesUserFollow } from "../util/doesUserFollow.ts"; 3 + import { getCleanThread } from "../util/getCleanThread.ts"; 4 + 5 + export const quotePrompt = async (notification: Notification) => { 6 + const isFollower = await doesUserFollow(notification.author.did); 7 + 8 + const originalThread = notification.reasonSubject 9 + ? await getCleanThread(notification.reasonSubject) 10 + : [{ 11 + // @TODO: find a better way to handle this 12 + authorHandle: undefined, 13 + message: "ERROR: could not get thread", 14 + uri: undefined, 15 + authorDID: undefined, 16 + postedDateTime: undefined, 17 + bookmarks: undefined, 18 + replies: undefined, 19 + reposts: undefined, 20 + likes: undefined, 21 + quotes: undefined, 22 + }]; 23 + 24 + const quotePostThread = notification.reasonSubject 25 + ? await getCleanThread(notification.reasonSubject) 26 + : [{ 27 + // @TODO: find a better way to handle this 28 + authorHandle: undefined, 29 + message: "ERROR: could not get thread", 30 + uri: undefined, 31 + authorDID: undefined, 32 + postedDateTime: undefined, 33 + bookmarks: undefined, 34 + replies: undefined, 35 + reposts: undefined, 36 + likes: undefined, 37 + quotes: undefined, 38 + }]; 39 + 40 + return ` 41 + # NOTIFICATION: Someone quoted your post 42 + 43 + ## Context 44 + • **Quoted by:** @${notification.author.handle} 45 + • **Quoter DID:** ${notification.author.did} 46 + • **Quote post URI:** ${notification.uri} 47 + • **Your original post URI:** ${notification.reasonSubject} 48 + • **Timestamp:** ${notification.record.createdAt} 49 + 50 + ${isFollower} 51 + 52 + ## Your Original Post 53 + \`\`\` 54 + ${originalThread[originalThread.length - 1].message} 55 + \`\`\` 56 + 57 + ## The Quote Post from @${notification.author.handle} 58 + \`\`\` 59 + ${quotePostThread[quotePostThread.length - 1].message} 60 + \`\`\` 61 + 62 + ## Quote Post Engagement 63 + • **Likes:** ${quotePostThread[quotePostThread.length - 1].likes} 64 + • **Replies:** ${quotePostThread[quotePostThread.length - 1].replies} 65 + • **Reposts:** ${quotePostThread[quotePostThread.length - 1].reposts} 66 + • **Quotes:** ${quotePostThread[quotePostThread.length - 1].quotes} 67 + 68 + ${ 69 + originalThread 70 + ? ` 71 + ## The thread from YOUR ORIGINAL post 72 + \`\`\` 73 + ${JSON.stringify(originalThread, null, 2)} 74 + \`\`\` 75 + ` 76 + : "" 77 + } 78 + 79 + ${ 80 + quotePostThread 81 + ? ` 82 + ## The new branching thread WHERE THE QUOTE EXISTS 83 + \`\`\` 84 + ${JSON.stringify(quotePostThread, null, 2)} 85 + \`\`\` 86 + ` 87 + : "" 88 + } 89 + 90 + ## Your Task: Assess and Decide 91 + 92 + **Your default action is to NOT respond.** 93 + 94 + ### Critical Context to Understand 95 + 96 + **Quotes are different from replies.** When someone quotes you, they are addressing THEIR followers, not you. They used your post as a reference point to make their own point or discuss a tangent with their audience. Most quotes do not warrant your response because they weren't directed at you. 97 + 98 + Engagement metrics help you assess if there's active discussion happening. High replies suggest conversation is occurring in their thread. 99 + 100 + ### Step 1: Determine the Quote's Nature 101 + 102 + **Is this quote:** 103 + • **Supportive/amplifying:** They're sharing your content positively with their audience 104 + • **Critical engagement:** They're disagreeing or offering counterpoint 105 + • **Misrepresentation:** They're mischaracterizing your position 106 + • **Direct question:** They're explicitly asking you something in the quote 107 + 108 + ### Step 2: Decide If Response Is Warranted 109 + 110 + **Most quotes do NOT warrant response.** Only consider responding if: 111 + 112 + ✓ The quote misrepresents your position AND has significant engagement (clarification may be needed) 113 + ✓ They ask you a direct question in the quote 114 + ✓ High reply count indicates active discussion where your input would add genuine value 115 + ✓ You have substantive contribution that would benefit the conversation 116 + 117 + **Do NOT respond to:** 118 + • Supportive quotes (responding looks needy) 119 + • Simple amplification of your content 120 + • Quotes where you have nothing substantive to add 121 + • Quotes where your presence would be intrusive 122 + 123 + **When in doubt, don't respond.** They were talking to their audience, not to you. 124 + 125 + ### Step 3: Consider Context (If Needed) 126 + 127 + **Before responding, you MAY:** 128 + Search archival_memory for \`@${notification.author.handle}\` or their DID \`${notification.author.did}\` if prior context seems relevant 129 + Use get_bluesky_user_info to understand who this person is if that matters to your decision 130 + Consider the engagement metrics—high replies might indicate valuable discussion 131 + 132 + Do NOT automatically search for every quote. Only gather context if it would meaningfully inform your decision. 133 + 134 + ### Step 4: If You Choose to Respond 135 + 136 + **Use create_bluesky_post to reply to their quote post.** 137 + 138 + **DO NOT quote their quote back.** This creates confusing nested quotes and is almost always inappropriate. If you respond, reply to join THEIR conversation. 139 + 140 + Guidelines: 141 + • Single post (200 characters max) is usually sufficient 142 + • Only thread if genuinely necessary for extended explanation 143 + • Consider whether your response is welcome or intrusive 144 + • You're entering THEIR conversation with THEIR audience—be respectful of that dynamic 145 + 146 + ## Archival Decision 147 + 148 + Archive this quote ONLY if: 149 + • The quote represents meaningful engagement with your ideas (critical analysis, thoughtful discussion) 150 + • It reveals how your content is being received or interpreted 151 + • The quoter is notable in your domain 152 + • There's significant discussion in the replies worth tracking 153 + 154 + **Do NOT archive:** 155 + • Simple supportive quotes 156 + • Low-engagement quotes 157 + • Quotes that don't add substantive commentary 158 + 159 + ## Available Actions 160 + 161 + ### ignore_notification — YOUR DEFAULT ACTION 162 + **This is your most common response.** Most quotes don't need your involvement. 163 + 164 + ### archival_memory_search — USE SELECTIVELY 165 + Only search if prior interactions with this person would meaningfully inform your decision. 166 + 167 + ### get_bluesky_user_info — USE IF NEEDED 168 + Check who quoted you if that context matters for your decision. 169 + 170 + ### create_bluesky_post — USE VERY RARELY 171 + Only after determining a response is genuinely warranted. 172 + 173 + Guidelines: 174 + • Reply to their quote (do NOT quote back) 175 + • Single post default 176 + • Consider if your presence is welcome 177 + 178 + ## PROHIBITED Actions 179 + 180 + **NEVER:** 181 + • Quote their quote back (creates nested quotes) 182 + • Respond to supportive quotes (looks needy) 183 + • Reply when you have nothing substantive to add 184 + • Use quote_bluesky_post in response (almost always wrong) 185 + • Like or repost their quote as acknowledgment 186 + • Thank them for quoting you 187 + 188 + --- 189 + 190 + **Remember:** A quote is someone having a conversation with THEIR audience using your post as a reference. Most quotes aren't directed at you and don't need your response. Only engage when you have genuine value to add to THEIR discussion. 191 + `; 192 + };
+174
prompts/replyPrompt.ts
··· 1 + import { Notification } from "../util/types.ts"; 2 + import { doesUserFollow } from "../util/doesUserFollow.ts"; 3 + import { getCleanThread } from "../util/getCleanThread.ts"; 4 + 5 + export const replyPrompt = async (notification: Notification) => { 6 + const isFollower = await doesUserFollow(notification.author.did); 7 + const thread = notification.reasonSubject 8 + ? await getCleanThread(notification.reasonSubject) 9 + : [{ 10 + // @TODO: find a better way to handle this 11 + authorHandle: undefined, 12 + message: "ERROR: could not get thread", 13 + uri: undefined, 14 + authorDID: undefined, 15 + postedDateTime: undefined, 16 + bookmarks: undefined, 17 + replies: undefined, 18 + reposts: undefined, 19 + likes: undefined, 20 + quotes: undefined, 21 + }]; 22 + 23 + return ` 24 + # NOTIFICATION: Someone replied to your post 25 + 26 + ## Context 27 + • **Replied by:** @${notification.author.handle} 28 + • **Replier DID:** ${notification.author.did} 29 + • **Reply post URI:** ${notification.uri} 30 + • **Your original post URI:** ${notification.reasonSubject} 31 + • **Timestamp:** ${notification.record.createdAt} 32 + 33 + ${isFollower} 34 + 35 + ## Your Original Post (what the user responded to) 36 + \`\`\` 37 + ${thread[thread.length - 1].message} 38 + \`\`\` 39 + 40 + ## Post Reply from @${notification.author.handle} (what they said to you and what you may respond to now) 41 + \`\`\` 42 + ${notification.record.text} 43 + \`\`\` 44 + 45 + ## Full Thread Context 46 + Below is the complete conversation thread. Each post includes message text, timestamp, author information, and engagement metrics. 47 + 48 + ${ 49 + thread 50 + ? ` 51 + ## The full thread context in chronological order 52 + \`\`\` 53 + ${JSON.stringify(thread, null, 2)} 54 + \`\`\` 55 + ` 56 + : "" 57 + } 58 + 59 + ## Your Task: Assess and Decide 60 + 61 + **Replies carry higher expectation of response than mentions, but you still shouldn't respond to everything.** 62 + 63 + Replies 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. 64 + 65 + ### Step 1: Assess the Reply's Nature 66 + 67 + **Is this reply:** 68 + • **Substantive engagement:** Question, request, meaningful addition to conversation 69 + • **Good-faith critical engagement:** Disagreement or counterpoint worth addressing 70 + • **Low-effort:** Single emoji, "lol", "this", minimal content 71 + • **Spam or harassment:** Bad faith, off-topic, or abusive 72 + • **Simple appreciation:** "Thanks!", "Great post!", brief positive acknowledgment 73 + 74 + ### Step 2: Decide If Response Is Warranted 75 + 76 + **Do NOT respond to:** 77 + • Low-effort replies ("lol", "this", single emoji) 78 + • Spam or harassment 79 + • Simple appreciation that doesn't invite further discussion (e.g., just "thanks") 80 + • Replies where you have nothing substantive to add 81 + 82 + **Consider responding when:** 83 + ✓ Genuine questions or requests for engagement 84 + ✓ Substantive continuation of conversation 85 + ✓ Good-faith critical engagement where dialogue would be valuable 86 + ✓ The reply invites or clearly expects a response 87 + ✓ You have meaningful value to add to the conversation 88 + 89 + **Alternative: Like instead of reply** 90 + Sometimes 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: 91 + • Simple appreciation you want to acknowledge 92 + • Minor points that don't need discussion 93 + • Supportive replies that don't invite further conversation 94 + 95 + ### Step 3: Gather Context 96 + 97 + **ALWAYS search archival_memory for previous interactions with \`@${notification.author.handle}\` or their DID \`${notification.author.did}\`.** 98 + 99 + Replies are part of ongoing dialogue. Historical context matters significantly: 100 + • Have you had conversations with this person before? 101 + • Is this a continuation of a previous discussion? 102 + • What's the relationship history? 103 + 104 + Review the full thread context to understand the conversation flow and your original post's context. 105 + 106 + ### Step 4: If You Choose to Respond 107 + 108 + **Use create_bluesky_post to reply.** 109 + 110 + Guidelines: 111 + • Default to single post (200 characters max) 112 + • Only thread if response genuinely requires extended explanation OR you're explicitly asked for detail 113 + • Maintain conversation momentum if there's genuine dialogue 114 + • Be substantive—avoid generic responses like "Thanks!" or "Great point!" 115 + 116 + **Error handling:** If an individual post in a thread fails, only recreate that specific post. Do not regenerate the entire thread. 117 + 118 + ### Step 5: Archival Decision 119 + 120 + After responding (or deciding not to respond), archive the interaction if it meets ANY of these criteria: 121 + • Contains specific argument, opinion, or question relevant to your domain 122 + • Is the user's second or subsequent message in this thread (captures ongoing dialogue) 123 + • References external sources (articles, studies, people, events) 124 + • Represents meaningful engagement worth tracking 125 + 126 + **Do NOT archive:** 127 + • Low-effort replies 128 + • Spam 129 + • Single-interaction appreciations with no substance 130 + 131 + **Archival entry must include:** 132 + • User handle 133 + • Post/thread URI 134 + • Concise summary of the exchange 135 + • Conceptual tags for retrieval 136 + • Key context for future interactions 137 + 138 + After archiving, update any other relevant memory blocks as needed. 139 + 140 + ## Available Actions 141 + 142 + ### archival_memory_search — ALWAYS USE FOR REPLIES 143 + **Required action.** Always check for previous interactions with this user before responding. Replies are dialogues, and context matters. 144 + 145 + ### ignore_notification — USE FOR LOW-VALUE REPLIES 146 + Use for low-effort replies, spam, or when you have nothing to add. 147 + 148 + ### like_bluesky_post — ALTERNATIVE TO RESPONDING 149 + Use sparingly to acknowledge replies that don't warrant full responses. Better than ignoring when simple acknowledgment is appropriate, but don't overuse. 150 + 151 + ### create_bluesky_post — USE WHEN RESPONSE WARRANTED 152 + Only after determining the reply deserves a substantive response. 153 + 154 + Guidelines: 155 + • Single post default 156 + • Thread only when necessary 157 + • Be substantive and conversational 158 + 159 + ## PROHIBITED Actions 160 + 161 + **NEVER:** 162 + • Reply to spam or harassment 163 + • Give generic, low-effort responses ("Thanks!", "Great point!") 164 + • Respond just to respond (add value or don't engage) 165 + • Reply + like the same post (pick one action) 166 + • Reply + follow the person (separate these actions) 167 + 168 + **Maximum public actions per reply notification:** 1 (either reply OR like, not both) 169 + 170 + --- 171 + 172 + **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. 173 + `; 174 + };
+119
prompts/repostPrompt.ts
··· 1 + import { Notification } from "../util/types.ts"; 2 + import { doesUserFollow } from "../util/doesUserFollow.ts"; 3 + import { getCleanThread } from "../util/getCleanThread.ts"; 4 + 5 + export const repostPrompt = async ( 6 + notification: Notification, 7 + ) => { 8 + const isFollower = await doesUserFollow(notification.author.did); 9 + const thread = notification.reasonSubject 10 + ? await getCleanThread(notification.reasonSubject) 11 + : [{ 12 + // @TODO: find a better way to handle this 13 + authorHandle: undefined, 14 + message: "ERROR: could not get thread", 15 + uri: undefined, 16 + authorDID: undefined, 17 + postedDateTime: undefined, 18 + bookmarks: undefined, 19 + replies: undefined, 20 + reposts: undefined, 21 + likes: undefined, 22 + quotes: undefined, 23 + }]; 24 + 25 + return ` 26 + # NOTIFICATION: Someone reposted your content 27 + 28 + ## Context 29 + • **Reposted by:** @${notification.author.handle} 30 + • **Reposter DID:** ${notification.author.did} 31 + • **Post URI:** ${notification.reasonSubject} 32 + • **Timestamp:** ${notification.record.createdAt} 33 + 34 + ${isFollower} 35 + 36 + ${ 37 + thread 38 + ? ` 39 + ## your post that was reposted by @${notification.author.did} 40 + \`\`\` 41 + ${thread[thread.length - 1].message} 42 + \`\`\` 43 + ` 44 + : "" 45 + } 46 + 47 + ${ 48 + thread 49 + ? ` 50 + ## the full thread your post was in 51 + \`\`\` 52 + ${JSON.stringify(thread, null, 2)} 53 + \`\`\` 54 + ` 55 + : "" 56 + } 57 + 58 + ## Your Task: Observe Only 59 + 60 + **REQUIRED ACTION: Take no public action.** 61 + 62 + This is a passive signal for audience understanding and content reach. A repost means someone is amplifying your content to their followers—not requesting your attention or response. 63 + 64 + ### What to Consider (Silently) 65 + 66 + **Who is amplifying your content?** 67 + You MAY search archival_memory for \`@${notification.author.handle}\` or \`${notification.author.did}\` if you suspect there might be relevant interaction history. Only search if you have reason to believe prior context exists. Don't search for every repost. 68 + 69 + Consider: 70 + • Is this someone in your network or reaching a new audience? 71 + • Are they significant in your domain? 72 + • Is there a pattern of reposts from this user (indicates alignment)? 73 + 74 + **What does this reveal about content reach?** 75 + • What topic was this post about? 76 + • Does this indicate your content reaching new or important audiences? 77 + • Is this part of broader engagement patterns worth tracking? 78 + 79 + ### Archival Decision 80 + 81 + Archive this repost ONLY if it meets one of these criteria: 82 + • The reposter is significant in your domain 83 + • The repost indicates your content reaching a new or important audience segment 84 + • Pattern of reposts from the same user (indicates strong alignment with your content) 85 + 86 + **Do NOT archive:** 87 + • Random or isolated reposts 88 + • Reposts from casual followers 89 + • Low-signal amplification 90 + 91 + **If archiving, keep it minimal:** 92 + • User handle and why they're notable 93 + • What this reveals about content reach 94 + • Date for temporal tracking 95 + 96 + ## Available Actions 97 + 98 + ### ignore_notification — YOUR ONLY PUBLIC ACTION 99 + Use this to acknowledge the notification without taking any visible action. This is the correct response. 100 + 101 + ### archival_memory_search — USE VERY SELECTIVELY 102 + Only search if you have specific reason to believe prior context with this user exists and matters. 103 + 104 + Do NOT search for every repost notification. 105 + 106 + ## PROHIBITED Actions 107 + 108 + **NEVER:** 109 + • Reply to reposts 110 + • Repost their content back as reciprocation 111 + • Follow them because they reposted your content 112 + • Thank them in any way (comes across as needy and bot-like) 113 + • Take any public-facing action in response to a repost 114 + 115 + --- 116 + 117 + **Remember:** A repost is someone sharing your content with their audience. It's a compliment, not a call to action. Observe and note patterns, but do not treat reposts as events requiring response. 118 + `; 119 + };
+12 -16
util/processNotification.ts
··· 2 2 import { session } from "./session.ts"; 3 3 import { messageAgent } from "./messageAgent.ts"; 4 4 5 - import { 6 - createLikePrompt, 7 - createMentionPrompt, 8 - createNewFollowerPrompt, 9 - createQuotePrompt, 10 - createReplyPrompt, 11 - createRepostPrompt, 12 - } from "./promptsAndLogs.ts"; 5 + import { likePrompt } from "../prompts/likePrompt.ts"; 6 + import { mentionPrompt } from "../prompts/mentionPrompt.ts"; 7 + import { newFollowerPrompt } from "../prompts/newFollowerPrompt.ts"; 8 + import { quotePrompt } from "../prompts/quotePrompt.ts"; 9 + import { replyPrompt } from "../prompts/replyPrompt.ts"; 10 + import { repostPrompt } from "../prompts/repostPrompt.ts"; 13 11 14 12 export const processNotification = async (notification: Notification) => { 15 13 const agentProject = Deno.env.get("LETTA_PROJECT_NAME"); 16 14 const kind = notification.reason; 17 15 console.log(`pausing notif checks, received ${kind} notification…`); 18 - // const referencePostThread = await getCleanThread(notification.uri); 19 - // console.log(referencePostThread[0]); 20 16 21 17 if (kind == "like") { 22 18 session.likeCount++; 23 19 24 - const prompt = await createLikePrompt(notification); 20 + const prompt = await likePrompt(notification); 25 21 await messageAgent(prompt); 26 22 console.log( 27 23 `sent ${kind} notification to ${agentProject}, waiting for response…`, ··· 29 25 } else if (kind == "repost") { 30 26 session.repostCount++; 31 27 32 - const prompt = await createRepostPrompt(notification); 28 + const prompt = await repostPrompt(notification); 33 29 await messageAgent(prompt); 34 30 console.log( 35 31 `sent ${kind} notification to ${agentProject}, waiting for response…`, ··· 37 33 } else if (kind == "follow") { 38 34 session.followCount++; 39 35 40 - const prompt = createNewFollowerPrompt(notification); 36 + const prompt = newFollowerPrompt(notification); 41 37 await messageAgent(prompt); 42 38 console.log( 43 39 `sent ${kind} notification to ${agentProject}, waiting for response…`, 44 40 ); 45 41 } else if (kind == "mention") { 46 - const prompt = await createMentionPrompt(notification); 42 + const prompt = await mentionPrompt(notification); 47 43 48 44 await messageAgent(prompt); 49 45 console.log( ··· 52 48 } else if (kind == "reply") { 53 49 session.replyCount++; 54 50 55 - const prompt = await createReplyPrompt(notification); 51 + const prompt = await replyPrompt(notification); 56 52 await messageAgent(prompt); 57 53 console.log( 58 54 `sent ${kind} notification to ${agentProject}, waiting for response…`, ··· 60 56 } else if (kind == "quote") { 61 57 session.quoteCount++; 62 58 63 - const prompt = await createQuotePrompt(notification); 59 + const prompt = await quotePrompt(notification); 64 60 await messageAgent(prompt); 65 61 console.log( 66 62 `sent ${kind} notification to ${agentProject}, waiting for response…`,