Technical Design: Synapse Custom Agents Feature V2

Technical Design: Synapse Custom Agents Feature V2

Part 1: High-Level Concepts

This section is intended for all stakeholders. It explains what we are building and why, without requiring engineering background.

Problem Statement

The Synapse Chat Framework allows users to interact with private Synapse data through AI-powered chatbots. Today (V1), every chatbot is a single monolithic agent loaded with all available tools. This creates two problems:

  1. Difficult to extend: Developers who want to create custom chatbots (e.g., a portal-specific dataset finder) must manually copy hundreds of lines of tool definitions from the production agent. This is error-prone, hard to maintain, and creates version drift.

  2. Performance degradation: As conversations grow longer, the single agent accumulates raw API responses in its memory, causing token throttling and degraded response quality.

What We Are Building

V2 introduces a Specialist Agent architecture that solves both problems:

Concept

Description

Concept

Description

Specialist Agents

Production-grade, narrowly focused AI agents hosted by Synapse. Each specialist owns a specific capability (e.g., "Search" or "Metadata Lookup"). They are maintained by the Synapse Core Team.

Supervisor Agents

Lightweight custom agents created by developers. A supervisor contains domain-specific instructions and a knowledge base, but delegates heavy lifting to specialists instead of reimplementing tools.

Specialist Proxy

A single, simple mechanism that allows any custom agent to call a specialist. Replaces the need to copy-paste tool definitions.

Specialist Card

A discoverable description of each specialist's capabilities, inputs, and outputs. Developers use this to understand what specialists are available and how to invoke them (inspired by the Agent2Agent protocol).

How It Works (Conceptual)

