Observed since 20 Oct, 22:50 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Python SDK for building Agentpack tools
Zero-Config Python SDK for Agentpack Tools
Drop a .py file in your repo and get a production-ready, containerized, gRPC-enabled tool—instantly. No servers. No Docker. No infrastructure code.
pip install agentpack
# or
poetry add agentpack
# or
uv pip install agentpack
# tools/greet.py
from pydantic import BaseModel
from agentpack import tool, ExecutionContext
class GreetInput(BaseModel):
name: str
class GreetOutput(BaseModel):
greeting: str
@tool(
name="greet",
description="Greet a person by name",
input_schema=GreetInput,
output_schema=GreetOutput,
)
async def greet(input: GreetInput, ctx: ExecutionContext) -> GreetOutput:
ctx.logger.info("Greeting user", name=input.name)
return GreetOutput(greeting=f"Hello, {input.name}!")
No step 2. Agentpack Builder automatically:
tools/ directory.agentpack-manifest.jsonAgentpack Runtime automatically:
┌─────────────────────────────────────────────────┐
│ YOU: Write tools/calculator.py │
│ ↓ │
│ BUILDER: Scan → Manifest → Dockerfile → Image │
│ ↓ │
│ RUNTIME: Load → gRPC Server → Execute │
│ ↓ │
│ RUST CORE: Orchestrate with Tower middleware │
└─────────────────────────────────────────────────┘
You own: Business logic (100% focus) Agentpack owns: Servers, containers, orchestration, observability
Every tool handler receives ctx with production-ready utilities:
# Get optional secret
api_key = await ctx.secrets.get("API_KEY")
# Get required secret (raises if missing)
api_key = await ctx.secrets.require("API_KEY")
# Secrets configured via Agentpack Dashboard:
# - vault://kv/my-secret
# - aws-sm://secret-name
# - gcp-sm://projects/*/secrets/*
# GET with automatic retries
response = await ctx.http.get(
"https://api.example.com/data",
params={"limit": 10},
headers={"Authorization": f"Bearer {token}"},
)
# POST, PUT, DELETE also available
response = await ctx.http.post(
"https://api.example.com/data",
json={"name": "value"},
)
ctx.logger.info("Processing request", user_id="123")
ctx.logger.error("Something went wrong", error=str(err))
# Logs automatically include:
# - Request ID
# - Trace ID
# - Tool name
# - Timestamp
span = ctx.trace.start_span("external-api-call")
try:
# ... do work ...
span.set_attribute("items.count", len(items))
finally:
span.end()
# Traces sent to OpenTelemetry Collector automatically
See examples/ directory:
examples/calculator.py)from enum import Enum
from pydantic import BaseModel
from agentpack import tool, ExecutionContext
class Operation(str, Enum):
ADD = "add"
SUBTRACT = "subtract"
MULTIPLY = "multiply"
DIVIDE = "divide"
class CalculatorInput(BaseModel):
operation: Operation
a: float
b: float
class CalculatorOutput(BaseModel):
result: float
operation: str
@tool(
name="calculator",
description="Perform basic arithmetic operations",
input_schema=CalculatorInput,
output_schema=CalculatorOutput,
)
async def calculator(input: CalculatorInput, ctx: ExecutionContext) -> CalculatorOutput:
# Pure business logic
if input.operation == Operation.ADD:
result = input.a + input.b
elif input.operation == Operation.SUBTRACT:
result = input.a - input.b
elif input.operation == Operation.MULTIPLY:
result = input.a * input.b
elif input.operation == Operation.DIVIDE:
if input.b == 0:
raise ValueError("Division by zero")
result = input.a / input.b
return CalculatorOutput(
result=result,
operation=f"{input.a} {input.operation.value} {input.b}"
)
examples/weather.py)Demonstrates:
ctx.secrets.require())ctx.http.get())@tool DecoratorCreate a tool definition.
from pydantic import BaseModel
from agentpack import tool, ExecutionContext
class MyInput(BaseModel):
value: int
class MyOutput(BaseModel):
result: int
@tool(
name="my_tool", # Unique tool identifier
description="My tool description", # Human-readable description
input_schema=MyInput, # Pydantic input model
output_schema=MyOutput, # Pydantic output model
)
async def my_tool(input: MyInput, ctx: ExecutionContext) -> MyOutput:
return MyOutput(result=input.value * 2)
Parameters:
name (str): Unique tool namedescription (str): Human-readable descriptioninput_schema (BaseModel): Pydantic input validation modeloutput_schema (BaseModel): Pydantic output validation modelReturns: Tool instance (internal use by runtime)
from agentpack import (
ToolError,
ValidationError,
SecretNotFoundError,
HttpError,
)
async def my_handler(input: MyInput, ctx: ExecutionContext) -> MyOutput:
# Validation errors raised automatically by Pydantic
# Secret not found
key = await ctx.secrets.require("API_KEY") # raises SecretNotFoundError
# HTTP errors from external APIs
response = await ctx.http.get("...") # raises HttpError on failure
# Custom errors
if not input.valid:
raise ToolError("Invalid input", "INVALID_INPUT")
Tools can have dependencies:
tools/
├── calculator/
│ ├── __init__.py # Entry point (exports tool)
│ ├── operations.py # Helper functions
│ └── types.py # Shared types
└── weather.py # Single-file tool
Builder discovers both patterns automatically:
tools/weather.pytools/calculator/__init__.pyimport pytest
from pydantic import BaseModel
from agentpack import tool, ExecutionContext
class TestInput(BaseModel):
value: int
class TestOutput(BaseModel):
result: int
@tool(
name="test_tool",
description="Test tool",
input_schema=TestInput,
output_schema=TestOutput,
)
async def test_tool(input: TestInput, ctx: ExecutionContext) -> TestOutput:
return TestOutput(result=input.value * 2)
@pytest.mark.asyncio
async def test_my_tool():
# Create mock context
from agentpack.context import create_execution_context
from agentpack.types import RequestMetadata
metadata = RequestMetadata(request_id="123")
ctx = create_execution_context(metadata)
# Execute tool
result = await test_tool.execute({"value": 5}, ctx)
assert result.result == 10
Nothing to do! Builder + Runtime handle everything:
agentpack build (or automatic via GitHub App)Optional agentpack.yaml for advanced settings:
version: 0.1
project: my-tools
agents:
- name: default
model: { provider: anthropic, name: claude-3-5-sonnet }
tools: ["*"] # All tools in tools/
resources:
- name: stripe_secret
type: secret
source: vault://kv/stripe
policies:
outbound_network:
allow: ["*.stripe.com", "*.openai.com"]
This SDK is intentionally minimal:
@tool decorator for definitionsExecutionContext typesAll orchestration lives in Rust core for:
The SDK is fully typed and works with mypy:
mypy tools/ --strict
tools/ directory@tool decorator.agentpack-manifest.json was generatedOptional[T] or = Field(default=...) for optional fieldsagentpack-runtime-python - Internal gRPC runtime (private)@agentpack/sdk - TypeScript SDK (same zero-config approach)MIT OR Apache-2.0
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.1.0 | 19 Oct 2025 | 15 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 22:50 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:65f3ffb45e22c97d9202681576f0b5b449bbba13b5a7ec665fcb8f353a145d62