Skip to content

feat(taskflow): emit code-level completion notification on task submission - #1206

Open
LUOSENGWA wants to merge 3 commits into
agentscope-ai:mainfrom
LUOSENGWA:fix/task-completion-notification
Open

feat(taskflow): emit code-level completion notification on task submission#1206
LUOSENGWA wants to merge 3 commits into
agentscope-ai:mainfrom
LUOSENGWA:fix/task-completion-notification

Conversation

@LUOSENGWA

@LUOSENGWA LUOSENGWA commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

What

TeamHarness taskflow: code-level completion notification + lifecycle attention events (v2, design issue #1229).

Worker completion events used to be prompt-dependent (the LLM hand-sending a Matrix message; "a tool call does not count as the completion message" was unenforceable). This PR makes task-lifecycle attention signals first-class, idempotent, auditable room events generated by code:

  1. Completion events with a per-status contractsubmit_task now sends one first-line token per result status (TASK_COMPLETED / TASK_PARTIAL / TASK_REVISION_NEEDED / TASK_BLOCKED / TASK_FAILED), so leader-side prompts can branch on the line itself. The submitted status is validated against the accepted set; unknown values are rejected with a clear error.
  2. @initiator routing — human team members (the task initiator) are @mentioned alongside the leader in completion, attention, and project events, closing the "requester only gets ambient room" gap (Design: task-lifecycle attention events & channel routing (unified view for #1206 and #1219) #1229 Part A, Q2).
  3. P0 ordering: sync before notify — the completion event is an attention signal, not a receipt. submit_task syncs shared storage first; a failed sync withholds the notification and returns a retryable failure (the local task state is already submitted, so the retry is idempotent and the event is sent exactly once). A leader can no longer be woken by an event whose artifacts it cannot read.
  4. request_attention (new taskflow action) — in-flight human decisions (kind: approval / decision / escalation / other) as first-class events: ATTENTION_<KIND>: <task-id> - <question>, mentions leader + humans, idempotent per unresolved kind (retry reuses the recorded event), terminal tasks rejected, sync-first like submit. Resolved by accept_task_result (closes all outstanding attention on the task) or an explicit resolved: true. Roles: worker / leader / remote-member (Design: task-lifecycle attention events & channel routing (unified view for #1206 and #1219) #1229 Q1).
  5. PROJECT_COMPLETED on complete_project — a finished project now wakes the room (first task room, falling back to the project source room when it is a Matrix room) with an idempotent event (projectCompletionEventId persisted; retried complete_project reuses it).

Idempotency: transaction ids are stable per (task, status) / (task, kind, attempt) / (project, status); recorded event ids are persisted in task/project state so retries reuse instead of duplicate. A resubmission with a changed status invalidates the recorded pair and sends a fresh event (fixes the silent-resubmit-reuse gap). Pre-upgrade tasks (no recorded status) keep the old reuse behavior.

Best-effort (unchanged from v1): notification-level failures (no room / leader / Matrix env, membership missing, HTTP error) never block the state mutation; the notificationNeeded hint is kept for the requester reply-route report.

Verification

  • Full test-taskflow.rb harness green locally (extracted harness, clean env): all pre-existing assertions + 12 new groups — P0 ordering (fail → withhold → idempotent retry), per-status tokens, @initiator mentions, status validation, changed/same-status resubmit, request_attention lifecycle (send / idempotent / different kind / explicit close / terminal guard / accept-resolves-all), PROJECT_COMPLETED send + retry reuse.
  • Python suite: 73 passed; 4 pre-existing environment failures (ruby availability / subprocess env in trace tests) — identical on the base commit, unrelated to this change.

Follow-ups (noted, not in this PR)

Related


TeamHarness taskflow:代码级完成通知 + 生命周期 attention 事件(v2,设计 issue #1229)。

Worker 完成事件原依赖 prompt(LLM 手动发 Matrix 消息,"工具调用不算完成消息"不可强制)。本 PR 把任务生命周期的 attention 信号变成代码生成的一等、幂等、可审计的房间事件:

  1. 每状态一条契约首行——submit_task 按结果状态发送 TASK_COMPLETED / TASK_PARTIAL / TASK_REVISION_NEEDED / TASK_BLOCKED / TASK_FAILED 首行 token,Leader prompt 可直接按行分支。提交的状态对 accepted 集合做校验,非法值明确报错。
  2. @initiator 路由——人类成员(任务发起人)与 Leader 一起在完成 / attention / 项目事件中被 @,补上"发起人只拿到环境消息"的缺口(Design: task-lifecycle attention events & channel routing (unified view for #1206 and #1219) #1229 Part A Q2)。
  3. P0 顺序:先 sync 后 notify——完成事件是 attention 信号不是回执。sync 共享存储失败时扣住通知并返回 retryable 失败(本地状态已 submitted,重试幂等、事件恰好一次)。Leader 不会再被"产物不可读的事件"叫醒。
  4. request_attention(新 action)——进行中的需要人类决策(approval / decision / escalation / other)变成一等事件:未解决同类幂等复用、终态任务拒绝、sync-first;由 accept_task_result(关闭该任务全部未决 attention)或显式 resolved: true 解决。角色:worker / leader / remote-member(Q1)。
  5. complete_projectPROJECT_COMPLETED——项目完成唤醒房间(第一个 task room,回退项目 source room),projectCompletionEventId 持久化保证重试幂等。

幂等:txn 按 (task, status) / (task, kind, attempt) / (project, status) 稳定;事件 ID 落 task/project state,重试复用不重复;状态变化的重新提交作废旧事件并发新事件(修复静默复用缺口);升级前任务保持旧复用行为。

best-effort 不变:通知层失败(无房间/Leader/Matrix 环境、成员校验、HTTP 错误)永不阻塞状态变更;notificationNeeded 保留给 requester reply-route。

验证:test-taskflow.rb 全量 harness 本地绿(含 12 组新断言);Python 套件 73 过、4 个预存环境失败(base 同样失败,与本改动无关)。

follow-up(不在本 PR):attention 事件的 DM 高显著度通道(v1 仅房间 @);非房间成员 mention 过滤(Matrix 语义天然不可见,无需额外检查)。

@oss-maintainer oss-maintainer left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Mirrors the delegate_task notification pattern for submit_task: a code-level, stable-txn Matrix message with m.mentions to the leader, so completion wake signals no longer depend on the Worker LLM remembering the contract line. The design is sound and the implementation faithful to the existing pattern — verified against main@4ab6b15: all reused helpers exist (_load_runtime_config, _section, _matrix_content, _canonical_room_id, _validate_assignee_membership, _write_task), leader resolution normalizes roles the same way as _roomflow_room_meta, the membership guard prevents sending to users outside the room, failure is strictly best-effort (submission and artifact publishing never blocked), and the notificationNeeded requester hint is correctly preserved. The unused arguments parameter matches _send_delegate_notification's existing signature style. Contract tests cover send/content, event-id persistence, retry reuse, the BLOCKED line, and forced-500 non-blocking. One warning inline: the completionEventId reuse branch can suppress a later, different completion (BLOCKED → same-task_id resubmit → SUCCESS), because both the reuse shortcut and the stable submit-<task-id> txn assume one terminal notification per task_id.

Findings

  • [Warning] server.py:4219 — completionEventId reuse + stable txn assume a single terminal notification per task_id; a same-task_id resubmit with a different result_status gets no new mention

Automated review by github-manager-bot

"skipped": True,
"error": "team leader Matrix ID not found in runtime config",
}
if task.get("completionEventId"):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Warning] completionEventId reuse may swallow a different completion. submit_task leaves the task mutable ("submitted" is not in TERMINAL_TASK_STATUSES, and the plan node also becomes submitted), so the same task_id can be submitted again before the leader records a decision. Sequence: Worker submits BLOCKED → event persisted here → task later unblocked and resubmitted SUCCESS with the same task_id → this branch returns the stale BLOCKED event (reused: true) and no TASK_COMPLETED mention is ever sent — reproducing exactly the stall this PR fixes, just on a narrower path. Note the stable txn below (submit-<task-id>, line 4161) makes this structural: even without the reuse shortcut, Matrix would reject a re-PUT with a different body under the same txn id. Could you confirm whether a same-task_id resubmit after a BLOCKED submission is reachable in the runtime integration (Task Service resume flow)? If it is, consider keying the txn/event slot on (task_id, result_status) or an attempt counter, or invalidating completionEventId when the new result_status differs from the recorded one.

@oss-maintainer oss-maintainer left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

This PR adds automatic Matrix completion notifications to submit_task, mirroring the existing delegate_task pattern with stable transaction IDs for idempotency. The implementation is correct, best-effort by design (notification failures never block submission), and well-tested across success, retry, BLOCKED, and HTTP-failure paths. All findings are informational: an unused parameter, an edge case where _write_task failure could cause a redundant HTTP call on retry (but not a duplicate message), and missing test coverage for skip branches (standalone mode, membership failures). The change is backward-compatible (additive response field, preserves notificationNeeded hint) and solves a real production issue where workers forget to send completion mentions after context compaction.


Automated review by github-manager-bot

def _team_leader_matrix_id() -> str:
"""Resolve the team leader's Matrix user ID from the runtime config.

The controller projects the full team roster (with roles and Matrix

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_send_task_completion_notification accepts an arguments parameter but never reads it. The orchestrator _task_completion_notification passes it through, but the send function only uses its keyword arguments. Consider dropping the parameter (or documenting why it is reserved) to avoid dead-code confusion.

)
if notification.get("sent"):
task["completionEventId"] = notification.get("eventId")
_write_task(arguments, task)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If _write_task raises after the Matrix PUT succeeded, completionEventId is not persisted. The stable txn ID submit-<task-id> prevents a true duplicate in Matrix on retry, but the retry will make a redundant HTTP round-trip and return reused: false instead of reused: true. Consider persisting completionEventId before the HTTP call (optimistic write) or catching _write_task errors explicitly so the response reflects the actual state.

"action": "submit_task",
"payload": {
"taskId": task_id,
"status": "SUCCESS",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests cover SUCCESS, BLOCKED, retry, and HTTP-failure paths well, but the skip branches are untested: no-leader-in-config (standalone run), leader-not-in-room (membership guard), and no-Matrix-env. These are documented best-effort paths, but a missing leader or missing env var are common deployment configurations worth a smoke assertion (e.g., notification.skipped == true).

@shiyiyue1102

Copy link
Copy Markdown
Collaborator

Thanks for addressing the completion-notification gap. The direction is valuable, but there is one ordering issue that needs to be fixed before merge.

submit_task currently sends the TASK_COMPLETED Matrix mention before _sync_task(...) uploads the task directory to shared storage. The Leader is awakened by that mention and then pulls shared/tasks/<task-id>/result.md; if it reacts before the sync completes, it can observe missing or stale result data and the only completion wake-up has already been consumed.

Please move shared-storage sync before the completion notification and send the mention only after the result has been synced successfully. If sync fails, return a clear sent: false/skip reason and do not notify the Leader yet. Please also add a regression test that delays or fails sync and verifies that no completion mention is sent before shared state is durable.

Please also align the status contract while touching this path: FAILED and PARTIAL are accepted statuses but currently produce a generic TASK_COMPLETED line without the status, while INTERRUPTED is handled by the message builder but is not in the accepted status set. Validate the submitted status and render/test every accepted non-success state consistently. For same-task resubmission, either reject a changed second terminal result after completionEventId exists or version the transaction/event identity so a corrected result is not silently suppressed.

Finally, please create and link a public issue for this task-lifecycle design so the behavior and retry semantics can be discussed and tracked by the community. Once these points are addressed and CI is green, this should be ready for another merge-readiness check.


感谢补齐任务完成通知链路,这个方向是有价值的,但当前有一个发送顺序问题需要在合并前修复。

submit_task 目前会先发送 TASK_COMPLETED Matrix mention,之后才通过 _sync_task(...) 将任务目录同步到共享存储。Leader 被 mention 唤醒后会立即拉取 shared/tasks/<task-id>/result.md;如果此时同步尚未完成,就可能读到缺失或过期的结果,而唯一一次完成唤醒已经被消费。

请将共享存储同步放到完成通知之前,并且只在结果成功同步后再通知 Leader。如果同步失败,应明确返回 sent: false 或跳过原因,并暂时不要发送完成通知。也请补充一个回归测试:延迟或强制同步失败,并验证共享状态持久化成功前不会发送完成 mention。

另外请统一这个链路的状态契约:FAILEDPARTIAL 是允许的状态,但当前会生成不带状态的通用 TASK_COMPLETED;消息构造又处理了未包含在允许集合中的 INTERRUPTED。建议在提交时校验状态,并为每个允许的非成功状态提供一致的消息和测试。同一 task 再次提交不同终态时,也应明确拒绝,或者给事务及事件引入 attempt/version,避免新结果被旧 completionEventId 静默吞掉。

最后,请为这项任务生命周期设计创建并关联一个公开 Issue,方便社区讨论和追踪通知及重试语义。上述问题修复且 CI 通过后,可以再进行合并检查。

@LUOSENGWA
LUOSENGWA force-pushed the fix/task-completion-notification branch from 628ca0c to fffe862 Compare September 10, 2026 04:07
LUOSENGWA added a commit to LUOSENGWA/HiClaw that referenced this pull request Sep 10, 2026
LUOSENGWA added a commit to LUOSENGWA/HiClaw that referenced this pull request Sep 10, 2026
LUOSENGWA added a commit to LUOSENGWA/HiClaw that referenced this pull request Sep 10, 2026
…tack integration

- submit sync-failure: agentscope-ai#1183 _sync_failure_result contract (statePersisted)
  + agentscope-ai#1206 v2 withheld-notification clause; guard before notification
- reused-digest submit branch returns the reused completion notification
- _accept_task_result: resolve outstanding attention in place on task_meta
  (separate read-modify-write was clobbered by the terminal status write)
- test-taskflow.rb: fix latent heredoc escape bug (Result body \n), role
  env toggles for agentscope-ai#1183 runtime-identity-first role, mc fail-sync hook for
  per-file sync (mc cp), vocabulary PARTIAL/FAILED -> INTERRUPTED,
  changed-status resubmit -> digest-fence conflict expectation
- test-projectflow.rb: fixture runtime.yaml gains member role leader
LUOSENGWA added a commit to LUOSENGWA/HiClaw that referenced this pull request Sep 10, 2026
- RuntimeConfigHandler proxies qwenpaw worker running-config (5-tab
  settings + Loop Engine catalog/status + custom-loop CRUD) from the
  Controller, so plugins/dashboards can inspect/adjust worker runtime
  behavior without the docker network.
- runtime-aware: non-qwenpaw worker -> 400 (config model is qwenpaw-only).
- RBAC: L1 full; L2 (team-leader / L2 human) team-scoped via
  findTeamMember + TeamMatches (404 to hide existence, W8).
- 5-tab field whitelist: L2 PUT runtime-config passes only ReAct/Loop/
  LLM-retry/long-term-memory/tool-level keys; unknown keys rejected
  (fail-closed, agentscope-ai#1216 safe-write pattern).
- loop-change notification: custom-loop writes alert the team room with
  @leader + @Changer (Matrix m.mentions), per agentscope-ai#1206 infra.
- TuwunelClient.SendNotification (+ Client interface): admin-identity
  message with m.mentions.user_ids.
- routes: GET/PUT runtime-config, GET loops + loops/status,
  GET/POST/PUT/DELETE loops/custom[/{loop}] under /api/v1/workers/{name}.

Closes the B-phase of the workbench 5-tab gap (F22): config is now
controller-exposed and loop changes are auditable + notify the right @list.
LUOSENGWA added a commit to LUOSENGWA/HiClaw that referenced this pull request Sep 11, 2026
- RuntimeConfigHandler proxies qwenpaw worker running-config (5-tab
  settings + Loop Engine catalog/status + custom-loop CRUD) from the
  Controller, so plugins/dashboards can inspect/adjust worker runtime
  behavior without the docker network.
- runtime-aware: non-qwenpaw worker -> 400 (config model is qwenpaw-only).
- RBAC: L1 full; L2 (team-leader / L2 human) team-scoped via
  findTeamMember + TeamMatches (404 to hide existence, W8).
- 5-tab field whitelist: L2 PUT runtime-config passes only ReAct/Loop/
  LLM-retry/long-term-memory/tool-level keys; unknown keys rejected
  (fail-closed, agentscope-ai#1216 safe-write pattern).
- loop-change notification: custom-loop writes alert the team room with
  @leader + @Changer (Matrix m.mentions), per agentscope-ai#1206 infra.
- TuwunelClient.SendNotification (+ Client interface): admin-identity
  message with m.mentions.user_ids.
- routes: GET/PUT runtime-config, GET loops + loops/status,
  GET/POST/PUT/DELETE loops/custom[/{loop}] under /api/v1/workers/{name}.

Closes the B-phase of the workbench 5-tab gap (F22): config is now
controller-exposed and loop changes are auditable + notify the right @list.
…_task

The taskflow MCP's delegate_task already publishes the assignment to the
task room at the code layer (_send_delegate_notification, stable txn),
but submit_task only returns a notificationNeeded hint - the Worker must
self-remember to @mention the leader with the contract line. Real
deployments (Node1) lost this line after multi-turn sessions, leaving
the Leader with no resume signal.

Mirror the delegate pattern in the same file:

- _send_task_completion_notification: Matrix HTTP PUT with m.mentions
  (same path/auth as the message tool), stable txn 'submit-<task-id>'
  so a retry cannot duplicate, first line follows the task-execution
  skill contract (TASK_COMPLETED/BLOCKED + result path).
- _task_completion_notification: best-effort orchestration - resolve the
  leader from the runtime config team roster, reuse the recorded
  completionEventId on retry, validate room membership, persist the
  event id on success. A send failure returns {sent: false, error}
  and never blocks the terminal submission.
- submit_task response gains 'notification'; the notificationNeeded
  hint is kept (it also drives the requester reply-route report).

Contract tests (test-taskflow.rb): completion line + mentions + worker
+ summary + event-id persistence, retry reuses without duplicating,
BLOCKED status uses the BLOCKED line, forced 500 does not block the
submission and persists no event id. Context file-event selection made
mxcUri-based instead of positional.
- submit_task: per-status first-line tokens (TASK_COMPLETED / TASK_PARTIAL
  / TASK_REVISION_NEEDED / TASK_BLOCKED / TASK_FAILED), status validation,
  status-scoped txn and completionEventStatus so a changed-status
  resubmit sends a fresh event
- @initiator routing: human members (task initiator) mentioned alongside
  the leader in completion / attention / project events
- P0 ordering: sync shared storage before the completion notification;
  a failed sync withholds the event and returns a retryable failure
- new taskflow action request_attention (worker/leader/remote-member):
  in-flight human decisions as first-class idempotent room events
  (kind approval/decision/escalation/other), terminal guard,
  sync-first, resolved by accept_task_result or explicit resolved=true
- complete_project: code-level PROJECT_COMPLETED room event with
  idempotent projectCompletionEventId
- task-execution SKILL.md: contract rewritten around the code-generated
  per-status events; full accepted status set
- test-taskflow.rb: 12 new assertion groups (ordering, tokens, @initiator,
  validation, resubmit, request_attention, PROJECT_COMPLETED); mc shim
  gains a push-only sync-failure hook

Local verification: full extracted harness green (all pre-existing + new
assertions); pytest 73 passed (4 pre-existing env failures on base).
Refs: agentscope-ai#1229
@LUOSENGWA
LUOSENGWA force-pushed the fix/task-completion-notification branch from 346298c to 6e36b41 Compare September 11, 2026 09:20
LUOSENGWA added a commit to LUOSENGWA/HiClaw that referenced this pull request Sep 11, 2026
- RuntimeConfigHandler proxies qwenpaw worker running-config (5-tab
  settings + Loop Engine catalog/status + custom-loop CRUD) from the
  Controller, so plugins/dashboards can inspect/adjust worker runtime
  behavior without the docker network.
- runtime-aware: non-qwenpaw worker -> 400 (config model is qwenpaw-only).
- RBAC: L1 full; L2 (team-leader / L2 human) team-scoped via
  findTeamMember + TeamMatches (404 to hide existence, W8).
- 5-tab field whitelist: L2 PUT runtime-config passes only ReAct/Loop/
  LLM-retry/long-term-memory/tool-level keys; unknown keys rejected
  (fail-closed, agentscope-ai#1216 safe-write pattern).
- loop-change notification: custom-loop writes alert the team room with
  @leader + @Changer (Matrix m.mentions), per agentscope-ai#1206 infra.
- TuwunelClient.SendNotification (+ Client interface): admin-identity
  message with m.mentions.user_ids.
- routes: GET/PUT runtime-config, GET loops + loops/status,
  GET/POST/PUT/DELETE loops/custom[/{loop}] under /api/v1/workers/{name}.

Closes the B-phase of the workbench 5-tab gap (F22): config is now
controller-exposed and loop changes are auditable + notify the right @list.
LUOSENGWA added a commit to LUOSENGWA/HiClaw that referenced this pull request Sep 11, 2026
LUOSENGWA added a commit to LUOSENGWA/HiClaw that referenced this pull request Sep 11, 2026
LUOSENGWA added a commit to LUOSENGWA/HiClaw that referenced this pull request Sep 11, 2026
…tack integration

- submit sync-failure: agentscope-ai#1183 _sync_failure_result contract (statePersisted)
  + agentscope-ai#1206 v2 withheld-notification clause; guard before notification
- reused-digest submit branch returns the reused completion notification
- _accept_task_result: resolve outstanding attention in place on task_meta
  (separate read-modify-write was clobbered by the terminal status write)
- test-taskflow.rb: fix latent heredoc escape bug (Result body \n), role
  env toggles for agentscope-ai#1183 runtime-identity-first role, mc fail-sync hook for
  per-file sync (mc cp), vocabulary PARTIAL/FAILED -> INTERRUPTED,
  changed-status resubmit -> digest-fence conflict expectation
- test-projectflow.rb: fixture runtime.yaml gains member role leader
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants