| name | ax-provider-system |
| description | Use when adding new provider categories, modifying provider loading, plugin infrastructure, or understanding the provider contract pattern -- registry.ts, provider-map.ts, provider-sdk, and the create(config) convention |
Overview
AX uses a provider contract pattern: every subsystem is a TypeScript interface with pluggable implementations. There are 15 provider categories. Implementations are selected by name in ax.yaml, resolved via a static allowlist (provider-map.ts), and instantiated by registry.ts calling each module's create(config) export. This enforces SC-SEC-002 -- no dynamic path construction. Third-party providers are supported via the plugin system and provider SDK.
The Contract
- Each category lives in
src/providers/<category>/ with a co-located types.ts defining the interface.
- Each implementation exports
create(config: Config) returning the provider instance.
provider-map.ts maps (kind, name) pairs to static import paths.
registry.ts resolves the path, imports the module, and calls mod.create(config).
Provider Categories
| Category | Interface | Directory |
|---|
| llm | LLMProvider | src/providers/llm/ |
| memory | MemoryProvider | src/providers/memory/ |
| channel | ChannelProvider | src/providers/channel/ |
| web | WebProvider | src/providers/web/ |
| credentials | CredentialProvider | src/providers/credentials/ |
| skills | SkillStoreProvider | src/providers/skills/ |
| audit | AuditProvider | src/providers/audit/ |
| sandbox | SandboxProvider | src/providers/sandbox/ |
| scheduler | SchedulerProvider | src/providers/scheduler/ |
| database | DatabaseProvider | src/providers/database/ |
| storage | StorageProvider | src/providers/storage/ |
| eventbus | EventBusProvider | src/providers/eventbus/ |
| workspace | WorkspaceProvider | src/providers/workspace/ |
| mcp | McpProvider | src/providers/mcp/ |
| auth | AuthProvider | src/providers/auth/ |
Provider Map (SC-SEC-002)
src/host/provider-map.ts:
- Built-in allowlist (
_PROVIDER_MAP): frozen record mapping every valid (kind, name) to a relative import path. resolveProviderPath() throws on missing entries.
- Plugin registry (
_pluginProviderMap): separate runtime allowlist for third-party plugins. Functions: registerPluginProvider(), unregisterPluginProvider(), listPluginProviders(), clearPluginProviders().
- URL scheme guard: Post-resolution
assertFileUrl() ensures all resolved paths are file:// URLs (defense-in-depth against protocol confusion).
- Resolution order: Built-in allowlist checked first, then plugin registry.
Registry
src/host/registry.ts exports loadProviders(config, opts?) returning a ProviderRegistry:
- Reads provider names from
config.providers.*
- Three loading patterns based on provider needs:
- Simple:
loadProvider(kind, name, config) — resolveProviderPath → import → mod.create(config, name). Used by web, credentials, sandbox, eventbus, workspace.
- Manual import with options: resolve path, import, call
mod.create(config, name, { ...deps }). Used by providers that need injected dependencies:
- memory gets
{ llm, database, eventbus }
- skills gets
{ storage }
- storage gets
{ database }
- audit gets
{ database }
- Custom:
loadScheduler(config, database, eventbus) — scheduler has its own create(config, { database, eventbus }) shape.
- mcp gets
{ database, credentials } (via manual import, not loadProvider)
- Loading order matters: credentials → database → LLM → skills → eventbus → memory → storage → audit → workspace → mcp → everything else
- Channels load as an array (
config.providers.channels is string[])
- Tracing wrapper: LLM provider wrapped with
TracedLLMProvider when OTEL_EXPORTER_OTLP_ENDPOINT is set
- Plugin host integration: Optional
opts.pluginHost calls pluginHost.startAll() before loading (registers plugin providers)
Provider SDK (for third-party plugins)
src/provider-sdk/ provides a public SDK package for third-party provider authors:
src/provider-sdk/index.ts -- Main entry re-exporting all interfaces, test harness, and utilities
src/provider-sdk/interfaces/index.ts -- Re-exports all provider type interfaces from canonical src/providers/*/types.ts
src/provider-sdk/testing/harness.ts -- ProviderTestHarness for validating provider implementations against the contract
src/provider-sdk/testing/fixtures/ -- Ready-made test fixtures (memory)
src/provider-sdk/utils/safe-path.ts -- Re-exported safePath() for plugin authors
Shared Provider Types
src/providers/shared-types.ts: Re-export hub for types used across multiple provider categories. Prevents cross-provider directory imports.
src/providers/router-utils.ts: parseCompoundId() utility shared by LLM router.
- Typed unions:
provider-map.ts exports typed name unions for each category: LLMProviderName, MemoryProviderName, ChannelProviderName, WebProviderName, CredentialProviderName, SkillsProviderName, DatabaseProviderName, AuditProviderName, SandboxProviderName, SchedulerProviderName, StorageProviderName, EventBusProviderName, WorkspaceProviderName, McpProviderName, AuthProviderName. Used in Config.providers for type-safe config.
Common Tasks
Adding an implementation to an existing category
- Create
src/providers/<category>/<name>.ts implementing the category interface.
- Export
create(config: Config) returning the provider instance.
- Add the
(kind, name) entry to PROVIDER_MAP in src/host/provider-map.ts.
- Add a test in
tests/providers/<category>/<name>.test.ts.
Adding an entirely new provider category
- Create
src/providers/<category>/types.ts with the provider interface.
- Create at least one implementation file exporting
create(config).
- Add the category to
PROVIDER_MAP in src/host/provider-map.ts.
- Add the provider name field to
Config.providers in src/types.ts.
- Add the typed field to
ProviderRegistry in src/types.ts.
- Add the
loadProvider() call in registry.ts's loadProviders().
- Add the interface to
src/provider-sdk/interfaces/index.ts for third-party usage.
- Add tests in
tests/providers/<category>/.
Gotchas
- Static allowlist is mandatory. Skipping
provider-map.ts means a runtime throw.
ProviderRegistry must match. Forgetting the field in src/types.ts causes compile errors.
- Co-located
types.ts. Each category owns its interface. Shared types live in src/types.ts or src/providers/shared-types.ts.
channels is an array. config.providers.channels is string[], returning ChannelProvider[].
create() validated at runtime. loadProvider() throws if the export is missing.
- Use
safePath() for any file-based provider constructing paths from input.
- Tracing is opt-in. LLM provider only wrapped when
OTEL_EXPORTER_OTLP_ENDPOINT is set.
- Plugin providers need integrity verification. Must pass SHA-512 hash check before registration.
- Don't import across provider categories. Use
shared-types.ts or router-utils.ts instead.
- Providers with deps use manual import. Memory, skills, storage, audit all receive injected dependencies via the third
options arg to create(). Simple providers (web, credentials, etc.) go through loadProvider().