feat: agent coordination system (workflows, locks, components, messages)
Adds /api/coord/* endpoints for real-time cross-session coordination: - coord_workflows: named units of work per project - coord_work_items: tasks within workflows with dependency chains - coord_session_locks: exclusive resource locks with auto-expiry (TTL) - coord_component_states: live component state per project (upsert) - coord_messages: cross-session messaging and broadcasts - /api/coord/status: cross-project snapshot endpoint Replaces PROJECT_STATE.md as the coordination layer for Claude sessions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
100
api/services/coord_work_item_service.py
Normal file
100
api/services/coord_work_item_service.py
Normal file
@@ -0,0 +1,100 @@
|
||||
"""Service layer for CoordWorkItem."""
|
||||
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from api.models.coord_work_item import CoordWorkItem
|
||||
from api.models.coord_workflow import CoordWorkflow
|
||||
from api.schemas.coord_work_item import CoordWorkItemCreate, CoordWorkItemUpdate
|
||||
|
||||
|
||||
def get_work_items(
|
||||
db: Session,
|
||||
workflow_id: Optional[str] = None,
|
||||
project_key: Optional[str] = None,
|
||||
status_filter: Optional[str] = None,
|
||||
assigned_session: Optional[str] = None,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
) -> tuple[list[CoordWorkItem], int]:
|
||||
"""Return paginated work items with optional filters."""
|
||||
q = db.query(CoordWorkItem)
|
||||
if workflow_id:
|
||||
q = q.filter(CoordWorkItem.workflow_id == workflow_id)
|
||||
if project_key:
|
||||
q = q.filter(CoordWorkItem.project_key == project_key)
|
||||
if status_filter:
|
||||
q = q.filter(CoordWorkItem.status == status_filter)
|
||||
if assigned_session:
|
||||
q = q.filter(CoordWorkItem.assigned_session == assigned_session)
|
||||
total = q.count()
|
||||
items = q.order_by(CoordWorkItem.priority.desc(), CoordWorkItem.created_at.asc()).offset(skip).limit(limit).all()
|
||||
return items, total
|
||||
|
||||
|
||||
def get_work_item_by_id(db: Session, item_id: UUID) -> CoordWorkItem:
|
||||
"""Return a single work item or raise 404."""
|
||||
item = db.query(CoordWorkItem).filter(CoordWorkItem.id == str(item_id)).first()
|
||||
if not item:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Work item {item_id} not found"
|
||||
)
|
||||
return item
|
||||
|
||||
|
||||
def create_work_item(db: Session, data: CoordWorkItemCreate) -> CoordWorkItem:
|
||||
"""Create and persist a new work item, validating the parent workflow exists."""
|
||||
workflow = db.query(CoordWorkflow).filter(CoordWorkflow.id == data.workflow_id).first()
|
||||
if not workflow:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Workflow {data.workflow_id} not found"
|
||||
)
|
||||
try:
|
||||
item = CoordWorkItem(**data.model_dump())
|
||||
db.add(item)
|
||||
db.commit()
|
||||
db.refresh(item)
|
||||
return item
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to create work item: {e}"
|
||||
)
|
||||
|
||||
|
||||
def update_work_item(db: Session, item_id: UUID, data: CoordWorkItemUpdate) -> CoordWorkItem:
|
||||
"""Apply partial update to a work item."""
|
||||
item = get_work_item_by_id(db, item_id)
|
||||
try:
|
||||
for field, value in data.model_dump(exclude_unset=True).items():
|
||||
setattr(item, field, value)
|
||||
db.commit()
|
||||
db.refresh(item)
|
||||
return item
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to update work item: {e}"
|
||||
)
|
||||
|
||||
|
||||
def delete_work_item(db: Session, item_id: UUID) -> dict:
|
||||
"""Delete a work item by ID."""
|
||||
item = get_work_item_by_id(db, item_id)
|
||||
try:
|
||||
db.delete(item)
|
||||
db.commit()
|
||||
return {"message": "Work item deleted", "item_id": str(item_id)}
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to delete work item: {e}"
|
||||
)
|
||||
Reference in New Issue
Block a user