Daita Logo

Skills

Skills are reusable, composable units of agent capability that bundle related tools with domain-specific instructions. They sit between raw tools and infrastructure plugins in the abstraction hierarchy.

#Overview

A skill packages a set of tools together with the instructions that tell the LLM how to use them. Unlike plugins — which are infrastructure connectors (PostgreSQL, S3, Slack) — skills carry behavioral intelligence: domain knowledge, conventions, prompt snippets, and tool orchestration patterns.

Use skills to:

  • Bundle related tools so agents can pick them up in one call
  • Inject domain-specific instructions into the system prompt automatically
  • Share reusable capabilities across multiple agents and projects
  • Load prompt engineering from markdown files instead of hard-coded strings
  • Declare and resolve plugin dependencies at attach time

Skills are LifecyclePlugin subclasses, so on_before_run hooks inject instructions into the system prompt automatically before every agent run.


#Quick Start

#The Skill helper

For simple skills — a handful of tools plus a short instruction string — use the Skill convenience class:

python
from daita import Agent, Skill, tool
 
@tool
def format_report(data: list, title: str) -> str:
    """Render a markdown report."""
    rows = "\n".join(f"- {r}" for r in data)
    return f"# {title}\n\n{rows}"
 
@tool
def generate_chart(series: list, kind: str = "bar") -> str:
    """Generate a chart description."""
    return f"{kind} chart with {len(series)} series"
 
report_skill = Skill(
    name="report_gen",
    description="Produces polished analytical reports",
    instructions="Always render results as markdown with a title and bulleted rows.",
    tools=[format_report, generate_chart],
)
 
agent = Agent(name="Analyst", llm_provider="openai", model="gpt-4o")
agent.add_skill(report_skill)
 
result = await agent.run("Summarize Q3 revenue with a chart.")

#The BaseSkill subclass pattern

For skills that need dynamic instructions, plugin dependencies, or tools that are built at init time, subclass BaseSkill:

python
from daita import BaseSkill
from daita.core.tools import AgentTool
 
class SecurityReviewSkill(BaseSkill):
    name = "security_review"
    description = "Detect security vulnerabilities in Python code"
    version = "1.0.0"
    instructions_file = "prompts/security_review.md"
 
    def get_tools(self) -> list[AgentTool]:
        return [scan_security_patterns, check_input_validation]

Class attributes (name, description, version, instructions, instructions_file) keep static metadata out of __init__. Override get_tools() to build tools dynamically.


#Core Concepts

#Instructions

Skill instructions are returned from get_instructions(user_prompt) and resolved in this order:

  1. instructions_file (read once, cached) — a path to a file containing the instructions text. Relative paths resolve against the skill's module directory when using BaseSkill; Skill requires an absolute path.
  2. instructions — an inline string.
  3. None — no instructions injected.

Override get_instructions to generate dynamic, prompt-aware instructions:

python
class ContextAwareSkill(BaseSkill):
    name = "context_aware"
 
    def get_instructions(self, user_prompt: str = "") -> str:
        if "report" in user_prompt.lower():
            return "Format output as a formal report with headings."
        return "Respond concisely."

Because BaseSkill inherits from LifecyclePlugin, its on_before_run hook injects whatever get_instructions returns into the system prompt automatically — you don't need to wire anything else up.

#Tools

Skills expose tools through get_tools(), which returns a list of AgentTool objects. The agent's tool registry picks them up the moment the skill is added.

python
class CodeQualitySkill(BaseSkill):
    name = "code_quality"
    instructions = "Prefer clear naming and small functions."
 
    def get_tools(self):
        return [check_complexity, check_naming, check_line_length]

The Skill helper exposes the tools you pass to its constructor — no override needed.

#Plugin Dependencies

Skills can declare that they require one or more plugin types to be registered on the agent before they can be attached. Override requires() and return a mapping of logical names to plugin types:

python
from daita import BaseSkill
from daita.plugins.base_db import BaseDatabasePlugin
 
class MigrationsSkill(BaseSkill):
    name = "migrations"
    instructions = "Follow forward-only migration policy."
 
    def requires(self):
        return {"db": BaseDatabasePlugin}
 
    def get_tools(self):
        db = self._resolved_plugins["db"]
        return [build_migration_tool(db), build_rollback_tool(db)]

When agent.add_skill(skill) runs, Daita:

  1. Calls skill.requires() to get the dependency map
  2. Searches the agent's registered plugins for a match by isinstance
  3. Populates skill._resolved_plugins[key] with the matching plugin instance
  4. Raises SkillError if any requirement is unmet

Add required plugins before skills that depend on them:

python
agent = Agent(name="DBA", llm_provider="openai", model="gpt-4o")
agent.add_plugin(postgresql(host="localhost", database="app"))  # first
agent.add_skill(MigrationsSkill())                              # then

#Usage

#Attaching skills to an agent

python
agent = Agent(name="Analyst", llm_provider="openai", model="gpt-4o")
agent.add_skill(report_skill)
agent.add_skill(CodeQualitySkill())
 
# All attached skills
for skill in agent.skills:
    print(skill.name, skill.version)

agent.skills returns every BaseSkill currently attached.

#Multiple skills on one agent

Skills compose — attach as many as you need. Their instructions concatenate into the system prompt in attach order, and their tools merge into the shared registry.

python
agent.add_skill(SecurityReviewSkill())
agent.add_skill(CodeQualitySkill())
agent.add_skill(documentation_skill)   # a Skill() instance
 
