Ratel Docs
Python SDK

Quickstart

Install ratel-ai, put your tools and prompt playbooks in Ratel catalogs, and hand any agent loop the three capability tools.

This is the Python path. TypeScript reader? Start at the TypeScript quickstart. Rather not write the integration yourself? Point your coding agent at the skills suite.

Ratel is framework-agnostic: everything the SDK hands you is a plain executable tool — metadata plus an execute handler — that plugs into any agent loop. This page shows the two ways in: starting from scratch and migrating an existing agent.

Before you start

Use Python 3.10 or newer. Nothing on this page calls a model, so no API key is needed.

How it works

Everything starts with a ToolCatalog: register each of your tools once, pairing its metadata (id, description, JSON schemas) with the handler that runs it. A SkillCatalog holds Markdown playbooks the same way, ranked in their own corpus.

The agent reaches both through the three capability tools — search_capabilities (find tools and skills by description), invoke_tool (run a tool by id), and get_skill_content (load a skill's body) — so the full catalog never enters the prompt. How the pieces fit per turn is covered in Architecture.

Start from scratch

Create a project and install the SDK

mkdir ratel-quickstart && cd ratel-quickstart
python -m venv .venv
source .venv/bin/activate
python -m pip install "ratel-ai==0.4.0"

Requirements, prebuilt wheels, and the mcp / otlp extras are on Install.

Register a tool and a skill

Create main.py. Tools carry their metadata and handler; skills carry a description and a Markdown body:

import asyncio

from ratel_ai import (
    ExecutableTool,
    Skill,
    SkillCatalog,
    ToolCatalog,
    get_skill_content_tool,
    invoke_tool_tool,
    search_capabilities_tool,
)

catalog = ToolCatalog()
catalog.register(
    ExecutableTool(
        id="get_weather",
        name="get_weather",
        description="Get the current weather for a city.",
        input_schema={
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "City name"},
            },
            "required": ["city"],
        },
        output_schema={
            "type": "object",
            "properties": {
                "city": {"type": "string"},
                "temperatureC": {"type": "number"},
                "condition": {"type": "string"},
            },
            "required": ["city", "temperatureC", "condition"],
        },
        execute=lambda args: {
            "city": args["city"],
            "temperatureC": 24,
            "condition": "sunny",
        },
    )
)

skills = SkillCatalog()
skills.register(
    Skill(
        id="weather-briefing",
        name="weather-briefing",
        description="How to report the weather: temperature first, then condition.",
        body="## Weather briefing\nAnswer in one sentence: temperature first, then the condition. No filler.",
    )
)

# The three capability tools are the only tools your agent needs to see.
# Construct them after registering, so the skills bucket is advertised.
search = search_capabilities_tool(catalog, skills)  # id: "search_capabilities"
invoke = invoke_tool_tool(catalog)                  # id: "invoke_tool"
load = get_skill_content_tool(skills)               # id: "get_skill_content"

Run the discovery loop by hand

Append the exact calls a model makes at runtime — capability tools own async handlers, so await execute directly — then run the file:

async def main() -> None:
    print(await search.execute({"query": "weather in Rome"}))
    print(await invoke.execute({"toolId": "get_weather", "args": {"city": "Rome"}}))
    print(await load.execute({"skillId": "weather-briefing"}))


asyncio.run(main())
python main.py

Three results, no model key involved:

  • search_capabilities returns two independently-ranked buckets: get_weather under tools, weather-briefing under skills. Neither schema nor body entered any prompt to get there.
  • invoke_tool runs the handler and returns its result untouched: {"city": "Rome", "temperatureC": 24, "condition": "sunny"}.
  • get_skill_content returns the playbook: {"body": "## Weather briefing\n…"}.

The full result shapes and error contract are on Progressive disclosure.

Hand the loop to your agent

Each capability tool is a plain ExecutableToolid, name, description, input_schema, output_schema, execute. Map those fields onto your framework's tool type (a few lines anywhere), add the three tools to the agent's tool list, and tell it to search before answering:

Find tools and playbooks with search_capabilities. Run tools with invoke_tool.
When a skill matches, load it with get_skill_content and follow it.

Ready-made wiring — the Pydantic AI adapter and per-turn assembly — is on Framework integrations. Production agents usually add a top-K pre-filter next to the capability tools; that pattern (and the sync-vs-async dispatch rules) is on Use the discovery tools.

Migrate an existing agent

A migration changes where capabilities live, not your model or business logic. Start with one workflow, prove it still behaves the same, then repeat.

Existing assetAfter migration
Tool IDs, schemas, and handlersRegister once in ToolCatalog; keep authorization and side effects in the handler.
Conditional runbooks and examplesRegister in SkillCatalog; load only when relevant.
Identity, safety, universal policy, output rules, and runtime contextKeep in the core prompt.
Model and agent loopKeep both; reuse the loop's prompt and tool seams.

Find the integration points

Find where your process creates long-lived dependencies and where each request builds the model's tool list. Create the catalogs at startup; change the model-facing list at the request boundary.

Register an existing tool

Tool definitions leave the repeated model request, not your code. Preserve the ID, schema contract, handler, authorization, permissions, and side effects. Convert framework-native schemas to JSON Schema at this boundary.

# Before: the schema is sent to the model on every request
tools_for_every_request = [existing_refund_tool]

# After: the same contract is registered once at startup
catalog = ToolCatalog()
catalog.register(
    ExecutableTool(
        id="issue_refund",
        name="issue_refund",
        description="Issue an approved refund for an order.",
        input_schema=issue_refund_input_schema,
        output_schema=issue_refund_output_schema,
        execute=issue_refund,  # the existing handler
    )
)

Register each tool in this workflow the same way. During a gradual migration, leave unmigrated tools in the old list and remove each migrated definition from it.

Extract an on-demand skill

Keep instructions and runtime context that must govern every turn in the core prompt. Move only conditional guidance, such as the refund workflow, into a self-contained skill:

# Before: the refund workflow is sent on every turn
instructions = "\n\n".join(
    [CORE_PROMPT, SAFETY_POLICY, OUTPUT_RULES, RUNTIME_CONTEXT, REFUND_RUNBOOK]
)

# After: everything except the runbook stays in the core prompt
core_instructions = "\n\n".join(
    [CORE_PROMPT, SAFETY_POLICY, OUTPUT_RULES, RUNTIME_CONTEXT]
)

skills = SkillCatalog()
skills.register(
    Skill(
        id="refund-runbook",
        name="refund-runbook",
        description="Check refund eligibility, obtain approval, then issue the refund.",
        tools=["issue_refund"],
        body=REFUND_RUNBOOK,
    )
)

The description says when to load the skill. Its tools list surfaces issue_refund beside the matched playbook.

Rewire and verify the agent

Add the discovery policy to the core prompt. After both catalogs are populated, build the three Ratel capability tools and adapt them at the request boundary:

discovery_instructions = """
Find tools and playbooks with search_capabilities. Run tools with invoke_tool.
When a skill matches, load it with get_skill_content and follow it.
""".strip()

instructions = "\n\n".join([core_instructions, discovery_instructions])
capability_tools = [
    search_capabilities_tool(catalog, skills),
    invoke_tool_tool(catalog),
    get_skill_content_tool(skills),
]

The capability tools are plain ExecutableTool values. Framework integrations shows the adapter and the production top-K turn assembler.

A refund request should now follow this path:

  1. search_capabilities returns refund-runbook and issue_refund.
  2. get_skill_content loads the eligibility and approval steps.
  3. invoke_tool calls the unchanged issue_refund handler.
  4. The agent answers from the tool result.

Before migrating the next workflow, verify the skill ranks, its declared tool appears, the handler receives the same arguments, and the initial request excludes the full catalog and refund runbook.

Let a coding agent do the migration

The skills suite ships skills for Claude Code, Cursor, Codex, and similar: ratel-integrate plans this migration for your codebase, and ratel-decompose-prompt splits the system prompt into skills.

Next steps

On this page