When an agent fires and where its run executes. Triggering has two occasions: manually by button and by a simple schedule. And the agentic loop itself runs in a separate background lane — multi-step and unhurried, it must neither slow the live chat nor wait on source syncs.
Manual “Run now” needs no schedule but honors the locks: the run
starts if the agent is switched on by the owner, not locked by the
admin, and the weekly budget is not exhausted. The schedule is an
optional add-on: an interval (“every N hours”) or a
calendar slot (“daily / weekly at HH:MM”). It is
stored in a single field
schedule; empty means the agent is purely manual.
Raw cron and event triggers are deliberately out of scope:
v2.
The schedule computes the next slot into
next_run_at;
the scheduler wakes the agent by it and queues the run into the
background lane. An agent switched off by the owner, locked by the
admin, or left without a model takes no part in the scan: its
next_run_at is cleared — a closed
gate condition
leaves no trace in the journal (overlap, below, does). A calendar
“09:00” resolves against the owner's timezone (their
timezone, or on NULL the organization's
timezone from platform_settings) and only then lands in
next_run_at as UTC. Event triggers (a new commit, an
incident) and raw cron are on the v2 horizon.
A single agent's runs do not overlap: an agent has at most one
unfinished run — the base itself holds this lock (a partial unique on
state queued/running —
data
model). A slot that comes due, or a manual trigger, on top of a
loop still running breeds no new one — a skipped is
written with reason=already_running, so the skip is
visible in the journal rather than lost to silence. If a worker dies
without closing the run, a stale heartbeat_at releases
the lock (the hung run is reaped into
failed/stale), and the agent starts again:
a zombie does not lock it out.
An agent run is seconds to minutes of a multi-step loop. Putting that on the live-chat path would hang the user; queuing it behind sync would mean waiting for the import. So agents run in their own background lane, a separate worker pool. And since their KS reads are strictly read-only, the lane calmly runs in parallel with graph writes — sync and curation: there is nothing to contend for over the data.
three independent lanes · agents wait on no write and slow no chat
The model provider has one shared rate limit for the whole platform. The agentic loop is greedy — many calls per run — and if let into the common pot, it will eat the live chat's limit: the user waits while someone's background agent grinds through its tenth step. So agents have their own cap on concurrent LLM calls, separated from interactive traffic: background yields to response.
The cap itself is a platform setting
(platform_settings.agent_max_concurrency, one number
per organization), alongside the
iteration cap and the
weekly budget.
This is a cap on the lane's parallelism, not the weekly
spend: the budget limits how much an employee spends in a week; here it
is how many runs turn at once, so as not to touch the chat.