From cf6291ac8ef1fabb7601b17c9503e39f811e2a41 Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Mon, 5 Jan 2026 14:07:48 +0100 Subject: [PATCH] style(memory): apply ruff formatting and linting fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Auto-fixed linting errors and formatting issues: - Removed unused imports (F401): pytest, Any, AnalysisType, MemoryType, OutcomeType - Removed unused variable (F841): hooks variable in test - Applied consistent formatting across memory service and test files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../app/services/context/budget/allocator.py | 7 +- .../services/memory/cache/cache_manager.py | 11 +- .../services/memory/cache/embedding_cache.py | 8 +- .../app/services/memory/cache/hot_cache.py | 4 +- .../memory/integration/context_source.py | 5 +- .../services/memory/integration/lifecycle.py | 22 ++- backend/app/services/memory/mcp/service.py | 151 +++++++++++------- .../app/services/memory/metrics/collector.py | 8 +- .../app/services/memory/reflection/service.py | 87 +++++----- .../app/services/memory/reflection/types.py | 3 +- .../services/context/types/test_memory.py | 2 - .../memory/integration/test_context_source.py | 13 +- .../memory/integration/test_lifecycle.py | 3 +- .../unit/services/memory/mcp/test_service.py | 38 +++-- .../unit/services/memory/mcp/test_tools.py | 14 +- .../services/memory/metrics/test_collector.py | 18 ++- .../memory/reflection/test_service.py | 27 +--- 17 files changed, 236 insertions(+), 185 deletions(-) diff --git a/backend/app/services/context/budget/allocator.py b/backend/app/services/context/budget/allocator.py index ab4eaee..aca25a9 100644 --- a/backend/app/services/context/budget/allocator.py +++ b/backend/app/services/context/budget/allocator.py @@ -344,7 +344,12 @@ class BudgetAllocator: Rebalanced budget """ if prioritize is None: - prioritize = [ContextType.KNOWLEDGE, ContextType.MEMORY, ContextType.TASK, ContextType.SYSTEM] + prioritize = [ + ContextType.KNOWLEDGE, + ContextType.MEMORY, + ContextType.TASK, + ContextType.SYSTEM, + ] # Calculate unused tokens per type unused: dict[str, int] = {} diff --git a/backend/app/services/memory/cache/cache_manager.py b/backend/app/services/memory/cache/cache_manager.py index 2412109..b20022f 100644 --- a/backend/app/services/memory/cache/cache_manager.py +++ b/backend/app/services/memory/cache/cache_manager.py @@ -50,7 +50,9 @@ class CacheStats: "embedding_cache": self.embedding_cache, "retrieval_cache": self.retrieval_cache, "overall_hit_rate": self.overall_hit_rate, - "last_cleanup": self.last_cleanup.isoformat() if self.last_cleanup else None, + "last_cleanup": self.last_cleanup.isoformat() + if self.last_cleanup + else None, "cleanup_count": self.cleanup_count, } @@ -104,7 +106,8 @@ class CacheManager: else: self._embedding_cache = create_embedding_cache( max_size=self._settings.cache_max_items, - default_ttl_seconds=self._settings.cache_ttl_seconds * 12, # 1hr for embeddings + default_ttl_seconds=self._settings.cache_ttl_seconds + * 12, # 1hr for embeddings redis=redis, ) @@ -271,7 +274,9 @@ class CacheManager: # Invalidate retrieval cache if self._retrieval_cache: - uuid_id = UUID(str(memory_id)) if not isinstance(memory_id, UUID) else memory_id + uuid_id = ( + UUID(str(memory_id)) if not isinstance(memory_id, UUID) else memory_id + ) count += self._retrieval_cache.invalidate_by_memory(uuid_id) logger.debug(f"Invalidated {count} cache entries for {memory_type}:{memory_id}") diff --git a/backend/app/services/memory/cache/embedding_cache.py b/backend/app/services/memory/cache/embedding_cache.py index 52bea84..fe48f5e 100644 --- a/backend/app/services/memory/cache/embedding_cache.py +++ b/backend/app/services/memory/cache/embedding_cache.py @@ -405,9 +405,7 @@ class EmbeddingCache: count = 0 with self._lock: - keys_to_remove = [ - k for k, v in self._cache.items() if v.model == model - ] + keys_to_remove = [k for k, v in self._cache.items() if v.model == model] for key in keys_to_remove: del self._cache[key] count += 1 @@ -454,9 +452,7 @@ class EmbeddingCache: Number of entries removed """ with self._lock: - keys_to_remove = [ - k for k, v in self._cache.items() if v.is_expired() - ] + keys_to_remove = [k for k, v in self._cache.items() if v.is_expired()] for key in keys_to_remove: del self._cache[key] self._stats.expirations += 1 diff --git a/backend/app/services/memory/cache/hot_cache.py b/backend/app/services/memory/cache/hot_cache.py index f389521..cd7fc2f 100644 --- a/backend/app/services/memory/cache/hot_cache.py +++ b/backend/app/services/memory/cache/hot_cache.py @@ -384,9 +384,7 @@ class HotMemoryCache[T]: Number of entries removed """ with self._lock: - keys_to_remove = [ - k for k, v in self._cache.items() if v.is_expired() - ] + keys_to_remove = [k for k, v in self._cache.items() if v.is_expired()] for key in keys_to_remove: del self._cache[key] self._stats.expirations += 1 diff --git a/backend/app/services/memory/integration/context_source.py b/backend/app/services/memory/integration/context_source.py index 262fcb4..dc19021 100644 --- a/backend/app/services/memory/integration/context_source.py +++ b/backend/app/services/memory/integration/context_source.py @@ -321,10 +321,7 @@ class MemoryContextSource: min_confidence=min_relevance, ) - return [ - MemoryContext.from_semantic_memory(fact, query=query) - for fact in facts - ] + return [MemoryContext.from_semantic_memory(fact, query=query) for fact in facts] async def _fetch_procedural( self, diff --git a/backend/app/services/memory/integration/lifecycle.py b/backend/app/services/memory/integration/lifecycle.py index 76a0502..16cb2e5 100644 --- a/backend/app/services/memory/integration/lifecycle.py +++ b/backend/app/services/memory/integration/lifecycle.py @@ -287,7 +287,9 @@ class AgentLifecycleManager: # Get all current state all_keys = await working.list_keys() # Filter out checkpoint keys - state_keys = [k for k in all_keys if not k.startswith(self.CHECKPOINT_PREFIX)] + state_keys = [ + k for k in all_keys if not k.startswith(self.CHECKPOINT_PREFIX) + ] state: dict[str, Any] = {} for key in state_keys: @@ -483,7 +485,9 @@ class AgentLifecycleManager: # Gather session state for consolidation all_keys = await working.list_keys() - state_keys = [k for k in all_keys if not k.startswith(self.CHECKPOINT_PREFIX)] + state_keys = [ + k for k in all_keys if not k.startswith(self.CHECKPOINT_PREFIX) + ] session_state: dict[str, Any] = {} for key in state_keys: @@ -597,14 +601,16 @@ class AgentLifecycleManager: for key in all_keys: if key.startswith(self.CHECKPOINT_PREFIX): - checkpoint_id = key[len(self.CHECKPOINT_PREFIX):] + checkpoint_id = key[len(self.CHECKPOINT_PREFIX) :] checkpoint = await working.get(key) if checkpoint: - checkpoints.append({ - "checkpoint_id": checkpoint_id, - "timestamp": checkpoint.get("timestamp"), - "keys_count": checkpoint.get("keys_count", 0), - }) + checkpoints.append( + { + "checkpoint_id": checkpoint_id, + "timestamp": checkpoint.get("timestamp"), + "keys_count": checkpoint.get("keys_count", 0), + } + ) # Sort by timestamp (newest first) checkpoints.sort( diff --git a/backend/app/services/memory/mcp/service.py b/backend/app/services/memory/mcp/service.py index 03d8d29..d537744 100644 --- a/backend/app/services/memory/mcp/service.py +++ b/backend/app/services/memory/mcp/service.py @@ -414,12 +414,14 @@ class MemoryToolService: if args.query.lower() in key.lower(): value = await working.get(key) if value is not None: - results.append({ - "type": "working", - "key": key, - "content": str(value), - "relevance": 1.0, - }) + results.append( + { + "type": "working", + "key": key, + "content": str(value), + "relevance": 1.0, + } + ) elif memory_type == MemoryType.EPISODIC: episodic = await self._get_episodic() @@ -430,14 +432,18 @@ class MemoryToolService: agent_instance_id=context.agent_instance_id, ) for episode in episodes: - results.append({ - "type": "episodic", - "id": str(episode.id), - "summary": episode.task_description, - "outcome": episode.outcome.value if episode.outcome else None, - "occurred_at": episode.occurred_at.isoformat(), - "relevance": episode.importance_score, - }) + results.append( + { + "type": "episodic", + "id": str(episode.id), + "summary": episode.task_description, + "outcome": episode.outcome.value + if episode.outcome + else None, + "occurred_at": episode.occurred_at.isoformat(), + "relevance": episode.importance_score, + } + ) elif memory_type == MemoryType.SEMANTIC: semantic = await self._get_semantic() @@ -448,15 +454,17 @@ class MemoryToolService: min_confidence=args.min_relevance, ) for fact in facts: - results.append({ - "type": "semantic", - "id": str(fact.id), - "subject": fact.subject, - "predicate": fact.predicate, - "object": fact.object, - "confidence": fact.confidence, - "relevance": fact.confidence, - }) + results.append( + { + "type": "semantic", + "id": str(fact.id), + "subject": fact.subject, + "predicate": fact.predicate, + "object": fact.object, + "confidence": fact.confidence, + "relevance": fact.confidence, + } + ) elif memory_type == MemoryType.PROCEDURAL: procedural = await self._get_procedural() @@ -467,15 +475,17 @@ class MemoryToolService: limit=args.limit, ) for proc in procedures: - results.append({ - "type": "procedural", - "id": str(proc.id), - "name": proc.name, - "trigger": proc.trigger_pattern, - "success_rate": proc.success_rate, - "steps_count": len(proc.steps) if proc.steps else 0, - "relevance": proc.success_rate, - }) + results.append( + { + "type": "procedural", + "id": str(proc.id), + "name": proc.name, + "trigger": proc.trigger_pattern, + "success_rate": proc.success_rate, + "steps_count": len(proc.steps) if proc.steps else 0, + "relevance": proc.success_rate, + } + ) # Sort by relevance and limit results.sort(key=lambda x: x.get("relevance", 0), reverse=True) @@ -601,7 +611,11 @@ class MemoryToolService: if ep.task_type: task_types[ep.task_type] = task_types.get(ep.task_type, 0) + 1 if ep.outcome: - outcome_val = ep.outcome.value if hasattr(ep.outcome, "value") else str(ep.outcome) + outcome_val = ( + ep.outcome.value + if hasattr(ep.outcome, "value") + else str(ep.outcome) + ) outcomes[outcome_val] = outcomes.get(outcome_val, 0) + 1 # Sort by frequency @@ -613,11 +627,13 @@ class MemoryToolService: examples = [] if args.include_examples: for ep in episodes[: min(3, args.max_items)]: - examples.append({ - "summary": ep.task_description, - "task_type": ep.task_type, - "outcome": ep.outcome.value if ep.outcome else None, - }) + examples.append( + { + "summary": ep.task_description, + "task_type": ep.task_type, + "outcome": ep.outcome.value if ep.outcome else None, + } + ) return { "analysis_type": "recent_patterns", @@ -661,11 +677,13 @@ class MemoryToolService: examples = [] if args.include_examples: for ep in successful[: min(3, args.max_items)]: - examples.append({ - "summary": ep.task_description, - "task_type": ep.task_type, - "lessons": ep.lessons_learned, - }) + examples.append( + { + "summary": ep.task_description, + "task_type": ep.task_type, + "lessons": ep.lessons_learned, + } + ) return { "analysis_type": "success_factors", @@ -694,9 +712,7 @@ class MemoryToolService: failure_by_task[task].append(ep) # Most common failure types - failure_counts = { - task: len(eps) for task, eps in failure_by_task.items() - } + failure_counts = {task: len(eps) for task, eps in failure_by_task.items()} top_failures = sorted(failure_counts.items(), key=lambda x: x[1], reverse=True)[ : args.max_items ] @@ -704,12 +720,14 @@ class MemoryToolService: examples = [] if args.include_examples: for ep in failed[: min(3, args.max_items)]: - examples.append({ - "summary": ep.task_description, - "task_type": ep.task_type, - "lessons": ep.lessons_learned, - "error": ep.outcome_details, - }) + examples.append( + { + "summary": ep.task_description, + "task_type": ep.task_type, + "lessons": ep.lessons_learned, + "error": ep.outcome_details, + } + ) return { "analysis_type": "failure_patterns", @@ -794,15 +812,21 @@ class MemoryToolService: insights = [] if top_tasks: - insights.append(f"Most common task type: {top_tasks[0][0]} ({top_tasks[0][1]} occurrences)") + insights.append( + f"Most common task type: {top_tasks[0][0]} ({top_tasks[0][1]} occurrences)" + ) total = sum(outcome_dist.values()) if total > 0: success_rate = outcome_dist.get("success", 0) / total if success_rate > 0.8: - insights.append("High success rate observed - current approach is working well") + insights.append( + "High success rate observed - current approach is working well" + ) elif success_rate < 0.5: - insights.append("Success rate below 50% - consider reviewing procedures") + insights.append( + "Success rate below 50% - consider reviewing procedures" + ) return insights @@ -839,9 +863,13 @@ class MemoryToolService: if top_failures: worst_task, count = top_failures[0] - tips.append(f"'{worst_task}' has most failures ({count}) - needs procedure review") + tips.append( + f"'{worst_task}' has most failures ({count}) - needs procedure review" + ) - tips.append("Review lessons_learned from past failures before attempting similar tasks") + tips.append( + "Review lessons_learned from past failures before attempting similar tasks" + ) return tips @@ -912,7 +940,11 @@ class MemoryToolService: outcomes = {"success": 0, "failure": 0, "partial": 0, "abandoned": 0} for ep in recent_episodes: if ep.outcome: - key = ep.outcome.value if hasattr(ep.outcome, "value") else str(ep.outcome) + key = ( + ep.outcome.value + if hasattr(ep.outcome, "value") + else str(ep.outcome) + ) if key in outcomes: outcomes[key] += 1 @@ -942,7 +974,8 @@ class MemoryToolService: # Filter by minimum success rate if specified procedures = [ - p for p in all_procedures + p + for p in all_procedures if args.min_success_rate is None or p.success_rate >= args.min_success_rate ][: args.limit] diff --git a/backend/app/services/memory/metrics/collector.py b/backend/app/services/memory/metrics/collector.py index c959b1d..f428a0d 100644 --- a/backend/app/services/memory/metrics/collector.py +++ b/backend/app/services/memory/metrics/collector.py @@ -441,9 +441,7 @@ class MemoryMetrics: # Get hits/misses by cache type for labels_str, hits in self._counters["memory_cache_hits_total"].items(): - cache_type = self._parse_labels(labels_str).get( - "cache_type", "unknown" - ) + cache_type = self._parse_labels(labels_str).get("cache_type", "unknown") if cache_type not in stats: stats[cache_type] = {"hits": 0, "misses": 0} stats[cache_type]["hits"] = hits @@ -451,9 +449,7 @@ class MemoryMetrics: for labels_str, misses in self._counters[ "memory_cache_misses_total" ].items(): - cache_type = self._parse_labels(labels_str).get( - "cache_type", "unknown" - ) + cache_type = self._parse_labels(labels_str).get("cache_type", "unknown") if cache_type not in stats: stats[cache_type] = {"hits": 0, "misses": 0} stats[cache_type]["misses"] = misses diff --git a/backend/app/services/memory/reflection/service.py b/backend/app/services/memory/reflection/service.py index 6e08428..2e01620 100644 --- a/backend/app/services/memory/reflection/service.py +++ b/backend/app/services/memory/reflection/service.py @@ -149,8 +149,7 @@ class MemoryReflection: # Filter to time range episodes = [ - e for e in episodes - if time_range.start <= e.occurred_at <= time_range.end + e for e in episodes if time_range.start <= e.occurred_at <= time_range.end ] if not episodes: @@ -313,7 +312,9 @@ class MemoryReflection: f"Task type '{task_type}': {success_rate:.0%} success rate, " f"avg {avg_duration:.1f}s duration, {avg_tokens:.0f} tokens" ), - confidence=min(1.0, stats["total"] / 10), # Higher sample = higher confidence + confidence=min( + 1.0, stats["total"] / 10 + ), # Higher sample = higher confidence occurrence_count=stats["total"], episode_ids=[e.id for e in stats["episodes"]], first_seen=min(e.occurred_at for e in stats["episodes"]), @@ -397,7 +398,9 @@ class MemoryReflection: failed = [e for e in episodes if e.outcome == Outcome.FAILURE] if len(successful) >= 3 and len(failed) >= 3: - avg_success_duration = statistics.mean(e.duration_seconds for e in successful) + avg_success_duration = statistics.mean( + e.duration_seconds for e in successful + ) avg_failure_duration = statistics.mean(e.duration_seconds for e in failed) if avg_failure_duration > avg_success_duration * 1.5: @@ -409,7 +412,7 @@ class MemoryReflection: description=( f"Failed tasks average {avg_failure_duration:.1f}s vs " f"{avg_success_duration:.1f}s for successful tasks " - f"({avg_failure_duration/avg_success_duration:.1f}x longer)" + f"({avg_failure_duration / avg_success_duration:.1f}x longer)" ), confidence=0.8, occurrence_count=len(successful) + len(failed), @@ -427,9 +430,15 @@ class MemoryReflection: # Analyze token efficiency if len(successful) >= 3: avg_tokens = statistics.mean(e.tokens_used for e in successful) - std_tokens = statistics.stdev(e.tokens_used for e in successful) if len(successful) > 1 else 0 + std_tokens = ( + statistics.stdev(e.tokens_used for e in successful) + if len(successful) > 1 + else 0 + ) - efficient = [e for e in successful if e.tokens_used < avg_tokens - std_tokens] + efficient = [ + e for e in successful if e.tokens_used < avg_tokens - std_tokens + ] if len(efficient) >= self._config.min_pattern_occurrences: patterns.append( Pattern( @@ -508,8 +517,7 @@ class MemoryReflection: # Filter to time range episodes = [ - e for e in episodes - if time_range.start <= e.occurred_at <= time_range.end + e for e in episodes if time_range.start <= e.occurred_at <= time_range.end ] if len(episodes) < self._config.min_sample_size_for_factor: @@ -652,9 +660,7 @@ class MemoryReflection: avg_success_duration = statistics.mean( e.duration_seconds for e in successful ) - avg_failure_duration = statistics.mean( - e.duration_seconds for e in failed - ) + avg_failure_duration = statistics.mean(e.duration_seconds for e in failed) if avg_success_duration > 0: duration_ratio = avg_failure_duration / avg_success_duration @@ -837,7 +843,9 @@ class MemoryReflection: baseline_durations = [e.duration_seconds for e in baseline] baseline_mean = statistics.mean(baseline_durations) - baseline_std = statistics.stdev(baseline_durations) if len(baseline_durations) > 1 else 0 + baseline_std = ( + statistics.stdev(baseline_durations) if len(baseline_durations) > 1 else 0 + ) if baseline_std == 0: return anomalies @@ -997,7 +1005,10 @@ class MemoryReflection: ) / len(recent) # Detect significant failure rate increase - if recent_failure_rate > baseline_failure_rate * 1.5 and recent_failure_rate > 0.3: + if ( + recent_failure_rate > baseline_failure_rate * 1.5 + and recent_failure_rate > 0.3 + ): rate_increase = recent_failure_rate / max(baseline_failure_rate, 0.01) anomalies.append( @@ -1074,14 +1085,11 @@ class MemoryReflection: insights.extend(self._insights_from_anomalies(anomalies)) # Generate cross-cutting insights - insights.extend( - self._generate_cross_insights(patterns, factors, anomalies) - ) + insights.extend(self._generate_cross_insights(patterns, factors, anomalies)) # Filter by confidence and sort by priority insights = [ - i for i in insights - if i.confidence >= self._config.min_insight_confidence + i for i in insights if i.confidence >= self._config.min_insight_confidence ] insights.sort(key=lambda i: -i.priority) @@ -1182,9 +1190,7 @@ class MemoryReflection: source_patterns=[], source_factors=[f.id for f in top_positive], source_anomalies=[], - recommended_actions=[ - f"Reinforce: {f.name}" for f in top_positive - ], + recommended_actions=[f"Reinforce: {f.name}" for f in top_positive], generated_at=_utcnow(), metadata={ "factors": [f.to_dict() for f in top_positive], @@ -1200,17 +1206,16 @@ class MemoryReflection: insight_type=InsightType.WARNING, title="Factors correlating with failure", description=( - "Risky factors: " - + ", ".join(f.name for f in top_negative) + "Risky factors: " + ", ".join(f.name for f in top_negative) ), priority=0.75, - confidence=statistics.mean(abs(f.correlation) for f in top_negative), + confidence=statistics.mean( + abs(f.correlation) for f in top_negative + ), source_patterns=[], source_factors=[f.id for f in top_negative], source_anomalies=[], - recommended_actions=[ - f"Mitigate: {f.name}" for f in top_negative - ], + recommended_actions=[f"Mitigate: {f.name}" for f in top_negative], generated_at=_utcnow(), metadata={ "factors": [f.to_dict() for f in top_negative], @@ -1254,8 +1259,7 @@ class MemoryReflection: ) failure_rate_anomalies = [ - a for a in anomalies - if a.anomaly_type == AnomalyType.UNUSUAL_FAILURE_RATE + a for a in anomalies if a.anomaly_type == AnomalyType.UNUSUAL_FAILURE_RATE ] if failure_rate_anomalies: for anomaly in failure_rate_anomalies: @@ -1295,7 +1299,13 @@ class MemoryReflection: total_items = len(patterns) + len(factors) + len(anomalies) if total_items > 0: warning_count = ( - len([p for p in patterns if p.pattern_type == PatternType.RECURRING_FAILURE]) + len( + [ + p + for p in patterns + if p.pattern_type == PatternType.RECURRING_FAILURE + ] + ) + len([a for a in anomalies if a.is_critical]) + len([f for f in factors if f.correlation < -0.3]) ) @@ -1312,13 +1322,19 @@ class MemoryReflection: f"Found {warning_count} warning indicators." ), priority=0.6, - confidence=min(1.0, total_items / 20), # Higher sample = higher confidence + confidence=min( + 1.0, total_items / 20 + ), # Higher sample = higher confidence source_patterns=[p.id for p in patterns[:5]], source_factors=[f.id for f in factors[:5]], source_anomalies=[a.id for a in anomalies[:5]], recommended_actions=( - ["Continue current practices"] if health_score > 0.7 - else ["Review warnings and address issues", "Focus on improvement areas"] + ["Continue current practices"] + if health_score > 0.7 + else [ + "Review warnings and address issues", + "Focus on improvement areas", + ] ), generated_at=_utcnow(), metadata={ @@ -1374,8 +1390,7 @@ class MemoryReflection: agent_instance_id=agent_instance_id, ) episodes_in_range = [ - e for e in episodes - if time_range.start <= e.occurred_at <= time_range.end + e for e in episodes if time_range.start <= e.occurred_at <= time_range.end ] # Run all analyses diff --git a/backend/app/services/memory/reflection/types.py b/backend/app/services/memory/reflection/types.py index 5cf809d..d5cd041 100644 --- a/backend/app/services/memory/reflection/types.py +++ b/backend/app/services/memory/reflection/types.py @@ -70,8 +70,7 @@ class TimeRange: """Create time range for last N hours.""" end = _utcnow() start = datetime( - end.year, end.month, end.day, end.hour, end.minute, end.second, - tzinfo=UTC + end.year, end.month, end.day, end.hour, end.minute, end.second, tzinfo=UTC ) - __import__("datetime").timedelta(hours=hours) return cls(start=start, end=end) diff --git a/backend/tests/unit/services/context/types/test_memory.py b/backend/tests/unit/services/context/types/test_memory.py index dcfc2c4..5f01d0a 100644 --- a/backend/tests/unit/services/context/types/test_memory.py +++ b/backend/tests/unit/services/context/types/test_memory.py @@ -5,8 +5,6 @@ from datetime import UTC, datetime from unittest.mock import MagicMock from uuid import uuid4 -import pytest - from app.services.context.types import ContextType from app.services.context.types.memory import MemoryContext, MemorySubtype diff --git a/backend/tests/unit/services/memory/integration/test_context_source.py b/backend/tests/unit/services/memory/integration/test_context_source.py index 449488e..3e2b656 100644 --- a/backend/tests/unit/services/memory/integration/test_context_source.py +++ b/backend/tests/unit/services/memory/integration/test_context_source.py @@ -133,9 +133,7 @@ class TestMemoryContextSource: ) assert result.by_type["working"] == 2 - assert all( - c.memory_subtype == MemorySubtype.WORKING for c in result.contexts - ) + assert all(c.memory_subtype == MemorySubtype.WORKING for c in result.contexts) @patch("app.services.memory.integration.context_source.EpisodicMemory") async def test_fetch_episodic_memory( @@ -252,11 +250,10 @@ class TestMemoryContextSource: context_source: MemoryContextSource, ) -> None: """Results should be sorted by relevance score.""" - with patch.object( - context_source, "_fetch_episodic" - ) as mock_ep, patch.object( - context_source, "_fetch_semantic" - ) as mock_sem: + with ( + patch.object(context_source, "_fetch_episodic") as mock_ep, + patch.object(context_source, "_fetch_semantic") as mock_sem, + ): # Create contexts with different relevance scores from app.services.context.types.memory import MemoryContext diff --git a/backend/tests/unit/services/memory/integration/test_lifecycle.py b/backend/tests/unit/services/memory/integration/test_lifecycle.py index a39c108..4524112 100644 --- a/backend/tests/unit/services/memory/integration/test_lifecycle.py +++ b/backend/tests/unit/services/memory/integration/test_lifecycle.py @@ -105,6 +105,7 @@ class TestLifecycleHooks: def test_register_spawn_hook(self, lifecycle_hooks: LifecycleHooks) -> None: """Should register spawn hook.""" + async def my_hook(event: LifecycleEvent) -> None: pass @@ -115,7 +116,7 @@ class TestLifecycleHooks: def test_register_all_hooks(self, lifecycle_hooks: LifecycleHooks) -> None: """Should register hooks for all event types.""" - hooks = [ + [ lifecycle_hooks.on_spawn(AsyncMock()), lifecycle_hooks.on_pause(AsyncMock()), lifecycle_hooks.on_resume(AsyncMock()), diff --git a/backend/tests/unit/services/memory/mcp/test_service.py b/backend/tests/unit/services/memory/mcp/test_service.py index f403262..dee0fc8 100644 --- a/backend/tests/unit/services/memory/mcp/test_service.py +++ b/backend/tests/unit/services/memory/mcp/test_service.py @@ -2,7 +2,6 @@ """Tests for MemoryToolService.""" from datetime import UTC, datetime -from typing import Any from unittest.mock import AsyncMock, MagicMock, patch from uuid import UUID, uuid4 @@ -14,11 +13,6 @@ from app.services.memory.mcp.service import ( ToolResult, get_memory_tool_service, ) -from app.services.memory.mcp.tools import ( - AnalysisType, - MemoryType, - OutcomeType, -) from app.services.memory.types import Outcome pytestmark = pytest.mark.asyncio(loop_scope="function") @@ -192,7 +186,9 @@ class TestMemoryToolService: context: ToolContext, ) -> None: """Remember should store in episodic memory.""" - with patch("app.services.memory.mcp.service.EpisodicMemory") as mock_episodic_cls: + with patch( + "app.services.memory.mcp.service.EpisodicMemory" + ) as mock_episodic_cls: # Setup mock mock_episode = MagicMock() mock_episode.id = uuid4() @@ -260,7 +256,9 @@ class TestMemoryToolService: context: ToolContext, ) -> None: """Remember should store facts in semantic memory.""" - with patch("app.services.memory.mcp.service.SemanticMemory") as mock_semantic_cls: + with patch( + "app.services.memory.mcp.service.SemanticMemory" + ) as mock_semantic_cls: mock_fact = MagicMock() mock_fact.id = uuid4() @@ -311,7 +309,9 @@ class TestMemoryToolService: context: ToolContext, ) -> None: """Remember should store procedures in procedural memory.""" - with patch("app.services.memory.mcp.service.ProceduralMemory") as mock_procedural_cls: + with patch( + "app.services.memory.mcp.service.ProceduralMemory" + ) as mock_procedural_cls: mock_procedure = MagicMock() mock_procedure.id = uuid4() @@ -530,15 +530,21 @@ class TestMemoryToolService: mock_working_cls.for_session = AsyncMock(return_value=mock_working) mock_episodic = AsyncMock() - mock_episodic.get_recent = AsyncMock(return_value=[MagicMock() for _ in range(10)]) + mock_episodic.get_recent = AsyncMock( + return_value=[MagicMock() for _ in range(10)] + ) mock_episodic_cls.create = AsyncMock(return_value=mock_episodic) mock_semantic = AsyncMock() - mock_semantic.search_facts = AsyncMock(return_value=[MagicMock() for _ in range(5)]) + mock_semantic.search_facts = AsyncMock( + return_value=[MagicMock() for _ in range(5)] + ) mock_semantic_cls.create = AsyncMock(return_value=mock_semantic) mock_procedural = AsyncMock() - mock_procedural.find_matching = AsyncMock(return_value=[MagicMock() for _ in range(3)]) + mock_procedural.find_matching = AsyncMock( + return_value=[MagicMock() for _ in range(3)] + ) mock_procedural_cls.create = AsyncMock(return_value=mock_procedural) result = await service.execute_tool( @@ -603,8 +609,12 @@ class TestMemoryToolService: ) -> None: """Record outcome should store outcome and update procedure.""" with ( - patch("app.services.memory.mcp.service.EpisodicMemory") as mock_episodic_cls, - patch("app.services.memory.mcp.service.ProceduralMemory") as mock_procedural_cls, + patch( + "app.services.memory.mcp.service.EpisodicMemory" + ) as mock_episodic_cls, + patch( + "app.services.memory.mcp.service.ProceduralMemory" + ) as mock_procedural_cls, ): mock_episode = MagicMock() mock_episode.id = uuid4() diff --git a/backend/tests/unit/services/memory/mcp/test_tools.py b/backend/tests/unit/services/memory/mcp/test_tools.py index c35a344..d545d8a 100644 --- a/backend/tests/unit/services/memory/mcp/test_tools.py +++ b/backend/tests/unit/services/memory/mcp/test_tools.py @@ -358,10 +358,12 @@ class TestMemoryToolDefinition: ) # Valid args - validated = tool.validate_args({ - "memory_type": "working", - "content": "Test content", - }) + validated = tool.validate_args( + { + "memory_type": "working", + "content": "Test content", + } + ) assert isinstance(validated, RememberArgs) # Invalid args @@ -417,4 +419,6 @@ class TestToolDefinitions: """All tool schemas should have properties defined.""" for name, tool in MEMORY_TOOL_DEFINITIONS.items(): schema = tool.to_mcp_format() - assert "properties" in schema["inputSchema"], f"Tool {name} missing properties" + assert "properties" in schema["inputSchema"], ( + f"Tool {name} missing properties" + ) diff --git a/backend/tests/unit/services/memory/metrics/test_collector.py b/backend/tests/unit/services/memory/metrics/test_collector.py index d240096..e89270f 100644 --- a/backend/tests/unit/services/memory/metrics/test_collector.py +++ b/backend/tests/unit/services/memory/metrics/test_collector.py @@ -134,9 +134,7 @@ class TestMemoryMetrics: await metrics.set_memory_items_count("semantic", "project", 50) all_metrics = await metrics.get_all_metrics() - gauge_metrics = [ - m for m in all_metrics if m.name == "memory_items_count" - ] + gauge_metrics = [m for m in all_metrics if m.name == "memory_items_count"] assert len(gauge_metrics) == 2 @@ -181,7 +179,11 @@ class TestMemoryMetrics: all_metrics = await metrics.get_all_metrics() count_metric = next( - (m for m in all_metrics if m.name == "memory_working_latency_seconds_count"), + ( + m + for m in all_metrics + if m.name == "memory_working_latency_seconds_count" + ), None, ) sum_metric = next( @@ -204,9 +206,7 @@ class TestMemoryMetrics: assert summary["avg_retrieval_latency_ms"] == pytest.approx(62.5, rel=0.01) @pytest.mark.asyncio - async def test_observe_consolidation_duration( - self, metrics: MemoryMetrics - ) -> None: + async def test_observe_consolidation_duration(self, metrics: MemoryMetrics) -> None: """Should record consolidation duration histogram.""" await metrics.observe_consolidation_duration(5.0) await metrics.observe_consolidation_duration(10.0) @@ -236,7 +236,9 @@ class TestMemoryMetrics: assert len(all_metrics) >= 3 # Check we have different metric types - counter_metrics = [m for m in all_metrics if m.metric_type == MetricType.COUNTER] + counter_metrics = [ + m for m in all_metrics if m.metric_type == MetricType.COUNTER + ] gauge_metrics = [m for m in all_metrics if m.metric_type == MetricType.GAUGE] assert len(counter_metrics) >= 1 diff --git a/backend/tests/unit/services/memory/reflection/test_service.py b/backend/tests/unit/services/memory/reflection/test_service.py index 5e623cd..4ee124d 100644 --- a/backend/tests/unit/services/memory/reflection/test_service.py +++ b/backend/tests/unit/services/memory/reflection/test_service.py @@ -153,8 +153,7 @@ class TestPatternDetection: # Should find recurring success pattern for 'build' task success_patterns = [ - p for p in patterns - if p.pattern_type == PatternType.RECURRING_SUCCESS + p for p in patterns if p.pattern_type == PatternType.RECURRING_SUCCESS ] assert len(success_patterns) >= 1 assert any(p.name.find("build") >= 0 for p in success_patterns) @@ -193,8 +192,7 @@ class TestPatternDetection: patterns = await reflection.analyze_patterns(project_id, time_range) failure_patterns = [ - p for p in patterns - if p.pattern_type == PatternType.RECURRING_FAILURE + p for p in patterns if p.pattern_type == PatternType.RECURRING_FAILURE ] assert len(failure_patterns) >= 1 @@ -229,8 +227,7 @@ class TestPatternDetection: patterns = await reflection.analyze_patterns(project_id, time_range) action_patterns = [ - p for p in patterns - if p.pattern_type == PatternType.ACTION_SEQUENCE + p for p in patterns if p.pattern_type == PatternType.ACTION_SEQUENCE ] assert len(action_patterns) >= 1 @@ -438,8 +435,7 @@ class TestAnomalyDetection: anomalies = await reflection.detect_anomalies(project_id, baseline_days=30) duration_anomalies = [ - a for a in anomalies - if a.anomaly_type == AnomalyType.UNUSUAL_DURATION + a for a in anomalies if a.anomaly_type == AnomalyType.UNUSUAL_DURATION ] assert len(duration_anomalies) >= 1 @@ -475,8 +471,7 @@ class TestAnomalyDetection: anomalies = await reflection.detect_anomalies(project_id, baseline_days=30) outcome_anomalies = [ - a for a in anomalies - if a.anomaly_type == AnomalyType.UNEXPECTED_OUTCOME + a for a in anomalies if a.anomaly_type == AnomalyType.UNEXPECTED_OUTCOME ] assert len(outcome_anomalies) >= 1 @@ -510,8 +505,7 @@ class TestAnomalyDetection: anomalies = await reflection.detect_anomalies(project_id, baseline_days=30) token_anomalies = [ - a for a in anomalies - if a.anomaly_type == AnomalyType.UNUSUAL_TOKEN_USAGE + a for a in anomalies if a.anomaly_type == AnomalyType.UNUSUAL_TOKEN_USAGE ] assert len(token_anomalies) >= 1 @@ -650,9 +644,7 @@ class TestInsightGeneration: insights = await reflection.generate_insights(project_id) - trend_insights = [ - i for i in insights if i.insight_type == InsightType.TREND - ] + trend_insights = [i for i in insights if i.insight_type == InsightType.TREND] assert len(trend_insights) >= 1 async def test_insights_sorted_by_priority( @@ -662,10 +654,7 @@ class TestInsightGeneration: """Should sort insights by priority.""" project_id = uuid4() - episodes = [ - create_mock_episode(outcome=Outcome.SUCCESS) - for _ in range(10) - ] + episodes = [create_mock_episode(outcome=Outcome.SUCCESS) for _ in range(10)] mock_episodic = MagicMock() mock_episodic.get_recent = AsyncMock(return_value=episodes)