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:
@@ -5,6 +5,11 @@ This package contains all database models and their base classes.
|
||||
"""
|
||||
|
||||
from api.models.api_audit_log import ApiAuditLog
|
||||
from api.models.coord_workflow import CoordWorkflow
|
||||
from api.models.coord_work_item import CoordWorkItem
|
||||
from api.models.coord_session_lock import CoordSessionLock
|
||||
from api.models.coord_component_state import CoordComponentState
|
||||
from api.models.coord_message import CoordMessage
|
||||
from api.models.backup_log import BackupLog
|
||||
from api.models.base import Base, TimestampMixin, UUIDMixin
|
||||
from api.models.billable_time import BillableTime
|
||||
@@ -47,6 +52,11 @@ from api.models.work_item_tag import WorkItemTag
|
||||
|
||||
__all__ = [
|
||||
"ApiAuditLog",
|
||||
"CoordWorkflow",
|
||||
"CoordWorkItem",
|
||||
"CoordSessionLock",
|
||||
"CoordComponentState",
|
||||
"CoordMessage",
|
||||
"BackupLog",
|
||||
"Base",
|
||||
"BillableTime",
|
||||
|
||||
57
api/models/coord_component_state.py
Normal file
57
api/models/coord_component_state.py
Normal file
@@ -0,0 +1,57 @@
|
||||
"""Coordination component state model."""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import PrimaryKeyConstraint, String, Text
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from .base import Base, TimestampMixin
|
||||
|
||||
|
||||
class CoordComponentState(Base, TimestampMixin):
|
||||
"""Current state of a named component within a project."""
|
||||
|
||||
__tablename__ = "coord_component_states"
|
||||
|
||||
project_key: Mapped[str] = mapped_column(
|
||||
String(200),
|
||||
nullable=False,
|
||||
primary_key=True,
|
||||
doc="Project namespace"
|
||||
)
|
||||
|
||||
component: Mapped[str] = mapped_column(
|
||||
String(200),
|
||||
nullable=False,
|
||||
primary_key=True,
|
||||
doc="Component name, e.g. 'server', 'agent', 'dashboard', 'database'"
|
||||
)
|
||||
|
||||
state: Mapped[str] = mapped_column(
|
||||
String(50),
|
||||
nullable=False,
|
||||
doc="State: deployed, building, stable, broken, unknown"
|
||||
)
|
||||
|
||||
version: Mapped[Optional[str]] = mapped_column(
|
||||
String(100),
|
||||
doc="Version string or git SHA"
|
||||
)
|
||||
|
||||
notes: Mapped[Optional[str]] = mapped_column(
|
||||
Text,
|
||||
doc="Freeform notes about current state"
|
||||
)
|
||||
|
||||
updated_by: Mapped[str] = mapped_column(
|
||||
String(200),
|
||||
nullable=False,
|
||||
doc="Session that last updated this record"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint("project_key", "component", name="pk_coord_component_states"),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<CoordComponentState(project_key='{self.project_key}', component='{self.component}', state='{self.state}')>"
|
||||
56
api/models/coord_message.py
Normal file
56
api/models/coord_message.py
Normal file
@@ -0,0 +1,56 @@
|
||||
"""Coordination inter-session message model."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import DateTime, Index, String, Text
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from .base import Base, TimestampMixin, UUIDMixin
|
||||
|
||||
|
||||
class CoordMessage(Base, UUIDMixin, TimestampMixin):
|
||||
"""A message sent from one session to another (or broadcast)."""
|
||||
|
||||
__tablename__ = "coord_messages"
|
||||
|
||||
from_session: Mapped[str] = mapped_column(
|
||||
String(200),
|
||||
nullable=False,
|
||||
doc="Sending session, e.g. 'DESKTOP-0O8A1RL/Claude'"
|
||||
)
|
||||
|
||||
to_session: Mapped[Optional[str]] = mapped_column(
|
||||
String(200),
|
||||
doc="Recipient session; NULL means broadcast to all"
|
||||
)
|
||||
|
||||
project_key: Mapped[Optional[str]] = mapped_column(
|
||||
String(200),
|
||||
doc="Optional project context for the message"
|
||||
)
|
||||
|
||||
subject: Mapped[str] = mapped_column(
|
||||
String(500),
|
||||
nullable=False,
|
||||
doc="Message subject line"
|
||||
)
|
||||
|
||||
body: Mapped[str] = mapped_column(
|
||||
Text,
|
||||
nullable=False,
|
||||
doc="Message body, markdown ok"
|
||||
)
|
||||
|
||||
read_at: Mapped[Optional[datetime]] = mapped_column(
|
||||
DateTime,
|
||||
doc="NULL means unread"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("idx_coord_messages_to_read", "to_session", "read_at"),
|
||||
Index("idx_coord_messages_from", "from_session"),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<CoordMessage(from='{self.from_session}', to='{self.to_session}', subject='{self.subject}')>"
|
||||
64
api/models/coord_session_lock.py
Normal file
64
api/models/coord_session_lock.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""Coordination session lock model."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import DateTime, Index, String, Text
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from .base import Base, TimestampMixin, UUIDMixin
|
||||
|
||||
|
||||
class CoordSessionLock(Base, UUIDMixin, TimestampMixin):
|
||||
"""An exclusive lock held by a session on a resource path."""
|
||||
|
||||
__tablename__ = "coord_session_locks"
|
||||
|
||||
project_key: Mapped[str] = mapped_column(
|
||||
String(200),
|
||||
nullable=False,
|
||||
doc="Project namespace this lock applies to"
|
||||
)
|
||||
|
||||
session_id: Mapped[str] = mapped_column(
|
||||
String(200),
|
||||
nullable=False,
|
||||
doc="Session holding the lock, e.g. 'DESKTOP-0O8A1RL/Claude'"
|
||||
)
|
||||
|
||||
resource: Mapped[str] = mapped_column(
|
||||
String(500),
|
||||
nullable=False,
|
||||
doc="Resource path being locked, e.g. 'server/src/', 'migrations/'"
|
||||
)
|
||||
|
||||
description: Mapped[Optional[str]] = mapped_column(
|
||||
Text,
|
||||
doc="Why this lock was acquired"
|
||||
)
|
||||
|
||||
acquired_at: Mapped[datetime] = mapped_column(
|
||||
DateTime,
|
||||
nullable=False,
|
||||
server_default=func.now(),
|
||||
doc="When the lock was claimed"
|
||||
)
|
||||
|
||||
expires_at: Mapped[Optional[datetime]] = mapped_column(
|
||||
DateTime,
|
||||
doc="NULL means no expiry; otherwise the lock expires at this time"
|
||||
)
|
||||
|
||||
released_at: Mapped[Optional[datetime]] = mapped_column(
|
||||
DateTime,
|
||||
doc="NULL means still held; set when lock is explicitly released"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("idx_coord_locks_project_resource", "project_key", "resource"),
|
||||
Index("idx_coord_locks_session", "session_id"),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<CoordSessionLock(project_key='{self.project_key}', resource='{self.resource}', session_id='{self.session_id}')>"
|
||||
87
api/models/coord_work_item.py
Normal file
87
api/models/coord_work_item.py
Normal file
@@ -0,0 +1,87 @@
|
||||
"""Coordination work item model."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import CHAR, CheckConstraint, ForeignKey, Index, Integer, String, Text, DateTime
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from .base import Base, TimestampMixin, UUIDMixin
|
||||
|
||||
|
||||
class CoordWorkItem(Base, UUIDMixin, TimestampMixin):
|
||||
"""A discrete task within a coordination workflow."""
|
||||
|
||||
__tablename__ = "coord_work_items"
|
||||
|
||||
workflow_id: Mapped[str] = mapped_column(
|
||||
CHAR(36),
|
||||
ForeignKey("coord_workflows.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
doc="Parent workflow"
|
||||
)
|
||||
|
||||
project_key: Mapped[str] = mapped_column(
|
||||
String(200),
|
||||
nullable=False,
|
||||
doc="Denormalized project key for filtering without join"
|
||||
)
|
||||
|
||||
title: Mapped[str] = mapped_column(
|
||||
String(500),
|
||||
nullable=False,
|
||||
doc="Short title"
|
||||
)
|
||||
|
||||
description: Mapped[Optional[str]] = mapped_column(
|
||||
Text,
|
||||
doc="Full description, markdown ok — store design specs, schemas, etc."
|
||||
)
|
||||
|
||||
status: Mapped[str] = mapped_column(
|
||||
String(20),
|
||||
nullable=False,
|
||||
default="pending",
|
||||
doc="Status: pending, in_progress, blocked, completed, cancelled"
|
||||
)
|
||||
|
||||
priority: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
nullable=False,
|
||||
default=0,
|
||||
doc="Higher value = more urgent"
|
||||
)
|
||||
|
||||
assigned_session: Mapped[Optional[str]] = mapped_column(
|
||||
String(200),
|
||||
doc="Session currently working this item"
|
||||
)
|
||||
|
||||
depends_on_id: Mapped[Optional[str]] = mapped_column(
|
||||
CHAR(36),
|
||||
ForeignKey("coord_work_items.id", ondelete="SET NULL"),
|
||||
doc="Blocking predecessor item"
|
||||
)
|
||||
|
||||
started_at: Mapped[Optional[datetime]] = mapped_column(
|
||||
DateTime,
|
||||
doc="When work began"
|
||||
)
|
||||
|
||||
completed_at: Mapped[Optional[datetime]] = mapped_column(
|
||||
DateTime,
|
||||
doc="When item reached a terminal state"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
CheckConstraint(
|
||||
"status IN ('pending', 'in_progress', 'blocked', 'completed', 'cancelled')",
|
||||
name="ck_coord_work_items_status"
|
||||
),
|
||||
Index("idx_coord_work_items_workflow", "workflow_id"),
|
||||
Index("idx_coord_work_items_project_status", "project_key", "status"),
|
||||
Index("idx_coord_work_items_assigned", "assigned_session"),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<CoordWorkItem(title='{self.title}', status='{self.status}')>"
|
||||
61
api/models/coord_workflow.py
Normal file
61
api/models/coord_workflow.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""Coordination workflow model."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import CHAR, CheckConstraint, Index, String, Text, DateTime
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from .base import Base, TimestampMixin, UUIDMixin
|
||||
|
||||
|
||||
class CoordWorkflow(Base, UUIDMixin, TimestampMixin):
|
||||
"""A named unit of work spanning one or more sessions."""
|
||||
|
||||
__tablename__ = "coord_workflows"
|
||||
|
||||
project_key: Mapped[str] = mapped_column(
|
||||
String(200),
|
||||
nullable=False,
|
||||
doc="Project namespace slug, e.g. 'gururmm', 'client/acme-corp'"
|
||||
)
|
||||
|
||||
name: Mapped[str] = mapped_column(
|
||||
String(200),
|
||||
nullable=False,
|
||||
doc="Short identifier, e.g. 'discovery-feature'"
|
||||
)
|
||||
|
||||
description: Mapped[Optional[str]] = mapped_column(
|
||||
Text,
|
||||
doc="Freeform description, markdown ok"
|
||||
)
|
||||
|
||||
status: Mapped[str] = mapped_column(
|
||||
String(20),
|
||||
nullable=False,
|
||||
default="planning",
|
||||
doc="Status: planning, active, blocked, completed, cancelled"
|
||||
)
|
||||
|
||||
created_by: Mapped[str] = mapped_column(
|
||||
String(200),
|
||||
nullable=False,
|
||||
doc="Session that created this workflow, e.g. 'DESKTOP-0O8A1RL/Claude'"
|
||||
)
|
||||
|
||||
completed_at: Mapped[Optional[datetime]] = mapped_column(
|
||||
DateTime,
|
||||
doc="When the workflow reached a terminal state"
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
CheckConstraint(
|
||||
"status IN ('planning', 'active', 'blocked', 'completed', 'cancelled')",
|
||||
name="ck_coord_workflows_status"
|
||||
),
|
||||
Index("idx_coord_workflows_project_status", "project_key", "status"),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<CoordWorkflow(project_key='{self.project_key}', name='{self.name}', status='{self.status}')>"
|
||||
Reference in New Issue
Block a user