Contract: Shared Skill Catalog API
Feature: 097-skills-middleware-integration | Date: 2026-03-24 (search, visibility, pagination, API key)
Overviewβ
The catalog API exposes the merged skill list to the UI (skills gallery and /skills chat command) and can be consumed by the backend skills middleware for the supervisor. Same data source ensures consistency (FR-001, FR-004).
Endpoint: List skills (for UI and chat command)β
Method: GET
Path: /api/skills (or equivalent; e.g. Next.js API route under ui/src/app/api/skills/route.ts)
Requestβ
- Headers:
- Browser / UI session: Existing app auth (cookie/session) as today; Next.js route forwards Bearer to Python where required.
- Python catalog (
GET /skillsor/internal/skills): Either (1)Authorization: Bearer <JWT>validated via JWKS / Okta OIDC (FR-014), or (2) catalog API key per gateway-api.md (FR-018). Invalid/missing auth β 401 (generic message).
- Query (optional):
qβ Free-text search overname,description(and optional tags); empty returns full entitled page.pageβ 1-based page index (default1).page_sizeβ Max items per page (default e.g.50, hard cap e.g.200) for large catalogs (FR-019, FR-024).sourceβ Filter:default|agent_skills|hub(optional).visibilityβ Filter within callerβs entitlement only:global|team|personal(optional); omit = all entitled visibilities.include_content=false(default): Return list only (id, name, description, source, source_id, visibility, team_ids, metadata). Use for/skillsand gallery list.include_content=true: Include fullcontentfor each skill when needed (e.g. for runner or assistant); discouraged for unbounded list calls.
Responseβ
Success (200)
{
"skills": [
{
"id": "string",
"name": "string",
"description": "string",
"source": "default" | "agent_skills" | "hub",
"source_id": "string | null",
"visibility": "global" | "team" | "personal",
"team_ids": ["string"],
"owner_user_id": "string | null",
"content": "string | null",
"metadata": {}
}
],
"meta": {
"total": 0,
"page": 1,
"page_size": 50,
"sources_loaded": ["default", "hub:github/org/repo"],
"unavailable_sources": []
}
}
skills: Array of skill objects; order stable (e.g. default first, then by source, then by name). Only skills the caller is entitled to (FR-020: union of global + callerβs teams + personal).meta.total: Total matching entitled skills (post-filter), for pagination UI.meta.unavailable_sources: Optional list of hub ids (or "default") that failed to load so UI can show "partial catalog" or admin can debug.- Empty search: HTTP 200 with
skills: []and a user-visible message in UI; optionalmeta.messagee.g."no_matches"(not 500).
Catalog unavailable (503)
When the central catalog cannot be produced (e.g. MongoDB down and no cache):
{
"error": "skills_unavailable",
"message": "Skills are temporarily unavailable. Please try again later."
}
Client should show a non-technical message (SC-004).
Authentication and authorizationβ
- Next.js
GET /api/skills: Any authenticated UI user (existing session); forwards to Python with user token where applicable. - Backend (Python)
GET /skills: JWT (Okta/OIDC via JWKS) or valid catalog API key (FR-014, FR-018); then apply visibility using resolved principal (OIDCsub+ team claims from token/userinfo or API key owner record). Invalid or missing auth β 401 (generic). Hub registration remains admin-only (separate contract).
Endpoint: Refresh catalog and supervisor snapshotβ
Method: POST
Path: /skills/refresh (Python) / proxied POST /api/skills/refresh (UI)
Must invalidate merged-skills cache and trigger supervisor graph rebuild so MAS skills match the catalog (FR-012). Optional response fields: graph_generation, skills_loaded_count. See contracts/supervisor-skills-status.md.
Backend (Python) contract for skills middlewareβ
The supervisor integrates with skills through two layers:
1. Custom catalog layer (ai_platform_engineering/skills_middleware/)β
- Function:
get_merged_skills(include_content: bool = False) -> List[Skill](or equivalent). - Return: List of skill dicts or objects matching the same shape (id, name, description, source, source_id, content optional, metadata).
- Failure: On catalog unavailable, return empty list or raise; supervisor handles "no skills" in prompt or tool metadata.
2. Upstream SkillsMiddleware integration (FR-015)β
- Function:
write_skills_to_backend(skills, backend)β writes each normalized skill as aSKILL.mdfile (YAML frontmatter + markdown body) into theStateBackendunder source-specific paths (e.g./skills/default/<skill-name>/SKILL.md,/skills/hub-<hub-id>/<skill-name>/SKILL.md). - Middleware:
SkillsMiddleware(backend=lambda rt: StateBackend(rt), sources=["/skills/default/", "/skills/hub-<id>/", ...])is added to the supervisor'screate_deep_agent()middleware list. - Behavior: The upstream middleware's
abefore_agent()loadsSkillMetadatafrom the backend once per session (or per state reset);awrap_model_call()injects the "Skills System" section into the system prompt with progressive disclosure (name + description listed; full SKILL.md read on demand via backenddownload_files()). - Hot reload (FR-012): Invalidate catalog cache, rebuild
AIPlatformEngineerMASgraph (_rebuild_graph/_build_graph) soget_merged_skillsruns again andcreate_deep_agentreceives updatedskills/ injectedfiles. Optionally clearskills_metadatain agent state if middleware caches per thread.
This contract is internal (Python); the REST contract above is for the UI.
Supervisor status: See contracts/supervisor-skills-status.md (FR-016).