Skip to content

§11 Registries & Plugin Discovery

The connection registry stores pre-configured LLM clients for reuse across prompts.

API:

FunctionDescription
register_connection(name, client)Store a named client/connection
get_connection(name) → clientRetrieve by name; return null if absent
clear_connections()Remove all entries (for testing)
  • All registry operations MUST be thread-safe.
  • The registry MUST be a process-global singleton (or equivalent language-idiomatic scope).

Usage: When an executor encounters a ReferenceConnection (kind: "reference"), it MUST look up connection.name in the connection registry to obtain the pre-configured client. If the name is not found, the executor MUST raise an error.

The tool registry provides two layers of dispatch for tool execution in the agent loop (§9 Agent Loop):

  1. Name registry — per-tool handlers keyed by tool name. Explicit overrides and user-provided function callables live here.
  2. Kind handlers — per-kind handlers keyed by tool kind ("function", "prompty", "mcp", "openapi", "*"). These are extensible fallbacks that handle entire categories of tools without per-name registration.

When the agent loop needs to execute a tool call, it resolves the handler using the dispatch order defined below.

FunctionDescription
register_tool(name, handler)Store a per-name tool handler
get_tool(name) → handlerRetrieve by name; return null if absent
clear_tools()Remove all name entries (for testing)
  • All registry operations MUST be thread-safe.
  • The name registry MUST be a process-global singleton.
FunctionDescription
register_tool_handler(kind, handler)Store a kind handler; replaces any existing
get_tool_handler(kind) → handlerRetrieve by kind; return null if absent
clear_tool_handlers()Remove all kind entries (for testing)
  • Kind handlers MUST be a process-global singleton.
  • Implementations SHOULD register built-in kind handlers for "function" and "prompty" at startup. Handlers for "mcp", "openapi", and "*" (custom) MAY be registered by extension packages.

When resolving a tool call with name N and tool definition kind K:

1. handler = get_tool(N)
→ if found, execute handler(args) and return result
2. handler = get_tool_handler(K)
→ if found, execute handler(tool_def, args, agent, inputs) and return result
3. raise ValueError("No handler registered for tool: " + N + " (kind: " + K + ")")

The name registry (layer 1) always takes priority, allowing users to override any tool — including built-in prompty tools — with a custom callable.

Tool KindHandler Behaviour
"function"Look up callable in name registry, call handler(args)
"prompty"Load child .prompty via tool.path, execute per §9.6
"mcp"Send tool-call request via MCP client protocol
"openapi"Make HTTP call per OpenAPI specification
"*"Delegate to user-provided handler’s execute(args) method

Implementations that do not yet support a kind SHOULD register a handler that raises NotImplementedError with a descriptive message, rather than leaving the kind unregistered.

Renderers, parsers, executors, and processors are pluggable components resolved at runtime via a discovery mechanism.

API:

FunctionDescription
get_renderer(key) → instanceLook up renderer by key
get_parser(key) → instanceLook up parser by key
get_executor(key) → instanceLook up executor by key
get_processor(key) → instanceLook up processor by key
clear_cache()Reset cached instances (for testing)

If no implementation is found for a given key, the implementation MUST raise an InvokerError with a message identifying the missing component and key.

Key resolution. Each component type derives its lookup key from a specific field on the PromptAgent:

ComponentKey SourceExample Values
Rendereragent.template.format.kind"jinja2", "mustache"
Parseragent.template.parser.kind"prompty"
Executoragent.model.provider"openai", "foundry", "anthropic"
Processoragent.model.provider"openai", "foundry", "anthropic"

Discovery mechanism. This specification defines the contract (lookup by key → instance conforming to the protocol), not the mechanism. Each language SHOULD use its own idiomatic approach:

LanguageRecommended Mechanism
Pythonimportlib.metadata.entry_points() with groups prompty.renderers, prompty.parsers, prompty.executors, prompty.processors
TypeScriptExplicit registerRenderer("jinja2", Jinja2Renderer) calls
GoInterface implementations registered via init()
C#Dependency injection or assembly scanning

Built-in registrations. The following MUST or MAY be available without additional configuration:

KeyComponentRequirementNotes
jinja2RendererMUSTJinja2-compatible template engine
mustacheRendererMAYMustache template engine
promptyParserMUSTRole-marker chat parser
openaiExecutor + ProcessorMUSTOpenAI Chat/Embedding/Image/Responses
foundryExecutor + ProcessorMAYMicrosoft Foundry
anthropicExecutor + ProcessorMAYAnthropic Messages

Every component MUST conform to its protocol. Implementations MUST provide at least the async variants; sync variants SHOULD also be provided.

RendererProtocol:

RendererProtocol:
render(agent: PromptAgent, inputs: dict) → string
render_async(agent: PromptAgent, inputs: dict) → string

Receives the agent and user inputs. Returns the rendered template string with all variables substituted.

ParserProtocol:

ParserProtocol:
parse(agent: PromptAgent, rendered: string) → Message[]
parse_async(agent: PromptAgent, rendered: string) → Message[]

Receives the agent and the rendered string. Returns an ordered list of Message objects.

ExecutorProtocol:

ExecutorProtocol:
execute(agent: PromptAgent, messages: Message[]) → raw_response
execute_async(agent: PromptAgent, messages: Message[]) → raw_response

Receives the agent and parsed messages. Calls the LLM provider API. Returns the raw SDK response object.

ProcessorProtocol:

ProcessorProtocol:
process(agent: PromptAgent, response: any) → result
process_async(agent: PromptAgent, response: any) → result

Receives the agent and raw response. Extracts and returns the final result (string, parsed JSON object, list of embeddings, ToolCall list, etc.).