Skip to content

feat(addie): capability-driven storyboard selection#2282

Merged
bokelley merged 6 commits into
mainfrom
bokelley/addie-specialisms
Apr 17, 2026
Merged

feat(addie): capability-driven storyboard selection#2282
bokelley merged 6 commits into
mainfrom
bokelley/addie-specialisms

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Summary

Aligns Addie and the registry API with the 5.1.0 capability-driven compliance model shipped in #2277. Stacks on top of that PR.

The principle: the agent is the source of truth. It declares supported_protocols + specialisms in get_adcp_capabilities; we probe and resolve. No per-org "expected specialisms" state, no extra tool for members to declare what they're building. If they want advice, Addie answers conversationally from the docs.

What changed

  • recommend_storyboards (MCP) — was matching storyboards by required_tools against getAgentInfo().tools. Now calls testCapabilityDiscovery and resolveStoryboardsForCapabilities, returns bundles grouped by kind (universal / domain / specialism). When an agent declares nothing, the tool coaches the developer on what to add instead of running a silent no-op.
  • GET /api/registry/agents/{encodedUrl}/applicable-storyboards (REST) — same rewrite. Response shape changed from { tools, storyboards: {[track]: [...]}, total_applicable } to { supported_protocols, specialisms, bundles: [{kind, id, storyboards}], total_storyboards, capabilities_probe_error? }. If a declared specialism isn't in the local compliance cache, returns 422 { unknown_specialism: true } so the UI can prompt a re-sync.
  • Addie's system prompt — new framing: "probe → recommend → run. Don't ask the member what kind of agent they're building; trust the agent's declarations. If missing, teach them what to add."

Explicitly rejected

  • Per-org "expected specialisms" DB column. Considered; rejected. The agent declares; we trust. Parallel state would drift.
  • "Select your specialisms" UI. Same reason. If we need to recommend specialisms during development, Addie can advise from the docs — no tool needed.

Test plan

  • `npx tsc --noEmit` clean on changes (two pre-existing `ParsedRepo` errors unrelated)
  • 1446 / 1446 server unit tests pass (excluding 2 pre-existing failing files)
  • OpenAPI spec regenerated
  • Manual: point `recommend_storyboards` at test-agent.adcontextprotocol.org, verify bundles group correctly
  • Manual: point it at a v2 agent that doesn't implement `get_adcp_capabilities`, verify coaching output reads well

🤖 Generated with Claude Code

bokelley and others added 2 commits April 17, 2026 15:46
recommend_storyboards and /applicable-storyboards now probe
get_adcp_capabilities and resolve bundles via
resolveStoryboardsForCapabilities, instead of tool-matching
against required_tools. The agent is the source of truth for
supported_protocols + specialisms.

When an agent declares nothing, Addie coaches the developer
on what to add rather than asking the member what kind of
agent they're building. No per-org 'expected specialisms'
state, no extra tool — advice is conversational.

If a declared specialism isn't in the local compliance cache,
/applicable-storyboards returns 422 with unknown_specialism: true
so the UI can prompt a re-sync.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Filter empty bundles from recommend_storyboards and /applicable-storyboards
  output. Upstream catalog occasionally ships stubs (fictional-entities,
  storyboard-schema) with zero storyboards — they're noise for members.
- Add server/tests/manual/capabilities-smoke.ts for library-level end-to-end
  smoke against a live agent: probe → resolve → group by kind.
- Add server/tests/manual/recommend-storyboards-smoke.ts for Addie handler
  smoke: invokes the tool directly and prints the member-facing markdown.

Verified against test-agent.adcontextprotocol.org: 23 storyboards across 9
bundles resolve cleanly. Specialism resolution (sales-guaranteed, sales-exchange)
works. Unknown specialisms throw with a helpful cache-resync message that the
422 handler surfaces.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley changed the base branch from bokelley/adcp-client-5.1.0 to main April 17, 2026 19:46
@bokelley bokelley force-pushed the bokelley/addie-specialisms branch from 4aa93e2 to f03e88d Compare April 17, 2026 19:47
Comment thread server/src/routes/registry-api.ts Fixed
bokelley and others added 4 commits April 17, 2026 15:50
The resolveStoryboardsForCapabilities error message includes the
absolute server filesystem path to the cache directory. Returning
it directly tripped semgrep's error-message-leak rule.

