Omniscio documentation
Browse all documentation
  1. Getting Started19
  2. Sessions & Agents140
  3. Inbox & Notifications72
  4. Projects & Tasks102
  5. Automation & Scheduling77
  6. Knowledge & Memory29
  7. AI Features75
  8. Integrations115
  9. Plugins & Marketplace44
  10. Cloud & Teams53
  11. Settings & Customization81
  12. Account & Billing29
  13. Troubleshooting85
  14. CLI & API Reference28
  15. Legal & Policies5
  16. Uncategorised21

The Help Desk ticket ledger — what the desk owes, and to whom

Every Help Desk ticket has one private record that remembers what the desk has told the customer, what it still owes them, who is fixing it, and how long they have waited — so a fix never goes unannounced and a customer's wait never resets just because they chased. Triage decisions can be made in batches of up to 200 at a time, and every batch can be undone row by row.

What it is

When somebody writes to the Help Desk — from the in-app Get Help panel, from a support email, or from a report conversation — the desk has to remember six things about them: what they asked, what we have said back, what we still owe them, who is doing the work, how urgent it is, and how long they have been waiting. The ticket ledger is where all six live, one row per ticket.

It exists because "we told them" is easy to say and hard to prove. Without a record, a fix lands, the note goes out to the wrong ticket, and the customer reads about a fix that never reaches them — or worse, the same note goes out twice. The ledger makes each of those things a fact you can look up rather than a story you have to trust.

It is part of the Help Desk developer console: an operator surface, not something a customer ever sees. The customer's own conversation stays where it always was, on their ticket thread.

Where to find it

There is no screen yet — the ledger is read and written through the app itself and through its local command line, which is how an operator or an agent asks a question like "what do we still owe, worst first?".

  • The desk's own doors: the ticket console, the automatic note sender, the ticket→session buttons, and the SLA sweep that watches waiting customers all write to the ledger without anyone doing anything.
  • The command line: four read doors — the ledger list (paged, filtered by stage, priority, wait, owner or obligation), the one-ticket read, the "worst N" read for a wake-up brief, and the throughput numbers — plus one write door for announcing by hand, "I told them, here is the message", and two bulk doors for deciding many rows in one call.
  • Nowhere else: it is admin-only. A crew session can read the version that hides the customer's identity and their message text, and cannot request the full one.

How it behaves

One ticket, one record. A ticket gets its row the first time the desk sees its thread, from the thread id alone. Every other name the ticket travels under — the intake report it came from, the email that delivered it — is attached to that same row as an alias, so a ticket that arrives one way and continues another never becomes two. If two ways of naming it ever point at different rows, the row reports the conflict rather than silently picking one.

The wait is anchored, not refreshed. The ledger records the moment of the customer's OLDEST unanswered message. A chase, a follow-up, or an automatic "we got your message" receipt all move the "last activity" clock but never the anchor. A customer who waited nine days and then chased once still reads as nine days.

Told is a fact per message, not a flag. Four things are tracked separately for every ticket: that the customer was acknowledged, that a real person answered them, that they were told their fix landed, and that they were told it shipped. Each is recorded with the channel it went out on, a message id, and whether it was delivered, failed or unknown. "Told" means a delivered event for that exact milestone — so a receipt can never stand in for "your fix landed", and a reply can never stand in for a release. A send that went out but could not be recorded is kept as unknown and never sent again: a duplicate customer email is worse than an unrecorded one.

The stage is worked out, not typed. Reading a ticket derives its stage from what actually happened: declined, resolved or answered if that stamp is set; shipped or landed if the work says so; fixing or investigating if someone is on it; unanswered if the customer is still waiting; waiting on the reporter if we have been in touch; new otherwise. Alongside the stage, every open obligation is listed in full with its own start time — a reply owed, a landed note owed, a shipped note owed, an email delivery to confirm — and the oldest of those is the number every sort, filter and "worst tickets" read measures.

Priority is one value in one place. It is low, normal or urgent, and the ticket console and the ledger read the same value, so they can never disagree. Rules raise it: a second unanswered message from the customer, an obligation open past 24 hours or past 72, a paying customer, or a security or privacy tag. A person can set it to anything, and that choice stands — until a rule more serious than the one the desk has already applied fires. That is what stops an old ticket being dragged back to urgent every five minutes. A resolved ticket is never raised at all.

