mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-11-06 05:17:15 +00:00
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:
1 parent
eb1d54ad76
commit
08f954e505
3 files changed
+17
-6
No files matched your search
@@ -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,
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in new issue
Block a user