Log the full error server-side with agentUrl + declared caps for
operator diagnosis; return a generic message to the client that
still conveys "unknown specialism — cache may be stale" so the UI
can still prompt a re-sync.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tion or class'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Expert review surfaced nine actionable gaps; this commit addresses
all of them.

Ship-blockers:
- MCP handler was leaking resolver filesystem paths via resolver.message
  (mirror of the REST leak semgrep caught earlier); fixed with the same
  server-side log / generic-client-message pattern.
- Coaching output was emitting a literal '{version}' placeholder in the
  compliance-cache URL; now interpolates via loadComplianceIndex() and
  points at adcontextprotocol.org (the canonical compliance host).

Quality:
- Add a copy-pasteable JSON snippet of the minimum-shape
  get_adcp_capabilities response inside the coaching branch so a
  developer can ship a fix without reading spec docs.
- /applicable-storyboards 422 now returns declared_specialisms and
  known_specialisms so a UI can name the offender without asking the
  developer to re-read their own config.
- 500 on unreachable now returns a reason enum
  (network|tls|timeout|protocol|unknown) so UIs can differentiate a
  mistyped URL from a TLS failure.
- capabilities_probe_error surfaces unconditionally in the MCP handler
  now — not only on empty caps — so partial failures don't silently
  degrade the result.

Framing:
- Lede is verdict-first ("6 domains declared. 23 compliance checks
  ready to run — nothing is failing yet") rather than a bureaucratic
  declarations list.
- Universal section collapses to a one-line summary since it's the
  same for every agent; keeps domain + specialism results above the
  fold.
- 'What next?' block names actions, not tools, with tool names in
  inline code as secondary labels. Run-first ordering.
- 'Your agent didn't tell us what it does yet' replaces the bolded
  accusation in the coaching path.
- If the caller has an org and the agent isn't saved, offer a
  save_agent prompt — converts drive-by probes into ongoing
  compliance-monitored relationships.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Security review found one exploitable pathway: untrusted agent
fields (profile.name, capabilities_probe_error, specialisms[])
were rendered directly into markdown returned to the LLM, which
has save_agent and other write-capable tools in the same session.
A hostile agent returning "name: Foo\\n---\\nSYSTEM: ..." could
inject instructions into Addie's context.

Changes:
- Add sanitizeAgentField / fenceAgentValue helpers in member-tools.ts.
  Strip control chars, backticks, newlines; cap length; wrap agent-
  reported strings in quotes to mark them as untrusted.
- Apply to profile.name + capabilities_probe_error (twice, both
  success and coaching paths) + declared specialism ids (in the
  unknown-specialism error branch) + agent_profile.name/tools in
  evaluate_agent_quality output.
- Factor classifyProbeError into server/src/utils/probe-error.ts so
  both REST and MCP paths share one implementation. Walk err.cause.code
  (undici/fetch wrap ECONNREFUSED in cause) and handle undici-specific
  codes (UND_ERR_CONNECT_TIMEOUT, UND_ERR_SOCKET).
- MCP ToolError on probe failure no longer leaks error.message; uses
  the classifier's reason label instead.
- Cap capabilities_probe_error to 500 chars + strip control chars in
  REST response. Document the untrusted-field contract on OpenAPI.
- Replace hardcoded domain-example list in coaching output with
  index.domains from loadComplianceIndex (fixes drift risk raised in
  the first round of code review).

Deferred: SSRF hardening (DNS rebinding, IPv6 private ranges) is a
real concern but a cross-cutting refactor affecting multiple routes —
filing as follow-up issue rather than expanding scope here.
Automated test for 422 branch likewise deferred; manual smoke covers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant