Skip to main content

Listening to events

Your plugin follows what happens in Timon by listening to events: ctx.events.on(kind, handler) in the host half, ctx.events.subscribe(kind, handler) in the browser half. Both return a function that stops listening; Timon also stops every listener when your plugin unloads. Events are read-only: a plugin never emits one on Timon's bus. Your own events go through business events or live updates.

Every kind needs events:subscribe. Most also need the read permission of what they carry, because receiving the object is reading it. A kind that is not in the table below throws when you subscribe, and the message lists the allowed kinds.

"timon": {
"id": "acme-watch",
"apiVersion": 10,
"permissions": ["events:subscribe", "kanban:read"]
}

Catalogue​

EventsAlso needsPayloadBrowser halfGuide
mission_completenothingmissionId, status (completed, failed, terminated), exitCodeyesbelow
task_donenothingtaskIdyesbelow
task_blockednothingtaskId, reasonyesbelow
notifications_changednothingownerUserId, notificationId (on a creation only)yesbelow
workflow_runnothingrunId, nodeId, statusyesbelow
workflow_proposalnothingworkflowId, missionIdyesbelow
mission_started, turn_complete, mission_title_changedmissions:readthe mission and the origin of the changeyesControlling missions
event, event_deltamissions:transcripta transcript row; a live fragment of an answerevent onlyControlling missions
task_created, task_updated, task_assigned, task_deleted, task_unblockedkanban:readthe task, changed and previous for an update, originnoKanban sync
todo_done, todo_resolved, todo_deletedtodos:readthe idea, originnoPermissions
ui_document_created, ui_document_resolvedui-documents:readthe document, its creator or the answernoDocuments in the conversation
question_pending, question_resolvedquestions:readthe question, originnoQuestions and notifications
memory_rule_added, memory_rule_removedmemory:reada rule your plugin wrote, originnoMemory
project_created, project_updatedprojects:read (or projects:manage)the project, changed, originnoProjects, series and worktrees
agent_created, agent_updated, agent_deletedagents:readan agent your plugin created, originnoAgents
routine_*, trigger_*, pipeline_*, workflow_run_started, workflow_run_cancelledthe entity's read permissionthe object, originnoAutomations
browser_recording_*browser:recordings:read (or :write)the recording's metadata, originnoThe built-in browser
browser_annotation_*browser:annotations:read (or :write)the annotation's metadata, originnoThe built-in browser

origin is { kind, id } and says who made the change: user, agent, plugin, system, or the channel that started a mission. A change your plugin made carries { kind: 'plugin', id: ctx.id }: a sync ignores that echo.

ctx.events.on('task_updated', (ev) => {
if (ev.origin.kind === 'plugin' && ev.origin.id === ctx.id) return // your own write
if (ev.changed.includes('status')) console.log(ev.task.id, ev.previous.status, '→', ev.task.status)
})

The kinds with a permission of their own reach your plugin for the objects of the users allowed to use it, inside that user's identity (ctx.user()), and for the objects your plugin owns. Their handlers run one at a time per plugin, in event order.

Signals​

The first six kinds are signals: they carry identifiers, never the object, and need nothing but events:subscribe.

  • In the host half they reach your plugin for every account of the installation, and the handler runs outside any request. To read what changed, call the matching façade (ctx.missions.get, ctx.kanban.get, ctx.notifications.list, ctx.workflows.status): outside a request it answers like a background job, so it sees only what your plugin owns. Do not use these identifiers to tell anyone what happened in another account's work.
  • In the browser half they come from the signed-in user's stream, so they only carry what that user can see. notifications_changed reaches an account for its own notifications.
// Browser half: refresh a badge when this user's notifications change.
const stop = ctx.events.subscribe('notifications_changed', () => reloadBadge())

What each one means:

EventSent when
mission_completea mission's process ended. failed and terminated differ: a failure of the agent, or a stop (a person, a limit, a shutdown). A conversation that stays open sends turn_complete at the end of each turn instead.
task_done, task_blockeda Kanban task closed as done or blocked. task_updated (with kanban:read) says the same with the task and the origin.
notifications_changeda notification was created, read or dismissed. ownerUserId is the account it belongs to (null for a system notification); notificationId is set on a creation only. Read the list again rather than guessing what changed.
workflow_runa workflow run or one of its nodes changed status. nodeId is absent for the run itself.
workflow_proposalan agent proposed changes to a workflow from a mission. The proposal waits for a person to accept it in the workflow editor; accepting replaces the draft, it never publishes.

Handle them as invalidations: reload what you show, and expect several in a burst.

Handlers that fail​

A handler that throws or rejects is your plugin's own failure: the daemon logs it under your plugin's id, the other listeners of the event still run, and the write that caused the event is not affected (Manifest). Catch what you can recover from.