v0.4.0

Extending

Plugin SPIs, the forward-compatibility constraints that keep out-of-process plugins on the table, and external-extension patterns built on the HTTP API.

Podium is extensible at two layers:

  • In-process plugins. Swap or augment the registry's own behavior (different stores, different identity providers, different lint rules) by implementing a Go interface and being compiled into a registry build. See SPIs.
  • External extensions. Build on the registry's HTTP API, SDKs, and CLI without changing the registry itself: programmatic curation scripts, webhook receivers, custom CI checks, layer source bridges. See External extensions.

Most teams reach for external extensions first. SPI plugins are for cases where the registry's own behavior needs to change.


SPIs

The registry's pluggable interfaces:

InterfacePurpose
RegistryStoreManifest metadata, dependency edges, layer config, admin grants, and registry-side audit. Built-ins: sqlite and postgres.
RegistryObjectStoreBundled resource bytes and presigned URLs. Built-ins: the local filesystem and S3-compatible storage.
RegistrySearchProviderHybrid retrieval for search_artifacts. Built-ins: pgvector, sqlite-vec, pinecone, weaviate-cloud, qdrant-cloud. See Vector backends for the per-backend recipes.
EmbeddingProviderGenerates embeddings for ingest text and query text. Built-ins: openai, voyage, cohere, ollama.
LocalSearchProviderOptional semantic backing for the local-overlay index. Same SPI as RegistrySearchProvider.
RegistryAuditSinkStream for catalogue events; logically distinct from RegistryStore, separately mockable, separately routable.
LayerComposerResolves the caller's effective view from the configured layer list; applies merge semantics and extends: resolution.
LayerSourceProviderResolves and watches the source backing a layer. Built-ins: git, local. Custom backends: S3 versioned buckets, OCI registries, HTTP archives, internal CMS bridges.
GitProviderWebhook signature verification and Git fetch semantics, used by the built-in git LayerSourceProvider. Built-in support for GitHub, GitLab, Bitbucket.
TypeProviderType definitions: frontmatter JSON Schema + lint rules + adapter hints + field-merge semantics.
IngestLinterManifest validation, resource-reference checks, type-specific rules; runs pre-merge in CI and again at registry ingest.
IdentityProviderAttaches the caller's attested identity to every registry call. Client-side built-ins acquire the token at the consumer: oauth-device-code and injected-session-token. Registry-process built-ins resolve identity from the request at the registry: oidc-jwt and trusted-headers, covered in Gateway-delegated identity.
LocalOverlayProviderSource for the workspace-scoped local overlay layer. Default: workspace filesystem (.podium/overlay/).
LocalAuditSinkLocal audit log for meta-tool calls (when configured). Default: JSON Lines file at ~/.podium/audit.log.
HarnessAdapterTranslates canonical artifacts to the harness's native format at materialization time. The adapter Source carries a plugin descriptor (name, optional description, harness subtree prefix) so a marketplace emitter can render an artifact into a named plugin when podium sync renders a kind: marketplace target.
MaterializationHookPer-file pre-write transformation of materialized output. Use cases: redact secrets, rewrite paths, inject team-specific headers, enforce content policy.
NotificationProviderDelivery for ingest-failure and operational notifications. Built-ins: log, webhook, email (over SMTP, also accepted as smtp), and multi, which combines the log provider with whichever of webhook and email is configured. PODIUM_NOTIFICATION_PROVIDER selects one; an unset variable and noop both wire no notifier.
SignatureProviderArtifact signing and verification. Built-ins: noop, registry-managed, and sigstore-keyless. podium sign and podium verify read PODIUM_SIGNATURE_PROVIDER and fall back to noop. Registry-side ingest signing is off unless the registry starts with --sign registry-key, which signs each accepted manifest with a registry-managed key.

Marketplace publishing adds no new SPI. The git workflow a kind: marketplace sync target runs is operator-configured shell commands rather than a pluggable interface, so there is no write-side git provider. The only SPI surface publishing touches is the HarnessAdapter Source plugin descriptor noted above. See Consuming → Marketplace publishing.


Plugin distribution

Plugins ship as Go modules importable into a registry build. A deployment that needs a custom IdentityProvider or GitProvider builds a registry binary from source with the plugin imported.

A community plugin registry is hosted at the project's public URL.


Forward compatibility for out-of-process plugins

Plugins today are in-process Go modules. A future release may add an out-of-process plugin protocol (subprocess over stdin/stdout, gRPC, or similar) so plugins can ship as separate binaries: closed-source plugins, plugins written in other languages, plugins distributed without a registry rebuild.

The SPIs are designed today to make that transition source-compatible. Plugin authors who follow the constraints below will be able to ship the same plugin in-process now and out-of-process later, without code changes to the plugin's interface contract.

