metadata — PluginMetadata, SchemaProperty, ExecutionContext, Enums
On this page
Overview
This module defines the data model that makes plugins discoverable and callable by an LLM: rich,
structured metadata (PluginMetadata), a JSON-Schema-compatible property descriptor
(SchemaProperty), the runtime context threaded through every execution (ExecutionContext), and four
enumerations used across the rest of the package.
Enumerations
PluginStatus(str, Enum)
DISABLED = "disabled"
ENABLED = "enabled"
LOADING = "loading"
ERROR = "error"
UPDATING = "updating"
DEPRECATED = "deprecated"
INITIALIZING = "initializing"
Lifecycle state of a BasePlugin instance. Set by PluginManager._initialize_plugin (initializing →
enabled/error) and automatically by BasePlugin._record_failure (→ error, once failures exceed
10 and the failure rate is over 50%).
PluginPriority(int, Enum)
CRITICAL = 1000
HIGH = 100
NORMAL = 50
LOW = 10
BACKGROUND = 1
An integer-valued priority scale. Note: as of this source snapshot, PluginPriority is defined and
exported but not referenced anywhere else in the package's execution or selection logic — it appears to
be a reserved extension point rather than an active behavior. PluginManager.on() has its own separate
integer priority parameter for hook ordering, unrelated to this enum.
PermissionType(str, Enum)
FILE_READ = "file_read"
FILE_WRITE = "file_write"
NETWORK_ACCESS = "network_access"
SYSTEM_CALL = "system_call"
DATABASE_ACCESS = "database_access"
USER_DATA_ACCESS = "user_data_access"
LLM_CALL = "llm_call"
VECTOR_STORE = "vector_store"
The complete, closed set of permission kinds understood by PermissionGuard.
Permissions are declared per-plugin in PluginMetadata.permissions (as plain strings) and enforced at
grant/check time.
CostTier(str, Enum)
FREE = "free" # no external calls
CHEAP = "cheap" # < $0.001 per call
MODERATE = "moderate" # $0.001 – $0.01
EXPENSIVE = "expensive" # > $0.01
Used by AIPluginSelector.select(max_cost=...) to filter candidates, and folded into the LLM-facing
description text via PluginMetadata._build_llm_description().
SchemaProperty
@dataclass
class SchemaProperty:
name: str
type: Literal["string", "integer", "number", "boolean", "array", "object"]
description: str
required: bool = True
enum: Optional[List[Any]] = None
default: Optional[Any] = None
items: Optional[Dict[str, Any]] = None # for type="array"
properties: Optional[Dict[str, Any]] = None # for type="object"
Describes one field of a plugin's input_schema or output_schema. Compatible with JSON Schema
draft-07 and OpenAI function-calling parameter format.
to_json_schema() -> Dict[str, Any]
Builds {"type": ..., "description": ...}, then conditionally adds enum, default, items, and
properties keys only when they are not None. This is a pure function with no side effects and
is used by both PluginMetadata.to_tool_descriptor() and to_anthropic_tool().
Example:
prop = SchemaProperty(
name="top_k", type="integer", description="Number of results",
required=False, default=5,
)
prop.to_json_schema()
# {"type": "integer", "description": "Number of results", "default": 5}
PluginMetadata
@dataclass
class PluginMetadata:
name: str
version: str
author: str
description: str
capabilities: List[str] = field(default_factory=list)
tags: List[str] = field(default_factory=list)
use_cases: List[str] = field(default_factory=list)
cost_tier: CostTier = CostTier.FREE
cost_per_call_usd: float = 0.0
input_schema: List[SchemaProperty] = field(default_factory=list)
output_schema: List[SchemaProperty] = field(default_factory=list)
dependencies: List[str] = field(default_factory=list)
required_hooks: List[str] = field(default_factory=list)
permissions: List[str] = field(default_factory=list)
min_system_version: str = "1.0.0"
max_system_version: str = "999.0.0"
homepage: str = ""
license: str = "MIT"
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
__post_init__ validation
Runs automatically on construction. Raises ValueError if:
nameis empty or whitespace-only.versiondoes not match^\d+\.\d+\.\d+$(strictMAJOR.MINOR.PATCH, no pre-release/build suffixes allowed —"2.0.0-beta"would fail).descriptionis empty or whitespace-only.authoris empty or whitespace-only.
If valid, created_at/updated_at default to datetime.utcnow() when not supplied.
to_tool_descriptor() -> Dict[str, Any]
Builds an OpenAI function-calling descriptor:
{
"type": "function",
"function": {
"name": self.name,
"description": self._build_llm_description(),
"parameters": {
"type": "object",
"properties": {...}, # from input_schema via SchemaProperty.to_json_schema()
"required": [...], # names of input_schema entries with required=True
},
},
}
to_anthropic_tool() -> Dict[str, Any]
Same field derivation, Anthropic's flatter shape:
{
"name": self.name,
"description": self._build_llm_description(),
"input_schema": {"type": "object", "properties": {...}, "required": [...]},
}
_build_llm_description() -> str
Joins, with " | ": the base description; "Use cases: " + "; ".join(use_cases) if any use cases
are declared; and f"Cost: {cost_tier.value} (~${cost_per_call_usd:.4f}/call)" if cost_tier is not
CostTier.FREE.
is_compatible_with(system_version: str) -> bool
Returns True iff system_version >= min_system_version and system_version <= max_system_version, using _compare_versions (dotted-integer comparison, not full semver — see
below).
to_dict() / from_dict(data)
Round-trip JSON-serializable representation. to_dict() converts cost_tier to its .value and
datetimes to ISO strings; from_dict() reverses both conversions. Useful for persisting plugin
manifests as JSON/YAML.
Raises
ValueError— from__post_init__, on any of the four validation failures above.
ExecutionContext
@dataclass
class ExecutionContext:
query: str = ""
session_id: str = ""
user_id: str = ""
metadata: Dict[str, Any] = field(default_factory=dict)
memory: Optional[Any] = None
cache: Optional[Any] = None
router: Optional[Any] = None
trace_id: str = ""
max_tokens: int = 2048
Passed as the second argument to every execute() call. memory, cache, and router are untyped
handles — the framework threads them through but never constructs or interprets them; your application
is responsible for what these objects are.
PermissionGuard.check() reads context.session_id/context.user_id to enforce session/user-scoped
permission grants.
Helper functions (module-private, documented for completeness)
_valid_version(v: str) -> bool—bool(re.match(r"^\d+\.\d+\.\d+$", v or ""))._compare_versions(v1, v2) -> int— splits each string on., casts toint, compares component-wise, returns-1/0/1on the first differing component (or0if all compared components are equal — note this useszip(), so"1.2"vs"1.2.0"would compare equal on their two common components without checking the third).
Complete examples
1. Minimal metadata:
from zerda.plugins import PluginMetadata
meta = PluginMetadata(name="ping", version="1.0.0", author="me", description="Health check.")
2. Full metadata with schema, for LLM tool use:
from zerda.plugins import PluginMetadata, SchemaProperty, CostTier
meta = PluginMetadata(
name="web_search",
version="1.2.0",
author="fennec-team",
description="Searches the web and returns ranked results.",
capabilities=["search", "retrieval"],
tags=["web", "search"],
use_cases=["Find recent news", "Look up facts"],
cost_tier=CostTier.CHEAP,
cost_per_call_usd=0.0005,
input_schema=[
SchemaProperty(name="query", type="string", description="Search query"),
SchemaProperty(name="top_k", type="integer", description="Max results", required=False, default=5),
],
permissions=["network_access"],
)
3. Error handling — invalid version:
try:
PluginMetadata(name="x", version="v1", author="a", description="d")
except ValueError as e:
print(e) # "Invalid semantic version: 'v1'"
4. Exporting for OpenAI / Anthropic:
tool_openai = meta.to_tool_descriptor()
tool_claude = meta.to_anthropic_tool()
5. Compatibility check before manual registration:
if meta.is_compatible_with("1.0.0"):
...
Best practices
- Always set
permissionsto the minimal set your plugin actually needs —PluginManager.startup()grants exactly (and only) what's declared here whensafe_mode=False. - Use
use_casesliberally; it directly improvesAIPluginSelectorkeyword-match quality. - Keep
versionstrictMAJOR.MINOR.PATCH— pre-release suffixes will raiseValueError.
Common mistakes
- Passing a non-dict
customtoPluginConfigexpecting it to merge with defaults — it replaces the default{}entirely. - Assuming
_compare_versionsimplements full semver ordering (pre-release tags, build metadata) — it only compares dotted integers.
Related APIs
BasePlugin— consumesPluginMetadataasMETADATAPluginRegistry— indexes plugins bytags/PLUGIN_TYPEInputSanitizer— validates data againstSchemaPropertylists