feat(#160): Implement basic orchestration loop

Implements the Coordinator class with main orchestration loop:
- Async loop architecture with configurable poll interval
- process_queue() method gets next ready issue and spawns agent (stub)
- Graceful shutdown handling with stop() method
- Error handling that allows loop to continue after failures
- Logging for all actions (start, stop, processing, errors)
- Integration with QueueManager from #159
- Active agent tracking for future agent management

Configuration settings added:
- COORDINATOR_POLL_INTERVAL (default: 5.0s)
- COORDINATOR_MAX_CONCURRENT_AGENTS (default: 10)
- COORDINATOR_ENABLED (default: true)

Tests: 27 new tests covering all acceptance criteria
Coverage: 92% overall (100% for coordinator.py)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-01 18:03:12 -06:00
parent f0fd0bed41
commit 88953fc998
9 changed files with 1043 additions and 24 deletions

View File

@@ -1,13 +1,12 @@
"""Tests for issue parser agent."""
import os
from unittest.mock import Mock, patch
import pytest
from unittest.mock import Mock, patch, AsyncMock
from anthropic import Anthropic
from anthropic.types import Message, TextBlock, Usage
from src.parser import parse_issue_metadata, clear_cache
from src.models import IssueMetadata
from src.parser import clear_cache, parse_issue_metadata
@pytest.fixture(autouse=True)
@@ -88,7 +87,10 @@ def mock_anthropic_response() -> Message:
content=[
TextBlock(
type="text",
text='{"estimated_context": 46800, "difficulty": "medium", "assigned_agent": "sonnet", "blocks": [159], "blocked_by": [157]}'
text=(
'{"estimated_context": 46800, "difficulty": "medium", '
'"assigned_agent": "sonnet", "blocks": [159], "blocked_by": [157]}'
),
)
],
model="claude-sonnet-4.5-20250929",
@@ -107,7 +109,10 @@ def mock_anthropic_minimal_response() -> Message:
content=[
TextBlock(
type="text",
text='{"estimated_context": 50000, "difficulty": "medium", "assigned_agent": "sonnet", "blocks": [], "blocked_by": []}'
text=(
'{"estimated_context": 50000, "difficulty": "medium", '
'"assigned_agent": "sonnet", "blocks": [], "blocked_by": []}'
),
)
],
model="claude-sonnet-4.5-20250929",
@@ -306,7 +311,10 @@ class TestParseIssueMetadata:
content=[
TextBlock(
type="text",
text='{"estimated_context": 10000, "difficulty": "invalid", "assigned_agent": "sonnet", "blocks": [], "blocked_by": []}'
text=(
'{"estimated_context": 10000, "difficulty": "invalid", '
'"assigned_agent": "sonnet", "blocks": [], "blocked_by": []}'
),
)
],
model="claude-sonnet-4.5-20250929",
@@ -341,7 +349,10 @@ class TestParseIssueMetadata:
content=[
TextBlock(
type="text",
text='{"estimated_context": 10000, "difficulty": "medium", "assigned_agent": "invalid_agent", "blocks": [], "blocked_by": []}'
text=(
'{"estimated_context": 10000, "difficulty": "medium", '
'"assigned_agent": "invalid_agent", "blocks": [], "blocked_by": []}'
),
)
],
model="claude-sonnet-4.5-20250929",