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>
2026-03-19 21:07:50 +01:00
---
title: Capability Requests
---
```js
import {API} from "./components/config.js";
const POLL = 30_000;
```
```js
// Live poll for capability requests
const reqState = (async function*() {
while (true) {
let data = [], ok = false;
try {
const r = await fetch(`${API}/capability-requests/` );
ok = r.ok;
data = ok ? await r.json() : [];
} catch {}
yield {data, ok, ts: new Date()};
await new Promise(res => setTimeout(res, POLL));
}
})();
```
```js
const requests = reqState.data ?? [];
const _ok = reqState.ok ?? false;
const _ts = reqState.ts;
```
# Capability Requests
```js
import {injectTocTop} from "./components/toc-sidebar.js";
import {withDocHelp} from "./components/doc-overlay.js";
const _liveEl = html`< div class = "live-indicator" >
< span style = "color:${_ok ? 'var(--theme-foreground-focus)' : 'red'}" > ●< / span >
${_ok ? `Live · ${_ts?.toLocaleTimeString()}` : html`<span style="color:red">API offline</span>` }
< / div > `;
withDocHelp(_liveEl, "/docs/live-data");
injectTocTop("live-indicator", _liveEl);
const _h1 = document.querySelector("#observablehq -main h1");
if (_h1) { _h1.style.position = "relative"; withDocHelp(_h1, "/docs/capabilities"); }
```
```js
// KPI sidebar
2026-03-21 23:58:52 +01:00
const open = requests.filter(r => ["requested","routing_disputed","accepted","in_progress","ready_for_review"].includes(r.status));
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>
2026-03-19 21:07:50 +01:00
const completed = requests.filter(r => r.status === "completed");
const avgFulfill = completed.length > 0
? (completed.reduce((s, r) => s + (new Date(r.completed_at) - new Date(r.created_at)), 0) / completed.length / 86400000).toFixed(1)
: "—";
const critical = open.filter(r => r.priority === "critical" || r.priority === "high").length;
const kpiEl = html`< div class = "kpi-infobox" >
< div class = "kpi-infobox-title" > Capability Requests< / div >
< div style = "display:grid;grid-template-columns:1fr 1fr;gap:0.4rem 0.8rem;font-size:0.82rem" >
< span > Open< / span > < strong > ${open.length}< / strong >
< span > Avg fulfill< / span > < strong > ${avgFulfill}d< / strong >
< span > High/Critical< / span > < strong style = "color:${critical > 0 ? 'orange' : 'inherit'}" > ${critical}< / strong >
< span > Total< / span > < strong > ${requests.length}< / strong >
< / div >
< / div > `;
injectTocTop("cap-req-kpi", kpiEl);
```
```js
// Filters
const typeFilter = Inputs.select(
["all", ...new Set(requests.map(r => r.capability_type))],
{label: "Type", value: "all"}
);
const statFilter = Inputs.select(
2026-03-21 23:58:52 +01:00
["all", "routing_disputed", "requested", "accepted", "in_progress", "ready_for_review", "completed", "rejected", "withdrawn"],
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>
2026-03-19 21:07:50 +01:00
{label: "Status", value: "all"}
);
const domFilter = Inputs.select(
["all", ...new Set([...requests.map(r => r.requesting_domain_slug), ...requests.map(r => r.fulfilling_domain_slug).filter(Boolean)])],
{label: "Domain", value: "all"}
);
display(html`< div style = "display:flex;gap:1rem;flex-wrap:wrap;margin-bottom:1rem" >
${typeFilter}${statFilter}${domFilter}
< / div > `);
```
```js
const tf = typeFilter.value;
const sf = statFilter.value;
const df = domFilter.value;
const filtered = requests.filter(r =>
(tf === "all" || r.capability_type === tf) & &
(sf === "all" || r.status === sf) & &
(df === "all" || r.requesting_domain_slug === df || r.fulfilling_domain_slug === df)
);
```
## Summary
```js
const priorityColors = {critical: "#e53935 ", high: "orange", medium: "steelblue", low: "#aaa "};
2026-03-21 23:58:52 +01:00
const disputed = requests.filter(r => r.status === "routing_disputed");
// Disputed banner — shown at top when any exist
if (disputed.length > 0) {
display(html`< div class = "disputed-banner" >
< div class = "disputed-banner-title" > ⚠ Routing Disputed (${disputed.length})< / div >
${disputed.map(r => html`
< div class = "disputed-card" >
< div class = "disputed-card-header" >
< span class = "cap-title" > ${r.title}< / span >
< span class = "cap-domains" > ${r.requesting_domain_slug} → < strong > ${r.fulfilling_domain_slug ?? "unassigned"}< / strong > < / span >
< / div >
< div class = "disputed-reason" > < strong > Dispute:< / strong > ${r.dispute_reason ?? "(no reason given)"}< / div >
${r.dispute_suggested_domain ? html`<div class="disputed-suggestion">Suggested domain: <strong>${r.dispute_suggested_domain}</strong></div>` : ""}
${r.disputed_by ? html`<div class="disputed-meta">Raised by <em>${r.disputed_by}</em> · ${new Date(r.disputed_at).toLocaleString()}</div>` : ""}
< / div >
`)}
< / div > `);
}
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>
2026-03-19 21:07:50 +01:00
display(html`< div class = "grid grid-cols-4" style = "gap:1rem;margin-bottom:1.5rem" >
< div class = "card" > < h3 > Requested< / h3 > < p class = "big-num" > ${requests.filter(r => r.status === "requested").length}< / p > < / div >
< div class = "card" > < h3 > In Progress< / h3 > < p class = "big-num" > ${requests.filter(r => ["accepted","in_progress"].includes(r.status)).length}< / p > < / div >
< div class = "card" > < h3 > Ready for Review< / h3 > < p class = "big-num" > ${requests.filter(r => r.status === "ready_for_review").length}< / p > < / div >
< div class = "card" > < h3 > Completed< / h3 > < p class = "big-num" > ${completed.length}< / p > < / div >
< / div > `);
```
## Status Kanban
```js
const statusCols = [
2026-03-21 23:58:52 +01:00
{key: "routing_disputed", label: "⚠ Routing Disputed", color: "#f59e0b "},
{key: "requested", label: "Requested", color: "steelblue"},
{key: "accepted", label: "Accepted", color: "#f0a500 "},
{key: "in_progress", label: "In Progress", color: "#2196f3 "},
{key: "ready_for_review", label: "Ready for Review", color: "#4caf50 "},
{key: "completed", label: "Completed", color: "#2e7d32 "},
{key: "rejected", label: "Rejected", color: "#e53935 "},
{key: "withdrawn", label: "Withdrawn", color: "#bbb "},
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>
2026-03-19 21:07:50 +01:00
];
const colMap = {};
for (const r of filtered) {
(colMap[r.status] = colMap[r.status] ?? []).push(r);
}
const activeCols = statusCols.filter(s => colMap[s.key]?.length);
if (activeCols.length === 0) {
display(html`<p style="color:gray">No capability requests match the current filters.</p>` );
} else {
const ageDays = (r) => ((Date.now() - new Date(r.created_at)) / 86400000).toFixed(0);
display(html`< div class = "kanban" >
${activeCols.map(s => html`
< div class = "kanban-col" >
< div class = "kanban-header" style = "border-bottom:2px solid ${s.color}" > ${s.label} < span class = "kanban-count" > ${colMap[s.key].length}< / span > < / div >
${colMap[s.key].map(r => html`
< div class = "cap-card" >
< div class = "cap-type-badge" style = "background:${priorityColors[r.priority] ?? ' #aaa '}20;color:${priorityColors[r.priority] ?? ' #aaa '}" > ${r.capability_type}</ div >
< div class = "cap-priority-badge" style = "color:${priorityColors[r.priority] ?? ' #888 '}" > ${r.priority}</ div >
< div class = "cap-title" > ${r.title}< / div >
< div class = "cap-domains" >
< span > ${r.requesting_domain_slug}< / span >
${r.fulfilling_domain_slug ? html` → <strong>${r.fulfilling_domain_slug}</strong>` : html` → <em>unassigned</em>` }
< / div >
< div class = "cap-age" > ${ageDays(r)}d old< / div >
< / div >
`)}
< / div >
`)}
< / div > `);
}
```
## All Requests
```js
display(Inputs.table(filtered.map(r => ({
Type: r.capability_type,
Title: r.title,
Priority: r.priority,
Status: r.status,
Requester: r.requesting_domain_slug,
Provider: r.fulfilling_domain_slug ?? "—",
Agent: r.requesting_agent,
Created: new Date(r.created_at).toLocaleDateString(),
})), {maxWidth: 1000}));
```
---
## Capability Catalog
```js
// Live poll for catalog entries
const catalogState = (async function*() {
while (true) {
let data = [];
try {
const r = await fetch(`${API}/capability-catalog/?status=all` );
if (r.ok) data = await r.json();
} catch {}
yield data;
await new Promise(res => setTimeout(res, POLL));
}
})();
```
```js
const catalog = catalogState ?? [];
```
```js
if (catalog.length === 0) {
display(html`<p style="color:gray">No capabilities registered yet. Add <code>```capability</code> blocks to SCOPE.md files and run <code>make ingest-capabilities-all</code>.</p>` );
} else {
// Group by domain
const byDomain = {};
for (const c of catalog) {
(byDomain[c.domain_slug] = byDomain[c.domain_slug] ?? []).push(c);
}
const typeColors = {
infrastructure: "#e65100 ", api: "#1565c0 ", data: "#2e7d32 ",
security: "#c62828 ", documentation: "#6a1b9a ", other: "#888 "
};
display(html`< div class = "catalog-grid" >
${Object.entries(byDomain).sort((a, b) => a[0].localeCompare(b[0])).map(([domain, caps]) => html`
< div class = "catalog-domain" >
< div class = "catalog-domain-header" > ${domain} < span class = "kanban-count" > ${caps.length}< / span > < / div >
${caps.map(c => html`
< div class = "catalog-entry ${c.status === 'deprecated' ? 'catalog-deprecated' : ''}" >
< div class = "cap-type-badge" style = "background:${(typeColors[c.capability_type] ?? ' #888 ')}18;color:${typeColors[c.capability_type] ?? ' #888 '}" > ${c.capability_type}</ div >
< div class = "catalog-title" > ${c.title}< / div >
${c.description ? html`<div class="catalog-desc">${c.description}</div>` : ""}
${c.keywords?.length ? html`<div class="catalog-kw">${c.keywords.map(k => html` < span class = "kw-tag" > ${k}</ span > `)}</div>` : ""}
< / div >
`)}
< / div >
`)}
< / div > `);
}
```
< style >
.live-indicator { font-size: 0.8rem; color: gray; padding: 0.55rem 0.7rem; margin-bottom: 0.75rem; }
.card { background: var(--theme-background-alt); border-radius: 8px; padding: 1rem; }
.big-num { font-size: 2.2rem; font-weight: bold; margin: 0.25rem 0; }
.kanban { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 1.5rem; }
.kanban-col { background: var(--theme-background-alt); border-radius: 8px; padding: 0.75rem; }
.kanban-header { font-weight: 600; font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.04em; padding-bottom: 0.5rem; margin-bottom: 0.5rem; display: flex; align-items: center; gap: 0.4rem; }
.kanban-count { font-size: 0.75rem; background: var(--theme-background); border-radius: 10px; padding: 0.1rem 0.4rem; font-weight: 500; }
.cap-card { background: var(--theme-background); border-radius: 6px; padding: 0.6rem 0.75rem; margin-bottom: 0.5rem; }
.cap-type-badge { display: inline-block; font-size: 0.65rem; font-weight: 700; border-radius: 3px; padding: 0.1rem 0.35rem; margin-bottom: 0.15rem; }
.cap-priority-badge { display: inline-block; font-size: 0.6rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; margin-left: 0.3rem; }
.cap-title { font-size: 0.85rem; font-weight: 600; margin-bottom: 0.2rem; line-height: 1.3; }
.cap-domains { font-size: 0.75rem; color: steelblue; font-family: monospace; }
.cap-age { font-size: 0.7rem; color: gray; margin-top: 0.3rem; }
.catalog-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 1rem; margin-bottom: 1.5rem; }
.catalog-domain { background: var(--theme-background-alt); border-radius: 8px; padding: 0.75rem; }
.catalog-domain-header { font-weight: 600; font-size: 0.9rem; text-transform: uppercase; letter-spacing: 0.04em; padding-bottom: 0.5rem; margin-bottom: 0.5rem; border-bottom: 2px solid var(--theme-foreground-faint, #ddd ); display: flex; align-items: center; gap: 0.4rem; }
.catalog-entry { background: var(--theme-background); border-radius: 6px; padding: 0.6rem 0.75rem; margin-bottom: 0.5rem; }
.catalog-deprecated { opacity: 0.5; }
.catalog-title { font-size: 0.85rem; font-weight: 600; margin-bottom: 0.15rem; }
.catalog-desc { font-size: 0.75rem; color: var(--theme-foreground-muted, #666 ); line-height: 1.35; margin-bottom: 0.3rem; }
.catalog-kw { display: flex; flex-wrap: wrap; gap: 0.25rem; }
.kw-tag { font-size: 0.6rem; background: var(--theme-background-alt, #f0f0f0 ); border: 1px solid var(--theme-foreground-faint, #ddd ); border-radius: 3px; padding: 0.05rem 0.3rem; font-family: monospace; color: var(--theme-foreground-muted, #666 ); }
2026-03-21 23:58:52 +01:00
.disputed-banner { background: #fff8e1 ; border: 1.5px solid #f59e0b ; border-radius: 8px; padding: 0.85rem 1rem; margin-bottom: 1.25rem; }
.disputed-banner-title { font-weight: 700; font-size: 0.9rem; color: #b45309 ; margin-bottom: 0.6rem; }
.disputed-card { background: #fffbf0 ; border: 1px solid #fcd34d ; border-radius: 6px; padding: 0.6rem 0.75rem; margin-bottom: 0.5rem; }
.disputed-card-header { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; margin-bottom: 0.3rem; flex-wrap: wrap; }
.disputed-reason { font-size: 0.8rem; color: #92400e ; margin-bottom: 0.2rem; }
.disputed-suggestion { font-size: 0.78rem; color: #1d4ed8 ; margin-bottom: 0.15rem; }
.disputed-meta { font-size: 0.72rem; color: gray; margin-top: 0.2rem; }
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>
2026-03-19 21:07:50 +01:00
< / style >