Files
librefang-registry/schema.toml
T
Evan 6439fb193c fix: add api_key field to provider schema for inline key setup (#57)
The provider creation form only collected api_key_env (the env var
name) but not the actual key value. New providers were always created
as "unconfigured" because no key was stored.

Add an optional secret api_key field so the dashboard can pass the
key value during creation. The backend strips it from the TOML and
saves it to secrets.env instead.
2026-04-15 12:49:38 +09:00

907 lines
24 KiB
TOML

# LibreFang Registry Schema
# =========================
# Machine-parseable schema definition for all registry content types.
# Consumed by the RegistrySchema Rust type (librefang-types).
# ═══════════════════════════════════════════════════════════════════════════════
# PROVIDER / MODEL SCHEMA
# ═══════════════════════════════════════════════════════════════════════════════
[provider]
description = "LLM provider configuration"
file_pattern = "providers/*.toml"
[provider.fields.id]
type = "string"
required = true
description = "Unique provider identifier (lowercase, hyphenated)"
example = "anthropic"
[provider.fields.display_name]
type = "string"
required = true
description = "Human-readable display name"
example = "Anthropic"
[provider.fields.api_key_env]
type = "string"
required = true
description = "Environment variable name for the API key"
example = "PROVIDER_API_KEY"
[provider.fields.base_url]
type = "string"
required = true
description = "Default API base URL"
example = "https://api.example.com"
[provider.fields.key_required]
type = "bool"
required = true
description = "Whether an API key is needed (false for local providers)"
example = true
[provider.fields.api_key]
type = "string"
required = false
description = "API key value (stored securely in secrets.env, not in the TOML file)"
secret = true
# --- Provider: nested [[models]] section ---
[provider.sections.models]
description = "Model entries"
repeatable = true
[provider.sections.models.fields.id]
type = "string"
required = true
description = "Unique model identifier (API model ID)"
example = "claude-sonnet-4-20250514"
[provider.sections.models.fields.display_name]
type = "string"
required = true
description = "Human-readable display name"
example = "Claude Sonnet"
[provider.sections.models.fields.tier]
type = "string"
required = true
description = "Capability tier: frontier (cutting-edge, most capable), smart (cost-effective), balanced (speed/cost), fast (cheapest for simple tasks), local (Ollama, vLLM, LM Studio)"
options = ["frontier", "smart", "balanced", "fast", "local"]
example = "smart"
[provider.sections.models.fields.context_window]
type = "number"
required = true
description = "Maximum input tokens"
example = 128000
[provider.sections.models.fields.max_output_tokens]
type = "number"
required = true
description = "Maximum output tokens"
example = 16384
[provider.sections.models.fields.input_cost_per_m]
type = "number"
required = true
description = "USD per million input tokens (0.0 for free/local)"
example = 2.5
[provider.sections.models.fields.output_cost_per_m]
type = "number"
required = true
description = "USD per million output tokens (0.0 for free/local)"
example = 10.0
[provider.sections.models.fields.last_verified]
type = "string"
required = false
description = "ISO date when pricing was last verified against official sources"
example = "2025-03-15"
[provider.sections.models.fields.supports_tools]
type = "bool"
required = false
description = "Tool/function calling support"
default = false
[provider.sections.models.fields.supports_vision]
type = "bool"
required = false
description = "Vision/image input support"
default = false
[provider.sections.models.fields.supports_streaming]
type = "bool"
required = false
description = "Streaming response support"
default = true
[provider.sections.models.fields.supports_thinking]
type = "bool"
required = false
description = "Extended thinking / reasoning support"
default = false
[provider.sections.models.fields.aliases]
type = "array"
required = false
description = "Alternative names for this model"
item_type = "string"
example = ["alias1", "alias2"]
# ═══════════════════════════════════════════════════════════════════════════════
# AGENT SCHEMA
# ═══════════════════════════════════════════════════════════════════════════════
[agent]
description = "Agent configuration"
file_pattern = "agents/<name>/agent.toml"
[agent.fields.name]
type = "string"
required = true
description = "Agent identifier, must match directory name"
example = "agent-name"
[agent.fields.version]
type = "string"
required = false
description = "Semver version string"
example = "0.1.0"
[agent.fields.description]
type = "string"
required = true
description = "One-sentence description of what this agent does"
example = "What this agent does"
[agent.fields.author]
type = "string"
required = false
description = "Author or organization"
example = "author-name"
[agent.fields.module]
type = "string"
required = true
description = "Runtime module (builtin:chat, builtin:tool, etc.)"
example = "builtin:chat"
# --- Agent: [model] section ---
[agent.sections.model]
description = "Agent's LLM configuration"
repeatable = false
[agent.sections.model.fields.provider]
type = "string"
required = false
description = "Provider ID or \"default\""
example = "default"
[agent.sections.model.fields.model]
type = "string"
required = false
description = "Model ID or \"default\""
example = "default"
[agent.sections.model.fields.max_tokens]
type = "number"
required = false
description = "Max response tokens"
example = 4096
[agent.sections.model.fields.temperature]
type = "number"
required = false
description = "Sampling temperature (0.0-2.0)"
example = 0.7
[agent.sections.model.fields.system_prompt]
type = "string"
required = true
description = "Behavioral instructions for the agent"
# --- Agent: [metadata.routing] section ---
[agent.sections.metadata]
description = "Agent metadata"
repeatable = false
[agent.sections.metadata.sections.routing]
description = "Routing configuration"
repeatable = false
[agent.sections.metadata.sections.routing.fields.aliases]
type = "array"
required = false
description = "Phrases that route directly to this agent"
item_type = "string"
example = ["exact match phrases"]
[agent.sections.metadata.sections.routing.fields.weak_aliases]
type = "array"
required = false
description = "Keywords that suggest this agent"
item_type = "string"
example = ["partial match keywords"]
# --- Agent: [resources] section ---
[agent.sections.resources]
description = "Resource limits"
repeatable = false
[agent.sections.resources.fields.max_llm_tokens_per_hour]
type = "number"
required = false
description = "Token budget per hour"
example = 100000
# --- Agent: [capabilities] section ---
[agent.sections.capabilities]
description = "Agent permissions"
repeatable = false
[agent.sections.capabilities.fields.tools]
type = "array"
required = false
description = "List of allowed tool names"
item_type = "string"
example = ["tool1", "tool2"]
[agent.sections.capabilities.fields.network]
type = "array"
required = false
description = "Network access patterns (\"*\" = unrestricted)"
item_type = "string"
example = ["*"]
[agent.sections.capabilities.fields.memory_read]
type = "array"
required = false
description = "Memory read permissions"
item_type = "string"
example = ["*"]
[agent.sections.capabilities.fields.memory_write]
type = "array"
required = false
description = "Memory write permissions"
item_type = "string"
example = ["self.*"]
[agent.sections.capabilities.fields.agent_spawn]
type = "bool"
required = false
description = "Whether this agent can spawn sub-agents"
default = false
# ═══════════════════════════════════════════════════════════════════════════════
# HAND SCHEMA
# ═══════════════════════════════════════════════════════════════════════════════
[hand]
description = "Hand configuration (a hand is a user-facing capability backed by an agent)"
file_pattern = "hands/<name>/HAND.toml"
[hand.fields.id]
type = "string"
required = true
description = "Unique identifier, must match directory name"
example = "hand-id"
[hand.fields.name]
type = "string"
required = true
description = "Human-readable display name"
example = "Hand Name"
[hand.fields.description]
type = "string"
required = true
description = "One-sentence description of what this hand does"
example = "What this hand does"
[hand.fields.category]
type = "string"
required = true
description = "Hand category"
options = [
"communication",
"content",
"data",
"development",
"devops",
"finance",
"productivity",
"research",
"social",
]
example = "productivity"
[hand.fields.icon]
type = "string"
required = false
description = "Emoji icon"
[hand.fields.tools]
type = "array"
required = true
description = "List of tool names this hand uses"
item_type = "string"
example = ["tool1", "tool2"]
# --- Hand: [routing] section ---
[hand.sections.routing]
description = "Routing configuration"
repeatable = false
[hand.sections.routing.fields.aliases]
type = "array"
required = false
description = "Phrases that activate this hand"
item_type = "string"
example = ["activate phrases"]
[hand.sections.routing.fields.weak_aliases]
type = "array"
required = false
description = "Keyword hints for routing"
item_type = "string"
example = ["keyword hints"]
# --- Hand: [[requires]] section ---
[hand.sections.requires]
description = "External dependencies"
repeatable = true
[hand.sections.requires.fields.key]
type = "string"
required = true
description = "Unique dependency key"
example = "python3"
[hand.sections.requires.fields.label]
type = "string"
required = true
description = "Human-readable label"
example = "Python 3"
[hand.sections.requires.fields.requirement_type]
type = "string"
required = true
description = "Type of requirement"
options = ["binary", "package", "service"]
example = "binary"
[hand.sections.requires.fields.check_value]
type = "string"
required = true
description = "Binary name or check command"
example = "python3"
[hand.sections.requires.fields.optional]
type = "bool"
required = false
description = "Whether the dependency is optional"
default = false
[hand.sections.requires.fields.description]
type = "string"
required = false
description = "Why this dependency is needed"
# --- Hand: [[requires]] -> [install] nested section ---
[hand.sections.requires.sections.install]
description = "Platform-specific install instructions"
repeatable = false
[hand.sections.requires.sections.install.fields.macos]
type = "string"
required = false
description = "macOS install command"
example = "brew install python3"
[hand.sections.requires.sections.install.fields.windows]
type = "string"
required = false
description = "Windows install command"
example = "winget install Python.Python.3.12"
[hand.sections.requires.sections.install.fields.linux_apt]
type = "string"
required = false
description = "Linux (apt) install command"
example = "sudo apt install python3"
[hand.sections.requires.sections.install.fields.manual_url]
type = "string"
required = false
description = "URL for manual installation instructions"
example = "https://..."
# --- Hand: [[settings]] section ---
[hand.sections.settings]
description = "User-configurable settings"
repeatable = true
[hand.sections.settings.fields.key]
type = "string"
required = true
description = "Unique setting key"
example = "setting_key"
[hand.sections.settings.fields.label]
type = "string"
required = true
description = "Human-readable label"
example = "Setting Label"
[hand.sections.settings.fields.description]
type = "string"
required = false
description = "What this setting controls"
[hand.sections.settings.fields.setting_type]
type = "string"
required = true
description = "Setting input type"
options = ["toggle", "select", "text", "number"]
example = "toggle"
[hand.sections.settings.fields.default]
type = "string"
required = false
description = "Default value as string"
example = "true"
# --- Hand: [[settings]] -> [[options]] nested section ---
[hand.sections.settings.sections.options]
description = "Available options for select-type settings"
repeatable = true
[hand.sections.settings.sections.options.fields.value]
type = "string"
required = true
description = "Option value"
example = "option1"
[hand.sections.settings.sections.options.fields.label]
type = "string"
required = true
description = "Option display label"
example = "Option Label"
# --- Hand: [agent] section ---
[hand.sections.agent]
description = "The agent that powers this hand"
repeatable = false
[hand.sections.agent.fields.name]
type = "string"
required = true
description = "Agent name"
example = "hand-agent-name"
[hand.sections.agent.fields.description]
type = "string"
required = false
description = "Agent description"
[hand.sections.agent.fields.module]
type = "string"
required = true
description = "Runtime module"
example = "builtin:chat"
[hand.sections.agent.fields.provider]
type = "string"
required = false
description = "Provider ID"
example = "default"
[hand.sections.agent.fields.model]
type = "string"
required = false
description = "Model ID"
example = "default"
[hand.sections.agent.fields.max_tokens]
type = "number"
required = false
description = "Max response tokens"
example = 16384
[hand.sections.agent.fields.temperature]
type = "number"
required = false
description = "Sampling temperature"
example = 0.3
[hand.sections.agent.fields.max_iterations]
type = "number"
required = false
description = "Maximum tool-use iterations"
example = 60
[hand.sections.agent.fields.system_prompt]
type = "string"
required = true
description = "Detailed behavioral prompt for the agent"
# --- Hand: [dashboard] section ---
[hand.sections.dashboard]
description = "Dashboard metrics configuration"
repeatable = false
# --- Hand: [dashboard] -> [[metrics]] nested section ---
[hand.sections.dashboard.sections.metrics]
description = "Dashboard metric entries"
repeatable = true
[hand.sections.dashboard.sections.metrics.fields.label]
type = "string"
required = true
description = "Metric display name"
example = "Metric Name"
[hand.sections.dashboard.sections.metrics.fields.memory_key]
type = "string"
required = true
description = "Memory key to read the metric value from"
example = "metric_memory_key"
[hand.sections.dashboard.sections.metrics.fields.format]
type = "string"
required = true
description = "Display format for the metric value"
options = ["number", "currency", "percentage"]
example = "number"
# --- Hand: [metadata] section ---
[hand.sections.metadata]
description = "Operational metadata"
repeatable = false
[hand.sections.metadata.fields.frequency]
type = "string"
required = false
description = "How often this hand runs"
options = ["continuous", "on-demand", "scheduled"]
example = "continuous"
[hand.sections.metadata.fields.token_consumption]
type = "string"
required = false
description = "Expected token usage level"
options = ["low", "medium", "high"]
example = "low"
[hand.sections.metadata.fields.default_active]
type = "bool"
required = false
description = "Whether this hand is active by default"
default = true
# ═══════════════════════════════════════════════════════════════════════════════
# INTEGRATION SCHEMA
# ═══════════════════════════════════════════════════════════════════════════════
[integration]
description = "External service integration via MCP server"
file_pattern = "integrations/<name>.toml"
[integration.fields.id]
type = "string"
required = true
description = "Unique identifier, must match filename"
example = "integration-id"
[integration.fields.name]
type = "string"
required = true
description = "Human-readable display name"
example = "Service Name"
[integration.fields.description]
type = "string"
required = false
description = "One-sentence description of what this integration provides"
[integration.fields.category]
type = "string"
required = false
description = "Integration category"
options = [
"devtools",
"communication",
"storage",
"monitoring",
"data",
"productivity",
]
example = "devtools"
[integration.fields.icon]
type = "string"
required = false
description = "Emoji icon"
[integration.fields.tags]
type = "array"
required = false
description = "Searchable tags"
item_type = "string"
example = ["tag1", "tag2"]
[integration.fields.setup_instructions]
type = "string"
required = false
description = "Multi-line setup guide"
# --- Integration: [transport] section ---
[integration.sections.transport]
description = "MCP server transport configuration"
repeatable = false
[integration.sections.transport.fields.type]
type = "string"
required = true
description = "Transport type"
options = ["stdio", "sse"]
example = "stdio"
[integration.sections.transport.fields.command]
type = "string"
required = true
description = "Command to launch the MCP server"
example = "npx"
[integration.sections.transport.fields.args]
type = "array"
required = false
description = "Command arguments"
item_type = "string"
example = ["-y", "@pkg/server"]
# --- Integration: [[required_env]] section ---
[integration.sections.required_env]
description = "Required environment variables"
repeatable = true
[integration.sections.required_env.fields.name]
type = "string"
required = true
description = "Environment variable name"
example = "SERVICE_API_KEY"
[integration.sections.required_env.fields.label]
type = "string"
required = true
description = "Human-readable label"
example = "API Key"
[integration.sections.required_env.fields.help]
type = "string"
required = false
description = "Help text explaining how to get this value"
[integration.sections.required_env.fields.is_secret]
type = "bool"
required = false
description = "Whether this is a secret value"
default = true
[integration.sections.required_env.fields.get_url]
type = "string"
required = false
description = "URL where user can obtain the value"
example = "https://..."
# --- Integration: [oauth] section ---
[integration.sections.oauth]
description = "OAuth configuration"
repeatable = false
[integration.sections.oauth.fields.provider]
type = "string"
required = true
description = "OAuth provider identifier"
example = "github"
[integration.sections.oauth.fields.scopes]
type = "array"
required = false
description = "OAuth scopes to request"
item_type = "string"
example = ["repo", "read:org"]
[integration.sections.oauth.fields.auth_url]
type = "string"
required = false
description = "OAuth authorization URL"
example = "https://..."
[integration.sections.oauth.fields.token_url]
type = "string"
required = false
description = "OAuth token exchange URL"
example = "https://..."
# --- Integration: [health_check] section ---
[integration.sections.health_check]
description = "Health check configuration"
repeatable = false
[integration.sections.health_check.fields.interval_secs]
type = "number"
required = false
description = "Health check interval in seconds"
example = 60
[integration.sections.health_check.fields.unhealthy_threshold]
type = "number"
required = false
description = "Number of consecutive failures before marking unhealthy"
example = 3
# ═══════════════════════════════════════════════════════════════════════════════
# SKILL SCHEMA
# ═══════════════════════════════════════════════════════════════════════════════
[skill]
description = "Reusable skill definition (prompt template or script)"
file_pattern = "skills/<name>/skill.toml"
# --- Skill: [skill] section (metadata) ---
# Note: the top-level "skill" content type contains a [skill] section for metadata.
# In the TOML file, these fields live directly under [skill].
[skill.sections.skill]
description = "Skill metadata"
repeatable = false
[skill.sections.skill.fields.name]
type = "string"
required = true
description = "Skill identifier, must match directory name"
example = "skill-name"
[skill.sections.skill.fields.version]
type = "string"
required = false
description = "Semver version string"
example = "0.1.0"
[skill.sections.skill.fields.description]
type = "string"
required = false
description = "One-sentence description of what this skill does"
[skill.sections.skill.fields.author]
type = "string"
required = false
description = "Author or organization"
example = "author-name"
[skill.sections.skill.fields.tags]
type = "array"
required = false
description = "Searchable tags"
item_type = "string"
example = ["tag1", "tag2"]
# --- Skill: [runtime] section ---
[skill.sections.runtime]
description = "Execution runtime configuration"
repeatable = false
[skill.sections.runtime.fields.type]
type = "string"
required = true
description = "Runtime type"
options = ["promptonly", "python", "node", "shell"]
example = "promptonly"
[skill.sections.runtime.fields.entry]
type = "string"
required = false
description = "Entry point file (required for non-promptonly runtimes)"
example = "main.py"
# --- Skill: [input] section ---
[skill.sections.input]
description = "Input parameter definitions (each field is a parameter with type, description, required)"
repeatable = false
# Note: input fields are dynamic — each key is a parameter name with an object value
# containing type, description, and required. This section uses a convention where
# individual parameters are defined as fields.
# --- Skill: [prompt] section ---
[skill.sections.prompt]
description = "Prompt template (required for promptonly runtime)"
repeatable = false
[skill.sections.prompt.fields.template]
type = "string"
required = true
description = "Prompt template string, supports {{param_name}} placeholders"
example = "Use {{param_name}} in the template"
# ═══════════════════════════════════════════════════════════════════════════════
# PLUGIN SCHEMA
# ═══════════════════════════════════════════════════════════════════════════════
[plugin]
description = "Plugin that extends agent behavior via lifecycle hooks"
file_pattern = "plugins/<name>/plugin.toml"
[plugin.fields.name]
type = "string"
required = true
description = "Plugin identifier, must match directory name"
example = "plugin-name"
[plugin.fields.version]
type = "string"
required = true
description = "Semver version string"
example = "0.1.0"
[plugin.fields.description]
type = "string"
required = true
description = "One-sentence description of what this plugin does"
[plugin.fields.author]
type = "string"
required = false
description = "Author or organization"
example = "author-name"
# --- Plugin: [hooks] section ---
# Hook scripts communicate via stdin/stdout JSON:
# ingest receives: {"type": "ingest", "agent_id": "...", "message": "..."}
# ingest returns: {"type": "ingest_result", "memories": [{"content": "..."}]}
# after_turn receives: {"type": "after_turn", "agent_id": "...", "messages": [...]}
# after_turn returns: {"type": "ok"}
[plugin.sections.hooks]
description = "Hook entry points — scripts invoked at specific lifecycle events"
repeatable = false
[plugin.sections.hooks.fields.ingest]
type = "string"
required = false
description = "Script called when a user message is received"
example = "hooks/ingest.py"
[plugin.sections.hooks.fields.after_turn]
type = "string"
required = false
description = "Script called after each conversation turn"
example = "hooks/after_turn.py"