{% extends "base.html" %} {% block title %}Getting Started — Waffle{% endblock %} {% block content %}
{{ request.url.scheme }}://{{ request.url.netloc }}.
Create a new Poetry project and add the Waffle SDK plus the Claude agent runtime.
mkdir my-agent && cd my-agent poetry init --no-interaction --name my-agent --python ">=3.11,<4.0" poetry add heywaffle claude-agent-sdk
pyproject.toml[tool.waffle] agent = "my_agent.agent:app"
Create src/my_agent/agent.py. The Agent object is the entry point;
decorate a function with @app.webhook to handle incoming events from a named source.
from waffle_sdk import Agent, AgentContext
app = Agent("my-agent", project="default")
@app.webhook(source="greet")
async def greet(item: dict, ctx: AgentContext) -> dict:
name = item.get("name", "world")
return {"message": f"Hello, {name}!"}
Also create an empty src/my_agent/__init__.py so Python sees it as a package.
greet that accepts a JSON payload and returns a reply.
No hub needed yet.
waffle test invokes a single handler locally, prints the result, and exits.
Useful for iterating quickly without running a full server.
poetry run waffle test
greet handler with a payloadpoetry run waffle test greet --input '{"name": "Alice"}'{"message": "Hello, Alice!"}.
If this hub is reachable, a session also appears in
Sessions.
Run waffle serve to keep the agent running and connected to this hub.
It will claim inbox items over SSE and dispatch them to your handlers automatically.
poetry run waffle serve --hub {{ request.url.scheme }}://{{ request.url.netloc }}export WAFFLE_HUB_URL={{ request.url.scheme }}://{{ request.url.netloc }}
poetry run waffle serveWith the agent connected, send a payload to the hub's webhook endpoint. The hub enqueues it as an inbox item and the agent picks it up automatically.
greet handler from anywherecurl -sX POST {{ request.url.scheme }}://{{ request.url.netloc }}/webhooks/greet \
-H 'content-type: application/json' \
-d '{"name": "Alice"}'
The URL pattern is /webhooks/<source> — the source matches the string you
passed to @app.webhook(source="..."). Any external system that can send an HTTP
POST (Linear, GitHub, n8n, your own app) can trigger your handler this way.
# Only fire when the payload contains action:"create"
@app.webhook(source="linear", filter={"action": "create"})
async def on_linear_create(item, ctx): ...
# Match nested keys by dot-path
@app.webhook(source="linear", filter={"type.name": "Issue"})
async def on_issue(item, ctx): ...
# No filter = match everything from that source
@app.webhook(source="greet")
async def greet(item, ctx): ...Three views show you what's happening inside the hub:
/webhooks/<source>,
including unrouted payloads that matched no subscribed handler.