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.
What does happen on a money-related ticket¶
- 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.
- Verdict, the agent loop returns
escalate(notauto_resolveand notpropose_resolution). - Route, the escalation surfaces in
/approvalsand (when the v0.4 roles system lands) routes to the configured billing-role channel. - Human acts, the billing-role human reviews the case Gaby assembled and issues the refund directly in the Stripe dashboard.
- Notify customer, once the refund posts, Gaby replies on the ticket thread with the refund id and settlement window.
Channels today¶
Gaby fans out every escalation to all enabled outbound channels,
configured at /settings/escalation, each with a per-channel test button
and per-row failure isolation:
- Slack webhook
- Email (SMTP) to a shared mailbox
- SMS (Twilio)
- Opsgenie
- SIGNL4
What's still ahead is per-role routing — sending different categories to different channels (below).
Roles + routing (planned)¶
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 per-role routing ships, every escalation fans out to all enabled channels. Tickets are still tagged with their inferred category in the dashboard so a human-eyes triage works without per-role routing.
Planned¶
- Drop
assign_to("user")from the escalation-rules DSL until role routing exists (today it parses but no-ops); replace withassign_to_role("billing"). - Add an "escalation category" inferred field to the verdict step's output so routing has a signal to work from.