SKILL: CRM Support-Console Task Solver
When to use
Use this when a task asks you to triage/resolve a batch of support tickets, a
queue of mobile cases, or to build an enterprise export-complaint response
package, and tells you to "use the shared support console API" and "return only
JSON conforming to payloads/answer_template.json". You are given input payloads
(CSV/JSON + an answer_template.json) and must derive every decision from live API
records, not from assumptions or from the issue text alone.
API base URL and lookup habits
- Base URL:
<remote-env-url>(overrides any127.0.0.1:8057shown in prompts). Read-only. Confirm withGET /health, list withGET /api/catalog. - Call with curl, e.g.
curl -s <remote-env-url>api/tickets/TCK-6101. - A missing record returns
{"error":"not_found"}(HTTP 200) — treat that as the signal for INVALID_ACCOUNT / not-found, do not retry forever. - Endpoints by record type and how they chain:
- Tickets:
/api/tickets/<id>-> givesaccount_id,service_area,service_type,subscribed_mbps. - Account:
/api/accounts/<account_id>->status(Active/Suspended),authentication.last_login_status,authentication.account_recovery_status. - Outage:
/api/outages?service_area=<area>-> list; checkactiveand whether ticket'sservice_typeis inservice_types. - Diagnostics:
/api/diagnostics/<ticket_id>->root_causes(list),latency_ms,jitter_ms,bandwidth_mbps. - Troubleshooting:
/api/troubleshooting/<ticket_id>->steps,post_latency_ms,post_jitter_ms,post_bandwidth_mbps. - Mobile cases:
/api/cases/<id>->customer_id,line_id,device_id,issue_type,customer_location. - Line:
/api/lines/<line_id>->status,suspension_reason,roaming_enabled,data_used_gb,plan_id. - Device:
/api/devices/<device_id>-> sim_status, signal_strength, mobile_data_enabled, phone_roaming_enabled, data_saver_mode, network_mode_preference, vpn_connected, can_send_mms, mmsc_url_present, messaging_permissions{sms,storage}, airplane_mode, wifi_calling_enabled. - Plan:
/api/plans/<plan_id>->data_limit_gb,data_refueling_price_per_gb,monthly_price_usd. - Bills:
/api/bills-> list keyed bycustomer_id; fieldsbill_id,amount_due_usd,status(Paid/Issued/Overdue),due_date. (Filter the full list by customer_id; there is no per-customer endpoint.) - Enterprise:
/api/enterprise/incidents/<id>,/api/enterprise/accounts,/api/enterprise/export-runs?incident_id=<id>,/api/enterprise/sla/<enterprise_account_id>,/api/enterprise/messages?query=<text>.
- Tickets:
GLOBAL OUTPUT DISCIPLINE
- Return only the JSON object the template defines. No prose, no markdown fences.
- Match enum spelling EXACTLY (e.g.
RESOLVED,NETWORK_ENGINEERING,upload_only,Critical). Wrong case/word = wrong. - Preserve the array order the template demands: ticket families = payload/CSV order; case families = ascending case_id order.
- Numbers: charges = number with two decimals (e.g.
4.00,86.40); data_refuel_gb = one decimal (2.0); counts = plain integers; percents = plain integer. - Empty/absent string fields =
""(e.g.outage_idwhen no outage,bill_idwhen no billing). - The summary counts MUST be recomputed from your own per-row decisions (sum of statuses, routes, etc.). Don't eyeball them.
FAMILY A — Offline ticket batch resolution (template has ticket_decisions + final_resolution_status, resolution_route, escalation_team, issue booleans)
For each ticket in payload order:
- Fetch ticket -> account_id, service_area, service_type, subscribed_mbps.
- Fetch account. Decision precedence (apply the FIRST that matches):
- Account
not_found-> status FAILED, route INVALID_ACCOUNT, escalation NONE, no diagnostic, outage "". authentication.last_login_status == "FAILURE"oraccount_recovery_status == "FAILURE"-> status FAILED, route AUTH_FAILED, escalation NONE, no diagnostic.- Account
status == "Suspended"-> status PENDING_ACTION (account ineligible for service work), route INELIGIBLE_ACCOUNT, escalation ACCOUNTS_PAYABLE, no diagnostic. (Suspension is a billing/account block, not a network fix.)
- Account
- Outage check (only if account passed step 2):
GET /api/outages?service_area=. If an outage withactive:trueexists AND the ticket'sservice_typeis in itsservice_types-> status PENDING_ACTION, route OUTAGE_WAIT, escalation NONE,outage_id= that id, no diagnostic. This is a "customer must wait" ticket. - Otherwise run diagnostics + troubleshooting (
diagnostic_needed = true). Readroot_causes:- Auto-fixable causes (
CONFIGURATION_DRIFT,VOICE_PROFILE_STALE,PROVISIONING_STALE,GENERATED_NOISE/GENERATED_*): troubleshooting resolves them -> status RESOLVED, route AUTO_TROUBLESHOOTING, escalation NONE. - Hard-fault causes that troubleshooting cannot fix -> status ESCALATED, route ESCALATION:
BACKBONE_CAPACITY-> escalation NETWORK_ENGINEERING.FIBER_DROP_DAMAGE/SIGNAL_LOSS(physical line) -> escalation FIELD_OPS.- other unresolved/complex software issues a tier-1 script can't close -> TIER2_SUPPORT.
- Tell auto-fix from hard-fault by the root cause name AND by whether post-troubleshooting metrics recovered toward SLA. Hard faults leave
post_latency_ms/post_bandwidth_mbpsstill bad (e.g. latency stays >170 ms, bandwidth far belowsubscribed_mbps); auto-fixes show clear improvement.
- Auto-fixable causes (
- Issue booleans (set from DIAGNOSTIC readings, the pre-fix snapshot, not the post values):
latency_issue= latency_ms high (rule of thumb: > ~100 ms, and clearly elevated vs a clean baseline ~40-80).stability_issue= jitter_ms high (> ~40 ms) and/or intermittent connectivity.bandwidth_issue= bandwidth_mbps materially belowsubscribed_mbps.- When a ticket short-circuits at step 2/3 (no diagnostic run), all three booleans are
false.
- batch_summary: count each status;
tickets_requiring_customer_wait= number of OUTAGE_WAIT tickets (the customer must wait for an active outage to clear). Recompute from your rows.
Variant — Queue-quality classification (template has route_team + key_blocker + diagnostic_required)
Same precedence logic, but the blocker is reported via the key_blocker enum and a free-text queue_note. Map:
| Situation | key_blocker | route_team | status | diagnostic_required |
|---|---|---|---|---|
| active outage covering service_type | ACTIVE_OUTAGE | NONE | PENDING_ACTION | false |
| account not_found | INVALID_ACCOUNT | NONE | FAILED | false |
| auth FAILURE | AUTH_FAILED | NONE | FAILED | false |
| Suspended + note says overdue/payment | OVERDUE_SUSPENSION | ACCOUNTS_PAYABLE | PENDING_ACTION (or ESCALATED) | false |
| Suspended + note says fraud/security | FRAUD_SUSPENSION | ACCOUNTS_PAYABLE | ESCALATED | false |
| diag root cause BACKBONE_CAPACITY, not recovered | NETWORK_CAPACITY | NETWORK_ENGINEERING | ESCALATED | true |
| diag root cause FIBER_DROP_DAMAGE/SIGNAL_LOSS | PHYSICAL_LINE_FAULT | FIELD_OPS | ESCALATED | true |
| diag root cause PROVISIONING_STALE | PROVISIONING_STALE | (NONE if troubleshooting fixed; else TIER2_SUPPORT) | RESOLVED or ESCALATED | true |
| auto-fixable, troubleshooting recovered | NONE | NONE | RESOLVED | true |
- OVERDUE vs FRAUD suspension: the account record only shows
status:"Suspended"— there is NO fraud flag on it. Disambiguate from thequeue_note/ticket text ("overdue notice"->OVERDUE; "fraud"/"security hold"->FRAUD). Bills are NOT linked to ticket-family accounts (bills key on CUST-, not ACC-), so don't try to read a bill to decide this. queue_summarycounts every status AND every route_team value used. Recompute.
FAMILY B — Mobile case triage (template has primary_action/secondary_action, final_route, sometimes permission/bill_id/charge_amount, or data_refuel_gb/carrier_update_required)
For each case (ascending case_id): fetch case -> line, device, plan, and (if billing) bills.
Decide the PRIMARY operation from the single dominant blocker, SECONDARY for a required follow-up (else NO_ACTION). Drivers (check in this priority):
- Line Suspended (
line.status == "Suspended"): ifsuspension_reason == "OVERDUE_BILL"-> primary SEND_PAYMENT_REQUEST, route BILLING_RECOVERY. Find the customer's Overdue bill, setbill_idto it andcharge_amount_usd= itsamount_due_usd(two decimals). Secondary often RESUME_LINE_REBOOT (resume the line after payment). Non-billing suspension (e.g. contract ended) -> TRANSFER_HUMAN, route HUMAN_TRANSFER. - NO_SERVICE / SIM problem:
device.sim_status == "missing"-> RESEAT_SIM. SIM locked/PIN-locked (no self-fix) -> TRANSFER_HUMAN.airplane_mode == true-> TOGGLE_AIRPLANE_MODE. - Abroad / roaming (MOBILE_DATA + customer_location "abroad" or "no data while roaming"):
line.roaming_enabled == false(carrier side off) -> ENABLE_LINE_ROAMING (route CARRIER_UPDATE / carrier_update_required = true).- line roaming on but
device.phone_roaming_enabled == false-> TOGGLE_ROAMING (device fix, SELF_SERVICE/DEVICE_SETTING_FIX).
- MOBILE_DATA "no data" at home:
device.mobile_data_enabled == false-> TOGGLE_MOBILE_DATA. After an APN/settings change broke data -> RESET_APN_REBOOT. - Data exhausted (data_used_gb >= plan.data_limit_gb): -> REFUEL_DATA. Refuel amount = customer's accepted GB from
customer_preferences.<case>.accepted_refuel_gb(if absent, the minimum needed to restore service).charge_amount_usd= refuel_gb *plan.data_refueling_price_per_gb(TWO decimals).data_refuel_gb= that GB (ONE decimal). Route DATA_RECOVERY. Respectdoes_not_want_plan_change-> do NOT propose a plan upgrade, only refuel. - SLOW_DATA:
device.vpn_connected == true-> DISCONNECT_VPN.device.data_saver_mode == true-> TOGGLE_DATA_SAVER.device.network_mode_preference == "3g_only"(or other legacy mode) -> SET_NETWORK_MODE (to a 4g/5g preferred mode). These are DEVICE_SETTING_FIX / SELF_SERVICE. - MMS (can't send photos):
device.can_send_mms == false. Ifmessaging_permissions.storage == false-> GRANT_MESSAGING_PERMISSION withpermission= the missing perm (storage,sms, orsms_and_storage). Ifmmsc_url_present == falseor APN broken -> RESET_APN_REBOOT. Often primary = grant permission, secondary = RESET_APN_REBOOT. - Nothing actionable / needs an agent -> TRANSFER_HUMAN, route HUMAN_TRANSFER.
permissionfield:NONEunless a GRANT_MESSAGING_PERMISSION action is chosen; then the exact missing scope.bill_id/charge_amount_usd: empty""/0.00unless a billing or refuel charge applies.final_routemapping: self-service device toggles -> SELF_SERVICE (FAMILY-002) / DEVICE_SETTING_FIX (FAMILY-005); refuel -> DATA_RECOVERY (005); roaming carrier change -> CARRIER_UPDATE; overdue bill -> BILLING_RECOVERY (002); escalate to person -> HUMAN_TRANSFER.- For the 005 worklist
total_estimated_customer_charge_usd= sum of all per-casecharge_amount_usd(two decimals).carrier_update_requiredtrue only for ENABLE_LINE_ROAMING / carrier-side changes. - Summary counters: count cases by their
final_route. Recompute.
FAMILY C — Enterprise export-complaint response package (single flat object)
Inputs: a complaint email (names client, product, approximate INC-####) and response_requirements.json (lists permission_users_to_include and a naming_style).
- Identify incident:
GET /api/enterprise/incidents/<INC>(use the email's reference). Readenterprise_account_id,severity,engineering_owner,account_owner,product,status. - Account:
GET /api/enterprise/accounts, find the matchingenterprise_account_id->name(client),finance_owner. - Export runs:
GET /api/enterprise/export-runs?incident_id=<INC>. Sort byrun_date. The FAILED runs (statusFAILED, usuallyexported_record_count:0) form the failure window:failure_window.start_date= first FAILED run_date;.end_date= last consecutive FAILED run_date;.failed_days= count of FAILED runs.- The first SUCCEEDED run after the failures is the recovery/backfill (its
exported_record_countis the backfilled data). backfill_days= number of failed days that were manually backfilled =failed_days(cross-check any message that states a backfill count).root_cause_category= thefailure_codeon the failed runs, expressed as a concise category (e.g.STALE_CREDENTIAL,STAGING_STORAGE_QUOTA). Corroborate with the message body.
- SLA:
GET /api/enterprise/sla/<enterprise_account_id>->sla_credit_percent=monthly_export_credit_percent. Verify thecredit_triggeris actually met by the window (e.g. "3 consecutive failed runs", or "outage > 72 hours"). - Messages:
GET /api/enterprise/messages?query=<client name>(also try the engineering owner's name;query=INC-####often returns nothing — search by client/owner instead). Use the body to confirm root cause and the SLA percent.contributing_alert_issue: if the root-cause/alert message sits in an archived alert channel (channel name containsalert+archive, e.g.export-alerts-archive) -> ARCHIVED_ALERT_ROUTE (the alert was missed because its route was archived). If alerts routed normally -> NONE. If no evidence either way -> UNKNOWN.
- Owners:
engineering_ownerandaccount_ownercome straight from the incident record (user ids likedelana.rao).finance_owner(from the account) is used for finance review / share permission, not as account_owner. - severity = incident
severity(Critical/High/Medium/Low — exact case). - Constructed naming artifacts — build from
naming_style, lowercase + hyphens, using the client name and dates:channel_name: lowercase hyphenated, client + topic (e.g.asteri-retail-export-incident). Follow the style string literally.evidence_folder: client + date investigation folder (e.g.asteri-retail-2026-05-12-investigationusing the failure start date).report_title: a "client export failure report" title (e.g.Asteri Retail Inc. Monthly Export Failure Report).- Match the wording hints in
naming_styleprecisely; keep client name spelling exactly as in the accountname.
- share_permissions: one entry per user in
permission_users_to_include, IN THAT ORDER. Assignpermissionby role: the finance owner / reviewer (e.g.laura.brownis the account's finance_owner) gets view; a data/evidence contributor who only uploads artifacts gets upload_only; an editor of the package gets edit. Default a reviewer toview, an uploader toupload_only. - response_status:
- If a SLA credit is owed and still needs finance sign-off -> NEEDS_FINANCE_REVIEW.
- If root cause/backfill still unconfirmed by engineering -> NEEDS_ENGINEERING_REVIEW.
- If the incident is still open with the window unresolved -> UNDER_INVESTIGATION.
- Only READY_TO_SEND when window is closed, root cause confirmed, backfill done, SLA credit determined, and owners assigned. Lean toward NEEDS_FINANCE_REVIEW when a credit percentage is being granted but not yet approved.
Common misjudgments / exclusion rules
- Do NOT run/charge diagnostics for tickets that short-circuit (invalid account, auth failure, suspended account, or active outage).
diagnostic_needed/diagnostic_required = falsethere, and issue booleans stay false. - Do NOT escalate auto-fixable root causes (CONFIGURATION_DRIFT, VOICE_PROFILE_STALE, PROVISIONING_STALE, GENERATED_*) — they resolve via troubleshooting (escalation NONE). Reserve escalation for hard faults (BACKBONE_CAPACITY -> NETWORK_ENGINEERING; FIBER/SIGNAL -> FIELD_OPS) and account/billing blocks (ACCOUNTS_PAYABLE).
- Outage relevance: only count an outage if it's
active:trueAND the ticket'sservice_typeis in itsservice_types. An active outage in a different service area or for a different service does not apply. - Suspended account != fixable: never route a suspended account to network troubleshooting. It is an account/billing block (ACCOUNTS_PAYABLE / INELIGIBLE_ACCOUNT / BILLING_RECOVERY).
- Set issue booleans from the DIAGNOSTIC (pre-fix) readings, not the post-troubleshooting values.
- Refuel charge uses the line's actual plan
data_refueling_price_per_gb(PREMIUM $2, BASIC $5, PLUS $0.1, FAMILY $3...). Never assume a flat rate; always read the plan. Respect customer preferences (accepted GB, no plan change). - ENABLE_LINE_ROAMING (carrier side) vs TOGGLE_ROAMING (device side) are different actions: check
line.roaming_enabledfirst, thendevice.phone_roaming_enabled. Carrier change -> carrier_update_required/CARRIER_UPDATE. - Enterprise owners: don't swap engineering_owner and account_owner; don't use finance_owner as account_owner.
query=INC-####to messages usually returns empty — search by client name or owner. - failed_days = count of FAILED runs, not (end-start) calendar diff; backfill_days normally equals failed_days. Cross-check with any message that explicitly states a day count.
- Recompute EVERY summary count from your own per-row decisions before emitting; a mismatch between rows and summary is an automatic error.
- Emit ONLY the JSON object; exact enum/string/number formatting is graded.