fix(kanban): author a card attempt's turns as the dispatcher on the owner gateway

Kanban workers no longer run `chat -q`: the spawned process is
hermes_cli.kanban_worker_client, which submits kanban.run to the profile's
owner gateway, and the owner runs the attempt in _run_task_turns. Those
three run_conversation calls (first turn, in-place recovery, goal turns)
passed no turn_author, so memory providers still filed the dispatcher's
prompt and the worker's notes under the operator (#137406). They now pass
a fixed bot author. Its id is kanban:dispatcher rather than bot:kanban,
which a Bot Mode profile named kanban would also produce.
This commit is contained in:
kshitijk4poor authored and kshitij committed 2026-10-26 13:04:58 +05:30
1 parent eb1d54ad76
commit 08f954e505
3 files changed
+17 -6

No files matched your search

+11 -3
View File
@@ -348,19 +348,26 @@ def worker_exit_code(path, params):
return worker_result(path, params).get('exit_code', 1)
# Who wrote a card attempt's prompts: the dispatcher, never the operator, so memory providers do not
# file "work kanban task t_..." and the worker's progress notes as the human's own turns (#137406).
# Not a ``bot:<profile>`` id: a Bot Mode profile named ``kanban`` would share its memory key.
KANBAN_TURN_AUTHOR = {'id': 'kanban:dispatcher', 'name': 'Kanban dispatcher', 'is_bot': True}
def _run_task_turns(agent, frame, history, context):
from agent.skill_commands import build_preloaded_skills_prompt
skills, loaded, missing = build_preloaded_skills_prompt(context['skills'], task_id=agent.session_id)
if missing and not loaded:
raise ValueError('kanban_skills_unavailable')
prompt = '\n\n'.join(p for p in (skills, context['context'], frame['text']) if p)
result = agent.run_conversation(prompt, conversation_history=history)
result = agent.run_conversation(prompt, conversation_history=history, turn_author=KANBAN_TURN_AUTHOR)
def recover(nudge):
nonlocal result
prior = result.get('messages') if isinstance(result, dict) else None
result = agent.run_conversation(nudge, conversation_history=prior or
agent._session_db.get_messages_as_conversation(agent.session_id))
agent._session_db.get_messages_as_conversation(agent.session_id),
turn_author=KANBAN_TURN_AUTHOR)
# Same in-place retry as the one-shot CLI worker (agent/kanban_turn_recovery.py), but the
# carrier is this frame's bound claim, never the owner process's environment.
from agent.kanban_turn_recovery import recover_failed_kanban_turns
@@ -382,7 +389,8 @@ def _run_task_turns(agent, frame, history, context):
kb.block_task(conn, context['task_id'], reason=reason, expected_run_id=context['run_id'])
def turn(prompt):
nonlocal result
result = agent.run_conversation(prompt, conversation_history=agent._session_db.get_messages_as_conversation(agent.session_id))
result = agent.run_conversation(prompt, conversation_history=agent._session_db.get_messages_as_conversation(agent.session_id),
turn_author=KANBAN_TURN_AUTHOR)
return result.get('final_response', '')
run_kanban_goal_loop(task_id=context['task_id'], goal_text=context['goal_text'], run_turn=turn,
task_status_fn=status, block_fn=block, max_turns=context['goal_max_turns'] or DEFAULT_MAX_TURNS,
+1 -1
View File
@@ -493,7 +493,7 @@ def owner_worker(tmp_path, clear_kanban_env, monkeypatch):
def get_messages_as_conversation(self, _sid):
return [{"role": "user", "content": "from-store"}]
def run_conversation(self, message, conversation_history=None):
def run_conversation(self, message, conversation_history=None, **_kwargs):
entered.append(message)
result = script() if callable(script) else script[seen["n"]]
seen["n"] += 1
@@ -15,14 +15,15 @@ class _Agent:
session_id = "s1"
def __init__(self, results):
self.results, self.calls = list(results), []
self.results, self.calls, self.authors = list(results), [], []
self._session_db = self
def get_messages_as_conversation(self, _sid):
return [{"role": "user", "content": "from-store"}]
def run_conversation(self, message, conversation_history=None):
def run_conversation(self, message, conversation_history=None, turn_author=None):
self.calls.append((message, conversation_history))
self.authors.append(turn_author)
return self.results.pop(0)
@@ -63,6 +64,8 @@ def test_failed_turn_retries_in_place_from_frame_carrier(tmp_path, monkeypatch):
assert context["task_id"] in nudge and "t_foreign" not in nudge
assert history == FAILED["messages"] # same session context, not a cold restart
assert session_kanban.worker_exit_code(path, context) == 0
# Both the first turn and the in-place retry are the dispatcher's, never the operator's (#137406).
assert agent.authors == [{"id": "kanban:dispatcher", "name": "Kanban dispatcher", "is_bot": True}] * 2
def test_unprovable_claim_denies_retry_and_goal_continuation(tmp_path, monkeypatch):