🐛 fix(delegate): strip kanban toolset from delegated children - #71
Conversation
A delegate_task child inherits the parent's enabled toolsets AND the parent's HERMES_KANBAN_TASK env. A child that kept the kanban toolset could call kanban_complete with no explicit task_id, which falls back to HERMES_KANBAN_TASK and resolves the PARENT's card — falsely flipping it to done with the child's summary while the real deliverable was never produced (false-done, control-surface-bleed). Add "kanban" to the composite toolsets stripped from every child in _strip_blocked_tools, alongside delegation and code_execution. The strip is unconditional — orchestrator children re-gain delegation by role but never regain kanban, so no delegated agent holds card-terminal authority bound to its parent's card.
cwest
left a comment
There was a problem hiding this comment.
The root cause holds end to end: a delegated child inherits the parent's HERMES_KANBAN_TASK, and kanban_complete with no explicit task_id falls back to that env var, so a child that kept the kanban toolset would resolve and retire the parent's card. Adding "kanban" to the composite strip set closes that off.
The scoping is right. All four toolset-resolution branches in _build_child_agent route through _strip_blocked_tools (explicit request, parent_enabled inheritance, derived parent set, and the DEFAULT_TOOLSETS fallback), and the orchestrator re-add only restores delegation, never kanban — so no child, leaf or orchestrator, keeps card-terminal authority. The kanban verbs live under exactly one toolset name, and nothing else includes it, so stripping the name is sufficient; there's no alias path that reintroduces them.
The two new tests earn their place: reverting the one-line change turns both red (2 failed, 9 passed), and they assert the contract on both the explicit-request and inherited paths rather than snapshotting a value. Ran the targeted file (11 passed), plus test_delegate.py and test_kanban_tools.py together (252 passed, 0 failed) against the head SHA in a throwaway clone. No changes needed.
A delegate_task child inherits the parent's enabled toolsets AND the parent's HERMES_KANBAN_TASK env. A child that kept the kanban toolset could call kanban_complete with no explicit task_id, which falls back to HERMES_KANBAN_TASK and resolves the PARENT's card — falsely flipping it to done with the child's summary while the real deliverable was never produced (false-done, control-surface-bleed). Add "kanban" to the composite toolsets stripped from every child in _strip_blocked_tools, alongside delegation and code_execution. The strip is unconditional — orchestrator children re-gain delegation by role but never regain kanban, so no delegated agent holds card-terminal authority bound to its parent's card. (cherry picked from commit e0e78a0)
Why
A worker that spawns a
delegate_tasksubagent can have that subagent inherit the kanban toolset. Because a delegated child also inherits the parent process'sHERMES_KANBAN_TASKenv, the child's end-of-runkanban_complete(called with no explicittask_id) falls back to that env var and resolves the parent's card — falsely flipping it todonewith the child's summary while the real deliverable was never produced (false-done, control-surface-bleed).What
Add
"kanban"to the composite toolsets stripped from every delegated child in_strip_blocked_tools(tools/delegate_tool.py), alongsidedelegationandcode_execution. Children get no card-terminal authority:delegationby role after the strip, but there is nokanbanre-add, so no delegated agent (leaf or orchestrator) ever holds the kanban terminal verbs bound to its parent's card.toolsets=["kanban", ...]and the no-toolsets-requested inheritance of the parent's full set both route through_strip_blocked_tools.How verified
_build_child_agent(parent set →_strip_blocked_tools) +kanban_complete's_default_task_idfallback toHERMES_KANBAN_TASK.tests/tools/test_delegate_toolset_scope.py: a child never receiveskanbanon the explicit-request path or the inherited-set path. RED before the fix, GREEN after.test_delegate_toolset_scope.py,test_delegate.py(except one pre-existing timing-sensitive heartbeat flake that passes 3/3 in isolation),test_kanban_tools.py.Verified STILL BROKEN on
origin/cwest/integrationHEAD a7ed14f before the fix.