A Zendesk ticket arrives. A webhook sensor shapes it into a CustomerTicket, and one agent decides whether the ticket points at real work in the codebase. When it does, the agent opens a pull request that links back to the ticket; when it is a question or nothing actionable, it does nothing. Most tickets are not code changes, so the workflow's first job is to tell the two apart.
One webhook sensor in sensors/sensors.py. Zendesk POSTs every new ticket to /hooks/zendesk, and the function shapes the payload into a typed CustomerTicket. Run loopy webhooks list to get the full URL to paste into Zendesk.
from loopy import sensor
from loopy.events import CustomerTicket
# webhook: Zendesk POSTs a new ticket; you shape it into a CustomerTicket
@sensor(webhook="/hooks/zendesk", emits="CustomerTicket")
def zendesk_tickets(req) -> CustomerTicket:
ticket = req.json["ticket"]
return CustomerTicket(ticket_id=ticket["id"], subject=ticket["subject"],
body=ticket["description"], link=ticket["url"])1 step in workflows/customer-feedback/, triggered by the CustomerTicket the sensor emits. The agent judges the ticket first and only writes code when it warrants it, so a how-to question costs a read and nothing else.
---
on: CustomerTicket
agent: SupportEngineer
output: { pr_url: url, verdict: str }
---
A customer opened Zendesk ticket {{ event.ticket_id }}:
"{{ event.subject }}".
{{ event.body }}
Decide whether this ticket points at real work in this codebase.
1. Search the code for the behavior the ticket describes.
2. If it is a bug or a small feature you can address, implement
the change on a branch and open a pull request that links back
to {{ event.link }}. If it is a question, a duplicate, or not
actionable in code, do nothing.
3. Return the PR URL (empty if you skipped) and a one-line verdict.This workflow writes, so it needs git write access: a GitHub App via loopy auth github, or a GITHUB_TOKEN with contents:write + pull_requests:write in the sandbox's env file.
CustomerTicket is emitted by the sensor and consumed by the workflow, so it is declared here because it crosses the bus. The workflow is terminal: it opens a pull request and returns its result, so it emits nothing.
agents:
SupportEngineer: {} # judges the ticket, opens a PR when it warrants work
events:
CustomerTicket: # inbound, from the Zendesk sensor
ticket_id: str
subject: str
body: str
link: url