this repo has no description
40
fork

Configure Feed

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

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: