feat(kanban): add task lifecycle plugin hooks (claimed/completed/blocked) (#50349)

Plugins could observe session/tool/approval lifecycle but had no way to
observe kanban task transitions. Adds three observer hooks fired by the
board's claim/complete/block transitions:

  - kanban_task_claimed   (dispatcher process, before worker spawn)
  - kanban_task_completed (worker process, carries summary)
  - kanban_task_blocked   (worker process, carries reason)

Each fires AFTER the DB write txn commits, so a plugin observes durable
state and a slow/hanging callback can never hold the SQLite write lock.
All firing is best-effort: a raising hook is logged and swallowed and
never breaks a board transition. profile_name is resolved from
HERMES_HOME so dispatcher- and worker-side hooks carry the right profile.

Requested by @Smithangshu on Discord.
This commit is contained in:
Teknium 2026-06-21 12:38:14 -07:00 committed by GitHub
parent 9d883ac90e
commit e217fd42e2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 214 additions and 2 deletions

View file

@ -167,6 +167,31 @@ VALID_HOOKS: Set[str] = {
# choice: "once" | "session" | "always" | "deny" | "timeout"
"pre_approval_request",
"post_approval_response",
# Kanban task lifecycle hooks. Fired by hermes_cli.kanban_db when a task
# transitions state, AFTER the change is committed to the board DB (so the
# hook always sees durable state and a slow plugin can never hold the
# SQLite write lock). Observers only: return values are ignored.
#
# WHICH PROCESS each fires in matters, because kanban workers run as
# separate `hermes -p <profile> chat -q` subprocesses:
# - kanban_task_claimed -> the DISPATCHER process (gateway-embedded
# dispatcher or `hermes kanban dispatch`),
# right before the worker subprocess spawns.
# - kanban_task_completed -> the WORKER process, when it calls
# kanban_complete (or a CLI/manual complete).
# - kanban_task_blocked -> the WORKER process (worker-initiated block)
# or whichever process drove the block.
# A plugin that needs to observe every transition centrally should hook in
# the dispatcher; one that needs per-task in-session context should hook in
# the worker.
#
# Common kwargs: task_id: str, board: str | None, assignee: str | None,
# run_id: int | None, profile_name: str.
# kanban_task_completed adds: summary: str | None.
# kanban_task_blocked adds: reason: str | None.
"kanban_task_claimed",
"kanban_task_completed",
"kanban_task_blocked",
}
ENTRY_POINTS_GROUP = "hermes_agent.plugins"