Imagine a portal developer wants a chatbot that helps researchers find Alzheimer's datasets:

  1. The developer discovers available specialists by calling the listing API, which returns a "calling card" for each specialist describing its capabilities and how to invoke it.

  2. The developer creates a Supervisor Agent with instructions like: "You are an Alzheimer's research assistant. When users ask for datasets, delegate to the Search Specialist."

  3. The developer attaches their portal's knowledge base (RAG) for domain context.

  4. The developer registers the agent with Synapse, declaring which specialists it may use (e.g., Search, Metadata).

  5. At runtime, when a user asks "Find me recent tau protein datasets," the supervisor recognizes this as a search task and delegates to the Search Specialist.

  6. The Search Specialist executes the query securely (respecting the user's permissions), produces a concise summary, and returns it to the supervisor.

  7. The supervisor uses the summary to formulate a user-friendly response.

Key Benefits

Benefit

Who It Helps

How

Benefit

Who It Helps

How

Dramatically simpler agent development

Custom Agent Developers

One action group replaces 500+ lines of copied tool definitions

Better conversation quality

End Users

Raw API payloads never enter the main conversation, preventing context overload

Centralized tool maintenance

Synapse Core Team

Specialists are updated once; all supervisors benefit immediately

Stronger security

Everyone

Cryptographic identity tokens ensure specialists always respect user permissions

Full backwards compatibility

Existing Agent Developers

V1 agents continue working without any changes

Discoverability

Custom Agent Developers

Calling cards describe each specialist's capabilities, making it easy to build against

Developer Experience: Before vs After

Step

V1 (Today)

V2 (New)

Step

V1 (Today)

V2 (New)

1

Copy 500+ lines of OpenAPI templates from baseline agent

Call GET /agent/specialists to discover available specialists and their calling cards

2

Copy multi-paragraph tool instructions

Create a lightweight agent with your custom instructions

3

Modify instructions for your domain

Attach your knowledge base (RAG)

4

Add your knowledge base

Add ONE action group: specialist_proxy

5

Deploy via CloudFormation

Deploy and register as SUPERVISOR with declared specialists

6

Register with Synapse

 

Security Model

When a supervisor delegates to a specialist, the system:

  1. Generates a short-lived cryptographic token (JWT) carrying the user's identity and permissions

  2. Validates the supervisor is authorized to call that specific specialist

  3. The specialist verifies the token and enforces the user's access level

  4. Results flow back only as bounded summaries (never raw data payloads)

This ensures that even if a custom supervisor is compromised, it cannot access data the user doesn't have permission to see, nor can it call specialists it hasn't been authorized to use.

Backwards Compatibility

All existing V1 custom agents continue to work without any code changes. The new architecture is purely additive. Developers can adopt V2 incrementally at their own pace.


Part 2: Technical Design

This section is for engineers implementing or reviewing the design. It assumes familiarity with the Synapse codebase architecture (Controller → Manager → DAO) and the existing agent framework.

Architecture Overview

User → Synapse Worker → invoke_agent(custom Bedrock agent) Custom agent (Haiku + custom instructions): "I need to search for datasets" → return_control: specialist_proxy(specialist_name="SYNAPSE_CORE_SEARCH", query="...") Worker → SpecialistProxyHandler.handleEvent(event) → Validate specialist_name is in registration's delegatedSpecialists → Generate ephemeral JWT (DelegationTokenService) → Resolve specialist (Spring AI service) → Invoke specialist with query + JWT → Receive bounded summary from specialist → Return summary as handler response (≤4000 chars) Custom agent receives summary → formulates user-facing response Worker → return response to user

Design Decisions

D1. Specialist Runtime: Spring AI

Specialists are hosted via Spring AI rather than AWS Bedrock Agent Collaboration. This gives us full control over:

  • Tool execution and context management

  • Identity propagation (JWT injection at agent boundaries)

  • Versioning specialists with our codebase (not separately managed AWS resources)

  • Tool definitions in Java (not CloudFormation OpenAPI templates)

D2. Bridge Pattern: specialist_proxy ReturnControlHandler

Rather than building a new orchestration engine in the worker, we add a single ReturnControlHandler implementation that intercepts proxy requests and routes them to the Spring AI specialist service. This:

  • Reuses the existing ReturnControlHandler infrastructure with zero changes to the invoke_agent loop

  • Provides natural context segregation (specialist response is what flows back through return_control)

  • Keeps custom Bedrock agents simple: one action group, one function

D3. Registration Declares Allowed Specialists

Custom agent registrations include a delegatedSpecialists array. The SpecialistProxyHandler validates at runtime that the requested specialist is in that list. This prevents prompt-injection attacks from tricking an agent into calling unauthorized specialists.

D4. Alias-Based Specialist Resolution (Hybrid)

Specialist agents have stable logical identifiers (e.g., SYNAPSE_CORE_SEARCH). Stack Builder creates per-stack aliases (e.g., prod-593) on specialist resources. Workers derive the correct alias from their own stack identity via StackConfiguration. No re-registration is needed on stack promotion.

D5. Layered Context Segregation

  • Specialists are instructed to produce concise structured summaries (they know what is relevant)

  • The handler enforces a hard character cap (MAX_SPECIALIST_RESPONSE_CHARS = 4000) as a safety net

D6. Deployment Topology Deferred

The SpecialistService interface is designed to work both in-process and over HTTP. We start with in-process invocation and can extract to a separate Spring Boot service later if scaling demands it.

API: Specialist Discovery

Custom agent developers need to discover what specialists are available and understand how to invoke them. Inspired by the Agent2Agent (A2A) protocol, each specialist publishes a calling card that describes its capabilities.

GET /agent/specialists

Lists all available specialists and their calling cards. No authentication required (specialist capabilities are public metadata).

Response: ListSpecialistsResponse

{ "description": "A page of specialist calling cards.", "properties": { "page": { "type": "array", "items": { "$ref": "org.sagebionetworks.repo.model.agent.SpecialistCard" }, "description": "The list of available specialist calling cards." }, "nextPageToken": { "type": "string", "description": "Token for the next page of results. Null if no more pages." } } }

SpecialistCard (New Schema)

The calling card for a specialist agent. Provides everything a developer needs to understand a specialist's capabilities, input contract, and expected output.

{ "description": "A specialist agent's calling card. Describes the specialist's capabilities, expected input, and output contract. Used by custom agent developers to understand how to invoke specialists via the specialist_proxy function.", "properties": { "specialistType": { "$ref": "org.sagebionetworks.repo.model.agent.SpecialistType", "description": "The stable logical identifier used when calling specialist_proxy." }, "displayName": { "type": "string", "description": "Human-readable name for display (e.g., 'Synapse Search Specialist')." }, "description": { "type": "string", "description": "A concise description of what this specialist does and when to use it." }, "version": { "type": "string", "description": "The current version of this specialist (semver)." }, "lifecycleState": { "$ref": "org.sagebionetworks.repo.model.agent.SpecialistLifecycleState", "description": "Current lifecycle state. Only ACTIVE specialists may be invoked." }, "capabilities": { "type": "array", "items": { "type": "string" }, "description": "A list of high-level capabilities this specialist provides (e.g., 'full-text search', 'entity metadata retrieval', 'annotation lookup')." }, "inputDescription": { "type": "string", "description": "Describes what the 'query' parameter should contain when invoking this specialist. Guides the supervisor on how to formulate delegation requests." }, "outputDescription": { "type": "string", "description": "Describes the format and content of the specialist's response. Helps the supervisor interpret results." }, "exampleInvocations": { "type": "array", "items": { "$ref": "org.sagebionetworks.repo.model.agent.SpecialistExample" }, "description": "Example query/response pairs demonstrating typical usage." } } }

SpecialistExample (New Schema)

{ "description": "An example invocation of a specialist, showing a sample query and the kind of response it produces.", "properties": { "query": { "type": "string", "description": "An example query string to pass as the 'query' parameter." }, "responsePreview": { "type": "string", "description": "A representative preview of what the specialist returns for this query." } } }

API: Extended Registration

AgentRegistrationRequest (Modified Schema)

The existing registration request gains two optional fields for V2 supervisor agents:

{ "description": "Request to register a custom AWS agent with Synapse. Currently, only internal users are authorized to register custom agents.", "properties": { "awsAgentId": { "type": "string", "description": "The AWS issued agent ID of the agent to be registered." }, "awsAliasId": { "type": "string", "description": "The AWS issued agent alias ID. Optional. If not provided, 'TSTALIASID' will be used." }, "agentType": { "$ref": "org.sagebionetworks.repo.model.agent.AgentType", "description": "The type of agent being registered. Optional. Defaults to CUSTOM if not provided (backwards compatible). Set to SUPERVISOR for V2 multi-agent delegation." }, "delegatedSpecialists": { "type": "array", "items": { "$ref": "org.sagebionetworks.repo.model.agent.DelegatedSpecialist" }, "description": "Required when agentType is SUPERVISOR. The list of specialists this supervisor is authorized to invoke. Each specialist must be ACTIVE in the registry." } } }

AgentRegistration (Modified Schema)

The registration response is extended to include delegated specialists for SUPERVISOR registrations:

{ "description": "The registration of a custom AWS agent.", "properties": { "agentRegistrationId": { "type": "string", "description": "The unique ID issued by Synapse when this agent was registered." }, "awsAgentId": { "type": "string", "description": "The AWS issued agent ID of the agent." }, "awsAliasId": { "type": "string", "description": "The AWS issued agent alias ID." }, "registeredOn": { "type": "string", "format": "date-time", "description": "The date this agent was registered." }, "type": { "$ref": "org.sagebionetworks.repo.model.agent.AgentType", "description": "The agent's type." }, "delegatedSpecialists": { "type": "array", "items": { "$ref": "org.sagebionetworks.repo.model.agent.DelegatedSpecialist" }, "description": "Present when type is SUPERVISOR. The specialists this agent is authorized to invoke." } } }

API: Supporting Schemas (New)

AgentType (Modified Enum)

{ "description": "The type of a registered agent.", "name": "AgentType", "type": "string", "enum": [ { "name": "BASELINE", "description": "The baseline agent designed for general interactions with Synapse." }, { "name": "CUSTOM", "description": "A custom agent designed for a specific use case (V1 pattern)." }, { "name": "SUPERVISOR", "description": "A custom agent that delegates to production specialists via the specialist_proxy mechanism (V2 pattern)." } ] }

SpecialistType (New Enum)

{ "description": "The logical identifier of a production specialist agent. Used as the 'specialist_name' parameter when invoking specialist_proxy.", "name": "SpecialistType", "type": "string", "enum": [ { "name": "SYNAPSE_CORE_SEARCH", "description": "Specialist for searching Synapse entities, datasets, and metadata via full-text and structured queries." }, { "name": "SYNAPSE_CORE_METADATA", "description": "Specialist for retrieving and interpreting entity metadata, annotations, and schema information." } ] }

SpecialistLifecycleState (New Enum)

{ "description": "The lifecycle state of a specialist agent in the registry.", "name": "SpecialistLifecycleState", "type": "string", "enum": [ { "name": "ACTIVE", "description": "The specialist is available for invocation." }, { "name": "DEPRECATED", "description": "The specialist still functions but is scheduled for retirement. Existing supervisors continue to work; new registrations should use a replacement." }, { "name": "RETIRED", "description": "The specialist is no longer available. Invocations will fail with actionable migration guidance." } ] }

DelegatedSpecialist (New Schema)

{ "description": "Identifies a specialist that a supervisor agent is authorized to invoke via specialist_proxy.", "properties": { "specialistType": { "$ref": "org.sagebionetworks.repo.model.agent.SpecialistType", "description": "The logical identifier of the specialist." } } }

Specialist Proxy: Action Group Contract

Custom agent developers include the following action group in their Bedrock agent definition. This is the only tool they need to delegate to Synapse specialists:

{ "actionGroupName": "specialist_tools", "description": "Delegates tasks to Synapse production specialist agents.", "functions": [ { "name": "specialist_proxy", "description": "Invoke a Synapse specialist agent to perform a specific task. The specialist will execute the query using the current user's permissions and return a concise summary.", "parameters": { "specialist_name": { "type": "string", "description": "The SpecialistType identifier (e.g., SYNAPSE_CORE_SEARCH, SYNAPSE_CORE_METADATA). Must match a specialist declared in this agent's registration.", "required": true }, "query": { "type": "string", "description": "The natural language query or structured request to send to the specialist. Refer to the specialist's calling card (inputDescription) for guidance on formatting.", "required": true } } } ] }

This action group uses Bedrock's RETURN_CONTROL invocation type, meaning the Synapse worker intercepts the call and handles it server-side via SpecialistProxyHandler.

Sequence Diagram

firefox_hrucPKb0TL.png

 

sequenceDiagram participant User participant UI as Synapse Chat UI participant Worker as Async Chat Worker participant Bedrock as Custom Bedrock Agent (Supervisor) participant Handler as SpecialistProxyHandler participant JWT as DelegationTokenService participant Specialist as Spring AI Specialist User->>UI: Submit prompt UI->>Worker: Async chat job Worker->>Bedrock: invoke_agent(prompt) Bedrock->>Worker: return_control: specialist_proxy(SYNAPSE_CORE_SEARCH, query) Worker->>Handler: handleEvent(specialist_name, query) Handler->>Handler: Validate specialist is in registration's delegatedSpecialists Handler->>JWT: generateDelegationToken(user, session) JWT-->>Handler: Short-lived RS256 JWT (5 min TTL) Handler->>Specialist: invoke(type, query, jwt, accessLevel) Specialist->>Specialist: Validate JWT, execute search with user permissions Specialist-->>Handler: Bounded summary (≤4000 chars) Handler-->>Worker: Return summary as handler response Worker->>Bedrock: invoke_agent(summary as return_control result) Bedrock-->>Worker: Final user-facing response Worker-->>UI: Stream response UI-->>User: Render answer

Security: Ephemeral JWT

When the SpecialistProxyHandler delegates to a specialist, it generates a short-lived JWT:

Claim

Description

Claim

Description

sub

Synapse user ID of the initiating user

groups

Team membership IDs (for access control decisions)

sessionId

The originating agent session (prevents cross-session token reuse)

exp

5-minute expiration (bound to single execution cycle)

Signed with RS256 using the existing OIDC signing keys. Specialists verify the signature locally (same JVM or shared key material) before executing any operation.

Implementation Components

SpecialistProxyHandler

A ReturnControlHandler implementation (auto-discovered via Spring component scan):

  • Action group: specialist_tools

  • Function: specialist_proxy

Responsibilities:

  1. Parse specialist_name and query from the return_control event

  2. Resolve the specialist from the registry (must be ACTIVE)

  3. Validate the specialist is in the registration's delegatedSpecialists list

  4. Generate ephemeral JWT via DelegationTokenService

  5. Invoke the specialist via SpecialistService interface

  6. Enforce hard character cap (4000) on response

  7. Emit trace events for UI rendering

  8. Return bounded summary to the agent

SpecialistService (Interface)

public interface SpecialistService { /** * Invoke a specialist agent with the given query. * The specialist validates the JWT, executes with the user's access level, * and returns a bounded summary. */ String invoke(SpecialistType type, String query, String delegationToken, AgentAccessLevel accessLevel); }

Abstracts deployment topology. Initial implementation is in-process (Spring AI agents in same JVM); can be extracted to a separate service later.

DelegationTokenService

Generates and validates short-lived RS256 JWTs. Reuses the existing JwtBuilder and OIDC RSA key infrastructure already in the codebase.

Spring AI Specialist Module (New Maven Module)

Contains the actual specialist implementations:

  • Spring AI agent definitions for each SpecialistType

  • Tool definitions wrapping existing Synapse manager methods (EntityManager, SearchManager, etc.)

  • Agent instructions tuned for concise structured output

  • JWT validation logic

Each specialist is stateless per-invocation, has a narrow tool set, and produces bounded summaries.

Backwards Compatibility

Scenario

V2 Behavior

Scenario

V2 Behavior

V1 CUSTOM agent (awsAgentId, no agentType)

Defaults to CUSTOM. No delegatedSpecialists. Existing invoke_agent path unchanged.

V1 BASELINE agent

Bootstrap registration ID=1 unchanged. Zero impact.

V1 Grid agent

GridAgentSessionContext resolves to grid agent. Zero impact.

New V2 SUPERVISOR agent

Registers with agentType=SUPERVISOR + delegatedSpecialists. Uses specialist_proxy action group.

Migration Safety

All changes are single-stack safe:

  • New database tables are purely additive

  • MySQL ENUM extension (adding SUPERVISOR) preserves existing data

  • No columns removed or renamed on existing tables

  • V1 registrations are untouched (new fields are optional)

  • New MigrationType: AGENT_SPECIALIST_REGISTRY for the specialist catalog table

Testing Strategy

Layer

Test

Validates

Layer

Test

Validates

DAO

AgentSpecialistRegistryDaoImplAutowiredTest

CRUD, lifecycle transitions, unique constraints

DAO

AgentDaoImplAutowiredTest (extended)

Junction table persistence, lazy population of delegatedSpecialists

Manager

AgentManagerImplTest (extended)

SUPERVISOR registration validation, V1 backwards compat

Handler

SpecialistProxyHandlerTest

Delegation flow, access validation, char cap, rejected specialists

JWT

DelegationTokenServiceTest

Token generation, validation, expiry

Migration

MigratableTableDAOImplAutowireTest

New migration type is registered

Integration

ITAgentController (extended)

Full round-trip: register SUPERVISOR, list specialists, V1 still works

Open Items (Future Work)

  1. Spring AI specialist implementations -- actual SYNAPSE_CORE_SEARCH and SYNAPSE_CORE_METADATA agent logic (tool definitions, instructions, model selection)

  2. Deployment topology -- in-process vs separate Spring Boot service decision

  3. Specialist versioning -- how specialist versions relate to stack versions and alias management

  4. Rate limiting -- per-user, per-session, per-specialist throttling

  5. Observability -- correlation IDs across supervisor-to-specialist boundary

  6. Additional specialist types -- Entity CRUD, Table Query, File Operations, etc.

  7. Calling card management API -- admin endpoints for creating/updating specialist cards

Nick Grosenbacher
June 18, 2026

Is model selection done today in Bedrock?