Skip to content

Hand-level skills allowlist does not propagate to per-role agent manifests #3135

Description

@neo-wanderer

Summary

When a multi-agent hand declares a top-level skills = [...] allowlist, the kernel does not propagate that list into the per-role agent manifests it derives at activation. Each derived agent ends up with AgentManifest.skills = [], and sorted_enabled_skills (crates/librefang-kernel/src/kernel/mod.rs:13185) treats empty as "unrestricted" — so the prompt_context.md of every installed skill gets inlined into every role's system prompt.

For a small role-specialised agent the impact is meaningful: a focused fetcher/dispatcher agent picks up multi-thousand-token skill bodies (vault skills, browser-automation fallbacks, etc.) it has no use for, on every turn.

Repro

HAND.toml:

id = "demo"
version = "0.1.0"
name = "Demo Hand"
description = "..."
category = "communication"

# Hand-level allowlist — author's intent: only these two for everyone.
skills = ["alpha", "beta"]

[agents.worker]
name = "demo-worker"
description = "..."

[agents.worker.model]
provider = "default"
model = "default"
system_prompt = """You are demo-worker."""

With several other unrelated skills installed (charlie, delta, epsilon), activate the hand. Inspect demo-worker's system prompt:

librefang hand activate demo
# Send any message to the agent so the system prompt is rendered;
# inspect the request transcript.

Observed: the prompt's <available_skills> section AND the inlined [EXTERNAL SKILL CONTEXT] blocks include charlie, delta, epsilon in addition to alpha, beta. The hand's allowlist had no effect.

Expected: only alpha and beta are inlined.

Workaround

Add the same skills = [...] field to each [agents.<role>] section. The per-agent allowlist IS honored:

[agents.worker]
name = "demo-worker"
description = "..."
skills = ["alpha", "beta"]   # workaround — duplicate the hand-level list

[agents.worker.model]
...

This works because the kernel reads e.manifest.skills per-agent at mod.rs:12058–12068 and mod.rs:13193 filters on the per-agent value.

Trace

  1. mod.rs:9046 — hand-activation loop iterates per-role agents, building each AgentManifest. The hand-level skills field is on HandDefinition but never copied into the role's AgentManifest.skills.
  2. mod.rs:12058 — capabilities resolution reads e.manifest.skills.clone(), which is empty for derived agents that didn't set their own.
  3. mod.rs:13127cached_skill_metadata calls collect_prompt_context(skill_allowlist) with the empty slice.
  4. mod.rs:13185 (sorted_enabled_skills) — allowlist.is_empty() || allowlist.contains(...) short-circuits to "all enabled", so every installed skill survives the filter.

Suggested fix

At hand activation (around mod.rs:9046–9051, where manifest.model.system_prompt is augmented with the resolved settings block), also copy the hand definition's top-level skills into the derived AgentManifest.skills IF the agent's own [agents.<role>] skills field is empty. Per-agent override wins; otherwise fall back to the hand-level list.

Pseudo-patch:

// In the per-role loop after manifest is materialised:
if manifest.skills.is_empty() && !def.skills.is_empty() {
    manifest.skills = def.skills.clone();
}

This preserves the existing [agents.<role>].skills override semantics and makes the hand-level field do what its presence suggests.

Out of scope

  • File-level skill granularity (allowlist a specific path within a skill). The current design is whole-skill or none, which is fine.
  • The mcp_servers allowlist follows the same pattern (mod.rs:6830 already has merge logic for it). Worth auditing parity but separate ticket.

Why it matters

Per-role agents that legitimately want narrow skill surfaces have to either:

  • duplicate the hand-level list per role (current workaround — boilerplate that drifts)
  • accept the inlined-everything bloat (token cost on every turn)

For small dispatcher-style agents the cost is the entire raison d'être of the role; for review-style agents it's noise. Either way, the manifest field labeled skills not actually constraining skills is a quiet footgun.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/kernelCore kernel (scheduling, RBAC, workflows)area/skillsSkill system and FangHub marketplaceawaiting-responseMaintainer replied — waiting for reporter/contributor feedbackhas-prA pull request has been linked to this issue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions