Ciaren

Plugin API Reference

Plugin API Reference

This is the reference for app.plugin_api — the versioned contract a plugin depends on (and which will publish separately as ciaren-plugin-api). It is still alpha and may change between releases until 1.0.0. A plugin imports only from this package, never from Ciaren's app, engine, or FastAPI internals.

New to plugins? Start with the Overview and the 10-minute tutorial; this page is the detailed contract.

The contract itself is versioned independently of the app: app.plugin_api.PLUGIN_API_VERSION (currently "0.1.0-alpha.1"). A plugin declares which contract it targets via its manifest's api_version; the loader rejects a plugin whose contract is incompatible with the running backend before importing it. Pre-1.0 (alpha) the contract makes no backward-compatibility promise — a plugin must target the exact major.minor the backend provides; from 1.0 on, minors become additive and only a major bump breaks. The backend's own value is exposed as plugin_api_version in GET /api/plugins/diagnostics. See Contract versioning for the full policy.

The 0.1 contract surface

ModelRef and typed model wires, ModelProvider/ModelTypeSpec (contribute trainable model types to the ML catalog), NodeContext/ModelStore (NodeRuntime.execute_with_context), executable connectors (ConnectorRuntime + ConnectorProvider.connector_implementations), and schema-driven forms (ConfigFieldSpec / config_schema).

from app.plugin_api import (
    Plugin, PluginMetadata, ServiceRegistry,
    NodeProvider, NodeSpec, NodeRuntime, NodeContext, ModelStore, PortSpec, Permission,
    ConnectorProvider, ConnectorSpec, ConnectorRuntime, ConnectorTestResult,
    ModelProvider, ModelTypeSpec, ModelRef,
    ConfigFieldSpec, validate_config_schema,
    StorageProvider, StorageSpec,
    ExecutionProvider, ExecutionSpec, ExporterProvider, ExporterSpec,
    ValidatorProvider, ValidatorSpec, AIProvider, AICapabilitySpec,
    AuthProvider, AuthMethodSpec, LicenseProvider, LicenseStatus,
)

Plugin

The entry point a plugin package exposes. The loader instantiates it and calls register().

MethodSignaturePurpose
metadata() -> PluginMetadataIdentity and headline contributions.
register(registry: ServiceRegistry) -> NoneRegister one or more providers on the supplied registry.
class GreetingPlugin(Plugin):
    def metadata(self) -> PluginMetadata: ...
    def register(self, registry: ServiceRegistry) -> None:
        registry.register_node_provider(_GreetingNodeProvider())

ServiceRegistry

Passed to Plugin.register(). Register each provider you implement:

MethodRegisters
register_node_provider(provider)a NodeProvider
register_connector_provider(provider)a ConnectorProvider
register_model_provider(provider)a ModelProvider
register_storage_provider(provider)a StorageProvider
register_execution_provider(provider)an ExecutionProvider
register_exporter_provider(provider)an ExporterProvider
register_validator_provider(provider)a ValidatorProvider
register_ai_provider(provider)an AIProvider
register_auth_provider(provider)an AuthProvider
register_license_provider(provider)a LicenseProvider

Provider interfaces

Each provider is an ABC. Implement the ones you need; a single plugin can implement several. Providers return serializable specs (for the catalog) and, where relevant, opaque implementations (duck-typed by the engine).

ProviderAbstract methodReturns
NodeProvidernodes()list[NodeSpec]
node_implementations() (optional)dict[str, NodeRuntime] — node id → runtime
ConnectorProviderconnectors()list[ConnectorSpec]
connector_implementations() (optional)dict[str, ConnectorRuntime] — connector id → runtime
ModelProvidermodel_types()list[ModelTypeSpec]
model_builders() (optional)dict[str, callable] — model type id → estimator builder
StorageProviderstorage_backends()list[StorageSpec]
ExecutionProviderexecution_backends()list[ExecutionSpec]
ExporterProviderexporters()list[ExporterSpec]
ValidatorProvidervalidators()list[ValidatorSpec]
AIProviderai_capabilities()list[AICapabilitySpec]
AuthProviderauth_methods()list[AuthMethodSpec]
LicenseProvidervalidate_license(plugin_id)LicenseStatus

