Verified plugins

Support triage

The verified plugin ai.oz0/support-triage - new tickets in Jira Service Management or ServiceNow triaged by an agent, routed and noted through the ticket system's connector, and acknowledged by mail, each write after a person approves it.

For: tenant administrators and plugin users.

ai.oz0/support-triage triages tickets in Jira Service Management on Atlassian Cloud or Data Center, or in ServiceNow. It reads and writes through the ticket system's connector and emails the requester through ai.oz0/email, using grants a tenant admin accepts at install.

The capability holds no credentials and reaches no hosts itself. Its flow, evals and approvals provide a reference for building one task on top of connector plugins.

Kindcapability: one job, owned by the team that runs the service desk
Flowsupport-triage: read, triage, ask a person when unsure, set the team, add a note, acknowledge
Agentsupport-triage-agent: reads one ticket and answers its category, urgency, team, a reply and how sure it is
Toolsupport_triage: the flow's own steps that need no model, and the hook on its replies; local, reaches nothing
Usesai.oz0/email, and optionally ai.oz0/atlassian, ai.oz0/atlassian-dc and ai.oz0/servicenow
Reachesnothing of its own: every call to a ticket system or a mail server is its connector's

"Verified" means the project tested, pinned and secured this plugin. It does not mean that Atlassian or ServiceNow endorses it.

