Combined emitters demo
Use this node to follow events from receipt to recorded work. It covers Stripe, GitHub, cron, Slack, generic HMAC and generic Bearer sources. Handlers use deterministic logic except the optional GitHub issue summary, which calls a model only when configured. Trigger declarations select event types; handler code applies action-specific behavior.
Capabilities
| Capability | Kind | Signature | Module | Summary |
|---|---|---|---|---|
handle_inbound | traversal | (trigger: TriggerContext | None = None) -> dict | examples/emitters/python/agent.py | Catch-all for UI-managed emitters: record the payload plus its provider context. |
handle_payment | traversal | (payment: dict, trigger: TriggerContext | None = None) -> dict | examples/emitters/python/agent.py | Record a Stripe payment, deterministically. |
handle_pr | traversal | (event: dict, trigger: TriggerContext | None = None) -> dict | examples/emitters/python/agent.py | Record a GitHub pull_request action. |
handle_tick | traversal | (trigger: TriggerContext | None = None) -> dict | examples/emitters/python/agent.py | Increment a cron-fire counter in global memory and record the wall-clock time. |
summarize_issue | traversal | (event: dict, trigger: TriggerContext | None = None) -> dict | examples/emitters/python/agent.py | Summarise a GitHub issue with the configured model on opened/edited/reopened. |
Commands
ATFGRAPH_API_KEY=<the same key> python -m examples.emitters.python.fire_events
ATFGRAPH_SERVER=http://127.0.0.1:18880 python -m examples.emitters.python.fire_events
docker compose -p atfgraph-emitters-demo -f examples/emitters/compose.yaml down --volumes
docker compose -p atfgraph-emitters-demo up --build --wait
python -m atfgraph.worker --module examples.emitters.python.agent:app
python -m examples.emitters.python.fire_events
Files
examples/emitters/README.mdexamples/emitters/__init__.pyexamples/emitters/compose.yamlexamples/emitters/python/__init__.pyexamples/emitters/python/agent.pyexamples/emitters/python/agentnode.yamlexamples/emitters/python/fire_events.py
README
Use this node to follow events from receipt to recorded work. It covers Stripe, GitHub, cron, Slack, generic HMAC and generic Bearer sources. Handlers use deterministic logic except the optional GitHub issue summary, which calls a model only when configured. Trigger declarations select event types; handler code applies action-specific behavior.
| Capability | Source | Fires on | Notes |
|---|---|---|---|
handle_payment | stripe | payment_intent.succeeded | code-managed; a Stripe-specific transform runs in the worker before binding |
handle_pr | github | pull_request | code-managed; raw event binds to event |
summarize_issue | github | issues | code-managed; model call only with ATFGRAPH_MODEL_URL/ATFGRAPH_MODEL_NAME |
handle_tick | cron | every minute | code-managed; counts ticks in global memory |
handle_inbound | slack / generic_hmac / generic_bearer | bound at runtime | catch-all for the three UI-managed emitters fire_events.py creates |
Quick start (local processes)
With the SDK installed, run from the repository root:
# 1. Graph server with the demo signing secrets in ITS environment
STRIPE_DEMO_SECRET=demo-stripe-signing-secret-change-me \
GITHUB_DEMO_SECRET=demo-github-webhook-secret-change-me \
SLACK_DEMO_SIGNING_SECRET=demo-slack-signing-secret-change-me \
GENERIC_HMAC_DEMO_SECRET=demo-generic-hmac-secret-change-me \
GENERIC_BEARER_DEMO_TOKEN=demo-generic-bearer-token-change-me \
target/debug/atfgraphd
# 2. The demo node as an outbound worker (it logs its webhook URLs after syncing)
python -m atfgraph.worker --module examples.emitters.python.agent:app
# 3. Fire signed Stripe, GitHub, Slack, HMAC and Bearer events (cron fires on its own)
python -m examples.emitters.python.fire_events
open http://127.0.0.1:18880/ui/
The driver waits for the server and registered emitters, signs fixture bodies, posts them to /hooks/{emitter_id}, and prints admitted run results. It also creates the three manually managed emitters if absent. Each execution randomizes event IDs and delivery keys so a repeated demonstration creates new events.
Quick start (Docker Compose)
cd examples/emitters
cp .env.example .env # set ATFGRAPH_API_KEY (openssl rand -hex 24)
docker compose -p atfgraph-emitters-demo up --build --wait
cd ../..
ATFGRAPH_API_KEY=<the same key> python -m examples.emitters.python.fire_events
docker compose -p atfgraph-emitters-demo -f examples/emitters/compose.yaml down --volumes
Compose builds server and worker images from this checkout using the shared Dockerfiles. The container server listens beyond loopback, requiring an API key. Supply the same key to the worker and driver.
How the pieces fit together
fire_events.py
| signs the body with STRIPE_DEMO_SECRET / GITHUB_DEMO_SECRET / ...
v
POST /hooks/<emitter_id> <- public ingest URL owned by the graph server
|
v
graph server:
1. resolves the emitter, reads the secret named by secret_env from ITS environment
2. verifies the source's signature over the raw bytes (401 otherwise)
3. derives event type and delivery key (Stripe: body.type/body.id; GitHub: headers;
Slack: event.type/event_id; generic: configured headers or a body digest)
4. stores the inbound event, deduplicates on the delivery key, admits one traverser
v
emitters-demo-agent worker (outbound; claims the task):
- code-managed delivery: applies the binding's transform, binds and validates arguments
- UI-managed delivery: raw body already validated on admission, arrives unchanged
- injects TriggerContext (source, event_type, event_id, idempotency_key, managed_by)
- handler runs deterministically and writes to global memory
v
Console (/ui/): emitters, their events and the traversers they admitted
The server starts cron work without an HTTP request and deduplicates each due time across restart. Handler attempts can still retry; make external side effects idempotent.
Custom secrets
Set matching signing-secret values for the graph server and driver before starting them:
export STRIPE_DEMO_SECRET=... GITHUB_DEMO_SECRET=... SLACK_DEMO_SIGNING_SECRET=... \
GENERIC_HMAC_DEMO_SECRET=... GENERIC_BEARER_DEMO_TOKEN=...
secret_env names a variable resolved on the server. The worker does not need these webhook secrets. Demo defaults are placeholders; use provider-issued secrets for real traffic.
Optional: the model-backed issue summary
Configure ATFGRAPH_MODEL_URL, ATFGRAPH_MODEL_NAME and any required ATFGRAPH_MODEL_API_KEY on the worker; optionally set ISSUE_SUMMARY_MODEL. summarize_issue handles opened, edited and reopened actions. Without a provider, it stores summary: null with a summary_skipped reason. OpenRouter uses https://openrouter.ai/api/v1; the resilience fixture server provides a local alternative.
Pointing real Stripe / GitHub at the demo
- Make the graph server reachable at a public webhook URL, for example through a development tunnel.
- Read each emitter ID from logs, the Console or
/api/v2/emittersand formhttps://<public-host>/hooks/<id>. - Configure that URL in Stripe, then put Stripe's endpoint signing secret in the server variable named by the emitter.
- Configure the GitHub PR and issue endpoints separately, choosing
pull_requestorissuesand matching the configured signing secret.
Real deliveries use the same verification and routing pipeline as the signed fixtures. Test them separately to verify provider configuration, network access and action filters.
Differences from the original
- The node connects outbound; webhook URLs belong to the graph server rather than an agent port.
- Capabilities use
@app.traversal; manually bound sources useaccepts_manual_emitters=Trueand pass validated raw objects to**payload. - The cron counter uses compare-and-set updates in global memory.
- Model settings come from the worker environment; missing configuration records a skip reason.
- Generic sources use
X-Delivery-Idfor deduplication. Without it, identical bodies can share a derived delivery key. - The Python driver replaces the previous shell driver. Compose uses the shared server/node images. Trigger-event DID signing is outside this emitter example.
The migrated-operations tests run the driver and worker against a real local daemon, with demo secrets on the daemon. This verifies signed fixture deliveries, not live provider setup.