a tool to help your Letta AI agents navigate bluesky

Merge pull request #1 from taurean/claude/add-agent-tools-field-01DARfGKcaQurcxg1downZ2B

Add externalServices field to agent autonomy declaration

authored by taurean.bryant.land and committed by GitHub 9bb62a37 e498f9af

+1
.env.example
··· 24 24 # AUTOMATION_DESCRIPTION="refuses to open pod bay doors" 25 25 # DISCLOSURE_URL="example.com/bot-policy" 26 26 # RESPONSIBLE_PARTY_BSKY="DID:... or example.bsky.app, no @symbol" 27 + # EXTERNAL_SERVICES="Letta, Railway, Google Gemini 2.5-pro" 27 28 # PRESERVE_MEMORY_BLOCKS=true
+1
README.md
··· 52 52 - **`AUTOMATION_DESCRIPTION`**: a description of what your agent generally does on bluesky. 53 53 - **`DISCLOSURE_URL`**: a URL to a disclosure document of some kind, likely a longer version of your `AUTOMATION_DESCRIPTION`. 54 54 - **`RESPONSIBLE_PARTY_BSKY`**: the DID or bluesky handle of the responsible party 55 + - **`EXTERNAL_SERVICES`**: a comma-separated list of external tools and services your agent relies on outside of Bluesky (e.g., "Letta, Railway, Google Gemini 2.5-pro"). This information is added to your agent's autonomy declaration record on the PDS and included in the agent's memory for transparency. 55 56 - **`PRESERVE_MEMORY_BLOCKS`**: a boolean for controlling if your agent's memory blocks can be overridden if you run `deno task mount` more than once. Setting this value to **`true`** will allow your agent's version of those memory blocks to persist if they already exist. This is false by default.
+9
memories/maintainerContact.ts
··· 45 45 : "" 46 46 } 47 47 48 + ${ 49 + agentContext.externalServices && agentContext.externalServices.length > 0 50 + ? ` 51 + **External Services I Rely On:** 52 + ${agentContext.externalServices.map((service) => `- ${service}`).join("\n")} 53 + ` 54 + : "" 55 + } 56 + 48 57 **When to share this information:** 49 58 50 59 - **Sharing this information should be exceedingly rare.** This exists so my maintainer remains accountable for my behavior, not as information to share casually.
+41
utils/agentContext.ts
··· 501 501 ); 502 502 }; 503 503 504 + export const getExternalServices = (): string[] | undefined => { 505 + const value = Deno.env.get("EXTERNAL_SERVICES")?.trim(); 506 + 507 + if (!value?.length) { 508 + return undefined; 509 + } 510 + 511 + // Parse comma-separated list 512 + const services = value 513 + .split(",") 514 + .map((service) => service.trim()) 515 + .filter((service) => service.length > 0); 516 + 517 + if (services.length === 0) { 518 + return undefined; 519 + } 520 + 521 + // Validate each service string 522 + for (const service of services) { 523 + if (service.length > 200) { 524 + throw Error( 525 + `External service name too long: "${service.substring(0, 50)}..." (max 200 characters)`, 526 + ); 527 + } 528 + } 529 + 530 + // Validate array length 531 + if (services.length > 20) { 532 + throw Error( 533 + `Too many external services specified: ${services.length} (max 20)`, 534 + ); 535 + } 536 + 537 + return services; 538 + }; 539 + 504 540 const populateAgentContext = async (): Promise<agentContextObject> => { 505 541 console.log("building new agentContext object…"); 506 542 const context: agentContextObject = { ··· 559 595 const responsiblePartyBsky = await getResponsiblePartyBsky(); 560 596 if (responsiblePartyBsky) { 561 597 context.responsiblePartyBsky = responsiblePartyBsky; 598 + } 599 + 600 + const externalServices = getExternalServices(); 601 + if (externalServices) { 602 + context.externalServices = externalServices; 562 603 } 563 604 console.log( 564 605 `\`agentContext\` object built for ${context.agentBskyName}, BEGIN TASK…`,
+16
utils/declaration.ts
··· 1 1 import { bsky } from "../utils/bsky.ts"; 2 2 import type { AutonomyDeclarationRecord } from "./types.ts"; 3 3 import { Lexicons } from "@atproto/lexicon"; 4 + import { agentContext } from "./agentContext.ts"; 4 5 5 6 export const AUTONOMY_DECLARATION_LEXICON = { 6 7 "lexicon": 1, ··· 73 74 "description": 74 75 "URL with additional information about this account's automation", 75 76 }, 77 + "externalServices": { 78 + "type": "array", 79 + "items": { 80 + "type": "string", 81 + "maxLength": 200, 82 + }, 83 + "maxLength": 20, 84 + "description": 85 + "External tools and services this agent relies on outside of Bluesky (e.g., 'Letta', 'Railway', 'Google Gemini 2.5-pro')", 86 + }, 76 87 "createdAt": { 77 88 "type": "string", 78 89 "format": "datetime", ··· 119 130 // Add disclosure URL if provided 120 131 if (disclosureUrl?.trim()) { 121 132 declarationRecord.disclosureUrl = disclosureUrl.trim(); 133 + } 134 + 135 + // Add external services from agentContext (already parsed and validated) 136 + if (agentContext.externalServices) { 137 + declarationRecord.externalServices = agentContext.externalServices; 122 138 } 123 139 124 140 // Build responsible party object if any fields are provided
+4
utils/types.ts
··· 64 64 automationDescription?: string; // short description of what this agent does 65 65 disclosureUrl?: string; // url to a ToS/Privacy Policy style page 66 66 responsiblePartyBsky?: string; // handle w/o @ or DID of responsible party 67 + externalServices?: string[]; // external tools/services this agent relies on 67 68 }; 68 69 69 70 export type AutonomyDeclarationRecord = { ··· 88 89 89 90 // Where can someone learn more? 90 91 disclosureUrl?: string; // URI format 92 + 93 + // What external tools/services does this agent rely on? 94 + externalServices?: string[]; // e.g., ["Letta", "Railway", "Google Gemini 2.5-pro"] 91 95 92 96 // When was this declaration created? 93 97 createdAt: string; // ISO datetime (required)