A catalog-only NodeProvider may omit node_implementations() (the node appears but isn't executable); return a {node_id: NodeRuntime} map to make it run.

NodeRuntime

The runnable side of a plugin node. The contract is pandas-based and engine-agnostic: you receive and return pandas DataFrames keyed by handle, and Ciaren converts to/from the active engine (polars/pandas) around the call.

MethodSignatureNotes
validate_config(config) -> NoneRaise ValueError on invalid config. Default: accept anything.
execute(inputs, config) -> dict[str, Any]inputs maps input-handle → DataFrame; return output-handle → DataFrame.
execute_with_context(inputs, config, context: NodeContext) -> dict[str, Any]Ciaren's actual entry point; the default delegates to execute. Override this one instead when the node needs host services (ModelStore, preview flag).
imports(config) -> list[str]Extra top-level import lines the exported script needs.
to_python_code(input_vars, output_vars, config) -> str | NoneReadable pandas code (df variables), or None if not exportable.

Override execute or execute_with_context — never neither. Handle conventions match the node's NodeSpec: a single-input node reads inputs["in"] and returns {"out": frame}.

class AddGreetingRuntime(NodeRuntime):
    def execute(self, inputs, config):
        df = inputs["in"].copy()
        df[config.get("column") or "greeting"] = "Hello!"
        return {"out": df}

NodeContext

Host services passed to execute_with_context:

FieldTypeNotes
plugin_idstrThe plugin the node belongs to.
permissionsfrozenset[Permission]Permissions the user actually granted (not what the manifest requested).
modelsModelStore | NoneMLflow-backed model persistence; None when the server has no ML support installed.
in_previewboolTrue during editor previews on sampled data — skip training/persisting and return a cheap placeholder, like the core train nodes do.
license_tokenstrThe plugin's own signed license token as raw JSON ("" when it has none). Forward it to your server to build a thin-client paid node — see below. Only the plugin's own token is ever exposed.

Thin-client plugins

For a paid plugin whose logic you don't want to ship to the user's disk (or whose license must be unskippable), keep the work on your server and make the node a thin client. Forward context.license_token with each request and validate it server-side — signature, expiry, revocation, per-user rate limits and quotas — where the user can't patch the check out:

class PremiumRuntime(NodeRuntime):
    def execute_with_context(self, inputs, config, context):
        resp = httpx.post(
            config["endpoint"],                    # a secret (env-var) config field
            json={"token": context.license_token, "data": inputs["in"].to_json()},
            timeout=30,
        )
        if resp.status_code == 402:                # quota exhausted / not entitled
            raise ValueError(resp.json()["detail"])
        resp.raise_for_status()
        return {"out": pd.read_json(resp.text)}

The node needs the network permission, and endpoint should be a secret config_schema field (an env-var name). See Plugin Security for why this is the only robust way to protect plugin logic and enforce per-buyer limits.

ModelStore

The sanctioned persistence path for plugin-trained models — estimators become MLflow artifacts; only a typed ModelRef travels through the graph.

MethodNotes
log_sklearn_model(model, *, model_type, task_type, target_column=None, feature_columns=(), params=None, metrics=None, input_example=None, experiment=None, preprocessing=None, seed=None, training_config=None) -> ModelRefPersist a fitted sklearn-compatible model to MLflow and return its reference. Raises when it cannot persist (never silently emits a dangling reference). The reference's model_config_json is part of the model-wire contract: it records the same shape the core train nodes emit (model_type, target_column, feature_columns, hyperparameters from params, preprocessing, seed) so core consumers like Cross-Validate can rebuild the estimator; training_config entries overlay the generated config.
load_model(ref_or_uri) -> AnyLoad after the host's security checks. Deserializing executes pickled code, so it is permission-gated: MLflow URIs need local_model_load (or joblib_load); a local .joblib path needs joblib_load and must live inside the server's artifact root. .pkl/.pickle are always refused.

ModelRef

The typed payload of a model wire — a frozen dataclass with a one-row-frame round-trip. The core train nodes emit exactly this frame, and the core consumers (mlPredict, featureImportance, mlCrossValidate) read it, so plugin and core models interoperate in both directions.

MemberNotes
task_type, model_typee.g. "classification", "mlp_classifier".
mlflow_run_id, model_uriWhere the artifact lives (runs:/… / models:/…); None for definition-only/preview references.
target_column, feature_columns, training_configWhat the model was trained on.
to_frame() / from_frame(frame)Convert to/from the one-row pandas frame carried on a model handle (MODEL_REF_COLUMNS is the public column layout).

ConnectorRuntime

The runnable side of a plugin connector — pandas-based like NodeRuntime. config is the saved connection flattened to a plain mapping (host, port, database, username, password resolved from the env var for the one call, options); options on read/write carries the flow node's config plus limit for bounded preview reads. Only read is required; unimplemented optional methods surface as a clear "not supported by this connector" error.

MethodNotes
test(config) -> ConnectorTestResultCheap reachability/auth check for the Connections page.
list_tables(config) -> list[dict]{"name", "schema", "row_count"} entries (query-style connectors).
list_objects(config, prefix="") -> list[str]Object/file names (storage-style connectors).
read(config, options) -> DataFrameRequired. Backs sqlInput (sql/api kinds) or storageInput (storage kind).
write(frame, config, options) -> NoneBacks sqlOutput / storageOutput.

ModelTypeSpec

Describes a trainable model type contributed to the ML catalog; it appears in the matching core train node's picker and trains through the core pipeline.

FieldNotes
idThe model_type id (unique across the catalog), e.g. "mlp_classifier".
label, task, supervised, provider, descriptionCatalog metadata; task is one of classification/regression/clustering/dimensionality_reduction/timeseries.
requires, install_hintImportable modules the builder needs; the hint shown when missing.
default_hyperparameters, hyperparameter_schemaDrive the model picker's hyperparameter form (same field dialect as config_schema). The defaults are also merged under the user's values before your builder is called, so an untouched form trains with what the catalog advertises.
import_linesTop-level imports exported training scripts use for the estimator. When empty, the import is derived from the estimator's class module — declare them whenever the estimator's repr needs anything beyond that.

The matching builder (from ModelProvider.model_builders()) is (hyperparameters: dict, seed: int | None) -> estimator and must return an sklearn-compatible estimator. Hyperparameters arrive sanitized to JSON-native values; inject the seed unless the user set one explicitly.

ConfigFieldSpec and config_schema

A deliberately small, UI-oriented form dialect (not full JSON Schema), shared by plugin node config forms (NodeSpec.config_schema), connector connection forms (ConnectorSpec.config_schema), and model hyperparameter forms (ModelTypeSpec.hyperparameter_schema). Shape: {"fields": [ … ]} where each field validates as:

FieldNotes
keyConfig key the field reads/writes.
label, help, placeholderPresentation (label defaults to the key).
typestring · number · integer · boolean · select · string_list · column · column_list (column kinds resolve against the node's incoming wire; node forms only).
required, defaultBehavior for fresh configs and validation.
optionsChoices (select only).
min / maxBounds (number/integer).
secretRender masked — for env-var names; Ciaren never stores secret values.

A malformed schema fails at registration (validate_config_schema), never at render time.

Spec types

Specs are Pydantic models — JSON-serializable descriptions of what a plugin contributes. They carry no executable behavior.

NodeSpec

FieldTypeDefaultNotes
idstrNode type, unique in the catalog (e.g. "greeting.add").
labelstrDisplay name.
categorystr"plugins"UI grouping ("input", "clean", "columns", "reshape", "chart", "ml", …); unrecognized values normalize to "plugins".
descriptionstr""Shown in the palette/inspector.
providerstr"ciaren.core"Namespaced provider id.
versionstr"1.0.0"Node version.
inputstuple[PortSpec, …]()Input handles.
outputstuple[PortSpec, …]()Output handles.
default_configdict{}Config for a freshly-created node.
capabilitiestuple[str, …]()Capabilities needed at run time.
permissionstuple[Permission, …]()Permissions the node needs.
requires_mlboolFalseOnly available when built-in ML is enabled.
is_model_sinkboolFalseTerminal that persists a model (e.g. a train node).
is_flow_terminalboolFalseNode can complete a valid flow without a downstream output node.
config_schemadict{}Schema-driven sidebar form — see ConfigFieldSpec. Without one, the editor infers editable fields from default_config.

PortSpec

FieldTypeDefaultNotes
idstrHandle id (e.g. "in", "out", "train").
type"dataframe" | "model""dataframe"A model output may only feed a model input.
requiredboolTrueWhether an incoming edge is required (inputs).
multiboolFalseAccept multiple incoming edges (e.g. concat).

Other specs (shared shape)

ConnectorSpec, StorageSpec, ExecutionSpec, ExporterSpec, ValidatorSpec, AICapabilitySpec, and AuthMethodSpec all carry an id, a label, a provider, and capabilities, plus a few type-specific fields:

  • ConnectorSpeckind ("sql" / "mongo" / "storage" / "mlflow", or your own, e.g. "api" — sql-ish kinds back the SQL nodes, storage backs the storage nodes), available, driver_module, extra (pip extra for the install hint), permissions, metadata (form flags: host/port/auth/bucket/…), and config_schema (extra connector-specific form fields, stored in the connection's options).
  • ExecutionSpecavailable (an engine name the executor understands).
  • ExporterSpecformat (e.g. "python") and file_extension.
  • ValidatorSpec / AICapabilitySpec — a description.

PluginMetadata

id, name, version, publisher, description, capabilities, permissions.

LicenseStatus

Returned by LicenseProvider.validate_license(): plugin_id, valid, license_type, expires_at, reason.

Permission

Permissions a plugin may request in its manifest. They are a trust and UX boundary surfaced before a plugin is enabled — not a hard sandbox. Model-load is enforced by the host, and an opt-in audit-hook mode (CIAREN_PLUGIN_PERMISSION_ENFORCEMENT=warn|enforce) can log or block ungranted network/filesystem-write/subprocess/shell actions at runtime. See Plugin Security & Permissions.

filesystem_read · filesystem_write · network · credentials · subprocess · shell · docker · local_model_load · joblib_load · database_access · cloud_access · llm_access · telemetry

See also