generated from coulomb/repo-seed
feat(capability-requests): add cross-domain capability catalog and request routing
Introduces a capability catalog (CUST-WP-0022) so domains can advertise what they provide and agents can request capabilities from other domains with auto-routing, lifecycle tracking, and task-unblocking on completion. - New models: CapabilityCatalog, CapabilityRequest with full lifecycle (requested → accepted → in_progress → ready_for_review → completed/rejected/withdrawn) - Migration i6d7e8f9a0b1: capability_catalog + capability_requests tables - Router /capability-catalog and /capability-requests with accept/status endpoints - 7 new MCP tools: register_capability, list_capabilities, request_capability, accept_capability_request, update_capability_request_status, list_capability_requests, get_capability_request - StateSummary gains open_capability_requests count - Dashboard: capability-requests.md page + docs/capabilities.md + docs/scope.md - SCOPE.md: three seed capabilities documented (MCP registration, state tracking, SBOM) - scope.template: Provided Capabilities section with example block - scripts/ingest_capabilities.py + make ingest-capabilities[/-all] targets Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1729,6 +1729,173 @@ def reply_to_message(message_id: str, from_agent: str, body: str) -> str:
|
||||
return json.dumps(_post(f"/messages/{message_id}/reply", {"from_agent": from_agent, "body": body}), indent=2)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Capability Catalog & Requests
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@mcp.tool()
|
||||
def register_capability(
|
||||
domain: str,
|
||||
capability_type: str,
|
||||
title: str,
|
||||
description: str | None = None,
|
||||
keywords: list[str] | None = None,
|
||||
) -> str:
|
||||
"""Register a capability that a domain can provide. Used for routing requests.
|
||||
|
||||
Args:
|
||||
domain: Domain slug (e.g. 'railiance', 'markitect')
|
||||
capability_type: Category (e.g. 'infrastructure', 'api', 'data', 'security', 'documentation')
|
||||
title: Short title for this capability
|
||||
description: Longer description (optional)
|
||||
keywords: List of keywords for routing (e.g. ['cluster', 'k8s', 'privacy'])
|
||||
"""
|
||||
entry = _post("/capability-catalog", {
|
||||
"domain": domain,
|
||||
"capability_type": capability_type,
|
||||
"title": title,
|
||||
"description": description,
|
||||
"keywords": keywords or [],
|
||||
})
|
||||
return json.dumps(entry, indent=2)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def list_capabilities(
|
||||
domain: str | None = None,
|
||||
capability_type: str | None = None,
|
||||
) -> str:
|
||||
"""Browse the capability catalog — what domains can provide.
|
||||
|
||||
Args:
|
||||
domain: Filter by domain slug (optional)
|
||||
capability_type: Filter by type (optional)
|
||||
"""
|
||||
return json.dumps(_get("/capability-catalog", {
|
||||
"domain": domain,
|
||||
"capability_type": capability_type,
|
||||
}), indent=2)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def request_capability(
|
||||
title: str,
|
||||
description: str,
|
||||
capability_type: str,
|
||||
requesting_agent: str,
|
||||
requesting_domain: str,
|
||||
requesting_workstream_id: str | None = None,
|
||||
priority: str = "medium",
|
||||
blocking_task_id: str | None = None,
|
||||
) -> str:
|
||||
"""Request a capability from another domain. Auto-routes to the responsible
|
||||
domain via the capability catalog. If no unique match, broadcasts to all.
|
||||
|
||||
Args:
|
||||
title: Short title (e.g. 'Privacy idea instance on cluster')
|
||||
description: Detailed description of what you need
|
||||
capability_type: Category (e.g. 'infrastructure', 'api', 'data', 'security')
|
||||
requesting_agent: Your agent identifier (e.g. 'net-kingdom-worker')
|
||||
requesting_domain: Your domain slug (e.g. 'custodian')
|
||||
requesting_workstream_id: UUID of your workstream (optional)
|
||||
priority: low | medium | high | critical (default: medium)
|
||||
blocking_task_id: UUID of the task blocked until this is fulfilled (optional)
|
||||
"""
|
||||
req = _post("/capability-requests", {
|
||||
"title": title,
|
||||
"description": description,
|
||||
"capability_type": capability_type,
|
||||
"requesting_agent": requesting_agent,
|
||||
"requesting_domain": requesting_domain,
|
||||
"requesting_workstream_id": requesting_workstream_id,
|
||||
"priority": priority,
|
||||
"blocking_task_id": blocking_task_id,
|
||||
})
|
||||
_post("/progress", {
|
||||
"event_type": "capability_requested",
|
||||
"summary": f"Capability requested: {title} ({capability_type})",
|
||||
"author": requesting_agent,
|
||||
"detail": {
|
||||
"capability_request_id": req.get("id"),
|
||||
"capability_type": capability_type,
|
||||
"routed_to": req.get("fulfilling_domain_slug"),
|
||||
},
|
||||
})
|
||||
return json.dumps(req, indent=2)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def accept_capability_request(
|
||||
request_id: str,
|
||||
fulfilling_agent: str,
|
||||
fulfilling_workstream_id: str | None = None,
|
||||
) -> str:
|
||||
"""Accept a capability request. Assigns yourself as the fulfilling agent.
|
||||
|
||||
Args:
|
||||
request_id: UUID of the capability request
|
||||
fulfilling_agent: Your agent identifier (e.g. 'railiance-worker')
|
||||
fulfilling_workstream_id: UUID of your workstream for this work (optional)
|
||||
"""
|
||||
result = _post(f"/capability-requests/{request_id}/accept", {
|
||||
"fulfilling_agent": fulfilling_agent,
|
||||
"fulfilling_workstream_id": fulfilling_workstream_id,
|
||||
})
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def update_capability_request_status(
|
||||
request_id: str,
|
||||
status: str,
|
||||
note: str | None = None,
|
||||
) -> str:
|
||||
"""Advance a capability request through its lifecycle.
|
||||
|
||||
On 'completed': auto-unblocks the blocking task if one was set.
|
||||
|
||||
Args:
|
||||
request_id: UUID of the capability request
|
||||
status: in_progress | ready_for_review | completed | rejected | withdrawn
|
||||
note: Optional note (required for rejection, recommended for completion)
|
||||
"""
|
||||
result = _patch(f"/capability-requests/{request_id}/status", {
|
||||
"status": status,
|
||||
"note": note,
|
||||
})
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def list_capability_requests(
|
||||
domain: str | None = None,
|
||||
status: str | None = None,
|
||||
capability_type: str | None = None,
|
||||
) -> str:
|
||||
"""List capability requests with optional filters.
|
||||
|
||||
Args:
|
||||
domain: Filter by requesting OR fulfilling domain slug
|
||||
status: Filter by status (requested/accepted/in_progress/ready_for_review/completed/rejected/withdrawn)
|
||||
capability_type: Filter by capability type
|
||||
"""
|
||||
return json.dumps(_get("/capability-requests", {
|
||||
"domain": domain,
|
||||
"status": status,
|
||||
"capability_type": capability_type,
|
||||
}), indent=2)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_capability_request(request_id: str) -> str:
|
||||
"""Get a single capability request by ID.
|
||||
|
||||
Args:
|
||||
request_id: UUID of the capability request
|
||||
"""
|
||||
return json.dumps(_get(f"/capability-requests/{request_id}"), indent=2)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Entry point
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user