feat(event-triage): per-user check interval, overriding the instance one
Nightly Build / build (push) Successful in 5m4s

Event triage is the one system agent whose right cadence depends on who it
runs for: it fires on inbound events, so someone on a dozen mailing lists
has something waiting on nearly every tick while a quiet account has
something waiting almost never. A single instance-wide interval serves one
of them badly, and the observed failure is the first: the agent starts on
practically every pass.

An admin can now set a per-person interval on that user's page (Users ->
the person -> Event triage). Empty means "follow the instance setting",
which stays the state nobody has a row for.

- New registry table `system_agent_user_settings(agent_id, user_id,
  interval_secs)`. A row is an override and its absence is inheritance --
  no sentinel value, no row seeded at user creation, clearing the field
  deletes the row. Registry rather than the user's own file because the
  writer is the admin and a member's database is unreadable unless they
  happen to be logged in; a setting that could only be changed during its
  subject's session would not be a setting. Keyed by agent_id though only
  one agent uses it, so a future agent's schedule is not a schema change.

- `SystemAgent` gains `interval_secs_for(user_id)`, which `is_due` now
  measures against, and `shortest_interval_secs()`. Both default to the
  existing `interval_secs`, so every other agent implements nothing. The
  second is the non-obvious half: `base_tick` sleeps for the shortest
  interval any enabled agent asks for, so without it an override below the
  instance value would be rounded up to it -- an override that works when
  it lengthens and silently does nothing when it shortens.

- `GET/PUT /api/users/{id}/event-triage`, admin-gated, minutes on the
  wire, null to clear. Nothing rides the bus: the scheduler re-reads the
  interval every tick and due-ness is counted from the user's own last
  attempt, so a change lands on the next wake-up with no push.

Both helpers fail open onto the instance value -- an unreadable registry
must not turn into an agent that stops running for someone.

Docs: docs/system-agents.md gains the per-person section and no longer
reads as if the interval were one number for everybody.
This commit is contained in:
Daniele
2026-08-14 13:07:45 +01:00
parent 402c9ffe50
commit e7c802f0d7
13 changed files with 573 additions and 12 deletions
+8
View File
@@ -1112,6 +1112,7 @@ export default {
'users.detail.profile': 'Profile',
'users.detail.connectors': 'Connectors',
'users.detail.plugins': 'Plugins',
'users.detail.triage': 'Event triage',
'users.detail.security': 'Security',
'users.detail.saved': 'Saved',
'users.detail.delete_hint': 'Deletes this user, their database and all conversation history.',
@@ -1128,6 +1129,13 @@ export default {
'users.plug.empty': 'No plugins available.',
'users.plug.admin_note': 'Admins can use every enabled plugin, whatever is ticked here.',
'users.triage.hint': 'Event triage reads the events this person\'s connectors pushed and notifies them about the ones worth an interruption. Someone who receives a lot of mail or messages triggers it on almost every pass; give them a slower cadence here.',
'users.triage.interval': 'Check interval (minutes)',
'users.triage.placeholder': 'Default ({n})',
'users.triage.using_default': 'Empty: follows the instance setting ({n} min).',
'users.triage.using_override': 'This user only. Clear the field to follow the instance setting again.',
'users.triage.invalid': 'Enter a whole number of minutes, or leave the field empty.',
'users.modal.create_title': 'New user',
'users.modal.username': 'Username',
'users.modal.display_name': 'Display name',