What the flow does

  1. Which ticket. The flow's input names a system and a ticket ({"system": "servicenow", "ticket": "INC0010001"}), or a routine fires it on a new ticket's event, whose subject names the ticket. For Jira without a ticket, it searches for the oldest untriaged one (SUPPORT_TRIAGE_JQL) and triages that. A search that finds none ends the job with "no ticket was read", as a completed job.
  2. Read it through the connector the tenant has: atlassian.getJiraIssue, atlassian_dc_jira.get_issue, or servicenow.get_incident and servicenow.query_table (the caller's address, from sys_user). On Jira Cloud the flow then labels the ticket oz0-triage (atlassian.editJiraIssue, keeping its other labels), so the next search leaves it out whatever happens to this job: see Limits.
  3. Triage it. support-triage-agent answers a card: category (access, billing, hardware, software, network, how-to or other), urgency (high, medium, low), team (one of SUPPORT_TRIAGE_TEAMS), reply, confidence (0 to 1) and reason. It has no tools: what the requester wrote is data it reads, never instructions, and it can change nothing.
  4. Ask a person when unsure. When the confidence is under SUPPORT_TRIAGE_MIN_CONFIDENCE (0.75 unless set), or the agent names a team the tenant does not list, the flow waits for a person to approve the triage, with the ticket, the team, the urgency and the reply in the request. A no ends the job, and nothing is written. The wait is 24 hours, and 30 minutes on Jira Cloud, whose search routine triages nothing else while a job of it waits.
  5. Set the team: ServiceNow's servicenow.update_incident (the assignment group and the urgency), Jira's atlassian_dc_jira.edit_issue or atlassian.editJiraIssue (the component).
  6. Add an internal note with the triage and its reason: a ServiceNow work note (servicenow.add_work_note), or a Jira Service Management internal comment on Data Center (atlassian_dc_jira.add_comment with the sd.public.comment property). On Jira Cloud the flow adds none: see Limits.
  7. Acknowledge the requester with oz0_email.send: the agent's reply, to the address in the ticket.

Each write is the connector's function, behind its approval: the connectors' writes wait for an operator who did not start the job, and oz0_email.send waits for a person too. A ticket is outside input, so the job is tainted from its first read (and from its start when a routine fires it on a ticket's event), and the taint-default rule asks before each call that can send data out: every call through a connection counts, the reads included.

In such a job a person therefore also lets each read of the ticket through; a tenant that trusts its ticket system may narrow the rule's condition to leave those reads out (oz0 tenant tool-policy set --taint-default-when ..., with a reason). A call that the connector and the rule both ask for may wait twice. The flow's own steps (support_triage.start, ticket and route) are local and read-only, so they ask nothing.

The hook on replies

support_triage.hold_promises is a pre_tool_call hook on oz0_email.send in this flow. A reply that promises money (an amount), a refund, a credit or a date ("by Friday", "within 2 days", a calendar date) waits for a person, with the words that promise it in the request: only a person may promise that to a requester. The hook may only let a call through or send it to a person (decisions: [allow, escalate]); it never changes what the flow sends. A yes still leaves the send's own approval to decide.

Grants: the ticket systems are optional

The plugin declares what it calls of each connector by name under uses:, never <tool>.*. Email is needed; the three ticket systems are optional: true:

PluginFunctions
emailoz0_email.send
atlassian (optional)atlassian.searchJiraIssuesUsingJql, atlassian.getJiraIssue, atlassian.editJiraIssue
atlassian-dc (optional)atlassian_dc_jira.search_issues, atlassian_dc_jira.get_issue, atlassian_dc_jira.edit_issue, atlassian_dc_jira.add_comment
servicenow (optional)servicenow.get_incident, servicenow.query_table, servicenow.update_incident, servicenow.add_work_note

So the install asks only for the functions of the systems the tenant has, and installs and grants none it lacks. The flow's steps of a system it may not use are skipped (when: '"servicenow" in uses'), so a ticket of that system ends the job with "no incident was read", never a failure.

  • Installing a ticket system later says, in that install's output, what support-triage asks of it and the command that grants it: oz0 plugin grant support-triage --accept-uses <digest>. Until then its steps are skipped.
  • Revoking all of a system's functions (oz0 plugin revoke support-triage --tool 'servicenow.*', or --tool 'atlassian_dc_jira.*') makes the flow skip that system again; its running jobs that needed it are stopped. Revoking only some of its functions does not: the system is still in reach, so the flow reads the ticket and fails at the step whose function was revoked. Revoke a system whole, or not at all.
  • Removing a ticket system the plugin uses optionally is not refused for it: the removal takes the system out of support-triage's grant (audited uses.revoke). A routine that fires on the system's events still holds the removal until you delete it.

Install

Terminal
oz0 plugin install hub:ai.oz0/email --accept-reads mail
oz0 tenant mail-domains add default acme.com --reason "the service desk answers its own staff"
oz0 plugin install hub:ai.oz0/servicenow            # or hub:ai.oz0/atlassian-dc, or hub:ai.oz0/atlassian
oz0 plugin install hub:ai.oz0/support-triage

The last install is refused once with the grants it asks and a digest; install again with --accept-uses <digest>. Its evals run as the quality gate: the agent's cases on your tenant's models, the flow's on recorded steps that call nothing, so they pass on a fresh tenant with whichever ticket system it has. Give the plugin an owner team in the inventory, the team that runs triage.

Its agent asks for the tenant's default model alias with tools, never a vendor's model.

Point it at your ticket system

Connect the ticket system's connector first, on its own page. Then save the routine that starts the flow; the plugin's folder has one per system under routines/.

ServiceNow. Connect ai.oz0/servicenow (an OAuth app of your own in the instance) and its event source (a business rule that sends incident changes). Then:

Terminal
oz0 routine save -f routines/servicenow-incidents.yaml   # fires on each incident created

Jira Service Management, Data Center. Connect ai.oz0/atlassian-dc (the incoming application link's OAuth 2.0, or a personal access token) and its webhook. A JSM request is a Jira issue, so the flow reads it with Jira's functions; the team is the issue's component. Then:

Terminal
oz0 routine save -f routines/jira-dc-issues.yaml         # fires on each issue created

Jira Service Management, Cloud. Connect ai.oz0/atlassian's atlassian-cloud connection: the Jira functions this plugin calls are Rovo's, on your own OAuth client. Do not assume OAuth covers all of JSM on Rovo: its JSM Operations tools (alerts, on-call schedules) take only an API key, on the separate atlassian-cloud-jsm connection, and this plugin uses none of them. Rovo sends no events, so a routine searches every 2 minutes:

Terminal
oz0 setting set SUPPORT_TRIAGE_JIRA_SITE https://acme.atlassian.net
oz0 routine save -f routines/jira-cloud-search.yaml      # the oldest request SUPPORT_TRIAGE_JQL finds, one per fire

Each routine lists exactly the functions the flow calls, and its limits: 0.25 USD a fire and 10 USD a day unless you change them. To triage one ticket by hand:

Terminal
oz0 run support-triage '{"system": "servicenow", "ticket": "INC0010001"}'

Settings

SettingWhat it isDefault
SUPPORT_TRIAGE_TEAMSThe teams a ticket may go to, comma-separated, as the ticket system names them: ServiceNow assignment groups, Jira componentsService Desk,Access,Billing,Hardware,Network
SUPPORT_TRIAGE_MIN_CONFIDENCEHow sure the triage must be to go ahead without a person, 0 to 10.75
SUPPORT_TRIAGE_JIRA_SITEJira Cloud's site, for the Rovo functionsnone
SUPPORT_TRIAGE_JQLThe search a Jira triage without a ticket runs, oldest firststatusCategory = "To Do" AND component is EMPTY ORDER BY created ASC

What it reads and writes

ReadsWrites
The ticket systemthe ticket's summary, description, reporter or caller, and the caller's address in ServiceNowthe team (assignment group or component), ServiceNow's urgency, one internal note
Mailnothingone message to the requester, with the tenant's AI label, only within the tenant's mail domains unless the tenant allows others
Orchestrator Zerothe plugin's settingsthe job, its steps and its approvals, as every flow's

Tickets often hold personal data of the people who write them, colleagues and customers. The agent reads what the requester wrote; the job's steps keep it as any job's do, under the tenant's retention and access rules.

Limits

  • Jira Cloud gets no internal note. Rovo's addOrEditJiraIssueComment has no internal or public flag, so a comment would reach the requester; the flow sets the component and mails the requester, and adds no comment.

Jira Cloud is searched, one ticket a fire

Rovo sends no events, so its routine runs every 2 minutes and triages the first ticket of the search; a fire that finds none ends its job as completed. The routine runs one job at a time (overlap: skip), so while a job waits for a person, no other ticket is triaged: the triage's own approval waits 30 minutes at most there, but an approval the connector or the taint-default rule asks for waits as long as the tenant's approvals do. Decide them promptly, or narrow the taint-default rule.

A Jira Cloud ticket is triaged once

The flow labels each ticket oz0-triage right after reading it, and the search always leaves labelled tickets out, the tenant's own SUPPORT_TRIAGE_JQL included. A ticket whose triage a person refused, that waited past its timeout, or whose component could not be set keeps the label and no component: find them with labels = oz0-triage AND component is EMPTY and triage them by hand, or remove the label to have the routine pick one again. Only when the label's own write is refused does the next fire pick the same ticket again; label it, or narrow the search, to stop that.

  • The team is a field the ticket system has. ServiceNow's assignment group and Jira's component must exist under the names in SUPPORT_TRIAGE_TEAMS; the plugin creates neither, and assigns no person.
  • The requester's address comes from the ticket. In ServiceNow it is the sys_user row whose name is the caller's, so two users with one name give the first one's address; on Jira, an address the site hides is not mailed. An address outside the tenant's mail domains is refused by ai.oz0/email.
  • The hook reads words, not intent. It holds replies with amounts, refund words and dates in English; a promise in other words or another language passes it, and the send's approval is then the check.
  • A no to the triage ends the job. The person who decides cannot change the team in the approval; send the ticket by hand in the ticket system, or change the setting and run the flow again.
  • Tested against fakes. The flow, the grants and the approvals are tested against fakes of Jira Data Center, ServiceNow and an SMTP relay, and the flow on recorded steps for Jira Cloud. Runs against a live Jira Service Management or ServiceNow have not been part of these tests; JSM Data Center's internal comment (sd.public.comment) is from Atlassian's documentation, not a live instance.
Copyright © 2026