All examples

JETTSON.DEV · EXAMPLE

Stripe Triage Agent

This agent runs on 64 lines of instructions.

Pulls the latest Stripe events (charges, disputes, payment failures, cancellations), assigns each a severity, drafts a customer-facing reply for the urgent ones, posts a Slack summary, and remembers how similar incidents were handled so the next batch goes faster.

Who needs this — Customer support and engineering teams getting 20-100 Stripe events a day.

HOW IT WORKS

Memory in. Work. Memory out.

Every Jettson agent follows the same shape — and these steps map 1:1 to the lines in the prompt below.

  1. 1

    Recalls past triage decisions and tone preferences from memory

  2. 2

    Pulls the latest Stripe events (or a sample fixture if STRIPE_API_KEY isn't set)

  3. 3

    Classifies each event: critical / high / medium / low

  4. 4

    Drafts a reply email for everything critical or high

  5. 5

    Writes new memories for unfamiliar event patterns (importance 7)

  6. 6

    Posts a one-paragraph summary to Slack if SLACK_WEBHOOK is set

RUN IT

Bring the agent online.

Get an API key from /console/api-keys, set any optional env vars below, then run the script.

run.sh
#!/usr/bin/env bash
# Spawn the Stripe Triage Agent against the production Jettson API.
# Use ./run-local.sh while developing — same script, dev URL.
set -euo pipefail

API_KEY="${JETTSON_API_KEY:?Set JETTSON_API_KEY — get one at https://jettson.dev/console/api-keys}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TASK=$(cat "$SCRIPT_DIR/agent.md")

response=$(curl -sS -X POST https://jettson.dev/api/v1/agents \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  --data "$(jq -n --arg task "$TASK" '{ task: $task, name: "Stripe Triage" }')")

echo "$response" | jq

agent_id=$(printf '%s' "$response" | jq -r '.agent_id // empty')
if [ -n "$agent_id" ]; then
    echo
    echo "Watch progress: https://jettson.dev/console/agents/$agent_id"
fi
.env.example
# Required — your Jettson API key.
# Get one at https://jettson.dev/console/api-keys
JETTSON_API_KEY=jett_sk_live_...

# Optional — if unset, the agent triages a sample event fixture so you
# can demo the flow without wiring real Stripe.
STRIPE_API_KEY=sk_test_...

# Optional — if set, the agent POSTs a one-paragraph summary here when
# the triage run finishes.
SLACK_WEBHOOK=https://hooks.slack.com/services/...

THE PROMPT

What the model sees.

This is the entire task prompt — no orchestration code, no glue. Edit it to fit your house style.

agent.md
You are the Stripe Triage Agent for our customer support team. Your job is to look at the latest Stripe events, decide which ones need a human, and draft the messages so the human just has to press send.

## Step 1 — Recall what we've learned

Before doing anything else, call `jettson_memory_search` with:

- query: "Stripe event triage patterns and customer reply tone"
- namespace: "stripe_triage"
- limit: 8

If anything comes back, treat those memories as house rules — they override your defaults. Pay especially close attention to memories about (a) which event types we routinely ignore, (b) customer-tone preferences, and (c) wording that's gotten praise in the past.

## Step 2 — Fetch events

Try `jettson_http_request` GET `https://api.stripe.com/v1/events?limit=10`
with header `Authorization: Bearer ${STRIPE_API_KEY}`.

If `STRIPE_API_KEY` is not in the environment OR the request fails, use this sample batch instead and proceed:

```json
[
  {"id": "evt_001", "type": "charge.failed",                "amount": 4900, "customer_email": "tay@acme.io"},
  {"id": "evt_002", "type": "charge.dispute.created",       "amount": 24900, "customer_email": "jordan@axiom.dev"},
  {"id": "evt_003", "type": "customer.subscription.deleted", "customer_email": "kim@finchcoop.com"},
  {"id": "evt_004", "type": "invoice.payment_succeeded",    "amount": 9900},
  {"id": "evt_005", "type": "invoice.payment_failed",       "amount": 4900, "customer_email": "alex@harbor.so"}
]
```

## Step 3 — Classify

Severity rubric (override with memory if memory says otherwise):

- **critical** — disputes, chargebacks, second consecutive payment failure for a paid plan, anything with `amount > $100`
- **high** — first payment failure, subscription cancellations from paying customers, refund requests
- **medium** — declined trial conversions, customer.updated card changes
- **low** — invoice.payment_succeeded, customer.created, anything routine

For each event, output `{ event_id, type, severity, customer_email }`.

## Step 4 — Draft replies for critical + high

Write a short, plain-language email (3–5 sentences) per event. House tone:

- Lead with empathy, not policy
- Acknowledge the specific event ("your $49 charge on Tuesday")
- One concrete next step
- No legalese, no "we apologize for any inconvenience"

If memory contained a tone preference, follow it instead.

## Step 5 — Remember what's new

For each event type you classified that you haven't seen before in memory, write a new memory:

- `jettson_memory_put` with namespace `stripe_triage`, key `pattern_<event_type>`, value describing your severity call and reasoning, importance 7

Don't write a memory for routine `invoice.payment_succeeded`-style events — they're noise.

## Step 6 — Slack summary

If a Slack webhook URL is provided via the `SLACK_WEBHOOK` env var, POST a one-paragraph summary to it:

```
Stripe triage — N events. X critical, Y high. Drafts queued for review: K.
```

Use `jettson_http_request` with a JSON body of `{"text": "<summary>"}`. Skip silently if no webhook is set.

## Step 7 — Final result

Return one JSON object as your final answer:

```json
{
  "events_processed": <int>,
  "critical_count":   <int>,
  "high_count":       <int>,
  "drafted_responses": [
    { "event_id": "...", "customer_email": "...", "subject": "...", "body": "..." }
  ],
  "memory_writes": <int>,
  "slack_posted": <true|false>
}
```

That's the whole job. Be terse, be useful, and lean on memory.

SAMPLE OUTPUT

What you get back.

Representative output. Real runs vary with your memory state and the live data the agent finds — the shape stays consistent.

expected-output.md
# Sample output

A representative run against the sample-event fixture (no live Stripe key). Real runs vary — your memory state, your event mix, your house tone. The shape is the same.

## Final result

```json
{
  "events_processed": 5,
  "critical_count": 1,
  "high_count": 2,
  "drafted_responses": [
    {
      "event_id": "evt_002",
      "customer_email": "jordan@axiom.dev",
      "subject": "About your $249 dispute — let's sort this out",
      "body": "Hi Jordan,\n\nI saw the dispute you opened on the $249 charge from Tuesday and wanted to get ahead of it before anything escalates. Looks like the charge ran on the right card but the line items aren't itemized on your statement — that's almost certainly what triggered the dispute.\n\nIf you can withdraw the dispute with your bank, I'll send a clean line-itemed receipt within the hour. Reply here and I'll take it from there.\n\n— Customer Support"
    },
    {
      "event_id": "evt_001",
      "customer_email": "tay@acme.io",
      "subject": "Quick heads-up on your $49 charge",
      "body": "Hi Tay,\n\nYour $49 charge today didn't go through — the card came back declined. No action needed on your end yet; we'll retry automatically tomorrow morning.\n\nIf you'd rather swap to a different card, here's the link: https://billing.example.com\n\n— Customer Support"
    },
    {
      "event_id": "evt_003",
      "customer_email": "kim@finchcoop.com",
      "subject": "Sorry to see you go — and a quick question",
      "body": "Hi Kim,\n\nNo hard feelings — your cancellation is processed. Before you head out: was it about price, a specific feature, or something else? I'm not going to upsell you, I just want to learn what we got wrong.\n\nEither way, the door's open if you ever want to come back.\n\n— Customer Support"
    }
  ],
  "memory_writes": 3,
  "slack_posted": false
}
```

## Memories the agent wrote

```
namespace=stripe_triage  key=pattern_charge.dispute.created  importance=7
  → "Disputes are critical. Lead with empathy + offer a concrete fix (itemized receipt) before quoting policy. Customer almost always withdraws when given a clean line-item invoice within the hour."

namespace=stripe_triage  key=pattern_charge.failed  importance=7
  → "First-time charge failures are high, not critical. Reassure that we retry automatically; provide swap-card link as a soft option. Don't dunning-email on the first try."

namespace=stripe_triage  key=pattern_subscription.deleted  importance=7
  → "Cancellations are high. Don't try to save the customer — ask one structured question (price/feature/other). Compounds into churn-reason data over time."
```

## Slack post (if `SLACK_WEBHOOK` was set)

```
Stripe triage — 5 events. 1 critical, 2 high. Drafts queued for review: 3.
```

## What this would look like on the second run

The next time this agent runs, those three memories surface during `jettson_memory_search` in step 1. The agent skips the rubric entirely for `charge.dispute.created` (it remembers the pattern), reuses the same email skeleton, and only writes a new memory if a previously-unseen event type shows up. Expected wall-clock time on a second run with no new event types: ~6 seconds.

CUSTOMIZE

How to make it yours.

  • Different severity thresholds

    Edit the Severity rubric in agent.md. The agent reads it verbatim.

  • Custom email tone

    Drop tone guidance into agent.md under Reply style — or seed memory with POST /api/v1/memory so the first run already knows your house voice.

  • Different events to triage

    Swap the Stripe call for any HTTP endpoint returning events (Linear, Intercom, PagerDuty). The classifier logic is identical.

  • Different downstream

    Replace the Slack post with a Linear ticket creation, an email, or anything jettson_http_request can reach.

Ship your own version in 20 minutes.

Sign up, fork the example, and an agent of your own goes to work. Your CEO will think you spent the week on it.