The eight scheduled jobs and what breaks when one stops
Every AI Support cron job by name, its cadence, the setting that gates it, and the slow-burn failure each one prevents — a stale index, a doubled bill, immortal tickets.
Almost nothing in this addon fails loudly. The failures that cost money are slow: an index that stopped rebuilding, a prompt cache that quietly stopped working, tickets that never close, and tickets that can never close. Eight scheduled jobs stand between you and each of those, and none of them announces itself when it is working.
Find them at Admin → System → Cron (/admin/system/cron), grouped under the
category AI Support. The registry key is ai_support; each row shows the
job's title, its period, its last run and its last error.
The eight
| Job | Runs | Gated on | What stops happening |
|---|---|---|---|
aiSupportReindexKnowledge |
24h | — | New and edited articles and FAQ rows never enter the index. Retrieval freezes at the last good run |
aiSupportCacheHealthCheck |
1h | — | Prompt-cache collapse goes undetected. This is the only detection there is |
aiSupportRetentionSweep |
24h | — | Turn rows keep the customer's words past aiSupportRetentionDays. You are holding them longer than you said you would |
aiSupportExpireStale |
1h | never gated | Stale offers stay listed as live work, and an abandoned process makes its ticket impossible to close |
aiSupportProactiveSweep |
15m | aiSupportProactiveEnabled |
Failed deposits and withdrawals get no ticket opened for them |
aiSupportHarvestAnswers |
24h | aiSupportEnabled |
Undocumented questions never become draft articles. Costs nothing to run |
aiSupportShareQuestions |
7d | aiSupportShareQuestionsEnabled |
Nothing leaves the install. No local effect at all |
aiSupportAutoCloseResolved |
24h | aiSupportAutoCloseDays above 0 |
Tickets nobody replied to stay open forever |
Four of them do nothing on a default install: proactive, share questions and auto-close are off until you switch them on, and harvest waits for the assistant's master switch. They still run on their schedule and return immediately, which is why a "last run" timestamp on those rows is not evidence that anything happened.
A job can be run by hand from the cron screen. Three different permissions are
involved and they are easy to grant inconsistently — access.cron opens the
page, view.cron fetches the rows that fill it, and manage.cron runs a job.
A role with the first and not the second gets an empty screen; a role with the
first two and not the third can watch the jobs and is refused when it presses
the button:
cronName, which is the job name in the table above. Returns 409 if that job is already running.The four that are always on
aiSupportExpireStale — hourly, and gated on nothing
An offer the assistant made expires after 24 hours; a multi-step process expires after 7 days. Both used to expire lazily — the moment somebody pressed a stale button, and never otherwise.
For a single offer that is enough: the row is inert until it is clicked. For a
process it is not, because a RUNNING workflow is read by something else.
aiSupportAutoCloseResolved deliberately skips any ticket that has one, so that
a customer mid-process is not swept up while they are still working through it.
Put those two together and a customer who starts a process and never comes back
leaves a RUNNING row that no click will ever expire — which exempts their
ticket from auto-close permanently. The feature that exists to stop a ticket
closing under a customer's feet had become a way to make a ticket that can never
close, and the more the assistant was used the more of them there were.
So expiry is a sweep as well as a check. This job is not gated on
aiSupportAutoCloseDays: those rows are wrong whether or not you use automatic
closing, and they show in your Live Inbox as live work either way. Tying a
data-integrity sweep to an unrelated convenience setting is how a fix ships that
most installs never run.
The 24-hour clock is applied to standalone offers only. A step inside a process is written when the previous step completes, so its own age says nothing about how long the process has been running — steps are retired by their process, on the 7-day clock, and the step and the workflow expire together.
It retires at most 500 abandoned processes per run — a bound, not a target, so that the first run after an upgrade does not touch every process an install ever had. Nothing that already ran is undone.
aiSupportCacheHealthCheck — hourly on purpose
Prompt-cache invalidation multiplies the bill two to ten times with no error, no exception and no log line. One unsorted list or one interpolated timestamp above a cache breakpoint, and every request silently pays full price. Watching the read ratio fall is the only detection mechanism that exists, which is why this runs hourly rather than nightly.
It looks at the last 24 hours of succeeded turns and compares cache-read tokens against total input tokens. Two conditions have to hold before it alarms:
- the read ratio has fallen below half, and
- there have been at least 10 turns in the window.
The second is what keeps a quiet install from alarming every day on three turns.
When it does fire it writes a warning to the console log and raises an admin
notification — delivered to holders of view.support.ticket, linking to
/admin/ai/support, titled "AI support is costing more than it should".
If that notification arrives, read Cost and budgets: the Overview dashboard reports the same ratio, and the cached/fresh token split underneath it is where you see the size of the problem.
aiSupportReindexKnowledge — nightly
Re-chunks the shipped documentation packs, your own published articles and your FAQ rows. A pack whose checksum has not changed is skipped, so a nightly run on an unchanged corpus costs almost nothing.
Sources that fail are named individually in the log rather than aborting the run. The full mechanics — the four source weights, where packs are found, and why a product's documentation goes missing without saying so — are in Knowledge pipeline, which also names the manual reindex you can run instead of waiting for the night.
aiSupportRetentionSweep — nightly, in two passes
Turn rows carry the customer's question indirectly, through the draft and sent text, the citations and the tool arguments — so those age out on your schedule. You are the data controller.
Pass one clears the words, on your schedule. Every turn older than
aiSupportRetentionDays has its text-bearing and JSON columns set to null:
draft text, sent text, error message, citations, tool calls and retrieved chunk
ids. What survives is a row of numbers — when it happened, what it cost, how
many tokens it moved.
Pass two deletes the row, once it can no longer move a ceiling. A turn is removed only when it is older than both your retention window and the first of the current UTC month.
The monthly ceiling is a sum over turns since the 1st. A sweep that deleted rows inside that window would hand the allowance back: an operator on 7-day retention with a $100 monthly cap could never reach it, because a week of spend was deleted out from under the sum every night. The guardrail against a runaway bill was quietly uncapped on exactly the installs most careful about data.
On the default 90-day retention the two passes cover the same rows and nothing looks different. On a retention shorter than a month, the emptied row survives until the month it was billed in has passed, then goes.
Sessions are deliberately not deleted alongside their turns: a session row is the handover record, carries no message text, and deleting it would break the Live Inbox's join for conversations that are still open. Resolved sessions that have no turns left behind them are removed separately — which on a short retention now happens a month later than the redaction, because their turns are still there, empty.
Your administrators' own conversations are on the same clock. The operator assistant stores its transcripts in a separate pair of tables, and a retention policy that quietly covered only half the stored conversations would be worse than none. Admin turns older than the cutoff have their question, answer, sources and proposals emptied — the row and its cost stay, for the same reason as above — and admin session rows whose last message is older than the cutoff are deleted outright, because a history entry whose turns are all empty is a row nobody can use.
The four that wait for a switch
aiSupportProactiveSweep — every 15 minutes
The only job in the addon that contacts a customer who did not contact support. Fifteen minutes against a 45-minute lookback; the overlap is deliberate so a slow or missed tick cannot leave a gap. It has its own page: Proactive support.
aiSupportHarvestAnswers — nightly, and free
This one is worth understanding because operators assume it costs money and turn it off.
It makes no model call. It is SQL and token overlap: for each cluster of
undocumented questions it finds the escalated ticket whose customer asked that
question, takes the reply one of your agents typed, and files it verbatim as a
DRAFT article.
That asymmetry is why it is on a timer at all. The drafting button on the Gaps screen calls a model twice for every gap — the re-ranker inside retrieval, then the drafting call — which is why it is a button somebody presses and watches with a stated cost. Running that unattended would be a machine spending money on drafts nobody asked for. The harvestable half of the loop is free, so it is automatic; the expensive half stays manual.
Three bounds worth knowing:
- Twenty drafts a night. Not a cost ceiling — a review ceiling. Every article lands in a person's queue, and a hundred a night produces the one outcome that kills a review queue, which is an operator who stops opening it.
- It never publishes. Only
PUBLISHEDarticles are indexed, quotable or servable, so nothing it files can reach a customer until a person reads it and presses publish. - One agent reply becomes one article. A ticket already used as a source is skipped, so the same paragraph does not appear four times under four titles.
Drafts it files carry generatedBy: "harvest_nightly", which distinguishes them
from the same harvest run manually — so drafts you did not ask for say which
machine produced them. See Gaps.
aiSupportAutoCloseResolved — nightly
Closes tickets in status REPLIED — meaning your team (or the assistant) spoke
last and the customer never came back — after aiSupportAutoCloseDays days of
silence. It ships at 0, which is off.
Replying to a closed ticket returns a 403; it does not reopen it. An auto-close is therefore you ending a conversation on the customer's behalf, and doing it silently would be indistinguishable from the product losing their ticket.
So a message is posted into the ticket before the status changes, saying what happened and what to do instead. Append first, then close — a closing notice the customer can no longer read is worse than no notice.
Two safeguards: at most 200 tickets per run, because switching this on for the
first time means a backlog of every ticket that ever went quiet; and any ticket
with a RUNNING workflow is skipped, because a customer part-way through a
multi-day process sits in REPLIED by design.
It can never close a ticket with an unanswered question in it. Such a ticket is
OPEN, not REPLIED — status on this platform is whose turn it is, so
"answered, and waiting on the customer" is a stored fact rather than a guess.
aiSupportShareQuestions — weekly
No-op unless aiSupportShareQuestionsEnabled is on. Weekly rather than nightly
because it is an aggregate over slow-moving data, and because weekly is the
honest cadence to describe to somebody deciding whether to switch it on.
What crosses is a word fingerprint and a count — the question's terms intersected with the shipped documentation's own vocabulary. No message text, no answers, no account data.
The jobs belong to the addon
They live in the extension, not in the platform core, like every other addon's cron. Two consequences:
- Disabling the AI Support extension deregisters all eight. They are stopped and removed from the schedule without a restart, and re-enabling it schedules them again. That is the supported way to stop every one of them at once.
- An install without the addon never registers them. The category simply does not appear on the cron screen.
If a single job is failing, its last error is on its row on the cron screen, and
every one of these writes to the console log under AI_SUPPORT. None of them
throws in a way that takes the others down with it — one bad cluster, one bad
ticket or one bad transaction is logged and skipped, and the run continues.