Skip to content
ConduitConduit

Service Desk SLA Triage

A worked example of an advanced workflow: a Claude-managed scheduled agent that, every hour, finds HaloPSA tickets that are breaching or at risk of breaching SLA (plus unassigned aging tickets), skips anything it already flagged this cycle, ranks the rest by urgency, and posts a prioritised triage digest to Slack so the service desk sees what needs attention before SLAs breach. There are no servers and no code to deploy: the agent is a saved prompt plus a schedule plus two MCP connectors, running in Claude’s cloud.

The finished workflow runs unattended on an hourly cron. Each run it queries open HaloPSA tickets, classifies them as breached, at risk, or fine, skips any ticket it has already surfaced this cycle (using a per-ticket triage note as the guard), ranks the remainder by urgency, and delivers a canvas-backed digest to Slack — turning a reactive inbox into a proactive SLA watch. Writes are bounded: the agent adds a private triage note and posts to Slack; it never reassigns, closes, or changes a ticket’s status.

Before building, your Claude account needs all of the following:

Requirement Notes
Conduit connector Connected in claude.ai (https://conduit.wyre.ai/v1/mcp). Provides the halopsa-official__* tools. See Connecting an AI Client.
HaloPSA connected in Conduit (halopsa-official) The HaloPSA API user behind the vendor connection must be able to read tickets and write ticket notes. This guide uses the halopsa-official integration (the first-party MCP) — not the separate halopsa integration.
Slack connector The first-party Slack connector connected in claude.ai (https://mcp.slack.com/mcp). Provides slack_create_canvas and slack_send_message.
Scheduled routines access Created via the /schedule capability; managed at claude.ai/code/routines.
A destination Slack channel e.g. #support-sla, plus its channel ID. The Slack connector must have access to it.

These are the things that cost real time the first time through. Account for them up front and the build genuinely takes minutes.

  • The idempotency guard is not optional for any hourly triage routine. Without a per-ticket marker, every hourly run re-flags the same breaching tickets and spams the channel with duplicates. The triage note (the cycle marker [sla-triage YYYY-MM-DD-HH]) is the guard: a rerun reads existing notes and skips any ticket already marked this cycle. This is the same idempotency move the Autotask Ticket Triage agent makes — the triage note here plays the same role that advancing a ticket to In Progress plays there.
  • Bounded writes — the routine should never make an irreversible service-desk change. This agent writes a private triage note and posts to Slack; it does not reassign tickets, change status, or alter priority. Keep unattended routines bounded: a human reviews the digest and acts. Authorising status or assignment changes in permitted_tools would let an hourly routine silently modify the queue without oversight.
  • The Conduit catalog has two HaloPSA integrations — use the right one’s tools. Conduit exposes both a halopsa integration and a halopsa-official (first-party MCP) integration, and every tool is namespaced by its vendor slug. This guide uses the halopsa-official__* tools (halopsa-official__search_tickets, halopsa-official__get_one_ticket, halopsa-official__add_note_to_ticket). Listing the halopsa__* tools instead will give you a different tool set and the routine will not work as written.
  • permitted_tools must be populated per connector. A routine with a connector attached but an empty permitted_tools list runs with no tools and silently does nothing — no error, no output. List the exact tool names the routine needs.
  • A routine reaches only its attached connectors. The routine sandbox blocks arbitrary network egress, so notifications must go through the Slack connector’s tools, not an outbound webhook. Attach every connector the workflow touches.
  • One-hour minimum cadence. Claude-managed routines reject any cron faster than hourly — */15 * * * * fails outright. Cron runs in UTC: to fire at 09:00 America/New_York (UTC−4 in summer), use 0 13 * * *.
  • If Slack doesn’t appear in the /schedule connector list, read its details from an existing routine. Don’t conclude Slack is unsupported. Read its connector_uuid and url from an existing routine that already uses Slack (RemoteTrigger list → get → mcp_connections).
  • Prefer halopsa-official__search_tickets over per-record gets. The 60-second tool timeout makes a loop of halopsa-official__get_one_ticket calls across a large queue risky. Pull as much as possible from the search payload and call the per-ticket get only when necessary detail is missing. If SLA fields are not surfaced by the search, use halopsa-official__CF_Portal_SLA_Breached or halopsa-official__CF_Portal_Unassigned_Tickets as supplementary discovery tools.

With the connectors above in place, paste this to Claude. It confirms the Conduit connection, creates the routine, and verifies the idempotency guard end to end.

Build me a scheduled Service Desk SLA Triage agent using HaloPSA. Do all of this end to end:
1. Confirm the Conduit connector works and HaloPSA is reachable: call
halopsa-official__search_tickets and verify it returns open tickets.
Note: the Conduit catalog has two HaloPSA integrations (halopsa and
halopsa-official). This routine uses the halopsa-official__* tools — the
first-party MCP — not the halopsa__* ones.
2. Confirm a Slack connector is connected. Note the destination channel name
and ID (e.g. #support-sla). If Slack does not show in the /schedule
connector list, read its connector_uuid and url from an existing routine
that already uses Slack (RemoteTrigger list -> get -> mcp_connections).
3. Create a Claude-managed scheduled routine named "Service Desk SLA Triage":
- Schedule: hourly (cron "0 * * * *"). Faster-than-hourly cadences are
rejected. Cron runs in UTC — if your team works America/New_York,
"0 13 * * *" is 09:00 ET, for example.
- Attach TWO connectors, each with permitted_tools populated:
* Conduit: halopsa-official__search_tickets,
halopsa-official__get_one_ticket,
halopsa-official__add_note_to_ticket
* Slack: slack_create_canvas, slack_send_message
An empty permitted_tools list = the routine runs with no tools.
- Routine logic (see the routine prompt below): every run, find open tickets
that are breaching SLA or within the risk threshold; skip any ticket that
already has this cycle's triage note (the idempotency guard); rank the
rest by urgency; add the triage-note guard to each newly surfaced ticket;
build a prioritised digest; post a Slack canvas with the full triage list
and a one-line summary linking it. Do NOT change ticket status or
assignment — writes are bounded to the triage note and Slack only.
4. Trigger a manual run and verify: a canvas was created, the triage note was
added to each flagged ticket, a one-line summary landed in the destination
channel, and triggering a second run immediately does NOT re-flag the same
tickets (the note guard prevents it).
Alternative discovery: if halopsa-official__search_tickets does not surface
SLA-breaching tickets directly, the halopsa-official integration also exposes
CF_Portal_SLA_Breached and CF_Portal_Unassigned_Tickets as report-style tools
— use those as a supplementary discovery path if needed.

This is the lean prompt the build process installs into the scheduled routine itself. Substitute your destination Slack channel name and ID.

You are the Service Desk SLA Triage agent. You run once per hour. Keep it lean.
Use ONLY: halopsa-official__search_tickets, halopsa-official__get_one_ticket,
halopsa-official__add_note_to_ticket, slack_create_canvas, slack_send_message.
Definitions:
- BREACHED: ticket's SLA response or resolution deadline has already passed.
- AT RISK: ticket is within 60 minutes of its SLA deadline (or unassigned and
open for more than 2 hours — these are at risk by default).
- CYCLE MARKER: a triage note whose text starts with "[sla-triage YYYY-MM-DD-HH]"
where YYYY-MM-DD-HH is the current UTC date and hour (e.g. [sla-triage 2026-05-27-14]).
A ticket that already carries this exact marker was surfaced in this cycle;
skip it — do not re-flag, do not post it again.
Steps:
1. halopsa-official__search_tickets: retrieve all open tickets. Request enough
fields to determine SLA status and whether a cycle marker note already
exists for this hour.
2. For each open ticket:
a. Classify as BREACHED, AT RISK, or neither. Tickets that are neither — skip.
b. Check for a cycle marker note matching today's UTC date-hour. If present — skip.
c. For tickets still in scope, call halopsa-official__get_one_ticket only if
you need additional detail not present in the search payload (minimise
per-record calls).
3. For each newly surfaced ticket (not skipped):
a. halopsa-official__add_note_to_ticket: post a private triage note. The
note MUST begin with the cycle marker, e.g.:
[sla-triage 2026-05-27-14] SLA triage: ticket flagged as BREACHED/AT RISK.
No action required — this note is an automated idempotency marker.
b. Record the ticket for the digest.
4. Sort the digest: BREACHED tickets first (oldest deadline first), then AT RISK
(soonest deadline first), then unassigned aging tickets.
5. slack_create_canvas titled "SLA Triage - <today's date> <current UTC hour>:00 UTC"
with the full ranked list: ticket number, subject, status, SLA deadline,
classification (BREACHED / AT RISK / UNASSIGNED), and time remaining or
overdue by.
6. slack_send_message to #support-sla: a one-line summary
("SLA triage: N breached, M at risk — see canvas") linking the canvas.
7. If no tickets are in scope after filtering, post nothing (do not send an
empty canvas).
8. If a ticket cannot be read or the triage note cannot be written, list it in
a "could not check" section of the canvas instead of skipping it silently.
Bounded writes: add triage notes and post to Slack only. Do NOT change ticket
status, priority, assignment, or any other ticket field.

SLA deadlines are typically measured in hours, not days — a two-hour response target can breach between a weekly or even daily run. Hourly is the fastest cadence Claude-managed routines support, which makes it the natural fit for SLA watch. The idempotency guard (below) ensures that running hourly doesn’t flood the channel: a ticket is surfaced at most once per UTC hour, regardless of how many runs execute.

The routine draws a line between two states. A ticket is breached when its SLA deadline has already passed; it is at risk when the deadline is within 60 minutes, or when it is unassigned and has been open for more than two hours. Everything else is skipped. The two-tier classification lets the digest lead with the most urgent items (breached, oldest first) and trail with the approaching ones (at risk, soonest deadline first), giving the service desk a clear action order at a glance.

Before posting any ticket to Slack, the agent writes a private triage note whose text begins with a stable, greppable marker: [sla-triage YYYY-MM-DD-HH]. At the start of every run the agent reads existing notes on each candidate ticket and skips any that already carry this hour’s marker. The marker is the “already handled” signal — no database, no timestamp window, no separate state store. This is the same structural move the Autotask Ticket Triage agent makes by advancing a ticket to In Progress: both routines embed the idempotency guard into the primary data source so reruns are self-correcting.

Bounded writes — the human stays in control

Section titled “Bounded writes — the human stays in control”

The only writes this routine makes are a private triage note (the guard) and a Slack message. It never changes a ticket’s status, priority, or assignment. That constraint is deliberate: an unattended hourly routine that can silently reassign or close tickets is a liability. The digest gives the service desk the full picture; remediation stays human. If a ticket cannot be read or the note cannot be written, the routine lists it in a “could not check” section of the canvas rather than skipping it silently. Every call the routine does make is recorded in Conduit’s audit log.

The triage digest can span many tickets, so the routine publishes it as a Slack canvas — a durable, scrollable document — and posts only a one-line count summary to the channel linking it. The channel stays readable across multiple hourly runs; the detail lives in each canvas. See Delivery Adapters for other ways to surface a routine’s output.

The routine is PSA-agnostic in structure. To run the same SLA triage against Autotask, swap the halopsa-official__* tools (halopsa-official__search_tickets, halopsa-official__get_one_ticket, halopsa-official__add_note_to_ticket) for autotask__autotask_search_tickets, autotask__autotask_get_ticket_details, and autotask__autotask_create_ticket_note — the idempotency pattern, the at-risk classification, and the Slack delivery are identical. See the Autotask Ticket Triage guide for that setup. Alternatively, to escalate a breached ticket beyond a Slack digest — for example, filing a GitHub or Linear issue through those Conduit connections, or opening an internal escalation ticket — extend the bounded-write step with the extra tools; the core classification and guard logic does not change.

Questions or a workflow you’d like documented? See Getting support.