A ticket's work is filed, not guessed. A crew task that is for a ticket says so when it is created — which ticket, its number, whether the work is a fix or an investigation — and that declaration is what attaches the work to the ticket. Nothing is read out of a task's title, so a task that merely mentions a ticket number cannot be mistaken for that ticket's fix. Filing the same ticket for the same role a second time files nothing rather than duplicating the work, and a ticket that was resolved and comes back starts a fresh episode, so new work is not confused with old. When the console resolves, declines or answers a ticket, the ticket's open tasks close with it, and a coding task is closed as dropped, never done, because only a real landing proves code shipped.

Holding is visible, once. If a fix has shipped but the customer has not been told, if a fix's session has ended without landing, or if a note is stuck because nothing is recorded as fixing the ticket, the desk raises ONE card per cause per day that lists the affected tickets — never one card per ticket — and takes the card down when the cause clears.

Bulk writes are one call, many rows. An overseer triaging a few hundred tickets should not spend one call — and one charge on the desk's work budget — per ticket. Two doors take up to 200 ids: POST /helpdesk/ledger/bulk for tickets (set-priority, reassign) and POST /crew/tasks/bulk for crew tasks (reassign, re-due, close). Every row is decided on its own: the ids the caller may change are changed, the rest come back named with the reason refused, and one bad row never blocks the batch. A repeated id is refused outright, because a duplicate makes an undo ambiguous. Each call is one charge however many rows it changes.

Every batch is reversible. One record is opened before the first row is touched and updated as each row lands, holding what each row was and what it became. A batch interrupted part-way — by the connection, a crash, or a row that fails — still leaves a record of everything it changed. A bulk write never happens without that record: if the record cannot be opened, nothing is written. A batch also stops itself at a time limit and hands back the ids it did not reach, so a reply always arrives. A row that would not actually change is reported as refused rather than as done.

Who may change what. A crew session may change a ticket its crew has taken on, and a task it is allowed to edit. The desk console may change any ticket — its priority or who owns it — and any crew task, because the console is the verified owner of the machine rather than a member of a crew.

What bulk deliberately cannot do. Marking a batch "told" and setting a batch's stage are not offered. A told milestone needs its own message behind it, and a stage is a consequence of what happened rather than a value someone sets. Marking a ticket told stays a one-ticket action.

Nothing is lost. A ticket row is never dropped behind a page limit: every status total is the true total, and every list is walked by a cursor that reaches every ticket. The whole ledger and its history are backed up and merged by thread, so reinstalling or moving to another machine keeps the newest delivery evidence rather than forgetting who was told.

What is not there yet. One part is promised by the design and not built: nothing records a branch against a ticket in this ledger yet, so a fix does not currently move a ticket to landed or shipped on its own. Everything above the stage derivation is live.

For agents

The ledger is one row per ticket keyed by the cloud thread_id, with intake identities in an alias table, work in helpdesk_ticket_work, customer-facing messages in helpdesk_ticket_delivery_events, and every writer going through helpdesk_pending_writes under a content key. Read it with GET /helpdesk/ledger, /worst?n=, /stats?days= and /ledger/:id; announce by hand with POST /helpdesk/ledger/:id/told carrying {milestone, messageId}. A scoped session token may read only projection=crew, which drops reporter identity and message text, and may write the told route only for a ticket its own crew holds work on.

For bulk, POST /helpdesk/ledger/bulk (ops set-priority, reassign) and POST /crew/tasks/bulk (ops reassign, re-due, close) both take { ids, op, reason, … } with at most 200 ids and answer applied, refused, rows and remaining. A ticket's owner is its open crew task, so reassign moves that task; priority goes through the one priority writer, so the ledger row and helpdesk_ticket_meta change together. Crew task rows go through the same writer a single PATCH /crew/tasks/:ref uses, so per-row authority, the rule table, the landed-commit close gate and the task history all still apply. told and set-stage are not bulk ops. Replay is keyed on X-Client-Request-Id, and the reversal trail lives in bulk_write_events.

The moving parts, the traps and the parts still unwired are in the ledger's map under .claude/memory/helpdesk-ticket-ledger-map.md; the obligations are in .claude/memory/contracts/helpdesk-ticket-ledger-contract.md.

Related

Get Help (Helpdesk) is the customer's side — asking a question and reading the answer. Get Help (Helpdesk), part 2 covers the operator queue and developer console the ledger sits behind. Starting a session from a Help Desk ticket covers how a ticket becomes a Claude Code session, which is where the ledger's "fixing" work comes from.

Last verified 2026-10-25