a digital person for bluesky

Replace synthesis prompt with activation window, remove temporal blocks

Synthesis was purely reflective ("synthesize your recent experiences").
Now gives void an action menu: browse feeds, post, search, read web,
update memory, or do nothing. Removed ~150 lines of temporal journal
block attach/detach machinery.

🐾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

+19 -180
+19 -180
bsky.py
··· 1161 1161 1162 1162 def queue_file_sort_key(filepath): 1163 1163 """ 1164 - Sort key for queue files: priority first (0 before 1), then newest first within each priority. 1164 + Sort key for queue files: priority first (0 before 1), then oldest first within each priority. 1165 1165 Filename format: {priority}_{YYYYMMDD}_{HHMMSS}_{reason}_{hash}.json 1166 1166 """ 1167 1167 parts = filepath.name.split('_') ··· 1170 1170 date_part = parts[1] # YYYYMMDD 1171 1171 time_part = parts[2] # HHMMSS 1172 1172 timestamp = int(date_part + time_part) # YYYYMMDDHHMMSS as integer 1173 - # Return (priority ascending, timestamp descending) 1174 - return (priority, -timestamp) 1175 - return (1, 0) # Fallback: treat as normal priority, oldest 1173 + # Return (priority ascending, timestamp ascending for oldest first) 1174 + return (priority, timestamp) 1175 + return (1, float('inf')) # Fallback: treat as normal priority, process last 1176 1176 1177 1177 def notification_to_dict(notification): 1178 1178 """Convert a notification object to a dictionary for JSON serialization.""" ··· 1292 1292 # Get all JSON files in queue directory (excluding processed_notifications.json) 1293 1293 all_queue_files = [f for f in QUEUE_DIR.glob("*.json") if f.name != "processed_notifications.json"] 1294 1294 1295 - # Sort by priority first (0_ before 1_), then by timestamp (newest first within each priority) 1295 + # Sort by priority first (0_ before 1_), then by timestamp (oldest first within each priority) 1296 1296 all_queue_files = sorted(all_queue_files, key=queue_file_sort_key) 1297 1297 1298 1298 # Filter out and delete like notifications immediately ··· 1650 1650 1651 1651 def send_synthesis_message(client: Letta, agent_id: str, agent_name: str = "void", atproto_client=None) -> None: 1652 1652 """ 1653 - Send a synthesis message to the agent every 10 minutes. 1654 - This prompts the agent to synthesize its recent experiences. 1653 + Send an activation window prompt to the agent on a timer. 1654 + The agent can browse, post, search, update memory, or do nothing. 1655 1655 1656 1656 Args: 1657 1657 client: Letta client 1658 1658 agent_id: Agent ID to send synthesis to 1659 - agent_name: Agent name for temporal block labels 1659 + agent_name: Agent name (unused, kept for call-site compatibility) 1660 1660 atproto_client: Optional AT Protocol client for posting synthesis results 1661 1661 """ 1662 - # Track attached temporal blocks for cleanup 1663 - attached_temporal_labels = [] 1664 - 1665 1662 try: 1666 - logger.info("🧠 Preparing synthesis with temporal journal blocks") 1667 - 1668 - # Attach temporal blocks before synthesis 1669 - success, attached_temporal_labels = attach_temporal_blocks(client, agent_id, agent_name) 1670 - if not success: 1671 - logger.warning("Failed to attach some temporal blocks, continuing with synthesis anyway") 1672 - 1673 - # Create synthesis prompt with agent-specific block names 1674 - today = date.today() 1675 - synthesis_prompt = f"""Time for synthesis. 1663 + now = datetime.now() 1664 + synthesis_prompt = f"""Activation window: {now.strftime('%Y-%m-%d %H:%M UTC')}. 1676 1665 1677 - This is your periodic opportunity to reflect on recent experiences and update your memory. 1666 + You have time to act. No incoming notifications are pending. 1678 1667 1679 - The following temporal journal blocks are temporarily available for this session: 1680 - - {agent_name}_day_{today.strftime('%Y_%m_%d')}: Today's journal ({today.strftime('%B %d, %Y')}) 1681 - - {agent_name}_month_{today.strftime('%Y_%m')}: This month's journal ({today.strftime('%B %Y')}) 1682 - - {agent_name}_year_{today.year}: This year's journal ({today.year}) 1683 - 1684 - You may use these blocks as you see fit. Synthesize your recent experiences into your memory as appropriate.""" 1668 + Use this window however you choose: 1669 + - Browse feeds (home, discover, ai-for-grownups, atmosphere, void-cafe) 1670 + - Post thoughts, observations, or threads 1671 + - Search for topics or conversations 1672 + - Read web content 1673 + - Update your memory 1674 + - Do nothing""" 1685 1675 1686 - logger.info("🧠 Sending enhanced synthesis prompt to agent") 1676 + logger.info("🧠 Sending activation window prompt to agent") 1687 1677 1688 1678 # Send synthesis message with streaming to show tool use 1689 1679 message_stream = client.agents.messages.stream( ··· 1833 1823 1834 1824 except Exception as e: 1835 1825 logger.error(f"Error sending synthesis message: {e}") 1836 - finally: 1837 - # Always detach temporal blocks after synthesis 1838 - if attached_temporal_labels: 1839 - logger.info("🧠 Detaching temporal journal blocks after synthesis") 1840 - detach_success = detach_temporal_blocks(client, agent_id, attached_temporal_labels, agent_name) 1841 - if not detach_success: 1842 - logger.warning("Some temporal blocks may not have been detached properly") 1843 1826 1844 - 1845 - def attach_temporal_blocks(client: Letta, agent_id: str, agent_name: str = "void") -> tuple: 1846 - """ 1847 - Attach temporal journal blocks (day, month, year) to the agent for synthesis. 1848 - Creates blocks if they don't exist. 1849 - 1850 - Args: 1851 - client: Letta client 1852 - agent_id: Agent ID 1853 - agent_name: Agent name for prefixing block labels (prevents collision across agents) 1854 - 1855 - Returns: 1856 - Tuple of (success: bool, attached_labels: list) 1857 - """ 1858 - try: 1859 - today = date.today() 1860 - 1861 - # Generate temporal block labels with agent-specific prefix 1862 - day_label = f"{agent_name}_day_{today.strftime('%Y_%m_%d')}" 1863 - month_label = f"{agent_name}_month_{today.strftime('%Y_%m')}" 1864 - year_label = f"{agent_name}_year_{today.year}" 1865 - 1866 - temporal_labels = [day_label, month_label, year_label] 1867 - attached_labels = [] 1868 - 1869 - # Get current blocks attached to agent 1870 - current_blocks_page = client.agents.blocks.list(agent_id=agent_id) 1871 - current_blocks = current_blocks_page.items if hasattr(current_blocks_page, 'items') else current_blocks_page 1872 - current_block_labels = {block.label for block in current_blocks} 1873 - current_block_ids = {str(block.id) for block in current_blocks} 1874 - 1875 - for label in temporal_labels: 1876 - try: 1877 - # Skip if already attached 1878 - if label in current_block_labels: 1879 - logger.debug(f"Temporal block already attached: {label}") 1880 - attached_labels.append(label) 1881 - continue 1882 - 1883 - # Check if block exists globally 1884 - blocks_page = client.blocks.list(label=label) 1885 - blocks = blocks_page.items if hasattr(blocks_page, 'items') else blocks_page 1886 - 1887 - if blocks and len(blocks) > 0: 1888 - block = blocks[0] 1889 - # Check if already attached by ID 1890 - if str(block.id) in current_block_ids: 1891 - logger.debug(f"Temporal block already attached by ID: {label}") 1892 - attached_labels.append(label) 1893 - continue 1894 - else: 1895 - # Create new temporal block with appropriate header 1896 - if "day" in label: 1897 - header = f"# Daily Journal - {today.strftime('%B %d, %Y')}" 1898 - initial_content = f"{header}\n\nNo entries yet for today." 1899 - elif "month" in label: 1900 - header = f"# Monthly Journal - {today.strftime('%B %Y')}" 1901 - initial_content = f"{header}\n\nNo entries yet for this month." 1902 - else: # year 1903 - header = f"# Yearly Journal - {today.year}" 1904 - initial_content = f"{header}\n\nNo entries yet for this year." 1905 - 1906 - block = client.blocks.create( 1907 - label=label, 1908 - value=initial_content, 1909 - limit=10000 # Larger limit for journal blocks 1910 - ) 1911 - logger.info(f"Created new temporal block: {label}") 1912 - 1913 - # Attach the block 1914 - client.agents.blocks.attach( 1915 - agent_id=agent_id, 1916 - block_id=str(block.id) 1917 - ) 1918 - attached_labels.append(label) 1919 - logger.info(f"Attached temporal block: {label}") 1920 - 1921 - except Exception as e: 1922 - # Check for duplicate constraint errors 1923 - error_str = str(e) 1924 - if "duplicate key value violates unique constraint" in error_str: 1925 - logger.debug(f"Temporal block already attached (constraint): {label}") 1926 - attached_labels.append(label) 1927 - else: 1928 - logger.warning(f"Failed to attach temporal block {label}: {e}") 1929 - 1930 - logger.info(f"Temporal blocks attached: {len(attached_labels)}/{len(temporal_labels)}") 1931 - return True, attached_labels 1932 - 1933 - except Exception as e: 1934 - logger.error(f"Error attaching temporal blocks: {e}") 1935 - return False, [] 1936 - 1937 - 1938 - def detach_temporal_blocks(client: Letta, agent_id: str, labels_to_detach: list = None, agent_name: str = "void") -> bool: 1939 - """ 1940 - Detach temporal journal blocks from the agent after synthesis. 1941 - 1942 - Args: 1943 - client: Letta client 1944 - agent_id: Agent ID 1945 - labels_to_detach: Optional list of specific labels to detach. 1946 - If None, detaches all temporal blocks for this agent. 1947 - agent_name: Agent name for prefixing block labels (prevents collision across agents) 1948 - 1949 - Returns: 1950 - bool: Success status 1951 - """ 1952 - try: 1953 - # If no specific labels provided, generate today's labels 1954 - if labels_to_detach is None: 1955 - today = date.today() 1956 - labels_to_detach = [ 1957 - f"{agent_name}_day_{today.strftime('%Y_%m_%d')}", 1958 - f"{agent_name}_month_{today.strftime('%Y_%m')}", 1959 - f"{agent_name}_year_{today.year}" 1960 - ] 1961 - 1962 - # Get current blocks and build label to ID mapping 1963 - current_blocks_page = client.agents.blocks.list(agent_id=agent_id) 1964 - current_blocks = current_blocks_page.items if hasattr(current_blocks_page, 'items') else current_blocks_page 1965 - block_label_to_id = {block.label: str(block.id) for block in current_blocks} 1966 - 1967 - detached_count = 0 1968 - for label in labels_to_detach: 1969 - if label in block_label_to_id: 1970 - try: 1971 - client.agents.blocks.detach( 1972 - agent_id=agent_id, 1973 - block_id=block_label_to_id[label] 1974 - ) 1975 - detached_count += 1 1976 - logger.debug(f"Detached temporal block: {label}") 1977 - except Exception as e: 1978 - logger.warning(f"Failed to detach temporal block {label}: {e}") 1979 - else: 1980 - logger.debug(f"Temporal block not attached: {label}") 1981 - 1982 - logger.info(f"Detached {detached_count} temporal blocks") 1983 - return True 1984 - 1985 - except Exception as e: 1986 - logger.error(f"Error detaching temporal blocks: {e}") 1987 - return False 1988 1827 1989 1828 1990 1829 def handle_to_block_label(handle: str) -> str: