Extension authoring

4 min read 6 glossary terms 7 connected pages not in the official sidebar
The short version auto-extract

Phoenix is an optional built-in extension that registers typed configuration and contributes a container definition to Transmutation. Follow it from selection through registration, Rune loading, Quadlet projection, and tests to learn where a contribution…

Words on this page, decoded click one for its card

The original text Underlined words open their plain-English card.

Phoenix is an optional built-in extension that registers typed configuration and contributes a container definition to Transmutation. Follow it from selection through registration, RuneService Config File loading, Quadlet projection, and tests to learn where a contribution belongs and what evidence a change needs.

The extension index maps the Extension DomainsPlug-in Areas. ADR 05 owns admission and compatibility; ADR 12 owns configuration. This guide follows one package through those contracts.

First follow the contribution guide's Setup and Commands, then run this focused test selection from the checkout:

make test PYTEST_TARGETS="tests/integration/test_extension_configuration.py tests/unit/extensions" N=0

The configuration integration test creates its own temporary CodexSettings Folder and CryptData Folder directories and runs separate Python interpreters. The snippets below explain that fixture's selection, configuration, and projection path. In an installation, lychd.toml and RuneService Config File documents live beneath the operator's CodexSettings Folder; Codex owns that operating workflow.

Select and register Phoenix

In the test's temporary CodexSettings Folder, lychd.toml selects Phoenix with:

[extensions]
builtins = ["observability/phoenix"]
crypt = []

Both selections default to empty. A built-in must have an explicit entry in the catalogue; adding a directory alone does not make it selectable.

Read Phoenix's registration shim. Its synchronous register(context: ExtensionRegistrationContext) -> None hook lazily imports ObservabilityConfig, PhoenixSettings, and PhoenixQuadletContributor, then registers them:

context.runes.add_schema(ObservabilityConfig)
context.runes.add_schema(PhoenixSettings)
context.transmutation.add_contributor(PhoenixQuadletContributor())

After registration, the host marks these contributions with builtin:observability/phoenix. This Registrant identity lets a reader trace them back to the selected package while each receiving store enforces its own contract. Selection makes Phoenix available during assembly. An active RuneService Config File is still needed before it contributes a container.

Give the contribution a typed Rune

Phoenix's configuration module defines a branch and a leaf. ObservabilityConfig has path_fragment = Path("observability"); PhoenixSettings adds Path("phoenix"), producing the relative path observability/phoenix. The branch cannot own TOML; the leaf can.

A RuneService Config File is a frozen, validated top-level TOML instance. The fixture writes its active document at <Codex>/runes/observability/phoenix/desk.toml. This example shows the same configuration shape with the default image:

name = "desk-eye"
ui_port = 16006
otlp_port = 14317

[quadlet]
image = "docker.io/arize-ai/phoenix:latest"

PhoenixSettings rejects unknown fields and validates name as a safe service identity. The example overrides the default name and ports; its image line repeats the typed Quadlet setting's default. It is a configuration example, with no image pull or live-service receipt. The loader derives source_file from the document's path; do not put that field in TOML.

The schema also supplies a sample_template. Its rendered sample is inactive, identified by the exact first nonempty line:

# lychd: sample-rune

Activation requires a configured document with that marker removed. A selected Phoenix package with an absent or marked sample RuneService Config File still produces no Phoenix container. Codex owns RuneService Config File and binding operations.

Follow the Rune into Quadlet data

Read the Phoenix contributor next. It requests ctx.runes.one_or_none(PhoenixSettings). No instance produces an empty contribution; more than one raises an error.

With exactly one active instance, the contributor creates typed QuadletContribution data for one Phoenix container and two pod ports. Trace each value to its source: the RuneService Config File supplies service name, image, and host ports; Settings supplies the database user and Phoenix secret reference. The container includes migration-gate dependencies, and Transmuter places the host ports on loopback.

This output is projection data. The binding workflow separately owns publication and host reconciliation. Follow External Eyes for Phoenix's observability boundary: this example establishes neither a running Phoenix service nor an application telemetry export path. Phoenix contributes no AnimatorModel Runtime runtime or CallGrant service.

Test the complete configuration path

The configuration integration test follows the real Settings, extension manager, RuneService Config File loader, and Transmuter across separate Python interpreters. Its assertions cover:

  • Default-disabled selection leaves the Phoenix shim unimported.
  • Selected Phoenix with an inactive sample has the exact schemas, contributor, and provenance.
  • An active RuneService Config File is frozen, retains its exact source_file, and projects its configured service name, image, ports, and Settings-owned database values.
  • Editing the Settings file leaves the current process's cached generation unchanged.
  • A fresh disabled process produces no Phoenix contribution while the RuneService Config File and representative retained data bytes survive.
  • An unselected CryptData Folder shim that would fail if imported remains unimported.

The fixture writes TOML directly. It does not execute the writer's Linux journaled inscription, bind units, or prove database recovery. Detailed registration collisions and import failures belong to the admission tests. For a contribution that supplies ana AnimatorModel Runtime, the Crypt vertical test continues through its separate adapter and DispatcherTraffic Controller contracts.

Apply the registration contract in Crypt

A private package is selected explicitly:

[extensions]
builtins = []
crypt = ["my-private-organ"]

Its exact shim is PATH_EXTENSIONS_DIR/my-private-organ/register.py; Crypt explains the source location. An activation ID must be canonical and POSIX-relative, without traversal or aliases. The manager resolves only the selected register.py, with no environment or entry-point scan.

The shim uses the same synchronous register(context) contract and returns None. It may import relative siblings within its synthetic package namespace; adjacent unselected packages remain unimported. CryptData Folder code is trusted, runs in process, and shares memory. This pre-v1 path accepts coupling to internal refactors. Built-ins release with Core; a versioned independent extension API remains future work under ADR 05.

Finish a bounded contribution

Finish by testing a new process generation. get_settings() and get_extensions() retain the current generation, and registration stores seal after assembly; edits to code, dependencies, contribution contracts, or selection require restart or replacement. Removing Phoenix's selector in the new process removes its contribution while leaving retained RunesService Config Files and storage alone. This guide does not implement installation, uninstallation, rollback, or automated Evolution. State of Work records delivery.

Change the owned schema, registration, or contributor together with a test of the affected behavior, then rerun the focused selection above. Follow the contribution quality checks for the applicable lint, format, type, and repository gates. Describe the boundary the change establishes and the evidence that supports it. Follow the current collaboration phase for branch and submission procedure.