§11 Registries & Plugin Discovery
§11.1 Connection Registry
Section titled “§11.1 Connection Registry”The connection registry stores pre-configured LLM clients for reuse across prompts.
API:
| Function | Description |
|---|---|
register_connection(name, client) | Store a named client/connection |
get_connection(name) → client | Retrieve 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.
§11.2 Tool Registry
Section titled “§11.2 Tool Registry”The tool registry provides two layers of dispatch for tool execution in the agent loop (§9 Agent Loop):
- Name registry — per-tool handlers keyed by tool name. Explicit overrides and user-provided function callables live here.
- 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.
Name Registry API
Section titled “Name Registry API”| Function | Description |
|---|---|
register_tool(name, handler) | Store a per-name tool handler |
get_tool(name) → handler | Retrieve 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.
Kind Handler API
Section titled “Kind Handler API”| Function | Description |
|---|---|
register_tool_handler(kind, handler) | Store a kind handler; replaces any existing |
get_tool_handler(kind) → handler | Retrieve 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.
Dispatch Order
Section titled “Dispatch Order”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.
Built-in Kind Handlers
Section titled “Built-in Kind Handlers”| Tool Kind | Handler 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.
§11.3 Invoker Discovery
Section titled “§11.3 Invoker Discovery”Renderers, parsers, executors, and processors are pluggable components resolved at runtime via a discovery mechanism.
API:
| Function | Description |
|---|---|
get_renderer(key) → instance | Look up renderer by key |
get_parser(key) → instance | Look up parser by key |
get_executor(key) → instance | Look up executor by key |
get_processor(key) → instance | Look 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:
| Component | Key Source | Example Values |
|---|---|---|
| Renderer | agent.template.format.kind | "jinja2", "mustache" |
| Parser | agent.template.parser.kind | "prompty" |
| Executor | agent.model.provider | "openai", "foundry", "anthropic" |
| Processor | agent.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:
| Language | Recommended Mechanism |
|---|---|
| Python | importlib.metadata.entry_points() with groups prompty.renderers, prompty.parsers, prompty.executors, prompty.processors |
| TypeScript | Explicit registerRenderer("jinja2", Jinja2Renderer) calls |
| Go | Interface implementations registered via init() |
| C# | Dependency injection or assembly scanning |
Built-in registrations. The following MUST or MAY be available without additional configuration:
| Key | Component | Requirement | Notes |
|---|---|---|---|
jinja2 | Renderer | MUST | Jinja2-compatible template engine |
mustache | Renderer | MAY | Mustache template engine |
prompty | Parser | MUST | Role-marker chat parser |
openai | Executor + Processor | MUST | OpenAI Chat/Embedding/Image/Responses |
foundry | Executor + Processor | MAY | Microsoft Foundry |
anthropic | Executor + Processor | MAY | Anthropic Messages |
§11.3.1 Protocol Interfaces
Section titled “§11.3.1 Protocol Interfaces”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) → stringReceives 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_responseReceives 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) → resultReceives the agent and raw response. Extracts and returns the final result (string, parsed JSON object, list of embeddings, ToolCall list, etc.).