Constraints on every SPI method:

  • Cancellable. Every method takes a context.Context (or equivalent) as the first parameter. Long-running work checks for cancellation; deadlines are respected.
  • Wire-serializable inputs and outputs. Every argument and return value is structurally serializable: primitives, slices, maps, and structs whose fields are themselves serializable. No Go channels, no closures, no func types, no interface{} without a stable encoding, and no opaque pointers to in-process state.
  • No shared in-process state across calls. State the plugin needs across calls is passed explicitly per method (e.g., a session token, a snapshot ID, a cursor). Plugins MUST NOT rely on package-level variables, singletons, or registered callbacks set at init time.
  • Structured errors. Failures use a structured envelope ({code, message, retryable, details}) rather than opaque Go error chains. Codes use the namespacing documented in Error codes.
  • Restartable long-lived operations. Subscriptions, watchers, and streaming results are modeled as cursor-style protocols (the registry holds the cursor; the plugin can be killed and respawned without losing track of where it was). Push-style callback registration is avoided in favor of pull-style polling or explicit re-subscribe with a resume token.
  • Idempotent retries. Methods are safe to retry on transient failure. Where a method has side effects, it accepts an idempotency key.
  • Bounded payloads. Method arguments and return values declare reasonable size limits. Payloads larger than the limit use a content-addressed reference (cache key, presigned URL) rather than inline bytes.

The default implementations (RegistryStore, HarnessAdapter, LayerSourceProvider, etc.) conform to these constraints today, even though they run in-process. The motivation is forward compatibility rather than present-day distribution: when the out-of-process protocol lands, no built-in needs reshaping.

This section commits to keeping SPIs wire-friendly. It does not commit to a specific transport (subprocess, gRPC, Wasm) or a timeline.


External extensions

The registry's HTTP API, SDKs, CLI, and outbound webhook stream are designed to be composed into team-specific tooling without touching the registry binary.

Programmatic curation (semantic discovery + scoped sync)

A script picks artifacts based on whatever context is meaningful (semantic match against a query, the user's recent work, the active project, an upstream ticket) and then invokes podium sync with --include flags to materialize the selected set. The script owns the discovery logic; Podium owns the materialization (visibility filtering, extends: resolution, harness adaptation, audit). The on-disk result is reproducible from the include list.

See Custom consumers via the SDK → Programmatic curation for a worked example.

Webhook-driven integrations

Receivers for the outbound webhooks feed Slack channels, ticket trackers, deployment pipelines, and internal dashboards. The registry delivers artifact.published, artifact.deprecated, domain.published, layer.ingested, and layer.history_rewritten to a receiver. Read events such as domains.searched are recorded in the audit log and are never delivered to a receiver. The registry emits the events; the receiver decides what to do.

Common targets:

  • Notify owners on artifact.deprecated.
  • Page on-call on layer.history_rewritten.
  • Kick off a downstream rebuild on artifact.published matching certain paths.

Triggering CI from a webhook (repository_dispatch relay)

A common receiver triggers a CI job from a registry event. The marketplace-publish trigger is one case: a receiver filtered to layer.ingested triggers a CI job that runs podium sync --config against a kind: marketplace target (see Consuming → Marketplace publishing). GitHub starts a workflow from an external system only through the authenticated REST API (repository_dispatch or workflow_dispatch), and a Podium receiver posts an HMAC-signed event body that GitHub's dispatch endpoint does not accept. So a small operator relay bridges the two: the receiver posts the signed event to the relay, the relay verifies the HMAC against the receiver secret, and the relay calls POST https://api.github.com/repos/<owner>/<repo>/dispatches with {"event_type":"podium-layer-ingested"} and a GitHub token. The workflow listens on repository_dispatch.

Set the receiver's debounce field to coalesce a burst of layer.ingested events into one batch delivery and one CI dispatch; with debounce unset the relay fires per event and the CI system's own concurrency control collapses the redundant runs. Registering the receiver requires the per-tenant admin role on the receiver CRUD endpoints, which return auth.forbidden for a non-admin caller. See HTTP API → Outbound webhooks for the receiver fields, the SSRF policy on the receiver URL, and the batch envelope.

Custom pre-merge CI

Each layer's source repo runs whatever CI checks the team wants (naming conventions, sensitivity sign-off, banned dependencies, structural rules) using podium lint plus team-specific scripts. These checks are out of Podium's scope; they're ordinary CI in the layer's source repository, gated by branch protection.

Layer source bridges

A script that pulls content from another system (a vendor SaaS, an internal CMS, a documentation generator) and writes it into a local-source layer's filesystem path. The registry ingests via podium layer reingest <id> (manually or on a schedule the bridge controls). The bridge runs wherever the team wants; Podium serves what's in the layer's path at the time of ingest.

For a fuller integration that handles its own ingest semantics (signature verification, content addressing, fetch), implement LayerSourceProvider directly.

Custom consumer surfaces

A runtime that doesn't fit the built-in consumers (a specialized agent framework, an internal orchestrator, an evaluation harness) wraps the registry HTTP API directly. Identity attaches via the same OAuth flow used by the SDKs; visibility filtering and layer composition still happen server-side. The custom consumer is responsible for caching and any harness-native translation it needs.


Where to learn more