From atomic-agents
Builds `BaseTool` subclasses with typed input/output schemas, config, and run methods for the Atomic Agents framework. Handles env-driven secrets and typed failure outputs.
How this skill is triggered — by the user, by Claude, or both
Slash command
/atomic-agents:create-atomic-toolThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
A tool is a deterministic capability an agent can invoke. In Atomic Agents, every tool is a `BaseTool[InSchema, OutSchema]` subclass with a typed `run()` (and optional `run_async()`). The input/output schemas double as the tool's signature for the LLM and as Pydantic validation at runtime.
A tool is a deterministic capability an agent can invoke. In Atomic Agents, every tool is a BaseTool[InSchema, OutSchema] subclass with a typed run() (and optional run_async()). The input/output schemas double as the tool's signature for the LLM and as Pydantic validation at runtime.
For deep material (MCP interop, distributing as a standalone package, advanced error patterns), the authority is ../framework/references/tools.md. This skill is the action-oriented path: clarify → write → verify.
framework skillframework skill: questions about Atomic Agents in general, or the user is doing something other than authoring a tool.Bundle into one message:
run_async() alongside run().Skip any question already answered in context.
Confirm the location and shape in one short block, then proceed:
<project>/tools/<tool_name>_tool.py (in-project tool — see ../framework/references/project-structure.md).<ToolName>Input, <ToolName>Output, optionally a typed failure shape.<ToolName>Config(BaseToolConfig) if the tool needs API keys, base URLs, timeouts, retries.from pydantic import Field
from atomic_agents import BaseIOSchema, BaseTool
class CalculatorInput(BaseIOSchema):
"""Arithmetic expression to evaluate."""
expression: str = Field(..., description="Python-style arithmetic, e.g. '2 + 2 * 3'.")
class CalculatorOutput(BaseIOSchema):
"""Result of evaluating the expression."""
result: float = Field(..., description="Numeric result.")
class CalculatorTool(BaseTool[CalculatorInput, CalculatorOutput]):
"""Evaluate simple arithmetic expressions safely."""
def run(self, params: CalculatorInput) -> CalculatorOutput:
import ast, operator as op
ops = {ast.Add: op.add, ast.Sub: op.sub, ast.Mult: op.mul, ast.Div: op.truediv}
def ev(n):
if isinstance(n, ast.Constant): return n.value
if isinstance(n, ast.BinOp): return ops[type(n.op)](ev(n.left), ev(n.right))
raise ValueError("unsupported")
return CalculatorOutput(result=ev(ast.parse(params.expression, mode="eval").body))
import os
import httpx
from typing import Literal, Optional
from pydantic import Field
from atomic_agents import BaseIOSchema, BaseTool, BaseToolConfig
class WeatherConfig(BaseToolConfig):
api_key: str = Field(
default_factory=lambda: os.environ.get("WEATHER_API_KEY", ""),
description="API key for the weather service.",
)
base_url: str = Field(
default="https://api.weather.example/v1",
description="Base URL for the weather API.",
)
timeout: float = Field(default=15.0, ge=1.0, le=120.0, description="Request timeout (s).")
class WeatherInput(BaseIOSchema):
"""A request for current weather conditions."""
city: str = Field(..., description="City name, e.g. 'Brussels'.")
class WeatherOutput(BaseIOSchema):
"""Current weather conditions, or a typed failure."""
status: Literal["ok", "error"] = Field(..., description="Outcome code.")
temperature_c: Optional[float] = Field(default=None, description="Temperature in Celsius.")
summary: Optional[str] = Field(default=None, description="Human-readable summary.")
error: Optional[str] = Field(default=None, description="Failure message when status='error'.")
class WeatherTool(BaseTool[WeatherInput, WeatherOutput]):
"""Fetch current conditions for a city from the weather API."""
def __init__(self, config: WeatherConfig | None = None):
super().__init__(config or WeatherConfig())
def run(self, params: WeatherInput) -> WeatherOutput:
cfg: WeatherConfig = self.config
if not cfg.api_key:
return WeatherOutput(status="error", error="WEATHER_API_KEY not set")
try:
r = httpx.get(
f"{cfg.base_url}/current",
params={"city": params.city},
headers={"Authorization": f"Bearer {cfg.api_key}"},
timeout=cfg.timeout,
)
r.raise_for_status()
except httpx.HTTPError as e:
return WeatherOutput(status="error", error=str(e))
data = r.json()
return WeatherOutput(status="ok", temperature_c=data["temp_c"], summary=data["summary"])
async def run_async(self, params: WeatherInput) -> WeatherOutput:
cfg: WeatherConfig = self.config
if not cfg.api_key:
return WeatherOutput(status="error", error="WEATHER_API_KEY not set")
async with httpx.AsyncClient(timeout=cfg.timeout) as client:
try:
r = await client.get(
f"{cfg.base_url}/current",
params={"city": params.city},
headers={"Authorization": f"Bearer {cfg.api_key}"},
)
r.raise_for_status()
except httpx.HTTPError as e:
return WeatherOutput(status="error", error=str(e))
data = r.json()
return WeatherOutput(status="ok", temperature_c=data["temp_c"], summary=data["summary"])
input_schema / output_schema as class attributes — it shadows the framework-managed property.run() returns the output schema instance, not a dict.BaseToolConfig, never hardcoded.async def run_async, not arun — the framework calls run_async.raise for programmer error.Two integration shapes (see ../framework/references/tools.md for more):
Single-tool agent — agent's output schema is the tool's input schema:
agent = AtomicAgent[UserQuery, WeatherInput](config=config)
tool = WeatherTool()
call = agent.run(UserQuery(question="weather in Brussels?"))
result = tool.run(call)
Router agent — agent picks among tools via a discriminated union of tool-call schemas. Use this when the agent has 2–10 tools to choose from. For dozens, see the search+execute pattern in ../framework/references/orchestration.md.
uv run python -c "from <project>.tools.<tool_name>_tool import <ToolName>Tool, <ToolName>Input; t = <ToolName>Tool(); print(t.run(<ToolName>Input(...)))"
If imports fail with the docstring error, add the docstring on the schema. If self.input_schema is None, the generic parameters are missing — write class FooTool(BaseTool[FooInput, FooOutput]):, not class FooTool(BaseTool):.
Tell the user:
create-atomic-agent skill.../framework/references/orchestration.md.../framework/references/tools.md.class FooTool(BaseTool): with input_schema = ... class attributes — use generics: BaseTool[FooInput, FooOutput].run() instead of OutputSchema(...).MCPTransportType.STREAMABLE_HTTP — the correct value is HTTP_STREAM.async def arun(...) — the framework calls run_async.For deeper material — MCP interop, packaging a tool for atomic download, advanced router patterns — load ../framework/references/tools.md.
npx claudepluginhub joshuarweaver/cascade-ai-ml-agents-agent-framework --plugin brainblend-ai-atomic-agentsGuides the creation and wiring of an AtomicAgent with schemas, config, system prompt, provider client, history, hooks, and context providers.
Guides tool design for AI agents, covering JSON Schema, descriptions, validation, error handling, and the MCP standard.
Whole-repo audit for over-engineering: finds dead code, unnecessary abstractions, stdlib-replaceable dependencies. Outputs ranked findings and net line/dep savings.