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¶
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 byassign_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.