All 32 agent manifests and 17 hands shipped with empty mcp_servers /
skills lists, which the kernel interprets as "no filter" — every
globally-configured MCP server's tools and every installed skill get
injected into the prompt on every LLM call. On a typical instance (9
MCP servers, ~85 MCP tools + ~82 built-in tools) that's ~50k input
tokens per turn spent on definitions the agent never uses.
Changes
-------
32 agents/*/agent.toml:
- mcp_servers: 1-4 per agent. memory wherever state persists across
turns; fetch / exa-search / brave-search only where the prompt
actually calls for web; git / github / filesystem on engineering
agents; gmail / google-calendar / linear / jira on productivity
agents whose prompts mention them.
- skills: per-role allowlist driven by what the system_prompt names
(e.g. coder → rust/python/typescript/git/shell-scripting; devops-
lead → docker/kubernetes/terraform/ansible/ci-cd/helm/prometheus/
sysadmin). Generalists (assistant) keep skills = [] (see "Open
items" below).
- skills_disabled = true on the four short-conversational agents
(hello-world, recipe-assistant, health-tracker, home-automation).
Their system prompts never instruct the LLM to consult any skill,
so loading all 60 was pure waste. They also drop the explicit
max_history_messages override and inherit the kernel default (60).
- max_history_messages tiered by workload shape:
60 short conversational (hello-world, recipe, health-tracker,
home-automation) — inherits the rising kernel default
(`DEFAULT_MAX_HISTORY_MESSAGES = 60`); no override needed.
60 single-turn task agents (writer, translator, doc-writer,
email-assistant, customer-support, sales-assistant, recruit-
er, social-media, personal-finance, tutor, travel-planner,
meeting-assistant, ops, devops-lead, planner) — explicit
override at the same value to lock the cap if the kernel
default moves again.
80 multi-step / tool-heavy (coder, debugger, architect, code-
reviewer, test-engineer, security-auditor, analyst, data-
scientist, academic-researcher, researcher, legal-assistant)
120 coordinators (assistant, orchestrator) — long multi-agent
sessions where prompt-cache continuity is critical
All values sit at or above the kernel default. Pinning lower
would thrash the prompt cache (the failure mode #91 fixed for
the creator hand by *raising* the cap, not lowering it).
17 hands/*/HAND.toml:
- hand-level mcp_servers / skills now declared on every hand, so
every [agents.*] inside inherits a sensible allowlist.
- skills_disabled = true placed on each [agents.*] inside clip and
creator (pure media pipelines that don't benefit from any skill).
HandDefinitionRaw in librefang-hands does NOT have a top-level
skills_disabled field — declaring it at the hand top level would
be silently dropped by serde, so the setting must live on the
AgentManifest of each sub-agent role.
- devteam: expand existing mcp_servers = ["github"] to include
memory / git / filesystem; populate skills with the expected
dev-team expertise (replacing the placeholder skills = []).
- wiki: replace placeholder mcp_servers = [] with [memory, fetch,
filesystem]. Hand-level skills stays [].
- lead: hand-level skills was originally [email-writer, writing-
coach, interview-prep]; interview-prep is for job-interview
preparation, not lead generation. Replaced with data-analyst
(used by the qualification-scoring step in the prompt).
schema.toml: register mcp_servers / skills / max_history_messages on
the agent field schema so machine consumers (RegistrySchema in
librefang-types) see the new top-level fields. The
max_history_messages description now points at
librefang_runtime::agent_loop::DEFAULT_MAX_HISTORY_MESSAGES (60
today) by name, so the schema doesn't go stale when the constant
moves again.
agents/README.md: example block + "Adding a New Agent" checklist
mention the allowlists; max_history_messages example is shown
commented out with a prompt-cache caveat.
Open items
----------
`assistant` (the default user-facing agent) keeps `skills = []`
deliberately. It is the generalist entry point — capping its skill
surface at a small allowlist would defeat its "delegate to any
specialist" job. The trade-off is that this single agent still pays
the full skill-definition load on every turn; operators who want a
strict allowlist for `assistant` can override it after install.
Why not adopt PR #89's approach
-------------------------------
#89 covers similar ground but with three issues this PR avoids:
1. mcp_servers = ["_none"] sentinel. #89's body explicitly notes
it's pending upstream librefang#4808 (mcp_disabled). Shipping a
magic-string today means coming back later to clean it up. This
PR uses real allowlists.
2. max_history_messages = 8 / 12 / 15 / 20. Far below today's
kernel default (60) and #91's direction for long-workflow hands
(80–120). Every turn that hits the cap invalidates the cached
prompt prefix; the cost of cache misses exceeds the saving from
shorter history. This PR uses 60–120.
3. Doubling max_llm_tokens_per_hour (coder 200k→500k, assistant
300k→500k) widens the per-agent budget — the opposite direction
from #87's "reduce per-call cost" goal. Left to the operator's
instance-specific tuning.
Refs librefang/librefang-registry#87, librefang/librefang-registry#89
960 lines
28 KiB
TOML
960 lines
28 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.modality]
|
|
type = "string"
|
|
required = false
|
|
description = "Model modality. 'text' (default) is a chat/LLM; 'image' is an image-generation model; 'audio' is a speech / TTS model; 'video' is a video-generation model; 'music' is a music / lyrics generation model. For non-text modalities, context_window / max_output_tokens are optional and input_cost_per_m / output_cost_per_m may be 0 when the model is billed per call."
|
|
options = ["text", "image", "audio", "video", "music"]
|
|
default = "text"
|
|
example = "text"
|
|
|
|
[provider.sections.models.fields.context_window]
|
|
type = "number"
|
|
required = true
|
|
description = "Maximum input tokens. Required for modality='text'; optional for image/audio models where no published context gate exists."
|
|
example = 128000
|
|
|
|
[provider.sections.models.fields.max_output_tokens]
|
|
type = "number"
|
|
required = true
|
|
description = "Maximum output tokens. Required for modality='text'; optional for image/audio models."
|
|
example = 16384
|
|
|
|
[provider.sections.models.fields.input_cost_per_m]
|
|
type = "number"
|
|
required = true
|
|
description = "USD per million text 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 text output tokens (0.0 for free/local)"
|
|
example = 10.0
|
|
|
|
[provider.sections.models.fields.image_input_cost_per_m]
|
|
type = "number"
|
|
required = false
|
|
description = "USD per million image input tokens (for image/multimodal models)"
|
|
example = 8.0
|
|
|
|
[provider.sections.models.fields.image_output_cost_per_m]
|
|
type = "number"
|
|
required = false
|
|
description = "USD per million image output tokens (for image-generation models)"
|
|
example = 30.0
|
|
|
|
[provider.sections.models.fields.per_call_cost]
|
|
type = "number"
|
|
required = false
|
|
description = "Flat USD cost per generation, for models billed per call rather than per token. Strongly recommended for modality='video' and modality='music' (which have no per-token pricing); may also be used for image/audio models that quote a flat per-image or per-request rate. When absent for a per-call modality, the runtime metering layer records the call as $0 and logs a warning."
|
|
example = 0.33
|
|
|
|
[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.reasoning_echo_policy]
|
|
type = "string"
|
|
required = false
|
|
description = "How the OpenAI-compatible driver must handle the reasoning_content field on historical assistant turns when this model is used. 'none' (default) omits the field entirely. 'strip' is required by DeepSeek-R1 / deepseek-reasoner — the API rejects multi-turn requests that carry reasoning_content from previous turns. 'echo' is required by DeepSeek V4 Flash and other thinking-mode-on models — the original thinking text MUST be round-tripped on assistant turns that contain tool_calls, otherwise the API returns 400. 'empty_string' is required by Moonshot / Kimi K2 — the field must be present (empty string) on tool_calls turns, with thinking disabled wire-side."
|
|
options = ["none", "strip", "echo", "empty_string"]
|
|
default = "none"
|
|
|
|
[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.fields.mcp_servers]
|
|
type = "array"
|
|
required = false
|
|
description = "MCP server allowlist. Empty list (default) means every connected MCP server's tools are injected into this agent's prompt — on a typical instance that is ~85 tool definitions and ~50k tokens per call. Set an explicit list to trim the surface to only the servers this agent actually uses (e.g. [\"memory\", \"git\", \"github\"])."
|
|
item_type = "string"
|
|
example = ["memory", "git", "github"]
|
|
|
|
[agent.fields.skills]
|
|
type = "array"
|
|
required = false
|
|
description = "Skill allowlist. Empty list (default) means every installed skill is available. Set an explicit list to load only what this agent needs."
|
|
item_type = "string"
|
|
example = ["rust-expert", "python-expert"]
|
|
|
|
[agent.fields.max_history_messages]
|
|
type = "number"
|
|
required = false
|
|
description = "Per-agent override for the message-history trim cap. Omit (default) inherits from KernelConfig.max_history_messages, which itself falls back to the compiled constant librefang_runtime::agent_loop::DEFAULT_MAX_HISTORY_MESSAGES (60 today). Values below 4 are clamped at runtime with a warning. Pinning this *below* the kernel default trashes the prompt cache — every turn that hits the cap invalidates the cached prefix and costs more than the smaller history saves. Set this only to raise the cap for iteration-heavy agents that legitimately need more context, or to lower it for agents whose conversations are demonstrably short and where you've measured the trade-off."
|
|
example = 80
|
|
|
|
# --- 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 = "mcp/<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"
|