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
Refs librefang/librefang#4842 — long-term replacement for the substring
match that the OpenAI driver currently uses to decide how to handle
`reasoning_content` on historical assistant turns.
Three provider-specific behaviours that the driver must distinguish at
wire time, now expressed as catalog metadata:
* `strip` — DeepSeek R1 / deepseek-reasoner. The API rejects requests
that carry reasoning_content on previous assistant messages.
* `echo` — DeepSeek V4 Flash. Thinking mode is on by default and the
API rejects multi-turn requests when assistant turns containing
tool_calls don't echo back the original reasoning text. This is the
bug surfaced in librefang/librefang#4842.
* `empty_string` — Moonshot / Kimi K2 family. The field must be present
(empty string) on tool_calls turns, with thinking disabled wire-side
for multi-turn compatibility.
* `none` (default) — most providers; field is omitted entirely.
V4 Pro is intentionally NOT marked `echo` — librefang#4842 reports it
working out-of-the-box; flip when there's an empirical reproducer.
Marks affected models:
providers/deepseek.toml
deepseek-v4-flash → echo
deepseek-reasoner → strip
providers/moonshot.toml
kimi-k2.6, kimi-k2.5, kimi-k2 → empty_string
providers/kimi-coding.toml
kimi-for-coding → empty_string
providers/byteplus-coding.toml
kimi-k2.5 → empty_string
providers/novita.toml
moonshotai/kimi-k2-thinking → empty_string
Tooling:
* schema.toml registers the field with the four enum options and a
`none` default so existing TOML files keep parsing unchanged.
* scripts/validate.py rejects unknown enum values; verified with a
hand-crafted negative case (`reasoning_echo_policy = "bogus"` →
validation fails with the expected message).
* `python3 scripts/validate.py` passes (267 models).
The librefang side that consumes this field will land in a follow-up
PR — until then, registry consumers ignore the field via
`#[serde(default)]` and the existing substring fallback continues to
work, so this commit is safe to ship independently.
Video and music modality models are billed per generation rather than
per token. Without this field the librefang runtime metering layer
records every call as $0 and emits a warning. Sourced from
https://platform.minimax.io/docs/guides/pricing-paygo.
- Hailuo 2.3 Fast: $0.33 per call (1080P/6s upper bound)
- Hailuo 2.3: $0.56 per call (1080P/6s upper bound)
- Hailuo 02: $0.56 per call (1080P/6s upper bound)
- Music 2.6: $0.15 per up-to-5-minute track
- Lyrics gen: $0.01 per song
Also adds per_call_cost to schema.toml so the field is documented
alongside the other cost fields.
Extend modality enum to support video and music, then register the
non-text MiniMax models that were already declared in
media_capabilities but had no concrete entries:
- image-01 ($0.0035/image)
- speech-2.8/2.6 hd & turbo ($60-$100 per 1M chars)
- Hailuo 2.3 Fast / 2.3 / 02 video models ($0.10-$0.56 per video)
- music-2.6, lyrics_generation
Per-call pricing is documented in inline comments since the schema's
token-based cost fields don't naturally fit per-call billing.
schema.toml and scripts/validate.py both updated; the change is
additive (existing modality values remain valid).
Introduces image-generation models as a first-class [[models]] entry via
a new `modality` field on the model schema ("text" default, "image",
"audio"). When modality != "text", context_window / max_output_tokens
are optional since no conventional context gate exists — OpenAI's
gpt-image-2 docs omit them.
Adds `image_input_cost_per_m` / `image_output_cost_per_m` alongside
existing text token cost fields to cover the 4-price structure OpenAI
uses for image generation (text $5/$10, image $8/$30 per 1M tokens).
Validator updated to:
- accept any modality in {text, image, audio}
- require context_window/max_output_tokens only for modality=text
- range-check the two new cost fields
gpt-image-2 entry added to providers/openai.toml with pricing sourced
from https://developers.openai.com/api/docs/pricing. Snapshot
gpt-image-2-2026-04-21 listed as alias.
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.
* feat: convert schema.toml to machine-parseable format
Replace comment-based documentation format with structured TOML that
can be deserialized into the RegistrySchema Rust type. All 6 content
types (provider, agent, hand, integration, skill, plugin) preserved
with every field, description, enum option, and nested section.
* fix: format options arrays in schema.toml for taplo compliance
* chore: remove router agent
builtin:router has been replaced by LLM intent routing in the kernel.
Assistant is now the sole entry point — see librefang/librefang#1336.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: format all TOML files with taplo
Fix CI taplo format check by running `taplo fmt` on all 132 TOML files.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Community-maintained TOML catalog for LibreFang. New models can be added
via PR without requiring a LibreFang binary release.
Includes validation script, bilingual docs, and GitHub templates.