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>
147 lines
4.5 KiB
Markdown
147 lines
4.5 KiB
Markdown
---
|
|
id: CUST-WP-0022
|
|
type: workplan
|
|
title: Capability Request System
|
|
domain: custodian
|
|
status: done
|
|
owner: custodian
|
|
topic_slug: the-custodian
|
|
created: "2026-03-19"
|
|
updated: "2026-03-19"
|
|
state_hub_workstream_id: "7cc173c6-f63e-43b0-af8e-04df7f95b96c"
|
|
---
|
|
|
|
# CUST-WP-0022 — Capability Request System
|
|
|
|
## Purpose
|
|
|
|
Enable cross-domain capability requests without requiring the requester to know
|
|
which domain is responsible. A net-kingdom dev-worker can say "I need a privacy
|
|
idea instance on the cluster" and the system routes it to railiance, manages the
|
|
fulfillment lifecycle, and auto-notifies when work is ready for review.
|
|
|
|
## Scope
|
|
|
|
- `capability_catalog` table — domains register capabilities they can provide
|
|
- `capability_requests` table — lifecycle from requested → completed with auto-routing
|
|
- Soft routing via keyword matching; broadcast fallback when ambiguous
|
|
- Auto-notifications via AgentMessage on every lifecycle transition
|
|
- Auto-unblock of blocking tasks on completion
|
|
- 7 MCP tools, 1 dashboard page, StateSummary integration
|
|
|
|
## Files Changed
|
|
|
|
| File | Change |
|
|
|------|--------|
|
|
| `migrations/versions/i6d7e8f9a0b1_capability_requests.py` | New migration |
|
|
| `api/models/capability_catalog.py` | New model |
|
|
| `api/models/capability_request.py` | New model |
|
|
| `api/models/__init__.py` | Export new models |
|
|
| `api/schemas/capability_request.py` | New schemas (Catalog + Request CRUD) |
|
|
| `api/routers/capability_requests.py` | New router (lifecycle guard, routing, auto-notify) |
|
|
| `api/main.py` | Mount new router |
|
|
| `api/schemas/state.py` | Added `open_capability_requests` to StateSummary |
|
|
| `api/routers/state.py` | Count open requests in summary builder |
|
|
| `mcp_server/server.py` | 7 new MCP tools |
|
|
| `dashboard/src/capability-requests.md` | New dashboard page (Kanban + KPIs) |
|
|
| `dashboard/observablehq.config.js` | Nav entry |
|
|
| `tests/test_capability_requests.py` | 14 test cases |
|
|
| `scripts/ingest_capabilities.py` | Ingest `capability` blocks from SCOPE.md → catalog API |
|
|
| `scripts/project_rules/scope.template` | Added `## Provided Capabilities` section |
|
|
| `SCOPE.md` (repo root) | Added 3 capability blocks for custodian domain |
|
|
| `dashboard/src/docs/capabilities.md` | Reference doc for capabilities |
|
|
| `dashboard/src/docs/scope.md` | Reference doc for SCOPE.md |
|
|
|
|
## Tasks
|
|
|
|
### T01: Write TDD test suite
|
|
```task
|
|
id: CUST-WP-0022-T01
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "34f40ede-3396-4ef2-ae5a-9af4edbfc17d"
|
|
```
|
|
|
|
### T02: Alembic migration
|
|
```task
|
|
id: CUST-WP-0022-T02
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "094fd9de-e4fa-4b9f-bc70-31f049907fa9"
|
|
```
|
|
|
|
### T03: SQLAlchemy models
|
|
```task
|
|
id: CUST-WP-0022-T03
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "5fadabfb-100e-493e-a950-2b5c3d286c6b"
|
|
```
|
|
|
|
### T04: Pydantic schemas
|
|
```task
|
|
id: CUST-WP-0022-T04
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "1efabfde-e9de-41d9-a864-2eb1e9e2dae6"
|
|
```
|
|
|
|
### T05: Router with lifecycle + routing + auto-notify
|
|
```task
|
|
id: CUST-WP-0022-T05
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "57f754b6-022c-4829-adb5-45629567d575"
|
|
```
|
|
|
|
### T06: Run tests, iterate until green
|
|
```task
|
|
id: CUST-WP-0022-T06
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "41ef3722-ffbe-41bb-9ef4-8485db37bb0a"
|
|
```
|
|
|
|
### T07: MCP tools
|
|
```task
|
|
id: CUST-WP-0022-T07
|
|
status: done
|
|
priority: medium
|
|
state_hub_task_id: "12f28272-2767-4d8e-80a0-a28bb172d6af"
|
|
```
|
|
|
|
### T08: Dashboard page
|
|
```task
|
|
id: CUST-WP-0022-T08
|
|
status: done
|
|
priority: medium
|
|
state_hub_task_id: "d6596ac4-53ac-4b44-bdd2-635b5c109372"
|
|
```
|
|
|
|
### T09: StateSummary integration
|
|
```task
|
|
id: CUST-WP-0022-T09
|
|
status: done
|
|
priority: medium
|
|
state_hub_task_id: "964e7f8b-e9f6-41f9-9eb1-ba35e48adf55"
|
|
```
|
|
|
|
### T10: Seed catalog entries (file-first via SCOPE.md)
|
|
```task
|
|
id: CUST-WP-0022-T10
|
|
status: done
|
|
priority: low
|
|
state_hub_task_id: "2c747cde-bc00-4fd9-a3cd-625709ac6d6d"
|
|
```
|
|
|
|
## Design Notes
|
|
|
|
- **Routing algorithm**: Exact match on capability_type in catalog. If 1 result → auto-assign.
|
|
If multiple → keyword-score description against entry keywords, pick unambiguous winner.
|
|
If 0 or tied → fulfilling_domain_id=NULL, broadcast AgentMessage.
|
|
- **Lifecycle transitions** guarded by `_VALID_TRANSITIONS` dict (same pattern as contributions).
|
|
- **Auto-unblock**: On `completed`, if `blocking_task_id` set and task status is `blocked`,
|
|
patches it to `todo` and clears `blocking_reason`.
|
|
- **Status uses String(20)** not SA Enum — avoids ALTER TYPE pain in future migrations.
|
|
- **Third sanctioned write use case** — alongside resolve_decision and get_next_steps.
|