Refactor cockpit to use DockerTmuxController pattern
Based on claude-code-tools TmuxCLIController, this refactor: - Added DockerTmuxController class for robust tmux session management - Implements send_keys() with configurable delay_enter - Implements capture_pane() for output retrieval - Implements wait_for_prompt() for pattern-based completion detection - Implements wait_for_idle() for content-hash-based idle detection - Implements wait_for_shell_prompt() for shell prompt detection Also includes workflow improvements: - Pre-task git snapshot before agent execution - Post-task commit protocol in agent guidelines Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
490
tests/test_sub_agent_context.py
Normal file
490
tests/test_sub_agent_context.py
Normal file
@@ -0,0 +1,490 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Tests for Sub-Agent Context Management
|
||||
|
||||
Verifies:
|
||||
1. Sub-agent context creation and retrieval
|
||||
2. Phase progression tracking
|
||||
3. Sibling agent discovery and coordination
|
||||
4. Context persistence
|
||||
5. Flow integration
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add parent directory to path for imports
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'lib'))
|
||||
|
||||
from sub_agent_context import (
|
||||
SubAgentContext,
|
||||
SubAgentContextManager,
|
||||
FlowPhase,
|
||||
)
|
||||
from sub_agent_flow_integration import SubAgentFlowIntegrator
|
||||
|
||||
|
||||
class TestSubAgentContextCreation:
|
||||
"""Test sub-agent context creation"""
|
||||
|
||||
def setup_method(self):
|
||||
"""Setup test fixtures"""
|
||||
self.temp_dir = tempfile.TemporaryDirectory()
|
||||
self.manager = SubAgentContextManager(Path(self.temp_dir.name))
|
||||
|
||||
def teardown_method(self):
|
||||
"""Cleanup"""
|
||||
self.temp_dir.cleanup()
|
||||
|
||||
def test_create_sub_agent_context(self):
|
||||
"""Test creating a new sub-agent context"""
|
||||
context = self.manager.create_sub_agent_context(
|
||||
parent_task_id="task-123",
|
||||
parent_project="admin",
|
||||
parent_description="Test parent task",
|
||||
parent_context={"key": "value"},
|
||||
parent_tags=["important", "research"],
|
||||
)
|
||||
|
||||
assert context.sub_agent_id is not None
|
||||
assert context.parent_task_id == "task-123"
|
||||
assert context.parent_project == "admin"
|
||||
assert context.parent_description == "Test parent task"
|
||||
assert len(context.phase_progression) == 9
|
||||
assert context.phase_progression[0].phase_name == "CONTEXT_PREP"
|
||||
|
||||
def test_phase_progression_initialization(self):
|
||||
"""Test that all 9 phases are initialized"""
|
||||
context = self.manager.create_sub_agent_context(
|
||||
parent_task_id="task-456",
|
||||
parent_project="test",
|
||||
parent_description="Phase test",
|
||||
)
|
||||
|
||||
phase_names = [p.phase_name for p in context.phase_progression]
|
||||
expected_phases = [
|
||||
"CONTEXT_PREP",
|
||||
"RECEIVED",
|
||||
"PREDICTING",
|
||||
"ANALYZING",
|
||||
"CONSENSUS_CHECK",
|
||||
"AWAITING_APPROVAL",
|
||||
"STRATEGIZING",
|
||||
"EXECUTING",
|
||||
"LEARNING",
|
||||
]
|
||||
|
||||
assert phase_names == expected_phases
|
||||
|
||||
def test_retrieve_sub_agent_context(self):
|
||||
"""Test retrieving sub-agent context"""
|
||||
created = self.manager.create_sub_agent_context(
|
||||
parent_task_id="task-789",
|
||||
parent_project="admin",
|
||||
parent_description="Retrieve test",
|
||||
)
|
||||
|
||||
retrieved = self.manager.get_sub_agent_context(created.sub_agent_id)
|
||||
|
||||
assert retrieved is not None
|
||||
assert retrieved.sub_agent_id == created.sub_agent_id
|
||||
assert retrieved.parent_task_id == "task-789"
|
||||
|
||||
|
||||
class TestSiblingDiscovery:
|
||||
"""Test sibling agent discovery and awareness"""
|
||||
|
||||
def setup_method(self):
|
||||
"""Setup test fixtures"""
|
||||
self.temp_dir = tempfile.TemporaryDirectory()
|
||||
self.manager = SubAgentContextManager(Path(self.temp_dir.name))
|
||||
|
||||
def teardown_method(self):
|
||||
"""Cleanup"""
|
||||
self.temp_dir.cleanup()
|
||||
|
||||
def test_single_sub_agent_no_siblings(self):
|
||||
"""Test first sub-agent has no siblings"""
|
||||
context = self.manager.create_sub_agent_context(
|
||||
parent_task_id="parent-1",
|
||||
parent_project="admin",
|
||||
parent_description="First agent",
|
||||
)
|
||||
|
||||
assert len(context.sibling_agents) == 0
|
||||
|
||||
def test_multiple_sub_agents_discover_siblings(self):
|
||||
"""Test multiple sub-agents discover each other as siblings"""
|
||||
# Create first sub-agent
|
||||
agent1 = self.manager.create_sub_agent_context(
|
||||
parent_task_id="parent-2",
|
||||
parent_project="admin",
|
||||
parent_description="Agent 1",
|
||||
)
|
||||
|
||||
# Create second sub-agent for same parent
|
||||
agent2 = self.manager.create_sub_agent_context(
|
||||
parent_task_id="parent-2",
|
||||
parent_project="admin",
|
||||
parent_description="Agent 2",
|
||||
)
|
||||
|
||||
# Create third sub-agent for same parent
|
||||
agent3 = self.manager.create_sub_agent_context(
|
||||
parent_task_id="parent-2",
|
||||
parent_project="admin",
|
||||
parent_description="Agent 3",
|
||||
)
|
||||
|
||||
# Verify sibling relationships
|
||||
assert agent2.sub_agent_id in self.manager.get_sibling_agents(agent1.sub_agent_id)
|
||||
assert agent3.sub_agent_id in self.manager.get_sibling_agents(agent1.sub_agent_id)
|
||||
assert len(self.manager.get_sibling_agents(agent1.sub_agent_id)) == 2
|
||||
|
||||
assert agent1.sub_agent_id in self.manager.get_sibling_agents(agent2.sub_agent_id)
|
||||
assert agent3.sub_agent_id in self.manager.get_sibling_agents(agent2.sub_agent_id)
|
||||
assert len(self.manager.get_sibling_agents(agent2.sub_agent_id)) == 2
|
||||
|
||||
def test_agents_from_different_parents_not_siblings(self):
|
||||
"""Test agents from different parents are not siblings"""
|
||||
agent1 = self.manager.create_sub_agent_context(
|
||||
parent_task_id="parent-a",
|
||||
parent_project="admin",
|
||||
parent_description="Agent 1",
|
||||
)
|
||||
|
||||
agent2 = self.manager.create_sub_agent_context(
|
||||
parent_task_id="parent-b",
|
||||
parent_project="admin",
|
||||
parent_description="Agent 2",
|
||||
)
|
||||
|
||||
assert agent2.sub_agent_id not in self.manager.get_sibling_agents(agent1.sub_agent_id)
|
||||
assert agent1.sub_agent_id not in self.manager.get_sibling_agents(agent2.sub_agent_id)
|
||||
|
||||
|
||||
class TestPhaseProgression:
|
||||
"""Test phase progression tracking"""
|
||||
|
||||
def setup_method(self):
|
||||
"""Setup test fixtures"""
|
||||
self.temp_dir = tempfile.TemporaryDirectory()
|
||||
self.manager = SubAgentContextManager(Path(self.temp_dir.name))
|
||||
self.context = self.manager.create_sub_agent_context(
|
||||
parent_task_id="task-phase",
|
||||
parent_project="admin",
|
||||
parent_description="Phase test",
|
||||
)
|
||||
|
||||
def teardown_method(self):
|
||||
"""Cleanup"""
|
||||
self.temp_dir.cleanup()
|
||||
|
||||
def test_update_phase_status(self):
|
||||
"""Test updating phase status"""
|
||||
success = self.manager.update_phase(
|
||||
self.context.sub_agent_id,
|
||||
"CONTEXT_PREP",
|
||||
"completed",
|
||||
output="Context prepared",
|
||||
)
|
||||
|
||||
assert success is True
|
||||
|
||||
updated = self.manager.get_sub_agent_context(self.context.sub_agent_id)
|
||||
phase = updated.phase_progression[0]
|
||||
assert phase.status == "completed"
|
||||
assert phase.output == "Context prepared"
|
||||
|
||||
def test_get_current_phase(self):
|
||||
"""Test getting current active phase"""
|
||||
# Initially should be first pending phase
|
||||
current = self.manager.get_current_phase(self.context.sub_agent_id)
|
||||
assert current == "CONTEXT_PREP"
|
||||
|
||||
# Mark first phase as complete
|
||||
self.manager.update_phase(
|
||||
self.context.sub_agent_id,
|
||||
"CONTEXT_PREP",
|
||||
"completed",
|
||||
)
|
||||
|
||||
# Now should be next pending phase
|
||||
current = self.manager.get_current_phase(self.context.sub_agent_id)
|
||||
assert current == "RECEIVED"
|
||||
|
||||
def test_phase_duration_calculation(self):
|
||||
"""Test duration calculation for completed phases"""
|
||||
# Mark phase as in progress
|
||||
self.manager.update_phase(
|
||||
self.context.sub_agent_id,
|
||||
"CONTEXT_PREP",
|
||||
"in_progress",
|
||||
)
|
||||
|
||||
# Mark as completed
|
||||
self.manager.update_phase(
|
||||
self.context.sub_agent_id,
|
||||
"CONTEXT_PREP",
|
||||
"completed",
|
||||
output="Done",
|
||||
)
|
||||
|
||||
updated = self.manager.get_sub_agent_context(self.context.sub_agent_id)
|
||||
phase = updated.phase_progression[0]
|
||||
assert phase.duration_seconds is not None
|
||||
assert phase.duration_seconds >= 0
|
||||
|
||||
def test_phase_progression_sequence(self):
|
||||
"""Test progressing through all phases"""
|
||||
sub_agent_id = self.context.sub_agent_id
|
||||
phases = [p.phase_name for p in self.context.phase_progression]
|
||||
|
||||
for phase_name in phases:
|
||||
self.manager.update_phase(
|
||||
sub_agent_id,
|
||||
phase_name,
|
||||
"completed",
|
||||
output=f"Completed {phase_name}",
|
||||
)
|
||||
|
||||
updated = self.manager.get_sub_agent_context(sub_agent_id)
|
||||
all_completed = all(p.status == "completed" for p in updated.phase_progression)
|
||||
assert all_completed is True
|
||||
|
||||
|
||||
class TestCoordination:
|
||||
"""Test sub-agent coordination and messaging"""
|
||||
|
||||
def setup_method(self):
|
||||
"""Setup test fixtures"""
|
||||
self.temp_dir = tempfile.TemporaryDirectory()
|
||||
self.manager = SubAgentContextManager(Path(self.temp_dir.name))
|
||||
|
||||
# Create two sibling agents
|
||||
self.agent1 = self.manager.create_sub_agent_context(
|
||||
parent_task_id="parent-coord",
|
||||
parent_project="admin",
|
||||
parent_description="Agent 1",
|
||||
)
|
||||
self.agent2 = self.manager.create_sub_agent_context(
|
||||
parent_task_id="parent-coord",
|
||||
parent_project="admin",
|
||||
parent_description="Agent 2",
|
||||
)
|
||||
|
||||
def teardown_method(self):
|
||||
"""Cleanup"""
|
||||
self.temp_dir.cleanup()
|
||||
|
||||
def test_send_message_to_sibling(self):
|
||||
"""Test sending coordination message to sibling"""
|
||||
success = self.manager.send_message_to_sibling(
|
||||
self.agent1.sub_agent_id,
|
||||
self.agent2.sub_agent_id,
|
||||
"request",
|
||||
{"type": "need_data", "data_type": "context"},
|
||||
)
|
||||
|
||||
assert success is True
|
||||
|
||||
def test_message_appears_in_both_agents(self):
|
||||
"""Test message is visible to both sender and receiver"""
|
||||
self.manager.send_message_to_sibling(
|
||||
self.agent1.sub_agent_id,
|
||||
self.agent2.sub_agent_id,
|
||||
"update",
|
||||
{"status": "ready"},
|
||||
)
|
||||
|
||||
agent1_updated = self.manager.get_sub_agent_context(self.agent1.sub_agent_id)
|
||||
agent2_updated = self.manager.get_sub_agent_context(self.agent2.sub_agent_id)
|
||||
|
||||
assert len(agent1_updated.coordination_messages) == 1
|
||||
assert len(agent2_updated.coordination_messages) == 1
|
||||
assert agent1_updated.coordination_messages[0]["type"] == "update"
|
||||
assert agent2_updated.coordination_messages[0]["type"] == "update"
|
||||
|
||||
def test_cannot_message_non_sibling(self):
|
||||
"""Test cannot send message to non-sibling agent"""
|
||||
# Create agent with different parent
|
||||
agent3 = self.manager.create_sub_agent_context(
|
||||
parent_task_id="parent-other",
|
||||
parent_project="admin",
|
||||
parent_description="Agent 3",
|
||||
)
|
||||
|
||||
# Try to send message across parent boundary
|
||||
success = self.manager.send_message_to_sibling(
|
||||
self.agent1.sub_agent_id,
|
||||
agent3.sub_agent_id,
|
||||
"request",
|
||||
{"data": "test"},
|
||||
)
|
||||
|
||||
assert success is False
|
||||
|
||||
|
||||
class TestContextPersistence:
|
||||
"""Test context persistence to disk"""
|
||||
|
||||
def test_context_saved_and_loaded(self):
|
||||
"""Test contexts are saved to disk and reloaded"""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
manager1 = SubAgentContextManager(Path(temp_dir))
|
||||
|
||||
# Create context in first manager
|
||||
context1 = manager1.create_sub_agent_context(
|
||||
parent_task_id="task-persist",
|
||||
parent_project="admin",
|
||||
parent_description="Persistence test",
|
||||
)
|
||||
sub_agent_id = context1.sub_agent_id
|
||||
|
||||
# Create new manager pointing to same directory
|
||||
manager2 = SubAgentContextManager(Path(temp_dir))
|
||||
|
||||
# Should be able to retrieve context from new manager
|
||||
context2 = manager2.get_sub_agent_context(sub_agent_id)
|
||||
|
||||
assert context2 is not None
|
||||
assert context2.parent_task_id == "task-persist"
|
||||
assert context2.sub_agent_id == sub_agent_id
|
||||
|
||||
|
||||
class TestFlowIntegration:
|
||||
"""Test flow integration with sub-agent context"""
|
||||
|
||||
def setup_method(self):
|
||||
"""Setup test fixtures"""
|
||||
self.temp_dir = tempfile.TemporaryDirectory()
|
||||
self.context_manager = SubAgentContextManager(Path(self.temp_dir.name))
|
||||
self.integrator = SubAgentFlowIntegrator(self.context_manager)
|
||||
|
||||
def teardown_method(self):
|
||||
"""Cleanup"""
|
||||
self.temp_dir.cleanup()
|
||||
|
||||
def test_execute_sub_agent_flow(self):
|
||||
"""Test executing full sub-agent flow"""
|
||||
results = self.integrator.execute_sub_agent_flow(
|
||||
parent_task_id="task-flow",
|
||||
parent_project="admin",
|
||||
parent_description="Flow test",
|
||||
parent_context={"key": "value"},
|
||||
)
|
||||
|
||||
assert results["sub_agent_id"] is not None
|
||||
assert "phases" in results
|
||||
# Should have results for all 9 phases
|
||||
assert len(results["phases"]) == 9
|
||||
|
||||
def test_execute_single_phase(self):
|
||||
"""Test executing a single phase"""
|
||||
context = self.context_manager.create_sub_agent_context(
|
||||
parent_task_id="task-single",
|
||||
parent_project="admin",
|
||||
parent_description="Single phase test",
|
||||
)
|
||||
|
||||
result = self.integrator.execute_phase(context.sub_agent_id, "CONTEXT_PREP")
|
||||
|
||||
assert result["status"] == "completed"
|
||||
assert "output" in result
|
||||
|
||||
def test_get_sub_agent_progress(self):
|
||||
"""Test getting progress report"""
|
||||
context = self.context_manager.create_sub_agent_context(
|
||||
parent_task_id="task-progress",
|
||||
parent_project="admin",
|
||||
parent_description="Progress test",
|
||||
)
|
||||
|
||||
# Execute a phase
|
||||
self.integrator.execute_phase(context.sub_agent_id, "CONTEXT_PREP")
|
||||
self.integrator.execute_phase(context.sub_agent_id, "RECEIVED")
|
||||
|
||||
progress = self.integrator.get_sub_agent_progress(context.sub_agent_id)
|
||||
|
||||
assert progress["completed_phases"] == 2
|
||||
assert progress["in_progress_phases"] == 0
|
||||
assert progress["total_phases"] == 9
|
||||
|
||||
def test_coordinate_sequential_sub_agents(self):
|
||||
"""Test sequential coordination of sub-agents"""
|
||||
# Create multiple sub-agents for same parent
|
||||
for i in range(3):
|
||||
self.context_manager.create_sub_agent_context(
|
||||
parent_task_id="task-coord",
|
||||
parent_project="admin",
|
||||
parent_description=f"Agent {i+1}",
|
||||
)
|
||||
|
||||
coordination = self.integrator.coordinate_sub_agents(
|
||||
parent_task_id="task-coord",
|
||||
coordination_strategy="sequential",
|
||||
)
|
||||
|
||||
assert len(coordination["sub_agents"]) == 3
|
||||
assert coordination["strategy"] == "sequential"
|
||||
|
||||
def test_collect_sub_agent_results(self):
|
||||
"""Test collecting results from multiple sub-agents"""
|
||||
# Create and execute multiple sub-agents
|
||||
for i in range(2):
|
||||
context = self.context_manager.create_sub_agent_context(
|
||||
parent_task_id="task-collect",
|
||||
parent_project="admin",
|
||||
parent_description=f"Agent {i+1}",
|
||||
)
|
||||
self.integrator.execute_phase(context.sub_agent_id, "CONTEXT_PREP")
|
||||
|
||||
results = self.integrator.collect_sub_agent_results("task-collect")
|
||||
|
||||
assert results["sub_agents_total"] == 2
|
||||
assert len(results["sub_agents"]) == 2
|
||||
assert all("progress" in s for s in results["sub_agents"])
|
||||
|
||||
|
||||
class TestContextSummary:
|
||||
"""Test context summary generation"""
|
||||
|
||||
def setup_method(self):
|
||||
"""Setup test fixtures"""
|
||||
self.temp_dir = tempfile.TemporaryDirectory()
|
||||
self.manager = SubAgentContextManager(Path(self.temp_dir.name))
|
||||
|
||||
def teardown_method(self):
|
||||
"""Cleanup"""
|
||||
self.temp_dir.cleanup()
|
||||
|
||||
def test_get_context_summary(self):
|
||||
"""Test getting human-readable summary"""
|
||||
context = self.manager.create_sub_agent_context(
|
||||
parent_task_id="task-summary",
|
||||
parent_project="admin",
|
||||
parent_description="Summary test",
|
||||
parent_tags=["important", "urgent"],
|
||||
)
|
||||
|
||||
# Create a sibling
|
||||
self.manager.create_sub_agent_context(
|
||||
parent_task_id="task-summary",
|
||||
parent_project="admin",
|
||||
parent_description="Sibling agent",
|
||||
)
|
||||
|
||||
summary = self.manager.get_context_summary(context.sub_agent_id)
|
||||
|
||||
assert summary is not None
|
||||
assert summary["sub_agent_id"] == context.sub_agent_id
|
||||
assert summary["parent_task_id"] == "task-summary"
|
||||
assert summary["sibling_count"] == 1
|
||||
assert summary["parent_tags"] == ["important", "urgent"]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
Reference in New Issue
Block a user