kebab-stack
desk — requests, queues, approvals
The hub signs you in with your passkey or SSO and sends you straight back here. No separate account for desk.
YOUR IT SUPPORT

Good work starts
with great support.

Everything you need to get back to what you do best.

How can we help?
WE'RE ON IT

Your requests

Request numberHow we can helpProgressLast update
LET’S GET YOU SORTED

What can we help you with?

Choose a service. We’ll take it from here.

SUPPORT WORKSPACE

Let’s move work forward.

The right context. A clear next step. One request at a time.

Create a request
RequestRequested byStatusAssigned toTarget date

Create a request for someone

Walk-up, phone, chat — capture it here on the requester's behalf.

← All requests
Checking for updates

Settings

Roles, catalog, AI and the hub wiring.

Organization & wiring

Roles from hub groups

Hub staff are desk staff. Whoever is owner or admin in the hub (Settings → Admins) is a desk admin automatically; hub helpdesk becomes a desk agent. On top of that, two hub groups (People → Groups) widen the team: members of the agents group work the queue, members of the admins group shape the catalog. Roles refresh at sign-in and with every directory pull (≤ 15 min). The bootstrap list below is the safety net for day one.

Demo data

Evaluation instances: seeds ~13 requests across every state using people from your directory. One-shot.

Request types

What people can ask for. Each type carries its own fields, checklist, queue, approval rule and SLA.
TypeQueueApprovalSLAFieldsState

AI (assistive)

checking where the key comes from…
Triage note on new requests, summaries and reply drafts for agents. Nothing AI does changes a ticket without a human click. Preferred: the company's key from the hub (lane AI for this app, set once under the hub's Settings → AI). The fields below are a local fallback for a desk without a hub key; the hub key wins whenever it is there. Keys never reach a browser; calls go out from this engine as single-node requests.

Slack intake

A support channel becomes a queue: a message from a colleague becomes a request, the bot answers in the thread with the key, replies go both ways, and a on the first message marks it resolved (removing it reopens). The Slack app itself lives in the hub — one bot for notifications and intake.

Channels

ChannelWorkspaceBecomesLast event
none yet

Set-up, once

1 · In the hub (as owner) — Settings → Slack: add the Slack app's bot token and signing secret, then under Tool access assign the bot to desk.
2 · In Slack (api.slack.com → your app): Event Subscriptions → Request URL → subscribe to bot events message.channels, message.groups, reaction_added, reaction_removed. OAuth & Permissions → bot scopes channels:history, groups:history, channels:read, groups:read, chat:write, reactions:read, reactions:write, users:read, users:read.email. Reinstall the app, then /invite @bot in the support channel.
3 · Here — Refresh from the hub, Load channels, pick the channel and what its messages become, Add. Say hello proves the wiring.

Admin log

Settings and catalog changes. Ticket history lives on each ticket.
WhenWhoWhat

How desk works

One page, kept current with the code. If something here disagrees with the product, the page is the bug.

HUB identity · directory · groups sign-in → ticket #uht people + attributes + groups hub_deactivate → sessions die hub_notify → bell / chat DM roles = groups desk-agents / desk-admins DESK catalog · tickets · event log · approvals · SLA request type ticket DSK-42 approval (manager | group) → queue (group) → agent events: comment · note · status · assign · task · file AI: triage · summary · draft SLA sweep · auto-close new → open → waiting(requester | third-party | approval) → resolved → closed REQUESTERportal · my requests AGENTqueue · ticket · actions redeemTicket connectorDirectory 15 min file · comment work

1 · Who is who

desk has no user list of its own. The hub's directory is cached here (pulled every 15 minutes and at sign-in), and roles fall out of it: hub owners and admins are desk admins, hub helpdesk are agents; members of the agents group work the queue, members of the admins group shape the catalog and settings; everyone else in the directory is a requester. Change it in the hub and the role follows. Your role and its reason show in the sidebar chip. A bootstrap admin list exists for day one.

2 · Signing in

Bootstrap: the CLI identity that installed desk runs setHub and addAdminEmail; alternatively a Hub owner or admin may initialize the admin role on an empty desk. Regular access: you never sign in to desk. You sign in to the hub (passkey or SSO) and open the desk tile; the hub hands over a one-time ticket that desk redeems canister-to-canister. desk then mints its own 10-hour session token. When the Hub deactivates a person, Desk denies their access within 60 seconds and removes them from active pickers; their requests stay as history. Desk also denies access when its Hub directory snapshot expires.

3 · The catalog

Everything starts as a request type: name, the fields the form asks for, a checklist template, the queue it lands in, an approval rule, SLA targets and who may see it. Onboarding, access, hardware, "something is broken" are all just entries. Admins edit them under Settings → Catalog. Open checklist items block "resolved" — that is how a type enforces its procedure.

4 · Approval

A type can require approval by the requester's manager (from the hub attribute; e-mail, or an unambiguous display name) or by a hub group. The ticket waits on approval, the approver gets a notification with a deep link, decides in one click. Approved → the ticket becomes new in its queue and the SLA clocks start; rejected → closed for good (file a new one). Closing a waiting ticket cancels its approval. If no manager is on file, the admins group is asked instead. Nobody can approve their own request — an admin re-routes it instead — and an approver's access to the ticket ends with the decision.

5 · Working a ticket

Status moves are explicit: new → open → waiting → resolved → closed, with waiting on requester, third party or approval. Replies never change status — except one case: the requester answers while we wait on them, which reopens. Internal notes and AI notes are never shown to the requester (filtered server-side, not in the UI). Every change is an event on the ticket's timeline; there is no other history.

5b · Telling the hub what a group does

desk answers the hub's hub_usesGroup: open a group in the hub and it shows what it drives here — agents/admins group, default queues, approver groups, open requests, pending approvals.

6 · Notifications

desk holds no chat tokens. Assignment, replies, approvals and SLA breaches go to the hub as hub_notify (title + deep link only); the hub delivers to the bell and, if the person opted in, their chat DM. Queue notifications reach the group's members that desk knows from the directory.

7 · AI

Optional and assistive. On a new request (if auto-triage is on), a triage note (summary, suggested priority/type, clarifying questions) is added for agents. The model sees the request with sensitive fields redacted and never sees internal notes. Agents can request a summary of the timeline (stored as a note) or a reply draft (never stored, never sent). Configure any OpenAI-compatible or Anthropic endpoint under Settings → AI; the key stays in the canister.

8 · SLA

Each type sets respond within and resolve within hours; a date field (start date, last day) can override the due date. An hourly sweep nudges the assignee (or the queue) once per breach and marks it on the ticket. Resolved tickets auto-close after a configurable number of days.

9 · Slack intake

Optional. A support channel in Slack becomes a queue: every top-level message from a person becomes a request of the type chosen for that channel (the first line is the subject, the whole message the description, the sender the requester when Slack knows their e-mail). The bot answers in the thread with the key and a link; replies in the thread land on the request as comments, and replies from desk land in the thread. A on the first message resolves the request (blocked while checklist items are open, with a note in the thread); removing it reopens. Request types with sensitive fields point people to the portal form instead of asking for details in the channel. The Slack app is configured once in the hub (token and signing secret) and assigned to desk there; desk fetches the credentials every 15 minutes and verifies every incoming event against the signing secret. Several channels, even from different workspaces, can feed one desk.

Secrets & trust

SecretWhereScope
Session tokenbrowser localStorage + canister10 hours, per person; ended by sign-out, deactivation or expiry
Hub ticketURL fragment, removed on load90 s, single use, only this backend can redeem it
AI API keycanister, write-onlyoutbound HTTPS only; cleared with one click
Slack bot token + signing secretthe hub; a copy in this canister, write-only, refreshed every 15 mintoken: Slack API calls from this engine; secret: verifying Slack's events (5-minute timestamp window, replay-deduped)

Gates: hub contract methods accept the hub canister only. Every read of a ticket checks requester, approver or staff role; every write checks role and ticket. Files are capped at 1.5 MB × 20 per ticket and stored in the canister. Stable state is append-only; deploys run moc --stable-compatible first.