> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lyzr.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Skills

> Writing, registering, and testing GitAgent skills.

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

```python theme={null}
# 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.

```python theme={null}
@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:

```python theme={null}
@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:

```python theme={null}
# 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:

```python theme={null}
# skills/database/__init__.py
from .queries import query_database   # re-export so GitAgent discovers it
```

## Async skills

```python theme={null}
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:

```python theme={null}
@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:

```python theme={null}
# 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:

```bash theme={null}
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.
