A3S Docs
A3S CodeExamples

Skill Tool - Permission Isolation

Using the Skill tool for callable skills with temporary permission grants

Skill Tool - Permission Isolation

The Skill tool allows skills to be invoked as first-class tools with temporary permission grants. This enforces skill-based access patterns and prevents agents from bypassing skills to directly access underlying tools.

Overview

Traditional skills inject instructions into the system prompt, but the agent can still bypass them and call tools directly. The Skill tool solves this by:

  1. Making skills callable — LLM can invoke Skill("skill-name") as a tool
  2. Temporary permission grants — Skill's allowed-tools are granted during execution
  3. Automatic revocation — Permissions are revoked after execution (RAII pattern)
  4. Permission isolation — Parent agent cannot bypass skills to access tools

How It Works

┌─────────────────────────────────────────────────────────────┐
│ User Prompt: "Use data-processor to read README.md"        │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ LLM (Kimi/Claude/GPT-4)                                     │
│ Calls: Skill("data-processor", prompt="Read README.md")    │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ SkillTool.execute()                                         │
│ 1. Get skill from SkillRegistry                            │
│ 2. Create PermissionPolicy from allowed-tools              │
│ 3. Create new AgentLoop with skill permissions             │
│ 4. Execute skill with temporary permissions                │
│ 5. Return result                                            │
│ 6. Permissions automatically revoked (RAII)                │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ Result returned to LLM                                      │
└─────────────────────────────────────────────────────────────┘

Basic Usage

Step 1: Define a Skill

Create a skill file with allowed-tools:

skills/data-processor.md
---
name: data-processor
description: Process and analyze data files
allowed-tools: read(*), grep(*)
---

# Data Processor Skill

You are a data processing specialist. You can:
- Read files using the read tool
- Search for patterns using grep
- Analyze and summarize file contents

You CANNOT:
- Write files
- Execute bash commands
- Edit files

Step 2: Create Session with Skill

import { Agent } from '@a3s-lab/code';

const agent = await Agent.create('agent.hcl');

const session = agent.session('.', {
  skillDirs: ['./skills'],
});

// LLM can now invoke the skill
const result = await session.send(
  'Use the data-processor skill to read and analyze README.md'
);
from a3s_code import Agent, SessionOptions

agent = Agent.create("agent.hcl")

session = agent.session(".", SessionOptions(
    skill_dirs=["./skills"],
))

# LLM can now invoke the skill
result = session.send(
    "Use the data-processor skill to read and analyze README.md"
)

Permission Isolation

The key feature of the Skill tool is permission isolation — the parent agent can only access tools through approved skills.

Example: Restricted Agent

import { Agent, SessionOptions, PermissionPolicy, PermissionRule } from '@a3s-lab/code';

const agent = await Agent.create('agent.hcl');

// Agent can ONLY use Skill tool, not read/grep directly
const session = agent.session('.', {
  skillDirs: ['./skills'],
  permissionPolicy: new PermissionPolicy({
    allow: [new PermissionRule('Skill(*)')],  // Only allow Skill tool
    deny: [
      new PermissionRule('read(*)'),          // Deny direct read
      new PermissionRule('grep(*)')           // Deny direct grep
    ],
    defaultDecision: 'deny'
  })
});

// ✅ This works - agent calls Skill tool
const result1 = await session.send(
  'Use data-processor to read file.txt'
);

// ❌ This fails - agent tries to call read directly
const result2 = await session.send(
  'Read file.txt'
);
// Error: Tool 'read' is blocked by permission policy
from a3s_code import Agent, SessionOptions, PermissionPolicy, PermissionRule

agent = Agent.create("agent.hcl")

# Agent can ONLY use Skill tool, not read/grep directly
session = agent.session(".", SessionOptions(
    skill_dirs=["./skills"],
    permission_policy=PermissionPolicy(
        allow=[PermissionRule("Skill(*)")],  # Only allow Skill tool
        deny=[
            PermissionRule("read(*)"),        # Deny direct read
            PermissionRule("grep(*)")         # Deny direct grep
        ],
        default_decision="deny"
    )
))

# ✅ This works - agent calls Skill tool
result1 = session.send("Use data-processor to read file.txt")

# ❌ This fails - agent tries to call read directly
result2 = session.send("Read file.txt")
# Error: Tool 'read' is blocked by permission policy

Use Cases

1. Restricted Environments

Allow agents to use powerful tools only through approved skills:

# Skill: safe-executor
allowed-tools: bash(npm test), bash(cargo test)

Agent can only run approved bash commands through the skill.

2. Audit Trails

Track which skills are invoked and what tools they use:

// Hook into skill invocations
session.on('tool_start', (event) => {
  if (event.name === 'Skill') {
    console.log(`Skill invoked: ${event.args.skill_name}`);
  }
});

3. Skill Composition

Build complex workflows by chaining skill invocations:

# Skill: data-pipeline
allowed-tools: Skill(data-processor), Skill(data-validator)

One skill can invoke other skills.

4. Security Boundaries

Enforce security policies at the skill level:

# Skill: production-deployer
allowed-tools: bash(kubectl apply -f *), bash(helm upgrade *)

Only allow specific deployment commands.

Real-World Example

Scenario: Data Analysis Agent

You want an agent that can analyze data files but cannot modify them or execute arbitrary commands.

Step 1: Create Skills

skills/data-reader.md
---
name: data-reader
description: Read and parse data files
allowed-tools: read(*), grep(*)
---

# Data Reader

Read and parse data files. You can:
- Read CSV, JSON, and text files
- Search for patterns
- Extract specific data
skills/data-analyzer.md
---
name: data-analyzer
description: Analyze data and generate insights
allowed-tools: Skill(data-reader)
---

# Data Analyzer

Analyze data files and generate insights. You can:
- Use data-reader to load data
- Calculate statistics
- Identify trends

Step 2: Configure Agent

const session = agent.session('.', {
  skillDirs: ['./skills'],
  permissionPolicy: new PermissionPolicy({
    allow: [new PermissionRule('Skill(*)')],
    defaultDecision: 'deny'
  })
});

// Agent can only access data through skills
const result = await session.send(
  'Analyze sales data in data/sales.csv and identify trends'
);
session = agent.session(".", SessionOptions(
    skill_dirs=["./skills"],
    permission_policy=PermissionPolicy(
        allow=[PermissionRule("Skill(*)")],
        default_decision="deny"
    )
))

# Agent can only access data through skills
result = session.send(
    "Analyze sales data in data/sales.csv and identify trends"
)

Result:

  • ✅ Agent can read data files through data-reader skill
  • ✅ Agent can analyze data through data-analyzer skill
  • ❌ Agent cannot write files
  • ❌ Agent cannot execute arbitrary bash commands
  • ❌ Agent cannot bypass skills to access tools directly

Testing

The Skill tool enforces permission isolation end-to-end:

Test Summary:

  • ✅ LLM correctly invokes Skill("file-reader") tool
  • ✅ Skill uses read and grep tools internally
  • ✅ Permissions are granted during execution
  • ✅ Permissions are revoked after execution
  • ✅ Parent agent cannot bypass skill to access tools

Best Practices

1. Principle of Least Privilege

Only grant skills the minimum tools they need:

# ❌ Too permissive
allowed-tools: "*"

# ✅ Specific tools only
allowed-tools: "read(*), grep(*)"

2. Skill Composition

Break complex tasks into focused skills:

# ❌ One skill does everything
allowed-tools: "read(*), write(*), bash(*), grep(*)"

# ✅ Multiple focused skills
# Skill 1: data-reader (read, grep)
# Skill 2: data-writer (write)
# Skill 3: data-processor (Skill(data-reader), Skill(data-writer))

3. Clear Descriptions

Write clear skill descriptions so the LLM knows when to use them:

# ❌ Vague
description: Process data

# ✅ Specific
description: Read and analyze CSV/JSON data files, extract statistics, identify trends

4. Permission Policies

Combine Skill tool with permission policies for defense-in-depth:

// Layer 1: Permission policy (only allow Skill tool)
permissionPolicy: new PermissionPolicy({
  allow: [new PermissionRule('Skill(*)')],
  defaultDecision: 'deny'
})

// Layer 2: Skill allowed-tools (only allow read/grep)
allowed-tools: "read(*), grep(*)"

Troubleshooting

Skill Not Found

Error: Skill 'data-processor' not found

Solution: Ensure the skill is loaded:

const session = agent.session('.', {
  skillsDir: './skills'  // Directory containing skill files
});

Permission Denied

Error: Tool 'read' is blocked by permission policy

Solution: Check the skill's allowed-tools:

# Skill must allow the tool
allowed-tools: "read(*), grep(*)"

Nested Skill Invocation

Question: Can a skill invoke another skill?

Answer: Yes! Use Skill(*) in allowed-tools:

# Skill: orchestrator
allowed-tools: "Skill(data-reader), Skill(data-writer)"
  • Skills System — Complete skills documentation
  • Security — Security features and permissions
  • Tools — Built-in tools reference

On this page