Architecture And Loading
The consumer has one stable command for normal use:
opamp-consumer --config-path ./opamp.json
That command enters opamp_consumer.client:main. The router is intentionally
small. It parses enough shared CLI/config state to know which service_type
has been requested, asks the plugin loader for the matching callable, injects
the loaded config into the target module when the module exposes CONFIG, and
then calls the plugin entry point.
For the full picture, keep the consumer client architecture diagram open. The “Runtime Entrypoints” diagram on that page shows the same flow visually.
Loading Flow
flowchart TD
A["opamp-consumer"] --> B["opamp_consumer.client:main"]
B --> C["load_config_from_cli_args(...)"]
C --> D["consumer.service_type"]
D --> E["plugin_loader.build_consumer_plugin_registry(config)"]
F["installed package entry points"] --> E
G["consumer.plugins config overlay"] --> E
E --> H{"service_type found?"}
H -->|"yes"| I["load plugin callable"]
I --> J["inject CONFIG when supported"]
J --> K["plugin main()"]
H -->|"no"| L["log error and fail startup"]
The code for this lives mainly in:
consumer/src/opamp_consumer/client.pyconsumer/src/opamp_consumer/plugin_loader.pyconsumer/src/opamp_consumer/config.pyconsumer/src/opamp_consumer/plugin_config.py
Plugin Registry Sources
The plugin registry has three sources.
Built-in plugins are loaded from:
consumer/src/opamp_consumer/builtin_consumer_plugins.json
That file is packaged with the consumer and is the source of truth for plugins
that ship in this repository, such as Fluent Bit, Fluentd, Elastic Agent,
Elastic Heartbeat, simulator, and Vector. Use it for in-repo built-ins instead
of adding a package entry point to consumer/pyproject.toml.
Installed package entry points are discovered from package metadata:
[project.entry-points."opamp_consumer.plugins"]
my_agent = "my_consumer_plugin.client:main"
Runtime config entries are read from consumer.plugins:
{
"consumer": {
"service_type": "my_agent",
"plugins": [
{
"service_type": "my_agent",
"entry_point": "my_consumer_plugin.client:main",
"enabled": true
}
]
}
}
The loader builds the registry in this order:
- built-in JSON mappings
- installed package entry points
- active config overlay from
consumer.plugins
Later sources override earlier sources for the same service_type. That means
config can:
- add a plugin that is importable but has no package entry point
- override an installed plugin mapping
- disable an installed plugin by setting
enabledtofalse
This is deliberate. It gives packaged plugins a clean deployment story, while still making local tests and one-off experiments easy.
Service Type Names
service_type is the registry key. It is normalized to lowercase and stripped
of surrounding whitespace.
Good names are short and stable:
fluentbitfluentdelastic_agentsimulatormy_agent
Avoid putting version numbers, environment names, or deployment names into the service type. Those belong in config, package versions, or deployment metadata.
What Happens After Loading?
After the plugin main() runs, the plugin is responsible for the normal
consumer bootstrap:
- parse common CLI args with
build_common_cli_parser() - load config with
load_config_from_cli_args() - configure logging with
configure_logging_for_config() - log the startup banner with
log_consumer_startup_banner() - process plugin/agent-specific config
- construct the concrete client
- call the shared
run_client(...)loop
Fluent Bit uses run_default_client_main(...) because its startup flow is the
default shared path. Fluentd, Elastic Agent, and simulator have their own
main() functions because they need extra startup work.
CLI And Demo Integration
The OpAMP CLI is not the plugin registry. It starts processes and records them
for guided start, stop, restart, status, and demo workflows. Actual
plugin selection still belongs to the consumer config.
The CLI launch helper lives in:
cli/src/opamp_cli/consumer_plugins.py
Use these rules when exposing a plugin through CLI guided starts or demo profiles:
- Prefer
python -m opamp_consumer.clientfor plugin-routed consumers. - Put the plugin choice in the OpAMP JSON as
consumer.service_type. - Put the managed agent config path in that same JSON as
consumer.agent_config_path. - Use profile-level
agent_config_pathonly as a legacy or explicit override. - Reuse the generic helpers in
consumer_plugins.py; do not copy a new per-agent command builder body.
For a built-in plugin that should appear as a top-level guided target, add:
- an action id and label in
cli/src/opamp_cli/constants.py - that action id in
GUIDED_START_ACTION_ORDERandGUIDED_STOP_ACTION_ORDER - aliases in
GUIDED_ACTION_ALIASES - a default start wrapper in
cli/src/opamp_cli/consumer_plugins.pythat calls the generic_consumer_start_action(...) - start/stop registration in
cli/src/opamp_cli/main.py - tests for action ordering and generated argv
For demo-only support, update cli/config/demo_consumer_profiles.json and add a
thin wrapper that calls _demo_consumer_start_action(...) with the component
key, label, and module name. Demo profiles should normally specify only the
consumer config_path; the referenced JSON should carry service_type and
agent_config_path.
OpAMP