9. **Claude Code tools are strings** — `["Read", "Edit", "Bash"]`, not @tool functions
Resources
12Install
npx skillscat add agentspan-ai/agentspan Install via the SkillsCat registry.
Agentspan — Build Durable AI Agents
Agentspan is a distributed, durable runtime for AI agents. Agents survive crashes, scale across machines, and pause for human approval. Use Python SDK.
Two Use Cases
Developer building agents: Define → deploy → serve → trigger by name. Long-lived, versioned, monitored.
Autonomous agent building ephemeral agents: Define → rt.run(agent, prompt) → get result → move on. No deploy. No serve. One call.
Quickstart (Ephemeral — for autonomous agents)
from agentspan.agents import Agent, AgentRuntime
agent = Agent(name="helper", model="openai/gpt-4o", instructions="You are a helpful assistant.")
with AgentRuntime() as rt:
result = rt.run(agent, "What is quantum computing?")
print(result.output["result"]) # String output
# Or: result.print_result() # Pretty-printedrt.run() handles deploy + workers + execution internally. The agent is ephemeral — created for this task, discarded after.
Production Pattern (for developers)
from agentspan.agents import Agent, AgentRuntime
agent = Agent(name="helper", model="openai/gpt-4o", instructions="...")
if __name__ == "__main__":
with AgentRuntime() as rt:
# Deploy to server. CLI alternative (recommended for CI/CD):
# agentspan deploy my_module
rt.deploy(agent) # Push definition to server (idempotent)
rt.serve(agent) # Start workers, poll for tasks (blocks forever)Trigger from outside: agentspan run helper "What is quantum computing?"
Configuration
# Default: reads AGENTSPAN_SERVER_URL from environment
rt = AgentRuntime()
# Explicit:
from agentspan.agents import AgentConfig
config = AgentConfig(server_url="http://localhost:6767/api", api_key="...")
rt = AgentRuntime(config=config)Environment variables: AGENTSPAN_SERVER_URL, AGENTSPAN_AUTH_KEY, AGENTSPAN_AUTH_SECRET
Agent
Agent(
name="my_agent", # Required. Unique. Alphanumeric + underscore/hyphen.
model="openai/gpt-4o", # "provider/model" format
instructions="You are a ...", # System prompt (str, callable, or PromptTemplate)
tools=[my_tool], # List of @tool functions
max_turns=25, # Max LLM iterations
timeout_seconds=0, # 0 = no timeout
max_tokens=None, # Max output tokens per LLM call
temperature=None, # LLM temperature
output_type=MyPydanticModel, # Structured output (Pydantic model)
planner=False, # Enable planning-first behavior
thinking_budget_tokens=None, # Extended reasoning token budget
credentials=["API_KEY"], # Credentials resolved from server
metadata={"team": "backend"}, # Custom metadata
)Model formats: "openai/gpt-4o", "anthropic/claude-sonnet-4-6", "google_gemini/gemini-2.5-flash", "claude-code/opus"
@agent Decorator
from agentspan.agents import agent
@agent(model="openai/gpt-4o", tools=[search])
def researcher():
"""You are a research assistant. Find and summarize information."""
# Use like: rt.run(researcher, "Find info about quantum computing")The docstring becomes the instructions.
AgentResult
result = rt.run(agent, "prompt")
result.output # Dict: {"result": "..."} or agent-specific shape
result.output["result"] # The text output (string)
result.status # "COMPLETED", "FAILED", "TERMINATED", "TIMED_OUT"
result.execution_id # Execution ID
result.error # Error message if failed, else None
result.token_usage # {"input_tokens": N, "output_tokens": N, ...}
result.finish_reason # "stop", "length", "error", "cancelled", "timeout", "guardrail"
result.is_success # True if COMPLETED
result.is_failed # True if FAILED/TERMINATED
result.sub_results # List of sub-agent results (multi-agent)
result.print_result() # Pretty-print the outputError Handling
result = rt.run(agent, "prompt")
if result.is_success:
print(result.output["result"])
elif result.is_failed:
print(f"Failed: {result.error}")
print(f"Status: {result.status}") # FAILED, TERMINATED, TIMED_OUT
print(f"Reason: {result.finish_reason}")For autonomous agents building ephemeral agents — always check result.is_success before using result.output.
Tools
from agentspan.agents import tool
@tool
def search(query: str) -> str:
"""Search the web for information."""
return f"Results for: {query}"
@tool(approval_required=True, credentials=["API_KEY"])
def delete_file(path: str) -> str:
"""Delete a file. Requires human approval."""
os.remove(path)
return f"Deleted {path}"Tool functions must have type hints and a docstring. The schema is generated automatically.
ToolContext (dependency injection)
from agentspan.agents import tool, ToolContext
@tool
def lookup(query: str, context: ToolContext) -> str:
"""Search with context."""
wf_id = context.execution_id
session = context.session_id
state = context.state # Mutable dict shared across tool calls
deps = context.dependencies # From Agent(dependencies={...})
return f"Found in execution {wf_id}"Server-side tools (no local worker needed)
from agentspan.agents import http_tool, mcp_tool, api_tool
weather = http_tool(
name="get_weather",
description="Get weather for a city",
url="https://api.weather.com/v1/current?city=${city}",
credentials=["WEATHER_API_KEY"],
)
github = mcp_tool(
server_url="https://mcp.github.com",
tool_names=["create_issue", "list_repos"],
credentials=["GITHUB_TOKEN"],
)
stripe = api_tool(
url="https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json",
tool_names=["CreatePaymentIntent", "ListCustomers"],
credentials=["STRIPE_SECRET_KEY"],
)Multi-Agent
Sequential Pipeline (>>)
researcher = Agent(name="researcher", model="openai/gpt-4o", instructions="Research the topic.")
writer = Agent(name="writer", model="openai/gpt-4o", instructions="Write a summary.")
pipeline = researcher >> writerParallel
Agent(
name="analysis",
model="openai/gpt-4o",
agents=[pros_agent, cons_agent],
strategy="parallel",
)Router
router_agent = Agent(name="router", model="openai/gpt-4o", instructions="Route to the right specialist.")
Agent(
name="team",
model="openai/gpt-4o",
agents=[billing, technical],
strategy="router",
router=router_agent,
)SWARM (peer-to-peer handoff)
from agentspan.agents.handoff import OnTextMention
coder = Agent(name="coder", model="openai/gpt-4o", instructions="Code. Say HANDOFF_TO_QA when done.")
qa = Agent(name="qa", model="openai/gpt-4o", instructions="Test. Say HANDOFF_TO_CODER if bugs found.")
Agent(
name="dev_team",
model="openai/gpt-4o",
agents=[coder, qa],
strategy="swarm",
handoffs=[
OnTextMention(text="HANDOFF_TO_QA", target="qa"),
OnTextMention(text="HANDOFF_TO_CODER", target="coder"),
],
)Scatter-Gather (fan-out/fan-in)
from agentspan.agents import scatter_gather
coordinator = scatter_gather(
name="multi_search",
worker=Agent(name="searcher", model="openai/gpt-4o-mini", instructions="Search and summarize."),
timeout_seconds=300,
)
# Spawns multiple copies of worker agent in parallel, aggregates resultsAgent as Tool
from agentspan.agents import agent_tool
specialist = Agent(name="math_expert", model="openai/gpt-4o", instructions="Solve math problems.")
orchestrator = Agent(
name="orchestrator",
model="openai/gpt-4o",
instructions="Delegate math to the specialist.",
tools=[agent_tool(specialist, description="Call the math expert")],
)Guardrails
from agentspan.agents import RegexGuardrail, LLMGuardrail, Guardrail, GuardrailResult
# Regex: block emails in output
RegexGuardrail(
name="no_emails",
patterns=[r"[\w.+-]+@[\w-]+\.[\w.-]+"],
message="Remove email addresses.",
on_fail="retry", # retry | raise | fix | human
max_retries=3,
)
# LLM: policy-based check
LLMGuardrail(
name="safety",
model="openai/gpt-4o-mini",
policy="Reject responses with medical advice.",
on_fail="raise",
)
# Custom function
def no_ssn(content: str) -> GuardrailResult:
if re.search(r"\b\d{3}-\d{2}-\d{4}\b", content):
return GuardrailResult(passed=False, message="Redact SSNs.")
return GuardrailResult(passed=True)
Guardrail(no_ssn, position="output", on_fail="retry", max_retries=3)Termination Conditions
from agentspan.agents import TextMentionTermination, MaxMessageTermination
Agent(
name="worker",
model="openai/gpt-4o",
instructions="Say DONE when finished.",
termination=TextMentionTermination("DONE"),
# OR: termination=MaxMessageTermination(10),
# Composable: termination=TextMentionTermination("DONE") | MaxMessageTermination(10),
)Gates (Conditional Pipelines)
from agentspan.agents.gate import TextGate
checker = Agent(name="checker", model="openai/gpt-4o",
instructions="Output NO_ISSUES if everything is fine.",
gate=TextGate("NO_ISSUES"), # Stops pipeline if text present
)
fixer = Agent(name="fixer", model="openai/gpt-4o", instructions="Fix the issue.")
pipeline = checker >> fixer # fixer only runs if checker finds issuesMemory
from agentspan.agents import ConversationMemory, SemanticMemory
# Conversation memory (chat history with windowing)
agent = Agent(
name="chatbot",
model="openai/gpt-4o",
memory=ConversationMemory(max_messages=50),
)
# Semantic memory (long-term, searchable)
memory = SemanticMemory()
memory.add("User prefers Python over JavaScript")
memory.add("User works at Acme Corp")
results = memory.search("What language does the user prefer?")Claude Code Agents
from agentspan.agents import Agent, ClaudeCode
# Simple: slash syntax
reviewer = Agent(
name="reviewer",
model="claude-code/sonnet",
instructions="Review code for quality.",
tools=["Read", "Glob", "Grep"], # Built-in Claude tools (strings only)
max_turns=10,
)
# With config
reviewer = Agent(
name="reviewer",
model=ClaudeCode("opus", permission_mode=ClaudeCode.PermissionMode.ACCEPT_EDITS),
instructions="Review code.",
tools=["Read", "Edit", "Bash"],
)Available tools: Read, Edit, Write, Bash, Glob, Grep, WebSearch, WebFetch
CLI Execution
Agent(
name="deployer",
model="openai/gpt-4o",
instructions="Use git and gh to manage repos.",
cli_commands=True,
cli_allowed_commands=["git", "gh", "curl"],
credentials=["GITHUB_TOKEN"],
)Code Execution
Agent(
name="data_scientist",
model="openai/gpt-4o",
instructions="Write and run Python code to analyze data.",
local_code_execution=True,
allowed_languages=["python"],
)Credentials
Credentials are always resolved from the server. No env var fallback. Missing credentials cause FAILED_WITH_TERMINAL_ERROR (non-retryable).
# Store credentials on server
agentspan credentials set --name GITHUB_TOKEN
agentspan credentials set --name OPENAI_API_KEYAgent(
name="github_agent",
model="openai/gpt-4o",
credentials=["GITHUB_TOKEN"], # Resolved at tool execution time
tools=[my_github_tool],
)Callbacks
from agentspan.agents import CallbackHandler
class MyCallbacks(CallbackHandler):
def on_agent_start(self, **kwargs): pass
def on_agent_end(self, **kwargs): pass
def on_model_start(self, **kwargs): pass
def on_model_end(self, **kwargs): pass
Agent(name="agent", model="openai/gpt-4o", callbacks=[MyCallbacks()])Structured Output
from pydantic import BaseModel
class Analysis(BaseModel):
sentiment: str
confidence: float
summary: str
Agent(name="analyzer", model="openai/gpt-4o", output_type=Analysis)Framework Integration
LangGraph
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI
from agentspan.agents import AgentRuntime
llm = ChatOpenAI(model="gpt-4o")
graph = create_react_agent(llm, tools=[my_tool])
with AgentRuntime() as rt:
result = rt.run(graph, "What is 15 * 7?") # Ephemeral
# Or production: rt.deploy(graph); rt.serve(graph)OpenAI Agents SDK
from agents import Agent as OpenAIAgent
from agentspan.agents import AgentRuntime
agent = OpenAIAgent(name="helper", instructions="...", model="gpt-4o")
with AgentRuntime() as rt:
result = rt.run(agent, "Hello") # EphemeralExecution API
with AgentRuntime() as rt:
# ── Ephemeral (autonomous agents) ──────────────────────
result = rt.run(agent, "prompt") # Sync: deploy + run + cleanup
result = await rt.run_async(agent, "prompt") # Async variant
# ── With options ───────────────────────────────────────
result = rt.run(agent, "prompt",
session_id="conv-123", # Multi-turn conversation
media=["https://example.com/image.png"], # Multimodal input
timeout=60000, # Timeout in ms
credentials=["MY_API_KEY"], # Runtime credentials
)
# ── Streaming ──────────────────────────────────────────
stream = rt.stream(agent, "prompt") # Sync stream
for event in stream:
print(event.type, event.content)
result = stream.get_result()
stream = await rt.stream_async(agent, "prompt") # Async stream
# ── Non-blocking ───────────────────────────────────────
handle = rt.start(agent, "prompt") # Returns immediately
status = rt.get_status(handle.execution_id) # Poll status
handle.pause() # Pause execution
handle.resume() # Resume
handle.cancel("no longer needed") # Cancel
# ── By name (trigger deployed agent) ───────────────────
result = rt.run("agent_name", "prompt")
# ── Production ─────────────────────────────────────────
rt.deploy(agent) # Push definition
rt.serve(agent) # Start workers (blocks)Key Rules
- Agent names must be unique — alphanumeric, underscore, hyphen. Start with letter or underscore.
- Tools need type hints + docstring — schema is auto-generated
result.outputis a dict — useresult.output["result"]for the text, orresult.print_result()- Always check
result.is_success— especially in autonomous agent flows - Credentials come from server — no env var fallback,
FAILED_WITH_TERMINAL_ERRORif missing - Deploy is idempotent — safe to call on every startup
- Serve blocks forever — run triggering comes from outside (CLI, API, another process)
rt.run()is self-contained — handles deploy + workers + execution. Use for ephemeral agents.- Claude Code tools are strings —
["Read", "Edit", "Bash"], not @tool functions