Skip to main content
The composer has four parallel extension points. Every one is a plain registry you extend with a single register() call at import time — no subclassing, no forks, no monkey-patching. All four live in orxhestra.composer:

Custom agent types

Register an async build function under a YAML type: key.
myapp/agents.py
orx.yaml
The build function must implement the BuildFn protocol:
Custom agent types are exempt from the per-type field validation applied to built-ins — your builder is free to consume any AgentDef field.

Custom model providers

Register any class that implements LangChain’s BaseChatModel. Reference it by name via model.provider.
myapp/models.py
orx.yaml
The composer ships with 20+ lazily-loaded providers (openai, anthropic, google, groq, ollama, etc.) — you probably only need register_provider for genuinely new integrations. Any unrecognised provider string is also treated as a dotted import path, so provider: myapp.models.OllamaChat works without registration.

Custom built-in tools

A “built-in” is a zero-arg factory returning one or more LangChain tools. Great for project-scoped helpers that every agent in the compose spec wants.
myapp/tools.py
orx.yaml
The factory runs lazily on first use — if redact imports something expensive, agents that don’t reference it don’t pay the cost.

Custom tool types

If the five built-in ToolDef shapes (function, mcp, builtin, agent, transfer) don’t fit, register a whole new type of tool. The YAML escape hatch is ToolDef.custom — an arbitrary dict with a type key that the composer dispatches to your resolver. Resolvers can be sync or async; the composer awaits whichever shape you return.
myapp/webhook_tool.py
orx.yaml
A resolver may return a single BaseTool or a list of them — useful when one YAML entry expands into a whole tool surface (e.g. a REST client that exposes each endpoint as a separate tool).
Resolvers receive the raw custom dict unchanged — no Pydantic model. That’s deliberate: you own the validation. Raise ComposerError with a helpful message when the config is invalid.

Where to register

Registration is a module-level side effect — the composer looks at the registry state at build time, so any code path that calls Composer.from_yaml(...) must have already imported the module that calls register_*. Two common patterns:

Inspection APIs

Each registry exposes a read-only listing so you can verify what’s plugged in:
The composer’s error messages use the same listings — if a YAML spec references an unknown type: or custom.type:, the message enumerates every registered name so you see exactly what’s legal.

See also