mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
TODOs done + cleanup
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
# TASK-008: Resolve All TODOs Across Codebase
|
||||
|
||||
**Status**: `completed`
|
||||
**Priority**: P1
|
||||
**Cell**: Board (cross-cutting)
|
||||
**Created**: 2025-12-10
|
||||
**Assigned To**: -
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Resolve all TODO comments scattered across agent implementations, API routes, and services. The codebase has a complete service layer (TaskService, NotificationService, JournalService, etc.) and MCP servers that wrap them, but the agent implementations contain placeholder methods with TODO comments that need to be wired up to these existing services.
|
||||
|
||||
## Context
|
||||
|
||||
### What Exists (Already Implemented)
|
||||
|
||||
| Component | Location | Status |
|
||||
|-----------|----------|--------|
|
||||
| TaskService | `roboco/services/task.py` | Complete (717 lines) |
|
||||
| NotificationService | `roboco/services/notification.py` | Complete (189 lines) |
|
||||
| JournalService | `roboco/services/journal.py` | Complete (691 lines) |
|
||||
| KanbanService | `roboco/services/kanban.py` | Complete (489 lines) |
|
||||
| MetricsService | `roboco/services/metrics.py` | Complete (592 lines) |
|
||||
| ExtractionService | `roboco/services/extraction.py` | Complete (462 lines) |
|
||||
| OptimalService | `roboco/services/optimal.py` | Partial (core methods stubbed) |
|
||||
| Task MCP Server | `roboco/mcp/task_server.py` | Complete (1173 lines) |
|
||||
| Message MCP Server | `roboco/mcp/message_server.py` | Complete (484 lines) |
|
||||
| Notify MCP Server | `roboco/mcp/notify_server.py` | Complete (486 lines) |
|
||||
| Journal MCP Server | `roboco/mcp/journal_server.py` | Complete (600 lines) |
|
||||
|
||||
### What's Missing (TODOs to Resolve)
|
||||
|
||||
37 TODO items across 9 files, categorized into 4 work packages.
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] All agent methods call appropriate services instead of returning placeholders
|
||||
- [ ] Health check endpoints verify actual database and Redis connectivity
|
||||
- [ ] WebSocket broadcasts are wired to actual notification delivery
|
||||
- [ ] LLM integration methods use Anthropic/OpenAI clients
|
||||
- [ ] OptimalService core methods are implemented with piragi
|
||||
- [ ] All TODO comments are removed or converted to tracked issues
|
||||
- [ ] Tests pass (if any exist for modified code)
|
||||
- [ ] Type checking passes (`mypy src/`)
|
||||
|
||||
---
|
||||
|
||||
## Work Packages
|
||||
|
||||
### WP-1: Agent Service Integration (24 TODOs) - P1
|
||||
|
||||
Wire agent methods to existing services. The agents need database sessions injected or access to API endpoints.
|
||||
|
||||
**Strategy**: Agents will call the Task/Message/Notification APIs via HTTP (same pattern as MCP servers) rather than direct service injection.
|
||||
|
||||
| File | Method | TODO | Resolution |
|
||||
|------|--------|------|------------|
|
||||
| `agents/base.py:322` | `send_message` | Integrate with Messaging API | Call `/api/v1/messages` endpoint |
|
||||
| `agents/base.py:339` | `stream_reasoning` | Integrate with WebSocket streaming | Call `broadcast_agent_chunk()` |
|
||||
| `agents/base.py:357` | `think` | Integrate with LLM provider | Use `anthropic` or `openai` client |
|
||||
| `agents/base.py:378` | `think_and_stream` | Integrate with LLM + streaming | Stream via client + broadcast |
|
||||
| `agents/board.py:151` | `_review_feature` | Check acceptance criteria | Query task, validate criteria |
|
||||
| `agents/board.py:578` | `_read_channel_silently` | Query messaging API silent | Call `/api/v1/channels/{id}/messages` |
|
||||
| `agents/board.py:591` | `_perform_audit` | Implement audits | Query tasks/messages for patterns |
|
||||
| `agents/developer.py:497` | `_find_paused_task` | Query task API | Call `/api/v1/tasks?status=paused&assigned_to={id}` |
|
||||
| `agents/developer.py:502` | `_find_assigned_task` | Query task API | Call `/api/v1/tasks?assigned_to={id}` |
|
||||
| `agents/developer.py:515` | `_get_task_title` | Query task API | Call `/api/v1/tasks/{id}` |
|
||||
| `agents/developer.py:520` | `_read_task_requirements` | Read from .tasks/ | Read file or call API |
|
||||
| `agents/developer.py:525` | `_update_task_status` | Update via API | Call `PUT /api/v1/tasks/{id}` |
|
||||
| `agents/developer.py:530` | `_check_qa_approved` | Check via API | Query task status |
|
||||
| `agents/developer.py:535` | `_check_docs_complete` | Check via API | Query handoff status |
|
||||
| `agents/documenter.py:408` | `_phase_publish` | Write file | Use `aiofiles` to write docs |
|
||||
| `agents/documenter.py:428-463` | 7 methods | Various queries | Call appropriate API endpoints |
|
||||
| `agents/pm.py:321-398` | 9 methods | Task/Agent queries | Call task/agent APIs |
|
||||
| `agents/qa.py:432-457` | 6 methods | Task queries | Call task API |
|
||||
|
||||
**Dependencies**: None - services exist
|
||||
**Effort**: Medium (mostly HTTP client calls)
|
||||
|
||||
---
|
||||
|
||||
### WP-2: Health Check Implementation (2 TODOs) - P0
|
||||
|
||||
Make health checks actually verify connectivity.
|
||||
|
||||
| File | Line | TODO | Resolution |
|
||||
|------|------|------|------------|
|
||||
| `api/routes/health.py:58` | readiness | Check DB connection | Use `session.execute(text("SELECT 1"))` |
|
||||
| `api/routes/health.py:60` | readiness | Check Redis | Use `redis.ping()` |
|
||||
|
||||
**Dependencies**: Database and Redis clients
|
||||
**Effort**: Small
|
||||
|
||||
---
|
||||
|
||||
### WP-3: WebSocket Notification Delivery (2 TODOs) - P2
|
||||
|
||||
Complete WebSocket integration for real-time notifications.
|
||||
|
||||
| File | Line | TODO | Resolution |
|
||||
|------|------|------|------------|
|
||||
| `api/websocket.py:236` | channel_stream | Validate agent access | Call PermissionService |
|
||||
| `api/websocket.py:432` | broadcast_notification | Per-agent delivery | Track agent connections, route |
|
||||
|
||||
**Dependencies**: ConnectionManager enhancements
|
||||
**Effort**: Medium
|
||||
|
||||
---
|
||||
|
||||
### WP-4: OptimalService / LLM Integration (3 TODOs) - P2
|
||||
|
||||
Complete RAG and LLM integration.
|
||||
|
||||
| File | Line | TODO | Resolution |
|
||||
|------|------|------|------------|
|
||||
| `services/extraction.py:379` | extract_with_llm | LLM classification | Use Anthropic client for classification |
|
||||
| `services/optimal.py` | search/query/index | RAG operations | Implement with piragi methods |
|
||||
|
||||
**Dependencies**: piragi library, Anthropic/OpenAI clients
|
||||
**Effort**: Large
|
||||
|
||||
---
|
||||
|
||||
## Implementation Order
|
||||
|
||||
```
|
||||
Phase 1 (Critical Path):
|
||||
├── WP-2: Health Checks (30 min) - Immediate value
|
||||
├── WP-1a: Base Agent LLM Integration (2 hours) - Enables all agents
|
||||
│ └── think(), think_and_stream(), send_message()
|
||||
│
|
||||
Phase 2 (Agent Functionality):
|
||||
├── WP-1b: Developer Agent (1.5 hours)
|
||||
├── WP-1c: QA Agent (1 hour)
|
||||
├── WP-1d: Documenter Agent (1 hour)
|
||||
├── WP-1e: PM Agents (1 hour)
|
||||
├── WP-1f: Board Agents (1 hour)
|
||||
│
|
||||
Phase 3 (Real-Time):
|
||||
├── WP-3: WebSocket (2 hours)
|
||||
│
|
||||
Phase 4 (Intelligence):
|
||||
└── WP-4: OptimalService (3 hours)
|
||||
|
||||
Total Estimated: ~13-15 hours
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Technical Notes
|
||||
|
||||
### Agent API Access Pattern
|
||||
|
||||
Agents should use an HTTP client to call the RoboCo API (same as MCP servers):
|
||||
|
||||
```python
|
||||
import httpx
|
||||
from roboco.config import settings
|
||||
|
||||
async def _call_api(self, method: str, path: str, **kwargs) -> dict:
|
||||
"""Make API call to RoboCo services."""
|
||||
url = f"http://{settings.host}:{settings.port}/api/v1{path}"
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.request(method, url, **kwargs)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
```
|
||||
|
||||
### LLM Client Integration
|
||||
|
||||
Use the Anthropic client from settings:
|
||||
|
||||
```python
|
||||
from anthropic import AsyncAnthropic
|
||||
from roboco.config import settings
|
||||
|
||||
client = AsyncAnthropic(api_key=settings.anthropic_api_key)
|
||||
|
||||
async def think(self, prompt: str, context: dict | None = None) -> str:
|
||||
messages = [{"role": "user", "content": prompt}]
|
||||
response = await client.messages.create(
|
||||
model=settings.default_model,
|
||||
max_tokens=self.config.max_tokens,
|
||||
system=self.config.system_prompt,
|
||||
messages=messages,
|
||||
)
|
||||
return response.content[0].text
|
||||
```
|
||||
|
||||
### Database Health Check
|
||||
|
||||
```python
|
||||
from sqlalchemy import text
|
||||
from roboco.db import get_async_session
|
||||
|
||||
async def check_database() -> bool:
|
||||
try:
|
||||
async with get_async_session() as session:
|
||||
await session.execute(text("SELECT 1"))
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Files to Modify
|
||||
|
||||
| Priority | File | Changes |
|
||||
|----------|------|---------|
|
||||
| P0 | `roboco/api/routes/health.py` | Add actual health checks |
|
||||
| P1 | `roboco/agents/base.py` | Wire LLM + messaging |
|
||||
| P1 | `roboco/agents/developer.py` | Wire task API calls |
|
||||
| P1 | `roboco/agents/qa.py` | Wire task API calls |
|
||||
| P1 | `roboco/agents/documenter.py` | Wire task API + file writes |
|
||||
| P1 | `roboco/agents/pm.py` | Wire task/agent API calls |
|
||||
| P1 | `roboco/agents/board.py` | Wire task/message API calls |
|
||||
| P2 | `roboco/api/websocket.py` | Complete notification delivery |
|
||||
| P2 | `roboco/services/extraction.py` | Add LLM classification |
|
||||
| P2 | `roboco/services/optimal.py` | Implement RAG methods |
|
||||
|
||||
---
|
||||
|
||||
## Risks & Mitigations
|
||||
|
||||
| Risk | Impact | Mitigation |
|
||||
|------|--------|------------|
|
||||
| Anthropic API key not configured | Agents can't think | Add fallback to local Ollama |
|
||||
| Database not running | Health checks fail | Document startup requirements |
|
||||
| Circular imports | Import errors | Use lazy imports or dependency injection |
|
||||
| Rate limiting on LLM | Agents blocked | Implement retry with backoff |
|
||||
|
||||
---
|
||||
|
||||
## Blockers
|
||||
|
||||
None identified - all dependencies exist.
|
||||
|
||||
---
|
||||
|
||||
## Journal
|
||||
|
||||
| Date | Author | Entry |
|
||||
|------|--------|-------|
|
||||
| 2025-12-10 | Claude | Created task. Analyzed 37 TODOs across 9 files. Categorized into 4 work packages. |
|
||||
| 2025-12-10 | Claude | Completed all phases. Implemented health checks, LLM integration in base agent, wired all agent types to APIs, added WebSocket channel validation and per-agent notification delivery, implemented LLM extraction in ExtractionService. |
|
||||
|
||||
@@ -0,0 +1,500 @@
|
||||
# Implementation Plan: TASK-008 - Resolve All TODOs
|
||||
|
||||
## Overview
|
||||
|
||||
This plan breaks down the TODO resolution into discrete, testable sub-tasks organized by priority and dependency order.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Critical Path (P0) - ~2.5 hours
|
||||
|
||||
### 1.1 Health Check Implementation (30 min)
|
||||
|
||||
**File**: `roboco/api/routes/health.py`
|
||||
|
||||
**Sub-tasks**:
|
||||
1. Add database health check function
|
||||
2. Add Redis health check function
|
||||
3. Update `readiness_check()` endpoint to call both
|
||||
4. Handle failures gracefully (return status per service)
|
||||
|
||||
**Changes**:
|
||||
```python
|
||||
# Add imports
|
||||
from sqlalchemy import text
|
||||
from roboco.db import get_async_session
|
||||
import redis.asyncio as redis
|
||||
from roboco.config import settings
|
||||
|
||||
# Add check functions
|
||||
async def check_database() -> tuple[str, bool]:
|
||||
try:
|
||||
async with get_async_session() as session:
|
||||
await session.execute(text("SELECT 1"))
|
||||
return "ok", True
|
||||
except Exception as e:
|
||||
return str(e), False
|
||||
|
||||
async def check_redis() -> tuple[str, bool]:
|
||||
try:
|
||||
client = redis.from_url(settings.redis_url)
|
||||
await client.ping()
|
||||
await client.close()
|
||||
return "ok", True
|
||||
except Exception as e:
|
||||
return str(e), False
|
||||
|
||||
# Update endpoint
|
||||
@router.get("/ready")
|
||||
async def readiness_check() -> ReadinessResponse:
|
||||
db_status, db_ok = await check_database()
|
||||
redis_status, redis_ok = await check_redis()
|
||||
overall = "ok" if (db_ok and redis_ok) else "degraded"
|
||||
return ReadinessResponse(
|
||||
status=overall,
|
||||
database=db_status,
|
||||
redis=redis_status,
|
||||
)
|
||||
```
|
||||
|
||||
**Tests**: Call `/ready` with DB up/down, Redis up/down
|
||||
|
||||
---
|
||||
|
||||
### 1.2 Base Agent LLM Integration (2 hours)
|
||||
|
||||
**File**: `roboco/agents/base.py`
|
||||
|
||||
**Sub-tasks**:
|
||||
1. Add Anthropic client initialization
|
||||
2. Implement `think()` method with actual LLM call
|
||||
3. Implement `think_and_stream()` with streaming response
|
||||
4. Implement `send_message()` with HTTP API call
|
||||
5. Implement `stream_reasoning()` with WebSocket broadcast
|
||||
|
||||
**Changes**:
|
||||
|
||||
```python
|
||||
# Add to imports
|
||||
from anthropic import AsyncAnthropic
|
||||
import httpx
|
||||
from roboco.config import settings
|
||||
from roboco.api.websocket import broadcast_agent_chunk
|
||||
|
||||
# Add to Agent.__init__
|
||||
self._llm_client: AsyncAnthropic | None = None
|
||||
|
||||
# Add property
|
||||
@property
|
||||
def llm_client(self) -> AsyncAnthropic:
|
||||
if self._llm_client is None:
|
||||
self._llm_client = AsyncAnthropic(api_key=settings.anthropic_api_key)
|
||||
return self._llm_client
|
||||
|
||||
# Implement think()
|
||||
async def think(self, prompt: str, context: dict[str, Any] | None = None) -> str:
|
||||
self.log.debug("Thinking", prompt_length=len(prompt))
|
||||
|
||||
messages = [{"role": "user", "content": prompt}]
|
||||
|
||||
response = await self.llm_client.messages.create(
|
||||
model=self.config.model,
|
||||
max_tokens=self.config.max_tokens,
|
||||
system=self.config.system_prompt,
|
||||
messages=messages,
|
||||
)
|
||||
|
||||
return response.content[0].text
|
||||
|
||||
# Implement think_and_stream()
|
||||
async def think_and_stream(
|
||||
self,
|
||||
prompt: str,
|
||||
context: dict[str, Any] | None = None,
|
||||
) -> str:
|
||||
self.log.debug("Thinking (streaming)", prompt_length=len(prompt))
|
||||
|
||||
messages = [{"role": "user", "content": prompt}]
|
||||
full_response = ""
|
||||
|
||||
async with self.llm_client.messages.stream(
|
||||
model=self.config.model,
|
||||
max_tokens=self.config.max_tokens,
|
||||
system=self.config.system_prompt,
|
||||
messages=messages,
|
||||
) as stream:
|
||||
async for text in stream.text_stream:
|
||||
full_response += text
|
||||
await self.stream_reasoning(text)
|
||||
|
||||
return full_response
|
||||
|
||||
# Implement send_message()
|
||||
async def send_message(
|
||||
self,
|
||||
channel_id: UUID,
|
||||
content: str,
|
||||
message_type: str = "dialogue",
|
||||
) -> None:
|
||||
self.state.messages_sent += 1
|
||||
self.state.last_activity = datetime.now(UTC)
|
||||
|
||||
url = f"http://{settings.host}:{settings.port}/api/v1/messages"
|
||||
async with httpx.AsyncClient() as client:
|
||||
await client.post(url, json={
|
||||
"channel_id": str(channel_id),
|
||||
"agent_id": str(self.id),
|
||||
"content": content,
|
||||
"message_type": message_type,
|
||||
})
|
||||
|
||||
self.log.debug("Message sent", channel_id=str(channel_id))
|
||||
|
||||
# Implement stream_reasoning()
|
||||
async def stream_reasoning(self, content: str) -> None:
|
||||
await broadcast_agent_chunk(self.id, content)
|
||||
self.log.debug("Streamed reasoning", content_length=len(content))
|
||||
```
|
||||
|
||||
**Tests**: Unit test with mocked Anthropic client
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Agent Functionality (P1) - ~5.5 hours
|
||||
|
||||
### 2.1 Agent API Helper (30 min)
|
||||
|
||||
**File**: `roboco/agents/base.py` (add helper method)
|
||||
|
||||
```python
|
||||
async def _api_call(
|
||||
self,
|
||||
method: str,
|
||||
path: str,
|
||||
**kwargs: Any,
|
||||
) -> dict[str, Any]:
|
||||
"""Make API call to RoboCo services."""
|
||||
url = f"http://{settings.host}:{settings.port}/api/v1{path}"
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.request(method, url, **kwargs)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.2 Developer Agent (1.5 hours)
|
||||
|
||||
**File**: `roboco/agents/developer.py`
|
||||
|
||||
**Sub-tasks**:
|
||||
1. Implement `_find_paused_task()` - GET `/tasks?status=paused&assigned_to={id}`
|
||||
2. Implement `_find_assigned_task()` - GET `/tasks?status=pending&assigned_to={id}`
|
||||
3. Implement `_get_task_title()` - GET `/tasks/{id}`
|
||||
4. Implement `_read_task_requirements()` - GET `/tasks/{id}` (description + criteria)
|
||||
5. Implement `_update_task_status()` - PUT `/tasks/{id}`
|
||||
6. Implement `_check_qa_approved()` - GET `/tasks/{id}` check status
|
||||
7. Implement `_check_docs_complete()` - GET `/tasks/{id}/handoffs`
|
||||
|
||||
**Pattern**:
|
||||
```python
|
||||
async def _find_paused_task(self) -> UUID | None:
|
||||
result = await self._api_call(
|
||||
"GET",
|
||||
"/tasks",
|
||||
params={"status": "paused", "assigned_to": str(self.id)}
|
||||
)
|
||||
tasks = result.get("items", [])
|
||||
return UUID(tasks[0]["id"]) if tasks else None
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.3 QA Agent (1 hour)
|
||||
|
||||
**File**: `roboco/agents/qa.py`
|
||||
|
||||
**Sub-tasks**:
|
||||
1. Implement `_find_awaiting_qa()` - GET `/tasks?status=awaiting_qa&team={team}`
|
||||
2. Implement `_get_task_title()` - same as developer
|
||||
3. Implement `_read_task_requirements()` - same as developer
|
||||
4. Implement `_read_dev_notes()` - GET `/tasks/{id}` (dev_notes field)
|
||||
5. Implement `_get_task_commits()` - GET `/tasks/{id}` (commits field)
|
||||
6. Implement `_update_task_status()` - same as developer
|
||||
|
||||
---
|
||||
|
||||
### 2.4 Documenter Agent (1 hour)
|
||||
|
||||
**File**: `roboco/agents/documenter.py`
|
||||
|
||||
**Sub-tasks**:
|
||||
1. Implement all query methods (same pattern as QA)
|
||||
2. Implement `_phase_publish()` with file writing:
|
||||
|
||||
```python
|
||||
import aiofiles
|
||||
|
||||
async def _phase_publish(self, ctx: DocContext) -> None:
|
||||
self.log.info("PUBLISH phase", task_id=str(ctx.task_id))
|
||||
|
||||
for doc_spec in ctx.documents_needed:
|
||||
if doc_spec.content:
|
||||
path = Path(doc_spec.path)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
async with aiofiles.open(path, 'w') as f:
|
||||
await f.write(doc_spec.content)
|
||||
self.log.info("Published", path=doc_spec.path)
|
||||
|
||||
await self._update_task_status(ctx.task_id, TaskStatus.COMPLETED)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.5 PM Agents (1 hour)
|
||||
|
||||
**File**: `roboco/agents/pm.py`
|
||||
|
||||
**Sub-tasks**:
|
||||
1. Implement task counting methods using `/tasks` with filters
|
||||
2. Implement agent counting using `/agents` endpoint
|
||||
3. Implement `_get_pending_questions()` using `/messages?type=dialogue`
|
||||
4. Implement `_check_task_progress()` using `/tasks/{id}`
|
||||
|
||||
---
|
||||
|
||||
### 2.6 Board Agents (1 hour)
|
||||
|
||||
**File**: `roboco/agents/board.py`
|
||||
|
||||
**Sub-tasks**:
|
||||
1. Implement `_review_feature()` - GET task, check acceptance criteria
|
||||
2. Implement `_read_channel_silently()` - GET `/channels/{slug}/messages`
|
||||
3. Implement `_perform_audit()` - Aggregate queries for patterns
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Real-Time (P2) - ~2 hours
|
||||
|
||||
### 3.1 WebSocket Channel Validation (1 hour)
|
||||
|
||||
**File**: `roboco/api/websocket.py`
|
||||
|
||||
```python
|
||||
from roboco.services.permissions import PermissionService
|
||||
|
||||
async def validate_channel_access(
|
||||
channel_id: UUID,
|
||||
agent_id: UUID,
|
||||
session: AsyncSession
|
||||
) -> bool:
|
||||
perm_service = PermissionService(session)
|
||||
return await perm_service.can_read_channel(agent_id, channel_id)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.2 Per-Agent Notification Delivery (1 hour)
|
||||
|
||||
**File**: `roboco/api/websocket.py`
|
||||
|
||||
Add agent-specific connections tracking:
|
||||
|
||||
```python
|
||||
class ConnectionManager:
|
||||
def __init__(self) -> None:
|
||||
# ... existing ...
|
||||
# Add per-agent notification connections
|
||||
self.notification_connections: dict[UUID, set[WebSocket]] = {}
|
||||
|
||||
async def connect_notifications(
|
||||
self, websocket: WebSocket, agent_id: UUID
|
||||
) -> None:
|
||||
await websocket.accept()
|
||||
if agent_id not in self.notification_connections:
|
||||
self.notification_connections[agent_id] = set()
|
||||
self.notification_connections[agent_id].add(websocket)
|
||||
|
||||
async def broadcast_notification(
|
||||
agent_ids: list[UUID],
|
||||
notification_id: UUID,
|
||||
notification_type: str,
|
||||
subject: str,
|
||||
priority: str,
|
||||
) -> None:
|
||||
event = {
|
||||
"type": "notification",
|
||||
"notification_id": str(notification_id),
|
||||
"notification_type": notification_type,
|
||||
"subject": subject,
|
||||
"priority": priority,
|
||||
"timestamp": datetime.now(UTC).isoformat(),
|
||||
}
|
||||
data = json.dumps(event)
|
||||
|
||||
for agent_id in agent_ids:
|
||||
connections = manager.notification_connections.get(agent_id, set())
|
||||
await asyncio.gather(
|
||||
*[conn.send_text(data) for conn in connections],
|
||||
return_exceptions=True,
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Intelligence (P2) - ~3 hours
|
||||
|
||||
### 4.1 LLM-based Message Extraction (1 hour)
|
||||
|
||||
**File**: `roboco/services/extraction.py`
|
||||
|
||||
```python
|
||||
async def extract_with_llm(
|
||||
self,
|
||||
content: str,
|
||||
agent_id: UUID,
|
||||
channel_id: UUID,
|
||||
session_id: UUID,
|
||||
group_id: UUID,
|
||||
task_id: UUID | None = None,
|
||||
) -> ExtractionResult:
|
||||
from anthropic import AsyncAnthropic
|
||||
from roboco.config import settings
|
||||
|
||||
client = AsyncAnthropic(api_key=settings.anthropic_api_key)
|
||||
|
||||
# Use LLM to classify message segments
|
||||
prompt = f"""Classify the following agent output into message types.
|
||||
For each distinct segment, identify:
|
||||
- type: reasoning, dialogue, decision, action, blocker, or technical
|
||||
- content: the segment text
|
||||
- confidence: 0.0 to 1.0
|
||||
|
||||
Agent output:
|
||||
{content}
|
||||
|
||||
Return as JSON array of objects with type, content, confidence."""
|
||||
|
||||
response = await client.messages.create(
|
||||
model="claude-3-haiku-20240307", # Fast, cheap for classification
|
||||
max_tokens=2000,
|
||||
messages=[{"role": "user", "content": prompt}],
|
||||
)
|
||||
|
||||
# Parse response and create ExtractedMessage objects
|
||||
# ... parsing logic ...
|
||||
|
||||
return ExtractionResult(...)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.2 OptimalService RAG Methods (2 hours)
|
||||
|
||||
**File**: `roboco/services/optimal.py`
|
||||
|
||||
Implement `search()`, `query()`, `index()` using piragi:
|
||||
|
||||
```python
|
||||
async def search(
|
||||
self,
|
||||
query: str,
|
||||
index_types: list[IndexType] | None = None,
|
||||
limit: int = 10,
|
||||
) -> list[SearchResult]:
|
||||
"""Search across knowledge base indexes."""
|
||||
types_to_search = index_types or list(IndexType)
|
||||
all_results: list[SearchResult] = []
|
||||
|
||||
for index_type in types_to_search:
|
||||
index = self._get_index(index_type)
|
||||
# Use piragi's search method
|
||||
results = await index.search(query, top_k=limit)
|
||||
for r in results:
|
||||
all_results.append(SearchResult(
|
||||
content=r.content,
|
||||
source=r.metadata.get("source", "unknown"),
|
||||
score=r.score,
|
||||
index_type=index_type,
|
||||
metadata=r.metadata,
|
||||
))
|
||||
|
||||
# Sort by score, return top limit
|
||||
all_results.sort(key=lambda x: x.score, reverse=True)
|
||||
return all_results[:limit]
|
||||
|
||||
async def query(
|
||||
self,
|
||||
query: str,
|
||||
context: QueryContext | None = None,
|
||||
) -> RAGResponse:
|
||||
"""RAG query with context."""
|
||||
# Get relevant context
|
||||
results = await self.search(
|
||||
query,
|
||||
index_types=context.index_types if context else None,
|
||||
limit=5,
|
||||
)
|
||||
|
||||
# Build context for LLM
|
||||
context_text = "\n\n".join([r.content for r in results])
|
||||
|
||||
# Query with RAG context
|
||||
prompt = f"""Based on the following context, answer the question.
|
||||
|
||||
Context:
|
||||
{context_text}
|
||||
|
||||
Question: {query}
|
||||
|
||||
Answer:"""
|
||||
|
||||
from anthropic import AsyncAnthropic
|
||||
client = AsyncAnthropic(api_key=settings.anthropic_api_key)
|
||||
response = await client.messages.create(
|
||||
model=settings.default_model,
|
||||
max_tokens=1000,
|
||||
messages=[{"role": "user", "content": prompt}],
|
||||
)
|
||||
|
||||
return RAGResponse(
|
||||
answer=response.content[0].text,
|
||||
citations=results,
|
||||
query=query,
|
||||
context_used=len(results),
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
After each phase, verify:
|
||||
|
||||
- [ ] `uv run ruff format .` passes
|
||||
- [ ] `uv run ruff check .` passes
|
||||
- [ ] `uv run mypy roboco/` passes
|
||||
- [ ] No TODO comments remain in modified files
|
||||
- [ ] API calls work against running server
|
||||
|
||||
---
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
Each phase is independent. If issues arise:
|
||||
1. Revert the specific file changes
|
||||
2. Leave TODO comments in place for that section
|
||||
3. Create a new sub-task for the problematic area
|
||||
|
||||
---
|
||||
|
||||
## Definition of Done
|
||||
|
||||
- [ ] All 37 TODOs resolved or converted to tracked issues
|
||||
- [ ] Health checks verify actual service connectivity
|
||||
- [ ] Agents can call LLM and receive responses
|
||||
- [ ] Agents can send messages via API
|
||||
- [ ] WebSocket notifications delivered to connected agents
|
||||
- [ ] RAG queries return relevant results
|
||||
- [ ] All type checks pass
|
||||
- [ ] All linting passes
|
||||
Reference in New Issue
Block a user