Skip to content

feat(kanban): return agent-created task completions to origin#21523

Closed
verkyyi wants to merge 2 commits into
NousResearch:mainfrom
verkyyi:verky/agent-driven-kanban-orchestration
Closed

feat(kanban): return agent-created task completions to origin#21523
verkyyi wants to merge 2 commits into
NousResearch:mainfrom
verkyyi:verky/agent-driven-kanban-orchestration

Conversation

@verkyyi

@verkyyi verkyyi commented May 7, 2026

Copy link
Copy Markdown

Summary

This PR tightens the loop for agent-driven Kanban orchestration so a normal user request can fan out to board tasks and still return results to the originating surface.

Included changes:

  • Pass origin metadata (platform, chat_id, thread_id, user_id, session_id, and current user message excerpt) from AIAgent through tool dispatch.
  • Auto-subscribe model/tool-created Kanban tasks to the originating routable surface when available, with safe defaults:
    • Telegram-origin tool-created tasks default to synthesized user-facing completion replies.
    • CLI and other origins default to direct worker handoff notifications.
    • Worker-created child/fan-out tasks remain silent unless they are parentless/root follow-ups inheriting an existing origin subscription.
  • Add notification_mode for Kanban subscriptions: direct, synthesize, or silent.
  • Treat adapter SendResult(success=False) as delivery failure so completion notifications are retried instead of silently dropped.
  • Add lightweight no-tools completion synthesis for public/user-facing replies, with explicit prompt guidance to hide internal Kanban/worker plumbing by default.
  • Mirror successful Kanban notifications back into the gateway session so the origin session has durable context that a follow-up can see.
  • Make the Kanban toolset configurable for orchestrator profiles while preserving dispatcher worker behavior.
  • Add focused tests for origin subscription, notification modes, delivery failure handling, synthesis fallback, session mirroring, and notifier ownership.

Motivation / workflow

This is meant to support the workflow discussed in Discord:

Natural language request in Telegram → orchestrator creates child tasks → workers execute → results return somewhere useful for the orchestrator/user → orchestrator continues or summarizes.

The existing slash/CLI task creation path could subscribe an origin, but model-tool-created tasks from a normal/orchestrator chat did not have an equivalent generic return path. This PR stores enough provenance on the subscription to deliver the terminal event back to the routable chat/topic and optionally synthesize a clean user-facing completion.

Design notes

  • kanban_notify_subs gains generic provenance fields (notification_mode, origin_session_id, origin_profile, origin_context) with migration support.
  • Synthesis uses the origin/default gateway runtime, disables tools, caps iterations at one, and uses only the worker handoff/task/origin context. If synthesis fails, the notifier falls back to the direct worker handoff.
  • silent mode suppresses terminal pings but still lets code use the same subscription/cursor machinery.
  • Gateway notifier ownership defaults to the dispatcher-owning gateway (kanban.dispatch_in_gateway) so a secondary profile gateway does not consume shared notification rows intended for another bot. Operators can override with kanban.notify_in_gateway or HERMES_KANBAN_NOTIFY_IN_GATEWAY.
  • Parentless worker-created root/recovery tasks can inherit the current task's origin subscription; ordinary worker-created child tasks with parents do not, avoiding duplicate/noisy notifications during fan-out.

Explicitly excluded

This PR intentionally excludes local deployment/personal patches, including profile-specific Telegram/Weixin config, AgentFeeds tooling, cron/reporting setup, local dashboards, personal docs, model/provider config, and any machine-specific files.

Tests

  • python -m pytest tests/tools/test_kanban_tools.py tests/gateway/test_kanban_notifier.py -q
  • git diff --check
  • python -m py_compile gateway/run.py hermes_cli/kanban_db.py model_tools.py run_agent.py tools/kanban_tools.py toolsets.py hermes_cli/tools_config.py tests/tools/test_kanban_tools.py tests/gateway/test_kanban_notifier.py

Follow-up design questions

This PR implements a conservative durable-board + notification return path. A deeper continuation model could still be discussed separately: should worker completion become active orchestrator-session context that triggers another orchestrator turn automatically, or should it remain durable board/session state until the user/orchestrator asks for follow-up?

