← Agent Engine

Triggering and lanes

agent-engine · workzone

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.

Two triggering occasions → Cache & Workers

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.

▶ Manual
Run now
a button on the agent's screen — needs no schedule; available while the agent is switched on and not locked by the admin.
↻ Interval
every 6 h
a simple period — for regular background watching.
Calendar
Mon 09:00
daily or weekly at a set hour — a morning digest.

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/runningdata 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.

Background execution lane → Cache & Workers

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.

Live chat synchronous
low-latency an answer to the user here and now — priority on response
Sync background
writes the graph knowledge import and curation — Harvester and Curation Pass
Agents background · our lane
reads the graph agentic loops — their own worker pool, read-only against KS

three independent lanes · agents wait on no write and slow no chat

A separate per-model limit → Cache & Workers

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.