Skip to content

OpenClaw available_skills Filtering: How Skills Get Selected for Agent Context

Problem

When I started building AI agents with skill systems, I assumed the model decided which skills to use. If a skill appeared in the <available_skills> section of the system prompt, the model could call it. Simple, right?

Not quite. I noticed skills disappearing from context without explanation. I’d configure a skill, run the agent, and it wouldn’t show up. No error message, no rejection at call time—just absence.

The model wasn’t rejecting these skills. They never reached the model in the first place.

What Happened?

Let me reconstruct the scenario.

I configured a new skill in my settings:

settings.yaml
skills:
entries:
my-dangerous-skill:
enabled: false # I'll enable it later

Then I expected the skill to appear in context when the agent started. But when I checked the system prompt:

<available_skills>
<!-- my-dangerous-skill is missing -->
</available_skills>

The skill wasn’t there. I checked the configuration—everything looked correct. I restarted the agent. Still nothing.

Then I noticed the enabled: false flag I’d set for testing. That was the problem.

The Four Filtering Layers

OpenClaw doesn’t just dump all configured skills into context. It applies four distinct filtering layers before any skill reaches the <available_skills> section.

Layer 1: Enable/Disable Switch

The first and most direct filter is the enabled flag:

skills:
entries:
dangerous-skill:
enabled: false # Kill switch engaged
safe-skill:
enabled: true # Explicit opt-in
default-skill: {} # enabled: true by default

When enabled === false, the skill exits the pipeline immediately. This is your emergency shutoff for any skill that’s causing problems.

I use this for skills that are experimental or temporarily broken:

skills:
entries:
experimental-feature:
enabled: false # Not ready for production

Layer 2: Bundled Skills Allowlist

Platform-provided skills have a separate governance mechanism:

skills:
allowBundled: true # Allow platform-provided skills
entries:
custom-skill:
enabled: true

When allowBundled === false, all bundled skills disappear from context—even if they’re individually enabled. This gives you control over platform-provided skills that ship with the agent framework.

// Pseudo-code for the bundled check
if (skill.source === 'bundled' && !config.skills.allowBundled) {
return false; // Filter out
}

This separation matters for security-conscious deployments where you want to audit every skill before allowing it.

Layer 3: Environment Eligibility

Skills can declare requirements that get checked at runtime:

interface SkillRequirements {
binaries?: string[]; // Required executables
envVars?: string[]; // Required environment variables
config?: string[]; // Required configuration keys
os?: string[]; // Supported operating systems
platform?: string; // Required platform (e.g., "remote")
}

The filtering logic checks each requirement:

// Pseudo-code for eligibility filtering
function checkEligibility(skill: Skill): boolean {
const requires = skill.requires || {};
// Check for required binaries
if (requires.binaries?.some(b => !hasBinary(b))) {
return false;
}
// Check for required environment variables
if (requires.envVars?.some(e => !process.env[e])) {
return false;
}
// Check for required configuration
if (requires.config?.some(c => !hasConfig(c))) {
return false;
}
// Check OS compatibility
if (requires.os && !requires.os.includes(currentOS)) {
return false;
}
// Check platform requirements
if (requires.platform === 'remote' && !isRemotePlatform()) {
return false;
}
return true;
}

Here’s a practical example. A skill that requires Git:

skills:
entries:
git-operations:
requires:
binaries:
- git
enabled: true

On a system without Git installed, this skill never appears in <available_skills>. The model never sees it, never tries to call it, and no error occurs.

Layer 4: Model Visibility Control

Some skills exist for internal use only—testing, auditing, pipeline operations. The disable-model-invocation flag hides them from the model:

skills:
entries:
audit-logger:
disable-model-invocation: true # Internal only

This is different from enabled: false. The skill might pass all eligibility checks and be fully functional, but it’s marked for internal use only:

// Layer 4: Model visibility
if (skill.disableModelInvocation) {
return false; // Don't show to model
}

I use this for skills that my orchestration layer calls directly, without model involvement.

The Complete Filtering Pipeline

Here’s how all four layers work together:

// Complete filtering logic
function filterSkills(skills: Skill[], config: Config): Skill[] {
return skills.filter(skill => {
// Layer 1: Enable/disable
if (config.skills.entries[skill.key]?.enabled === false) {
return false;
}
// Layer 2: Bundled allowlist
if (skill.source === 'bundled' && !config.skills.allowBundled) {
return false;
}
// Layer 3: Eligibility checks
const requires = skill.requires || {};
if (requires.binaries?.some(b => !hasBinary(b))) {
return false;
}
if (requires.envVars?.some(e => !process.env[e])) {
return false;
}
if (requires.config?.some(c => !hasConfig(c))) {
return false;
}
if (requires.os && !requires.os.includes(currentOS)) {
return false;
}
if (requires.platform && !isPlatform(requires.platform)) {
return false;
}
// Layer 4: Model visibility
if (skill.disableModelInvocation) {
return false;
}
return true;
});
}

Skills that pass all four checks appear in <available_skills>. Skills that fail any check get filtered out before reaching the model.

Why This Design?

The philosophy is simple: visibility precedes callability.

If a skill shouldn’t be used, don’t show it to the model. This prevents several problems:

  1. No confusing error messages. The model never tries to call a skill it can’t use, so users never see cryptic “skill not found” errors.

  2. Clean context window. Only relevant skills occupy context space, leaving more room for actual work.

  3. Fail-fast validation. Configuration problems surface early, not mid-conversation when a user tries to invoke a missing skill.

  4. Security by obscurity. Skills the model doesn’t know about can’t be accidentally invoked or exploited through prompt injection.

Common Mistakes

I made several mistakes when I first worked with this system:

Mistake 1: Checking the wrong layer. I assumed a skill was broken when it didn’t appear. Actually, I’d set enabled: false during testing and forgotten to re-enable it.

Mistake 2: Missing dependency checks. I configured a Docker skill but didn’t have Docker installed. The skill disappeared silently, and I spent hours debugging configuration that was actually correct.

Mistake 3: Confusing enabled with visible. I set disable-model-invocation: true thinking it would prevent execution. It only hides the skill—the orchestration layer can still call it directly.

Mistake 4: Platform-specific skills. I developed a skill on macOS with os: ["darwin", "linux"], then deployed to Windows. The skill vanished because Windows wasn’t in the allowlist.

Debugging Missing Skills

When a skill doesn’t appear in <available_skills>, I check in this order:

Terminal window
# 1. Is it explicitly disabled?
grep -r "enabled: false" ~/.config/openclaw/skills/
# 2. Are bundled skills blocked?
grep -r "allowBundled: false" ~/.config/openclaw/
# 3. Are dependencies missing?
which git docker npm # Check for required binaries
# 4. Are environment variables set?
echo $MY_REQUIRED_VAR
# 5. Is it hidden from the model?
grep -r "disable-model-invocation: true" ~/.config/openclaw/skills/

Configuration Best Practices

I follow these patterns for clean skill management:

# Good: Clear organization with explicit states
skills:
allowBundled: true
entries:
# Production skills - always visible
file-operations:
enabled: true
requires:
binaries: []
# Experimental skills - disabled by default
experimental-feature:
enabled: false
requires:
binaries: [node]
envVars: [OPENAI_API_KEY]
# Internal skills - functional but hidden
audit-logger:
enabled: true
disable-model-invocation: true

Summary

In this post, I explained how OpenClaw filters skills through four layers before they appear in <available_skills>. The key point is that visibility precedes callability—if a skill shouldn’t be used, the system hides it from the model rather than rejecting it at call time.

The four layers are: enable/disable flags, bundled skills allowlist, environment eligibility checks, and model visibility control. Understanding this pipeline helps debug missing skills and design better skill configurations.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments