diff --git a/bsky.py b/bsky.py index 4685c84..0c1576b 100644 --- a/bsky.py +++ b/bsky.py @@ -795,7 +795,36 @@ To reply, use the add_post_to_bluesky_reply_thread tool: logger.warning(f"Memory deletion missing reason, skipping: {memory_text[:50]}...") except json.JSONDecodeError as e: logger.error(f"Failed to parse flag_archival_memory_for_deletion arguments: {e}") - + + # Collect archival_memory_insert tool calls for recording to AT Protocol + elif message.tool_call.name == 'archival_memory_insert': + try: + args = json.loads(message.tool_call.arguments) + content = args.get('content', '') + tags_str = args.get('tags', None) + + # Parse tags from string representation if present + tags = None + if tags_str: + try: + tags = json.loads(tags_str) if isinstance(tags_str, str) else tags_str + except json.JSONDecodeError: + logger.warning(f"Failed to parse tags from archival_memory_insert: {tags_str[:50]}...") + + if content: + # Create stream.thought.memory record + try: + memory_result = bsky_utils.create_memory_record(client, content, tags) + if memory_result: + tags_info = f" ({len(tags)} tags)" if tags else "" + logger.info(f"📝 Recorded archival memory to AT Protocol{tags_info}: {content[:100]}...") + else: + logger.warning(f"Failed to record archival memory to AT Protocol") + except Exception as e: + logger.error(f"Error creating memory record: {e}") + except json.JSONDecodeError as e: + logger.error(f"Failed to parse archival_memory_insert arguments: {e}") + # Collect add_post_to_bluesky_reply_thread tool calls - only if they were successful elif message.tool_call.name == 'add_post_to_bluesky_reply_thread': tool_call_id = message.tool_call.tool_call_id @@ -1546,6 +1575,24 @@ Begin your synthesis and journaling now.""" elif tool_name == 'archival_memory_insert': content = args.get('content', '') log_with_panel(content[:200] + "..." if len(content) > 200 else content, f"Tool call: {tool_name}", "blue") + + # Record archival memory insert to AT Protocol + if atproto_client: + try: + tags_str = args.get('tags', None) + tags = None + if tags_str: + try: + tags = json.loads(tags_str) if isinstance(tags_str, str) else tags_str + except json.JSONDecodeError: + logger.warning(f"Failed to parse tags from archival_memory_insert: {tags_str[:50]}...") + + memory_result = bsky_utils.create_memory_record(atproto_client, content, tags) + if memory_result: + tags_info = f" ({len(tags)} tags)" if tags else "" + logger.info(f"📝 Recorded archival memory to AT Protocol{tags_info}") + except Exception as e: + logger.debug(f"Failed to create memory record during synthesis: {e}") elif tool_name == 'update_block': label = args.get('label', 'unknown') value_preview = str(args.get('value', ''))[:100] + "..." if len(str(args.get('value', ''))) > 100 else str(args.get('value', '')) diff --git a/bsky_utils.py b/bsky_utils.py index f6b9980..db6680a 100644 --- a/bsky_utils.py +++ b/bsky_utils.py @@ -1053,6 +1053,85 @@ def create_reasoning_record(client: Client, reasoning_text: str) -> Optional[Dic return None +def create_memory_record(client: Client, content: str, tags: Optional[List[str]] = None) -> Optional[Dict[str, Any]]: + """ + Create a stream.thought.memory record to store archival memory insertions. + + This creates a record of archival_memory_insert tool calls, preserving + important memories and context in the AT Protocol. + + Args: + client: Authenticated Bluesky client + content: The memory content being archived + tags: Optional list of tags associated with this memory + + Returns: + The response from creating the memory record or None if failed + """ + try: + import requests + import json + from datetime import datetime, timezone + + # Get session info from the client + access_token = None + user_did = None + + # Try different ways to get the session info + if hasattr(client, '_session') and client._session: + access_token = client._session.access_jwt + user_did = client._session.did + elif hasattr(client, 'access_jwt'): + access_token = client.access_jwt + user_did = client.did if hasattr(client, 'did') else None + else: + logger.error("Cannot access client session information") + return None + + if not access_token or not user_did: + logger.error("Missing access token or DID from session") + return None + + # Get PDS URI from config instead of environment variables + from config_loader import get_bluesky_config + bluesky_config = get_bluesky_config() + pds_host = bluesky_config['pds_uri'] + + # Create memory record + now = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") + memory_record = { + "$type": "stream.thought.memory", + "content": content, + "createdAt": now + } + + # Add tags if provided (can be null) + if tags is not None: + memory_record["tags"] = tags + + # Create the record + headers = {"Authorization": f"Bearer {access_token}"} + create_record_url = f"{pds_host}/xrpc/com.atproto.repo.createRecord" + + create_data = { + "repo": user_did, + "collection": "stream.thought.memory", + "record": memory_record + } + + response = requests.post(create_record_url, headers=headers, json=create_data, timeout=10) + response.raise_for_status() + result = response.json() + + tags_info = f" with {len(tags)} tags" if tags else " (no tags)" + logger.debug(f"Successfully recorded memory (length: {len(content)} chars{tags_info})") + return result + + except Exception as e: + logger.error(f"Error creating memory record: {e}") + return None + + def sync_followers(client: Client, dry_run: bool = False) -> Dict[str, Any]: """ Check who is following the bot and who the bot is following,