Skip to content

Escalation policy

Gaby's job ends at "surface the case to the right human" for a defined class of actions. The class is policy, not configuration.

The policy

Money-touching actions are never Gaby tools. Even behind an approval queue, asking a human to approve a refund draft from Slack is too thin a safeguard, one bad click moves money to the wrong customer. Better: Gaby never drafts the action; it surfaces the case (charge, amount, eligibility, customer history) and a human takes the action in the upstream system themselves.

Concretely, the Stripe connector exposes read-only tools. issue_refund, create_charge, update_invoice, and similar are not in the Gaby tool surface. A test_no_write_tools_at_all regression fence ensures they stay out.

This is a policy choice, not a technical limitation. The same approval queue that gates other writes (e.g. Postgres mutations under autonomy = act) would technically work for refunds, we chose not to use it for money for the cognitive-load reason above.

  1. Investigate: Gaby uses the Stripe read tools to assemble the facts: customer record, charge id, amount, amount-already-refunded, eligibility window, prior refunds on the same charge.
  2. Verdict, the agent loop returns escalate (not auto_resolve and not propose_resolution).
  3. Route, the escalation surfaces in /approvals and (when the v0.4 roles system lands) routes to the configured billing-role channel.
  4. Human acts, the billing-role human reviews the case Gaby assembled and issues the refund directly in the Stripe dashboard.
  5. Notify customer, once the refund posts, Gaby replies on the ticket thread with the refund id and settlement window.

Channels today

v0.3 ships one outbound escalation channel: Slack webhook, configured at /settings/escalation. v0.4 adds:

  • SMTP, escalation as email to a role-bound mailbox
  • PagerDuty / Opsgenie: SRE persona, infra incidents
  • Per-role routing, see below

Roles (v0.4)

The taxonomy will be fixed and code-aware (operators can't typo a role name into oblivion):

Role Catches
support_l1 Default catch-all. Password resets, account lookups, FAQ.
billing Anything money, refunds, invoices, subscriptions, charges, disputes. Structural rule.
infra Outages, slow queries, capacity, deploy correlation.
security Auth failures, permission grants, suspected breach, data deletion.

Per-workspace bindings (one row per role × channel) let you fan out a role to multiple notifiers, e.g. billing role pings #billing-urgent on Slack AND emails finance@.

Until v0.4, every escalation lands in the single Slack webhook configured at /settings/escalation. Tickets are still tagged with their inferred category in the dashboard so a human-eyes triage works without per-role routing.

What's left in v0.3.1

  • Drop assign_to("user") from the escalation-rules DSL until role routing exists (today it parses but no-ops). Replaced by assign_to_role("billing") in v0.4.
  • Add an "escalation category" inferred field to the verdict step's output so v0.4 routing has a signal to work from.