/\ ATFGraph /docs
README Downloads
Examples / ATFGraph
Framework reference

This article is retained from the existing framework docs. Self-hosting commands and server-admin access differ from the managed CLI workflow. Start with Get started for the installed product.

Combined emitters demo

Directory examples/emitters

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.

Agent node label emitters-demo-agent, version 1.0.0.

Capabilities

CapabilityKindSignatureModuleSummary
handle_inboundtraversal(trigger: TriggerContext | None = None) -> dictexamples/emitters/python/agent.pyCatch-all for UI-managed emitters: record the payload plus its provider context.
handle_paymenttraversal(payment: dict, trigger: TriggerContext | None = None) -> dictexamples/emitters/python/agent.pyRecord a Stripe payment, deterministically.
handle_prtraversal(event: dict, trigger: TriggerContext | None = None) -> dictexamples/emitters/python/agent.pyRecord a GitHub pull_request action.
handle_ticktraversal(trigger: TriggerContext | None = None) -> dictexamples/emitters/python/agent.pyIncrement a cron-fire counter in global memory and record the wall-clock time.
summarize_issuetraversal(event: dict, trigger: TriggerContext | None = None) -> dictexamples/emitters/python/agent.pySummarise a GitHub issue with the configured model on opened/edited/reopened.

Non-default capability options: handle_inbound: accepts_manual_emitters=true; summarize_issue: timeout_seconds=120

Commands

Taken from this example's own README and module docstrings.

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.md
  • examples/emitters/__init__.py
  • examples/emitters/compose.yaml
  • examples/emitters/python/__init__.py
  • examples/emitters/python/agent.py
  • examples/emitters/python/agentnode.yaml
  • examples/emitters/python/fire_events.py

README

Rendered from examples/emitters/README.md; the source file is not modified.

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.

CapabilitySourceFires onNotes
handle_paymentstripepayment_intent.succeededcode-managed; a Stripe-specific transform runs in the worker before binding
handle_prgithubpull_requestcode-managed; raw event binds to event
summarize_issuegithubissuescode-managed; model call only with ATFGRAPH_MODEL_URL/ATFGRAPH_MODEL_NAME
handle_tickcronevery minutecode-managed; counts ticks in global memory
handle_inboundslack / generic_hmac / generic_bearerbound at runtimecatch-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

  1. Make the graph server reachable at a public webhook URL, for example through a development tunnel.
  2. Read each emitter ID from logs, the Console or /api/v2/emitters and form https://<public-host>/hooks/<id>.
  3. Configure that URL in Stripe, then put Stripe's endpoint signing secret in the server variable named by the emitter.
  4. Configure the GitHub PR and issue endpoints separately, choosing pull_request or issues and 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 use accepts_manual_emitters=True and 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-Id for 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.

ATFGraph documentationHosted workflow · Framework reference