Skip to main content

Quickstart

A Waxell agent is a Python class decorated with @agent that exposes @tool methods (capabilities) and @workflow methods (orchestration). The runtime handles tracing, policy enforcement, working memory, and LLM routing — you write business logic.

One canonical import path: everything customer-facing is exposed under waxell_runtime. Don't import from waxell_sdk — that's an internal implementation detail.

from waxell_runtime import agent, workflow, tool   # ✓ canonical
from waxell_sdk import agent # ✗ internal, will break

Install

pip install waxell           # canonical meta-package — pulls runtime + observe
wax login

If you need just the runtime engine without observability (size-constrained, separate observability path, etc.), pip install waxell-runtime is the alternate entry point and stays available forever.

wax login writes credentials to ~/.waxell/config. Verify:

wax whoami

Minimum viable agent

from waxell_runtime import agent, workflow, tool

@agent(name="lead_research", description="Research a lead and draft outreach")
class LeadResearchAgent:

@tool
async def fetch_company(self, ctx, domain: str) -> dict:
"""Look up a company by domain."""
return await ctx.domain("company", "lookup", domain=domain)

@workflow("draft_email")
async def draft_email(self, ctx, lead_id: str):
company = await ctx.tool("fetch_company", domain="acme.com")
email = await ctx.llm.generate(
prompt=f"Write a 3-sentence intro for {company['name']}",
output_format="text",
task="outreach_email",
)
return {"email": email, "company": company["name"]}

Push it to the platform:

wax push agent.py
wax agents list

That's the loop. The runtime handles the rest.

@agent replaces the class

After decoration, LeadResearchAgent is an AgentSpec, not a class you instantiate. The platform constructs and runs the agent for you.

What ctx gives you

Inside any @workflow or @tool:

CallPurpose
await ctx.tool("name", **inputs)Invoke another @tool on this agent
await ctx.domain("entity", "action", **inputs)Call a Waxell domain endpoint
await ctx.llm.generate(prompt=..., output_format=...)Routed LLM call (cost-aware, policy-aware)
ctx.secrets.get("OPENAI_API_KEY")Read a secret from the configured provider

For IDE autocomplete, type-hint ctx:

from waxell_runtime import WorkflowContext, ToolContext, Context

@workflow("draft_email")
async def draft_email(self, ctx: WorkflowContext, lead_id: str): ...

@tool
async def fetch_company(self, ctx: ToolContext, domain: str) -> dict: ...

Directory layout for larger agents

When a single file gets unwieldy:

my_agent/
agent.py # @agent class with tools=[...] workflows=[...]
tools/
fetch_company.py
workflows/
draft_email.py
# my_agent/agent.py
from waxell_runtime import agent

@agent(
name="lead_research",
description="Research a lead and draft outreach",
tools=["fetch_company"], # references tools/fetch_company.py
workflows=["draft_email"], # references workflows/draft_email.py
)
class LeadResearchAgent:
pass

The decorator strings are filenames (no .py), not Python symbols. One workflow per file is the convention.

Configuration

Config priority (highest first):

  1. Explicit WaxellClient(api_url=..., api_key=...) constructor args
  2. WaxellClient.configure(...) set globally at startup
  3. ~/.waxell/config (written by wax login)
  4. Environment variables: WAX_API_KEY, WAX_API_URL, WAX_TENANT

If none resolve, calls silently no-op. Run wax whoami to verify.

Migrating from waxell-observe

If you previously set WAXELL_API_KEY for the observe SDK, also set WAX_API_KEY. The runtime SDK reads only WAX_* (no fallback). Setting WAX_API_KEY alone covers both packages — observe falls back to it.

Use Claude Code with Waxell

If you use Claude Code, install the bundled skill so Claude understands the SDK conventions when you ask it to extend your agent:

wax claude-init

This drops a project-scoped skill at .claude/skills/waxell-runtime/SKILL.md. Claude activates it whenever it sees from waxell_runtime import ... in your project and will write idiomatic agent code for you. Re-run wax claude-init after upgrading waxell-runtime to refresh the skill.

For a global install, use --scope user. For just printing the skill content, use --print-only.

Common mistakes

MistakeSymptomFix
Setting only WAXELL_API_KEYRuntime calls silently no-opSet WAX_API_KEY instead
os.environ["OPENAI_API_KEY"] inside a workflowWorks locally, breaks in deploymentUse ctx.secrets.get("OPENAI_API_KEY")
LeadResearchAgent() after @agentTypeError: 'AgentSpec' object is not callableDon't instantiate. The platform runs the agent.
from waxell_sdk import agentWorks in editable install, breaks for customersAlways from waxell_runtime import agent

What's next