Using the Prompt Registry
The Ellaworks Prompt Registry works like a package manager for AI prompts. Instead of embedding prompt strings in your application code, you author them in the registry with semantic versioning, then pull the compiled result at runtime via API.
Prompts as Versioned Artifacts
Every prompt in a registry is a versioned artifact with:
- Semantic versioning (e.g.
1.0.0,1.1.0,2.0.0) for safe rollbacks and predictable upgrades - Model-specific variants — the same prompt name can have different content optimized for
gpt-4o,claude-3, or agenericfallback - Metadata frontmatter — description, tags, and configuration stored alongside the prompt content
- Content hashing — every version is hashed for integrity verification and drift detection
Promptlets
Promptlets are reusable prompt components that can be composed into larger prompts. Think of them like shared utility functions — define a tone-of-voice section, a compliance disclaimer, or a tool-usage preamble once, then reference it across multiple prompts. Imports are expanded when a prompt is compiled and published, so each published prompt version is a self-contained snapshot. When you publish a new promptlet version, a consuming prompt picks it up the next time that prompt is compiled and published (as long as the new version satisfies its import range).
Pulling Prompts at Runtime
Fetch prompts over HTTP from https://app.ellaworks.ai/api. Private registries require an API key, sent as X-API-Key or Authorization: Bearer (same ela_… key; see API Keys). Public registries can be read without a key.
The simplest option for runtime use is the compile endpoint. version accepts an exact version (1.2.0), a semver range (^1.0), or latest, and defaults to the latest published version; model and environment select model and environment branches:
The JSON response contains compiled, metadata, and the resolved version and model.
Two other read endpoints return the stored artifact without applying an environment:
Response fields:
compiled begins with a <!-- prm-metadata … prm-metadata --> comment block carrying the same metadata. Strip it before sending the prompt to a model (the Python example below shows how). Prompts authored as a JSON array of role-tagged messages (e.g. system, user) compile to a JSON-encoded messages array rather than plain text.
If a model-specific variant isn’t published for the requested model, the generic variant is returned.
What compilation resolves vs what it leaves for you
Compilation happens in stages. Ellaworks handles structural resolution (at publish time, then at deploy or fetch time), and your application handles context substitution at runtime.
Stage 1 — Ellaworks resolves when the prompt is compiled and published:
- Promptlet imports —
{{ import "tone-of-voice@^1.0" }}is replaced with the promptlet’s content. Use{{ import "org/registry/name@^1.0" }}to import from another registry. The version range can also be declared in the prompt’s frontmatter instead of inline. - Model preprocessors —
#ECP-IF-MODEL gpt-4oblocks are kept only for that model;#ECP-NOT-MODEL gpt-4oblocks are kept for every model except that one. Each model variant is compiled separately.
Stage 2 — Ellaworks resolves once the target environment is known (at deploy, or via the compile endpoint’s environment parameter):
- Environment preprocessors —
#ECP-IF-ENV prod/#ECP-NOT-ENV prodblocks are included or excluded based on the target environment. Until then they are preserved incompiled.
Every directive is written as the header of a markdown code fence; the fence itself delimits the block (there is no closing directive). The legacy #PRM- prefix is still accepted.
Stage 3 — Ellaworks resolves once variable values are known (at deploy or per request):
- Variable preprocessors —
#ECP-IF-VAR store.size == "LARGE"blocks are included or excluded by comparing a template variable to a value (==/!=). Imports inside the block are still expanded at compile time; the block’s inclusion is decided later, once the variable’s value is known. If a variable is still unknown at deploy time, the directive is preserved and resolved per request.
Stage 4 — Your application substitutes at runtime:
- Template variables —
{{variable_name}}placeholders remain in the compiled output for you to fill in with your own runtime context
This separation is intentional. Ellaworks manages the structural composition of your prompt (which components, which model variant, which environment branch), while your application injects the dynamic context that changes per request (user names, session data, query parameters).
Template Variable Syntax
Template variables use Mustache double braces and support dot notation for nested access. Mustache sections ({{#name}}…{{/name}}) render their contents only when the variable is truthy, and {{env.variables.NAME}} references a variable defined on the target environment:
Example: What the compiled output looks like
Suppose you author this prompt in the registry:
When you fetch it with model=gpt-4o, the compiled field returns (after the metadata comment):
Notice: the promptlet import and model preprocessor are resolved, but {{company_name}}, {{caller.name}}, and {{caller.account_id}} remain as placeholders for your application to fill in.
Variable directives (#ECP-IF-VAR)
Use #ECP-IF-VAR to switch which content — including which promptlet import — ships, based on a template variable’s value. Like the other directives, each block is a directive-headed markdown code fence; the fence itself is the delimiter:
The comparison supports == and != against a string value. The import inside each block is expanded at compile time, but which block is kept is decided later, when the value of store.size is known — at deploy time (from agent/environment variables) or per request. If the variable is not yet known at deploy time, the directive is preserved in the compiled output and resolved on each request.
A few semantics worth knowing:
- Variable namespace. IF-VAR conditions resolve against the same variables as
{{ ... }}substitution. Agent/request variables are referenced at the top level (#ECP-IF-VAR store.size == "LARGE"); environment variables are referenced underenv.variables.*(#ECP-IF-VAR env.variables.TIER == "gold"), mirroring{{env.variables.TIER}}. - Fails closed at the final stage. At the last stage before the prompt reaches the model (the runtime proxy), any IF-VAR directive that still can’t be resolved — the variable was never supplied, or its value isn’t a comparable primitive — is removed (its block is excluded) and a warning is logged. The
#ECP-IF-VARsyntax is never sent to the model. So a missing variable excludes the gated content rather than including it. (At earlier stages the directive is preserved so a later stage can resolve it.) - Comparable values. Conditions compare against a string literal, so string, number, and boolean values are matched by their string form (
3matches"3",truematches"true"). A variable that resolves to an object, array, ornullis treated as unresolvable (not compared), and follows the fail-closed behavior above.
Substituting Variables in Your Application
After fetching the compiled prompt, substitute the template variables with your runtime context before sending it to an LLM:
When deploying agents through Ellaworks (e.g. to Vapi), environment preprocessors and template variable substitution are handled automatically using the target environment and the variables defined on your agent and environment. The manual substitution shown above is only needed when you pull prompts directly via the API for use in your own application code.
When to use body vs compiled
This pattern keeps your application code free of prompt strings. When you publish a new prompt version, an application that requests latest or a matching range (e.g. ^2.1) picks up the change without a code deploy.
Versioning & Compilation Workflow
The typical lifecycle for a prompt is:
Draft
Author or edit a prompt or promptlet under Prompts or Promptlets in the Ellaworks app. Drafts are saved automatically and are not visible to consumers of the registry.
Compile & Preview
The publish dialog shows a compilation preview so you can verify that promptlet imports resolve correctly and that model and environment preprocessors produce the expected result. Template variables are left as placeholders.
Publish
Publish the draft as a new version (e.g. 1.0.0 → 1.1.0). In the publish dialog, choose the Version, the Model (or All Variants), the target registry, and an optional Commit message (Ellaworks can generate one), which appears in the prompt’s version history. Only organization owners and admins can publish. Published versions are immutable — a version and model pair can’t be republished with different content.
Model-Specific Variants
The same prompt name can have variants optimized for different models. Publishing with All Variants creates a generic variant plus one variant for each model named in the prompt’s #ECP-IF-MODEL / #ECP-NOT-MODEL directives. When you request a prompt with model=gpt-4o, Ellaworks returns the GPT-4o-specific variant if one exists, otherwise falls back to the generic version.
This is useful when different models need different formatting, token budgets, or instruction styles. For example, Claude models may benefit from XML-structured prompts while GPT models may work better with markdown.
Structuring Your Registries
Registries are the primary organizational boundary in Ellaworks. How you partition them determines who can collaborate on which prompts, which artifacts version independently, and where access control boundaries fall. Below are three proven patterns — pick the one that matches your situation, or combine them.
Per-Client Registries
If you are a service provider or agency managing AI solutions for multiple clients, create a private registry per client alongside a shared registry for common promptlets.
Client-specific prompts import shared promptlets by their full org/registry/name reference, so you author common logic once:
The shared promptlets are inlined when each client prompt is published, so fetching from a client’s registry returns the fully composed prompt:
Pair each client registry with a dedicated environment for that client’s provider credentials and variables (company name, support URL, escalation contacts). Use fleets to group each client’s agents for bulk operations.
The Service Provider plan includes unlimited private registries, designed for exactly this scaling pattern. See Plans for details.
Per-Domain Registries
Larger organizations with multiple teams building AI products benefit from a registry per domain or business unit. Each team owns its registry and release cadence while sharing common promptlets through a cross-team registry.
The CX team can iterate on support prompts at their own pace without affecting sales prompts, and vice versa. Shared promptlets keep brand voice consistent across every domain:
This pattern works well when different teams need different approval workflows — the CX team can ship daily while the compliance-sensitive internal-tools registry requires review before every publish.
Shared Component Registries
Regardless of whether you structure by client or by domain, dedicate a registry to reusable promptlets only. Think of it as a shared library that other registries depend on.
Common promptlets to centralize:
- Compliance disclaimers — legal language that must appear in every customer-facing prompt
- Tone-of-voice guides — brand personality instructions
- Tool-usage preambles — instructions for how the model should invoke tools
- Error-handling patterns — how to respond when something goes wrong
- Output format standards — JSON schemas, markdown formatting rules
When you publish a new promptlet version in the shared registry, every prompt that imports it with a compatible semver range (e.g. @^1.0) picks it up the next time that prompt is compiled and published — no need to edit the import references. To see which prompts consume a promptlet, open its details and check Dependent Components.
Both prompts share the same output format. When you publish output-format 1.1.0, both pick it up on their next publish without any change to their source.
Registries vs Environments
Do not create separate registries for dev, staging, and prod. Use environments and #ECP-IF-ENV preprocessors instead. Registries define organizational boundaries (who owns which prompts); environments define deployment boundaries (which credentials and variables apply at runtime). Mixing the two leads to duplicated prompts and version drift.
Storing OpenClaw Souls
The registry is well suited for storing OpenClaw soul definitions. A soul’s personality, drives, and behavioral instructions are essentially structured prompts — they benefit from the same versioning, model-specific variants, and runtime resolution that Ellaworks provides.
Why store souls in Ellaworks?
- Version control — roll back to a previous soul version if a change causes regressions
- Model-specific tuning — maintain separate soul definitions optimized for different LLM backends
- Runtime resolution — pull the latest soul at startup without redeploying your application
- Collaboration — your team can iterate on soul definitions in the Ellaworks editor with compilation previews
Example: OpenClaw wrapper with Ellaworks
Store your soul definitions under a dedicated registry (e.g. your-org/souls) to keep them organized separately from your agent prompts and promptlets.

