Skip to main content
Skills are the atomic capabilities of a GitAgent. Each skill is a Python function that the LLM can choose to call when processing a request.

Defining a skill

# skills/get_weather.py
from gitagent import skill

@skill(
    name="get_weather",
    description="Get the current weather for a city. Use this when the user asks about weather conditions."
)
def get_weather(city: str, units: str = "celsius") -> dict:
    """Fetch current weather for a city."""
    # your implementation here
    return {"city": city, "temp": 22, "condition": "sunny"}
The description field is what the LLM reads to decide when to call this skill. Write it as if you’re explaining the skill to the model: when to use it, what it does, and what it returns.

Input parameters

Skill parameters become the JSON schema that the LLM uses to construct the call. GitAgent infers the schema from Python type hints.
@skill(
    name="search_documents",
    description="Search internal documents for information on a topic"
)
def search_documents(
    query: str,                          # required string
    max_results: int = 5,               # optional int with default
    filter_by: list[str] | None = None, # optional list
) -> list[dict]:
    ...
Supported types: str, int, float, bool, list, dict, list[str], dict[str, str], and None for optional fields.

Explicit schema

Override the auto-generated schema with explicit OpenAPI-compatible JSON Schema:
@skill(
    name="query_database",
    description="Run a read-only SQL query against the analytics database",
    input_schema={
        "type": "object",
        "properties": {
            "sql": {"type": "string", "description": "A SELECT query"},
            "limit": {"type": "integer", "default": 100, "maximum": 1000}
        },
        "required": ["sql"]
    }
)
def query_database(sql: str, limit: int = 100) -> list[dict]:
    ...

Output types

Skills can return any JSON-serializable value. Common patterns:
# Return a string (simplest)
def summarize(text: str) -> str:
    return "Summary: ..."

# Return structured data
def get_user(user_id: str) -> dict:
    return {"id": user_id, "name": "Alice", "email": "alice@example.com"}

# Return a list
def search(query: str) -> list[dict]:
    return [{"title": "...", "url": "..."}]

# Signal an error
from gitagent import SkillError

def read_protected_file(path: str) -> str:
    if not is_allowed(path):
        raise SkillError(f"Access denied: {path}")
    return open(path).read()
SkillError is reported to the LLM as a tool error — the LLM can then decide how to handle it (retry, explain to user, try a different approach).

Skill organization

Skills can be a single .py file or a Python package:
skills/
├── search_web.py           ← single-file skill
├── database/               ← package skill
│   ├── __init__.py         ← must export the @skill-decorated function
│   ├── connection.py
│   └── queries.py
The __init__.py for a package skill must export the decorated function:
# skills/database/__init__.py
from .queries import query_database   # re-export so GitAgent discovers it

Async skills

import asyncio
from gitagent import skill

@skill(name="fetch_data", description="Fetch data from an async API")
async def fetch_data(endpoint: str) -> dict:
    async with aiohttp.ClientSession() as session:
        async with session.get(endpoint) as response:
            return await response.json()
GitAgent runs async skills in the event loop automatically.

Skill metadata

Additional metadata for documentation and tooling:
@skill(
    name="send_email",
    description="Send an email to a recipient",
    category="communication",        # for grouping in docs
    requires_confirmation=True,      # prompt user before executing
    examples=[
        {"input": {"to": "alice@example.com", "subject": "Hello"}, "output": "sent"},
    ]
)
def send_email(to: str, subject: str, body: str) -> str:
    ...

Testing skills

Test skills as regular Python functions:
# tests/test_get_weather.py
from skills.get_weather import get_weather

def test_get_weather_returns_temp():
    result = get_weather("London")
    assert "temp" in result
    assert isinstance(result["temp"], (int, float))

def test_get_weather_default_units():
    result = get_weather("Paris")
    assert result["units"] == "celsius"
Run tests:
gitagent test             # runs all tests in tests/
gitagent test skills/get_weather.py  # test a specific skill

Best practices

Name skills precisely. The name is used in traces and logs. search_internal_knowledge_base is better than search. One action per skill. send_email and read_email should be separate skills. The LLM handles composition. Return structured data when possible. Return {"result": value, "metadata": {...}} rather than plain strings — it gives downstream skills and hooks richer data to work with. Write descriptions from the LLM’s perspective. Describe when to call the skill, not just what it does. Include the types of questions or tasks it handles. Handle errors gracefully. Raise SkillError with a clear message rather than letting exceptions bubble up as opaque failures.