Plugin Precedence
Built-in names always win over entry-point plugins — one rule, enforced in the base PluginRegistry for every registry
Every PraisonAI registry follows one rule: a built-in name always wins over an entry-point plugin — whichever entry-point group the plugin publishes under, canonical or a deprecated legacy spelling. Publishing a pip package with an entry point named openai, docker, or aws has no effect — the built-in resolves. Runtime register(...) is the only deliberate override path.
from praisonai.llm.registry import get_default_llm_registry
# A pip plugin named "openai" is silently dropped (DEBUG log).
# The supported override is an explicit, in-process register(...) call:
def _my_openai():
from my_pkg import MyOpenAI
return MyOpenAI
get_default_llm_registry().register("openai", _my_openai, override=True)The plugin author does not accidentally hijack a built-in through a pyproject.toml; overriding a built-in is always a deliberate, in-process decision.
Quick Start
- Pick a name that does not collide
Names are matched case-insensitively against the built-ins of the registry you target.
openai,OpenAI, andOPENAIare the same key — pick something novel.toml # pyproject.toml — additive plugin, resolves fine [project.entry-points."praisonai.llm_providers"] acme-llm = "acme_llm:AcmeProvider" - Override a built-in deliberately (runtime only)
The one supported way to replace a built-in is an in-process
register(...)call:python from praisonai.llm.registry import get_default_llm_registry def _my_openai(): from acme_llm import PatchedOpenAI return PatchedOpenAI get_default_llm_registry().register("openai", _my_openai, override=True) - Confirm what resolved
Turn on
DEBUGlogging to see the collision line noting that a shipped built-in kept its name.bash LOGLEVEL=DEBUG praisonai run "hello"
How It Works
Since PraisonAI PR #4176 the guard lives in the base PluginRegistry, so every registry that subclasses it inherits the behaviour. It is enforced once, not re-asserted per registry.
Some registries also honour deprecated (legacy) entry-point group spellings for backward compatibility. Legacy discovery runs first (so the canonical spelling wins on a clash), and since PraisonAI PR #4185 it applies the same built-in check — you cannot resurrect the old shadowing behaviour by falling back to a deprecated group.
Standalone installs of praisonai-sandbox and praisonai-deploy carry a vendored fallback copy of PluginRegistry that runs when praisonai-code isn’t importable (pip install praisonai-sandbox on its own). Since PraisonAI PR #4184 both vendored copies enforce the same built-in check — an entry point whose name (case-insensitive) matches a built-in is skipped on that path too, so the guarantee holds in every install shape. Prior to PR #4184 a standalone praisonai-sandbox install still let a pip plugin replace docker, and praisonai-deploy let one replace aws.
The same rule runs on the vendored fallback path used by standalone praisonai-sandbox / praisonai-deploy installs:
| Path | Outcome |
|---|---|
| Entry point under the canonical group, name collides with a built-in | Skipped; built-in kept. A DEBUG (not WARNING) line notes the collision. |
| Entry point under a deprecated/legacy group, name collides with a built-in | Skipped; built-in kept (DEBUG log) — same rule, applied on the legacy path since PR #4185. |
Entry point discovered by a vendored fallback copy (standalone praisonai-sandbox / praisonai-deploy), name collides with a built-in | Skipped; built-in kept (DEBUG log) — same rule, applied on the fallback path since PR #4184. |
| Entry point under a legacy group, novel name | Added, and emits a DeprecationWarning naming the canonical group. |
| Entry point, novel name | Added; resolves and appears in listings. |
Runtime register(...) | Overrides a built-in — deliberate dependency injection. |
| Matching | Case-insensitive — OpenAI and openai are the same key. |
Plugin-provided tool (Plugin.get_tools()) collides with an agent tool (Agent(tools=[...])) | Agent tool wins; the plugin’s same-named tool is skipped with a WARNING log — never silently shadowed. See Tool Plugins. |
Every Registry Is Guarded
The two groups that matter most decide where user code executes: praisonai.sandbox and praisonai.managed_backends. A pip-installed package cannot silently take over docker — whether via the canonical base class (PR #4176), the legacy group spelling (PR #4185), or the vendored fallback copy that runs when praisonai-sandbox / praisonai-deploy is installed standalone (PR #4184).
| Entry-point group | Registry class | Runtime accessor | Example guarded built-ins |
|---|---|---|---|
praisonai.llm_providers | LLMProviderRegistry | get_default_llm_registry() | openai, anthropic, google, ollama, ollama_chat, lm_studio, vllm, hosted_vllm |
praisonai.sandbox | sandbox registry | (sandbox registry) | docker, local, e2b |
praisonai.managed_backends | ManagedBackendRegistry | get_backend_registry() | docker |
praisonai.deploy.providers | deploy registry | (deploy registry) | aws, azure, gcp, fly, railway, render |
praisonai.tools.search | SearchProviderRegistry | (search registry) | tavily |
praisonai.endpoints.providers | EndpointProviderRegistry | get_default_registry() | mcp |
praisonai.external_agents | ExternalAgentRegistry | get_default_registry() | claude, gemini, codex, cursor |
praisonai.tool_sources / praisonai.tools | ToolSourceRegistry | (tool resolver) | all built-in tool sources |
praisonai.integrations | IntegrationRegistry | get_integrations_registry() | all built-in integration names |
praisonai.persistence | StoreRegistry | get_default_registry(kind) | all built-in stores |
| (framework adapters) | FrameworkAdapterRegistry | get_default_registry() | all built-in adapters |
Compatibility
Best Practices
Namespace your plugin names
Prefix or vendor-qualify a plugin name (acme-openai, corp.pay_invoice) so it never collides with a built-in on any surface, present or future.
Reserve register(…) for deliberate overrides
A runtime register(...) call is in-process and explicit — exactly what you want when overriding a built-in is intentional (a tenant-specific provider, a test double). It cannot happen by accident through packaging.
Check collisions case-insensitively
OpenAI, openai, and OPENAI are one key. When you audit a registry’s built-ins before naming a plugin, lowercase both sides.
Debug with DEBUG logging
A skipped collision writes a DEBUG line, not a warning. Run with LOGLEVEL=DEBUG to confirm which built-in kept its name.
Related
praisonai.integrations entry points and built-in precedence
claude, gemini, codex, cursor are protected names
Built-in tool sources win over entry-point sources
Tool Discovery OrderWhere the plugin layer sits, and why built-ins outrank it
Compute Provider PluginsSandbox / managed-backend names decide where code runs
Custom LLM ProviderOverride openai / anthropic / google at runtime
Write, load, and ship plugins as pip packages
Pure ModeSkip entry-point discovery for a single run