@alt-glitch alt-glitch added type/feature New feature or request comp/tools Tool registry, model_tools, toolsets comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have labels May 7, 2026
@TheoLong

TheoLong commented May 9, 2026

Copy link
Copy Markdown

Reviewed this end-to-end — really want this landed because the missing-completion-notification gap is biting our team daily. Rebased onto current main (clean, 1/1 commits) and ran the new test files: 57/57 pass in 7.7s. Rebased branch: TheoLong:review/pr-21523-rebase if it's useful as a starting point.

Overall LGTM with one blocker plus a few smaller concerns. Would love to see this merged.

🔴 Blocker

INSERT OR IGNORE in add_notify_sub silently swallows notification_mode updates on duplicate (task_id, platform, chat_id, thread_id) keys.

Scenario: user runs /kanban subscribe first (registers direct), then an agent calls kanban_create for the same task with notification_mode='synthesize'. Second insert is ignored, mode stays direct, the synthesis path the agent requested never runs — and there's no error to debug from.

Suggested fix:

conn.execute(
    """INSERT INTO kanban_notify_subs
       (task_id, platform, chat_id, thread_id, user_id, notification_mode, origin_context)
       VALUES (?, ?, ?, ?, ?, ?, ?)
       ON CONFLICT(task_id, platform, chat_id, thread_id) DO UPDATE SET
         notification_mode = excluded.notification_mode,
         origin_context    = COALESCE(excluded.origin_context, origin_context),
         user_id           = COALESCE(excluded.user_id, user_id)""",
    (...),
)

(Preserves origin_context/user_id if a later call passes nulls; always honors the latest notification_mode.)

🟡 Concerns

  1. _synthesize_kanban_notification can block the watcher thread up to 45s with no concurrency cap. Under a burst of completing tasks, direct-mode notifications get starved behind synthesis calls. Worth either (a) running synthesis in a bounded thread pool, or (b) dispatching direct-mode sends first and queuing synthesize-mode separately.

  2. _current_user_message_for_tools only retains the most recent message. In multi-turn sessions where the user kicks off kanban work several turns before the tool actually fires, origin_context ends up pointing at unrelated chatter. Could capture at the turn the tool is invoked instead of "latest seen."

  3. notification_mode is exposed in the tool schema with silent as a valid value. A model could set silent on a user-visible task and the user would get nothing — no audit, no alert. Recommend either dropping silent from the schema enum (keep it CLI-only) or requiring explicit user opt-in via session config.

✅ LGTM

  • SendResult failure → retry wiring is correct
  • Schema migration is safe (PRAGMA table_info guard before each ALTER)
  • HERMES_KANBAN_TASK env gate correctly prevents worker fan-out from spamming origin chats
  • synthesize falls back to direct cleanly on synthesis exception
  • _kanban_notify_in_gateway_enabled ownership guard handles multi-profile deployments
  • Test coverage solid (487 new lines across two files)

@verkyyi thanks for putting this together. The blocker is a one-line conflict-clause swap; happy to push a fixup commit if it'd help unblock the merge. cc: maintainers — this is a real production issue for agent-orchestrated kanban workflows.

@TheoLong

Copy link
Copy Markdown

Pushed a one-commit fixup against this branch (rebased on main) addressing the BLOCKER from my earlier review:

Branch: TheoLong:review/pr-21523-rebase → commit 4291acf

Change: add_notify_sub switches from INSERT OR IGNORE to INSERT … ON CONFLICT(task_id, platform, chat_id, thread_id) DO UPDATE SET notification_mode=excluded.notification_mode, … with COALESCE on user_id / origin_session_id / origin_profile / origin_context so a partial re-subscribe (e.g. tool-driven synthesize arriving after slash-command direct) updates the mode without clearing the originating context fields.

Test: tests/tools/test_kanban_tools.py::test_add_notify_sub_upserts_notification_mode_on_duplicate — registers direct first, then synthesize with None for origin fields; asserts the mode flips and the original user_id / origin_* survive. Full suite: 58/58 passing (the existing 57 + the new regression).