result = await agent.run("Review this PR: ...")

#Loading instructions from a file

Keep prompt engineering out of Python. instructions_file points at a markdown (or any text) file; BaseSkill resolves relative paths against the module the subclass is defined in:

python
my_project/
  skills/
    security.py
    prompts/
      security_review.md
python
# skills/security.py
class SecurityReviewSkill(BaseSkill):
    name = "security_review"
    instructions_file = "prompts/security_review.md"   # resolved alongside security.py

Files are read once and cached on the instance.


#API Reference

#BaseSkill

Abstract base class for skills. Subclass this for anything non-trivial.

Class attributes

AttributeTypeDescription
namestrUnique skill name. Used in logs and error messages.
descriptionstrHuman-readable description of what the skill does.
versionstrSkill version (defaults to "0.1.0").
instructionsstrInline instruction string. Ignored if instructions_file is set.
instructions_fileOptional[str]Path to a text file containing instructions. Relative paths resolve to the subclass module directory.

Methods

  • get_instructions(user_prompt: str = "") -> Optional[str] — return the instruction text. Override for dynamic prompts.
  • get_tools() -> List[AgentTool] — return the tools this skill exposes. Default: empty list.
  • requires() -> Dict[str, type] — declare plugin dependencies. Default: {}.
  • on_before_run(prompt: str) -> Optional[str] — lifecycle hook that injects instructions. You usually don't override this.

Instance attributes

  • config — any keyword arguments passed to __init__.
  • _resolved_plugins — dict of resolved dependencies, populated by add_skill.

#Skill

Concrete skill built inline from instructions and a list of tools.

python
Skill(
    name: str,
    *,
    instructions: str = "",
    instructions_file: Optional[str] = None,
    tools: Optional[List[AgentTool]] = None,
    description: str = "",
    version: str = "0.1.0",
    **config,
)

Raises ValueError if:

  • Both instructions and instructions_file are provided.
  • instructions_file is a relative path (use BaseSkill for relative path resolution).

#agent.add_skill(skill)

Attach a skill to an agent. Resolves skill.requires() against already-registered plugins.

Raises SkillError if any required plugin is missing.

#agent.skills

Property returning every BaseSkill currently attached to the agent.

#SkillError

Subclass of PluginError raised for skill-specific failures — most commonly unmet plugin dependencies. Importable from the top-level daita package.

python
from daita import SkillError
 
try:
    agent.add_skill(MigrationsSkill())
except SkillError as exc:
    print(f"Skill attach failed: {exc}")

#Complete Example

A code-review agent composed of two skills — security checks (from a BaseSkill subclass with a file-based prompt) and code quality checks:

python
import asyncio
from daita import Agent, BaseSkill
from daita.core.tools import AgentTool, tool
 
 
# -- Security skill ----------------------------------------------------------
 
@tool
async def scan_security_patterns(code: str) -> dict:
    """Scan Python source code for known vulnerability patterns."""
    ...
 
@tool
async def check_input_validation(code: str) -> dict:
    """Identify functions that accept string parameters without validation."""
    ...
 
class SecurityReviewSkill(BaseSkill):
    name = "security_review"
    description = "Detect security vulnerabilities in Python code"
    version = "1.0.0"
    instructions_file = "prompts/security_review.md"
 
    def get_tools(self) -> list[AgentTool]:
        return [scan_security_patterns, check_input_validation]
 
 
# -- Code quality skill ------------------------------------------------------
 
@tool
async def check_complexity(code: str) -> dict:
    """Compute cyclomatic complexity per function."""
    ...
 
@tool
async def check_naming(code: str) -> dict:
    """Flag non-PEP8 names."""
    ...
 
class CodeQualitySkill(BaseSkill):
    name = "code_quality"
    description = "Static analysis for clarity and maintainability"
    instructions = (
        "Prefer small functions and clear naming. "
        "Report issues grouped by severity."
    )
 
    def get_tools(self) -> list[AgentTool]:
        return [check_complexity, check_naming]
 
 
# -- Agent -------------------------------------------------------------------
 
async def main():
    agent = Agent(
        name="code_reviewer",
        llm_provider="anthropic",
        model="claude-sonnet-4-6",
    )
    agent.add_skill(SecurityReviewSkill())
    agent.add_skill(CodeQualitySkill())
 
    with open("module.py") as f:
        source = f.read()
 
    report = await agent.run(f"Review this file and produce a report:\n\n{source}")
    print(report)
 
 
asyncio.run(main())

See examples/deployments/code-review-agent/ in the daita-agents repo for the full runnable version, including the security_review.md prompt, tests, and deploy manifest.


#Skills vs Plugins vs Tools

PrimitivePurposeExample
ToolA single callable the LLM can invoke@tool def get_weather(city: str) -> str
SkillBundle of tools + domain instructionsSecurityReviewSkill, ReportGenSkill
PluginInfrastructure connector (stateful)postgresql, s3, slack

Rule of thumb:

  • Need to call one function? Use @tool.
  • Need an agent to connect to a database, queue, or API? Use a plugin.
  • Need to teach an agent how to do something — conventions, prompt engineering, multi-tool workflows — use a skill.

#See Also

  • Tools — how @tool and AgentTool work
  • Plugins Overview — the infrastructure side of the abstraction
  • Agent — attaching skills and plugins to agents