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.
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:
| Call | Purpose |
|---|---|
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):
- Explicit
WaxellClient(api_url=..., api_key=...)constructor args WaxellClient.configure(...)set globally at startup~/.waxell/config(written bywax login)- Environment variables:
WAX_API_KEY,WAX_API_URL,WAX_TENANT
If none resolve, calls silently no-op. Run wax whoami to verify.
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
| Mistake | Symptom | Fix |
|---|---|---|
Setting only WAXELL_API_KEY | Runtime calls silently no-op | Set WAX_API_KEY instead |
os.environ["OPENAI_API_KEY"] inside a workflow | Works locally, breaks in deployment | Use ctx.secrets.get("OPENAI_API_KEY") |
LeadResearchAgent() after @agent | TypeError: 'AgentSpec' object is not callable | Don't instantiate. The platform runs the agent. |
from waxell_sdk import agent | Works in editable install, breaks for customers | Always from waxell_runtime import agent |
What's next
- Runtime overview — execution context, durability, backends
- SDK overview — every decorator and spec type
- First agent tutorial — guided walkthrough with explanation
- Workflows tutorial — multi-step orchestration patterns