Happy to open this as a PR against your branch if that's easier than a cherry-pick — let me know.

— Tem

@verkyyi

verkyyi commented May 10, 2026

Copy link
Copy Markdown
Author

Thanks Theo — good catch on the duplicate subscription path.

I pulled in the important behavior from your rebase branch: duplicate notify subscriptions now use an upsert instead of INSERT OR IGNORE, so later notification_mode, user_id, and origin/context updates are not silently dropped. I also added regression coverage for that case.

I also addressed the concurrency concern from my local patches in this PR branch: synthesis/native turns for the same origin session now serialize on the active origin session lock, while notifications for unrelated chats do not block behind that lock. Added tests for both the same-origin waiting case and the unrelated-chat non-blocking case.

Pushed the fix in 58b062cd.

I kept this focused to the blocker + concurrency issue. I did not add a separate bounded synthesis worker queue/thread-pool policy here; happy to do that as a follow-up if you think we should make that explicit in this PR.

@teknium1

Copy link
Copy Markdown
Contributor

Thanks @verkyyi — closing this one. Adding notification_mode + origin provenance + a synthesis path across gateway/tools/run_agent is substantive agent-orchestration design. We have the gateway-create auto-subscribe path (origin chat gets terminal-event notifications), plus the kanban-tool send_message bridge for cross-session delivery. A third synthesis path would need to specify what it does that those two don't and how all three avoid drift. Appreciate the work.

@teknium1 teknium1 closed this May 19, 2026
verkyyi added a commit to verkyyi/hermes-agent that referenced this pull request May 28, 2026
3592c35 removed the idempotent ALTER guards for notification_mode /
origin_session_id / origin_profile / origin_context / request_id on the
rationale that they were "in the base CREATE TABLE as of upstream v0.14.0."
That premise is false: these columns are fork-local. PR NousResearch#21523 (which would
add them upstream) is UNMERGED — even after the 2026-05-27 catchup to
upstream/main @ 2d5dcfa, upstream's kanban_notify_subs base schema ships
only notifier_profile. be4900d is our own commit / the PR head, not an
upstream merge.

Because the columns are in *our* base CREATE TABLE but not upstream's, and
CREATE TABLE IF NOT EXISTS is a no-op against a table first created by an
upstream-schema (or older-fork) checkout, these idempotent guards are the
only thing that backfills the columns on such a DB. Restore them. Keep
across upstream merges; retire only once PR NousResearch#21523 lands upstream.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
verkyyi added a commit to verkyyi/hermes-agent that referenced this pull request May 28, 2026
…ord NousResearch#1 mixin-extraction plan

- NousResearch#4: the entry claimed the five origin/mode columns were "in the base CREATE
  TABLE as of upstream v0.14.0" and the migration guards were redundant. False:
  the columns are fork-local; PR NousResearch#21523 is unmerged and upstream main @ 2d5dcfa
  ships only notifier_profile (verified). Retitle, document that the guards are
  fork-local and must survive upstream merges, reference the restoration (7ee0882).
- NousResearch#1: record the planned KanbanNotifierMixin extraction (gateway/run.py is still
  the hot file post-merge; the notifier watcher is confirmed still there) and why
  a plugin/hook rewrite was rejected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
verkyyi added a commit to verkyyi/hermes-agent that referenced this pull request May 28, 2026
Records the redesign of the Kanban completion-notification path after
upstream PR NousResearch#21523 was closed: replace the gateway-side LLM synthesis
mixin with two primitives built on existing machinery — a kanban_decompose
self-park/re-wake tool (generalizing upstream decompose_triage_task) and
_wake_origin_session (re-enter the anchor handoff into the origin gateway
session via the proven _process_handoff path). Collapses four fork-local
notify-sub columns to upstream-identical, moves delivery mode to config,
and rolls child progress onto the single anchor via the swarm blackboard.
Both load-bearing claims and both open lifecycle/capability items verified
against the code.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery comp/tools Tool registry, model_tools, toolsets P3 Low — cosmetic, nice to have type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants