13 KiB
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:
- Add database health check function
- Add Redis health check function
- Update
readiness_check()endpoint to call both - Handle failures gracefully (return status per service)
Changes:
# 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:
- Add Anthropic client initialization
- Implement
think()method with actual LLM call - Implement
think_and_stream()with streaming response - Implement
send_message()with HTTP API call - Implement
stream_reasoning()with WebSocket broadcast
Changes:
# 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)
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:
- Implement
_find_paused_task()- GET/tasks?status=paused&assigned_to={id} - Implement
_find_assigned_task()- GET/tasks?status=pending&assigned_to={id} - Implement
_get_task_title()- GET/tasks/{id} - Implement
_read_task_requirements()- GET/tasks/{id}(description + criteria) - Implement
_update_task_status()- PUT/tasks/{id} - Implement
_check_qa_approved()- GET/tasks/{id}check status - Implement
_check_docs_complete()- GET/tasks/{id}/handoffs
Pattern:
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:
- Implement
_find_awaiting_qa()- GET/tasks?status=awaiting_qa&team={team} - Implement
_get_task_title()- same as developer - Implement
_read_task_requirements()- same as developer - Implement
_read_dev_notes()- GET/tasks/{id}(dev_notes field) - Implement
_get_task_commits()- GET/tasks/{id}(commits field) - Implement
_update_task_status()- same as developer
2.4 Documenter Agent (1 hour)
File: roboco/agents/documenter.py
Sub-tasks:
- Implement all query methods (same pattern as QA)
- Implement
_phase_publish()with file writing:
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:
- Implement task counting methods using
/taskswith filters - Implement agent counting using
/agentsendpoint - Implement
_get_pending_questions()using/messages?type=dialogue - Implement
_check_task_progress()using/tasks/{id}
2.6 Board Agents (1 hour)
File: roboco/agents/board.py
Sub-tasks:
- Implement
_review_feature()- GET task, check acceptance criteria - Implement
_read_channel_silently()- GET/channels/{slug}/messages - 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
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:
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
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:
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 .passesuv run ruff check .passesuv 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:
- Revert the specific file changes
- Leave TODO comments in place for that section
- 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