diff --git a/src/crud/peer.py b/src/crud/peer.py index acacd235..14f7a652 100644 --- a/src/crud/peer.py +++ b/src/crud/peer.py @@ -11,6 +11,7 @@ from src.cache.client import cache, get_cache_namespace from src.config import settings from src.crud.workspace import get_or_create_workspace from src.exceptions import ConflictException, ResourceNotFoundException +from src.models import Peer from src.utils.filter import apply_filter logger = getLogger(__name__) @@ -63,21 +64,37 @@ async def get_or_create_peers( .where(models.Peer.name.in_(peer_names)) ) result = await db.execute(stmt) - existing_peers = list(result.scalars().all()) + existing_peers: list[Peer] = list(result.scalars().all()) # Create a mapping of peer names to peer schemas for easy lookup peer_schema_map = {p.name: p for p in peers} + # Track which peers actually changed + changed_peers: list[Peer] = [] + # Update existing peers with metadata and configuration if provided for existing_peer in existing_peers: peer_schema = peer_schema_map[existing_peer.name] + changed = False - # Update with metadata and configuration if provided - if peer_schema.metadata is not None: + # Update with metadata if provided AND different + if ( + peer_schema.metadata is not None + and existing_peer.h_metadata != peer_schema.metadata + ): existing_peer.h_metadata = peer_schema.metadata + changed = True - if peer_schema.configuration is not None: + # Update with configuration if provided AND different + if ( + peer_schema.configuration is not None + and existing_peer.configuration != peer_schema.configuration + ): existing_peer.configuration = peer_schema.configuration + changed = True + + if changed: + changed_peers.append(existing_peer) # Find which peers need to be created existing_names = {p.name for p in existing_peers} @@ -104,10 +121,15 @@ async def get_or_create_peers( ) from None return await get_or_create_peers(db, workspace_name, peers, _retry=True) - # Invalidate cache for all updated/created peers - read-through pattern - for peer_obj in existing_peers + new_peers: + # Only invalidate cache for changed/new peers - read-through pattern + for peer_obj in changed_peers + new_peers: cache_key = peer_cache_key(workspace_name, peer_obj.name) await cache.delete(cache_key) + logger.debug( + "Peer %s cache invalidated in workspace %s (changed or new)", + peer_obj.name, + workspace_name, + ) # Return combined list of existing and new peers return existing_peers + new_peers @@ -204,16 +226,31 @@ async def update_peer( ) )[0] - if peer.metadata is not None: - honcho_peer.h_metadata = peer.metadata + needs_update = False - if peer.configuration is not None: + if peer.metadata is not None and honcho_peer.h_metadata != peer.metadata: + honcho_peer.h_metadata = peer.metadata + needs_update = True + + if ( + peer.configuration is not None + and honcho_peer.configuration != peer.configuration + ): honcho_peer.configuration = peer.configuration + needs_update = True + + # Early exit if unchanged + if not needs_update: + logger.debug( + "Peer %s unchanged in workspace %s, skipping update", + peer_name, + workspace_name, + ) + return honcho_peer await db.commit() await db.refresh(honcho_peer) - # Invalidate cache - read-through pattern cache_key = peer_cache_key(workspace_name, honcho_peer.name) await cache.delete(cache_key) diff --git a/src/crud/session.py b/src/crud/session.py index 625626e7..a7ef27d4 100644 --- a/src/crud/session.py +++ b/src/crud/session.py @@ -126,6 +126,9 @@ async def get_or_create_session( if honcho_session is not None: honcho_session = await db.merge(honcho_session, load=False) + # Track if we need to update cache + needs_cache_update = False + # Check if session already exists if honcho_session is None: if session.peer_names: @@ -151,6 +154,7 @@ async def get_or_create_session( db.add(honcho_session) # Flush to ensure session exists in DB before adding peers await db.flush() + except IntegrityError: await db.rollback() logger.debug( @@ -163,10 +167,18 @@ async def get_or_create_session( return await get_or_create_session(db, session, workspace_name, _retry=True) else: # Update existing session with metadata and feature flags if provided - if session.metadata is not None: + if ( + session.metadata is not None + and honcho_session.h_metadata != session.metadata + ): honcho_session.h_metadata = session.metadata - if session.configuration is not None: + needs_cache_update = True + if ( + session.configuration is not None + and honcho_session.configuration != session.configuration + ): honcho_session.configuration = session.configuration + needs_cache_update = True # Add all peers to session if session.peer_names: @@ -187,10 +199,16 @@ async def get_or_create_session( await db.commit() await db.refresh(honcho_session) - cache_key = session_cache_key(workspace_name, session.name) - await cache.set( - cache_key, honcho_session, expire=settings.CACHE.DEFAULT_TTL_SECONDS - ) + # Only update cache if session data changed or was newly created + if needs_cache_update: + cache_key = session_cache_key(workspace_name, session.name) + await cache.set( + cache_key, honcho_session, expire=settings.CACHE.DEFAULT_TTL_SECONDS + ) + logger.debug( + "Session %s cache updated in workspace %s", session.name, workspace_name + ) + return honcho_session @@ -251,16 +269,32 @@ async def update_session( db, schemas.SessionCreate(name=session_name), workspace_name=workspace_name ) - if session.metadata is not None: - honcho_session.h_metadata = session.metadata + # Track if anything changed + needs_update = False - if session.configuration is not None: + if session.metadata is not None and honcho_session.h_metadata != session.metadata: + honcho_session.h_metadata = session.metadata + needs_update = True + + if ( + session.configuration is not None + and honcho_session.configuration != session.configuration + ): honcho_session.configuration = session.configuration + needs_update = True + + if not needs_update: + logger.debug( + "Session %s unchanged in workspace %s, skipping update", + session_name, + workspace_name, + ) + return honcho_session await db.commit() await db.refresh(honcho_session) - # Invalidate cache - read-through pattern + # Only invalidate if we actually updated cache_key = session_cache_key(workspace_name, session_name) await cache.delete(cache_key) diff --git a/src/crud/workspace.py b/src/crud/workspace.py index c872b728..5d212d40 100644 --- a/src/crud/workspace.py +++ b/src/crud/workspace.py @@ -172,16 +172,32 @@ async def update_workspace( ), ) - if workspace.metadata is not None: - honcho_workspace.h_metadata = workspace.metadata + # Track if anything changed + needs_update = False - if workspace.configuration is not None: + if ( + workspace.metadata is not None + and honcho_workspace.h_metadata != workspace.metadata + ): + honcho_workspace.h_metadata = workspace.metadata + needs_update = True + + if ( + workspace.configuration is not None + and honcho_workspace.configuration != workspace.configuration + ): honcho_workspace.configuration = workspace.configuration + needs_update = True + + # Early exit if unchanged + if not needs_update: + logger.debug("Workspace %s unchanged, skipping update", workspace_name) + return honcho_workspace await db.commit() await db.refresh(honcho_workspace) - # Invalidate cache + # Only invalidate if we actually updated cache_key = workspace_cache_key(workspace_name) await cache.delete(cache_key) diff --git a/src/utils/summarizer.py b/src/utils/summarizer.py index 67eb3467..3f905a0a 100644 --- a/src/utils/summarizer.py +++ b/src/utils/summarizer.py @@ -373,7 +373,7 @@ async def _create_and_save_summary( message_public_id=message_public_id, ) - # Only track tokens if this was a real LLM call + # Only track tokens and save summary if this was a real LLM call if not is_fallback: # Get base prompt tokens based on summary type if summary_type == SummaryType.SHORT: @@ -397,12 +397,13 @@ async def _create_and_save_summary( component="total", ).inc(new_summary["token_count"]) - await _save_summary( - db, - new_summary, - workspace_name, - session_name, - ) + # Save summary to database + await _save_summary( + db, + new_summary, + workspace_name, + session_name, + ) accumulate_metric( f"summary_{workspace_name}_{message_id}",