feat(#152): Implement session rotation (TDD)
Implement session rotation that spawns fresh agents when context reaches 95% threshold. TDD Process: 1. RED: Write comprehensive tests (all initially fail) 2. GREEN: Implement trigger_rotation method (all tests pass) Changes: - Add SessionRotation dataclass to track rotation metrics - Implement trigger_rotation method in ContextMonitor - Add 6 new unit tests covering all acceptance criteria Rotation process: 1. Get current context usage metrics 2. Close current agent session 3. Spawn new agent with same type 4. Transfer next issue to new agent 5. Log rotation event with metrics Test Results: - All 47 tests pass (34 context_monitor + 13 context_compaction) - 97% coverage on context_monitor.py (exceeds 85% requirement) - 97% coverage on context_compaction.py (exceeds 85% requirement) Prevents context exhaustion by starting fresh when compaction is insufficient. Acceptance Criteria (All Met): ✓ Rotation triggered at 95% context threshold ✓ Current session closed cleanly ✓ New agent spawned with same type ✓ Next issue transferred to new agent ✓ Rotation logged with session IDs and context metrics ✓ Unit tests with 85%+ coverage Fixes #152 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -51,6 +51,41 @@ class CompactionResult:
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SessionRotation:
|
||||
"""Result of session rotation operation.
|
||||
|
||||
Attributes:
|
||||
old_agent_id: Identifier of the closed agent session
|
||||
new_agent_id: Identifier of the newly spawned agent
|
||||
agent_type: Type of agent (sonnet, haiku, opus, glm)
|
||||
next_issue_number: Issue number transferred to new agent
|
||||
context_before_tokens: Token count before rotation
|
||||
context_before_percent: Usage percentage before rotation
|
||||
success: Whether rotation succeeded
|
||||
error_message: Error message if rotation failed
|
||||
"""
|
||||
|
||||
old_agent_id: str
|
||||
new_agent_id: str
|
||||
agent_type: str
|
||||
next_issue_number: int
|
||||
context_before_tokens: int
|
||||
context_before_percent: float
|
||||
success: bool
|
||||
error_message: str = ""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""String representation."""
|
||||
status = "success" if self.success else "failed"
|
||||
return (
|
||||
f"SessionRotation(old={self.old_agent_id!r}, "
|
||||
f"new={self.new_agent_id!r}, "
|
||||
f"issue=#{self.next_issue_number}, "
|
||||
f"status={status})"
|
||||
)
|
||||
|
||||
|
||||
class ContextCompactor:
|
||||
"""Handles context compaction to free agent memory.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from collections import defaultdict
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
from src.context_compaction import CompactionResult, ContextCompactor
|
||||
from src.context_compaction import CompactionResult, ContextCompactor, SessionRotation
|
||||
from src.models import ContextAction, ContextUsage
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -164,3 +164,83 @@ class ContextMonitor:
|
||||
logger.error(f"Compaction failed for {agent_id}: {result.error_message}")
|
||||
|
||||
return result
|
||||
|
||||
async def trigger_rotation(
|
||||
self,
|
||||
agent_id: str,
|
||||
agent_type: str,
|
||||
next_issue_number: int,
|
||||
) -> SessionRotation:
|
||||
"""Trigger session rotation for an agent.
|
||||
|
||||
Spawns fresh agent when context reaches 95% threshold.
|
||||
|
||||
Rotation process:
|
||||
1. Get current context usage metrics
|
||||
2. Close current agent session
|
||||
3. Spawn new agent with same type
|
||||
4. Transfer next issue to new agent
|
||||
5. Log rotation event with metrics
|
||||
|
||||
Args:
|
||||
agent_id: Unique identifier for the current agent
|
||||
agent_type: Type of agent (sonnet, haiku, opus, glm)
|
||||
next_issue_number: Issue number to transfer to new agent
|
||||
|
||||
Returns:
|
||||
SessionRotation with rotation details and metrics
|
||||
"""
|
||||
logger.warning(
|
||||
f"Triggering session rotation for agent {agent_id} "
|
||||
f"(type: {agent_type}, next issue: #{next_issue_number})"
|
||||
)
|
||||
|
||||
try:
|
||||
# Get context usage before rotation
|
||||
usage = await self.get_context_usage(agent_id)
|
||||
context_before_tokens = usage.used_tokens
|
||||
context_before_percent = usage.usage_percent
|
||||
|
||||
logger.info(
|
||||
f"Agent {agent_id} context before rotation: "
|
||||
f"{context_before_tokens}/{usage.total_tokens} ({context_before_percent:.1f}%)"
|
||||
)
|
||||
|
||||
# Close current session
|
||||
await self.api_client.close_session(agent_id)
|
||||
logger.info(f"Closed session for agent {agent_id}")
|
||||
|
||||
# Spawn new agent with same type
|
||||
spawn_response = await self.api_client.spawn_agent(
|
||||
agent_type=agent_type,
|
||||
issue_number=next_issue_number,
|
||||
)
|
||||
new_agent_id = spawn_response["agent_id"]
|
||||
|
||||
logger.info(
|
||||
f"Session rotation successful: {agent_id} -> {new_agent_id} "
|
||||
f"(issue #{next_issue_number})"
|
||||
)
|
||||
|
||||
return SessionRotation(
|
||||
old_agent_id=agent_id,
|
||||
new_agent_id=new_agent_id,
|
||||
agent_type=agent_type,
|
||||
next_issue_number=next_issue_number,
|
||||
context_before_tokens=context_before_tokens,
|
||||
context_before_percent=context_before_percent,
|
||||
success=True,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Session rotation failed for agent {agent_id}: {e}")
|
||||
return SessionRotation(
|
||||
old_agent_id=agent_id,
|
||||
new_agent_id="",
|
||||
agent_type=agent_type,
|
||||
next_issue_number=next_issue_number,
|
||||
context_before_tokens=0,
|
||||
context_before_percent=0.0,
|
||||
success=False,
|
||||
error_message=str(e),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user