mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-06 17:38:35 +00:00
fix(loop): document subagent-followup persistence and guard empty content
- Add inline rationale for persisting before ContextBuilder and for passing current_message="" on subagent follow-ups (avoids double-projection after merge). - Skip persistence for empty subagent content (no-op messages should not pollute history). - Add regression test covering the empty-content guard. Made-with: Cursor
This commit is contained in:
parent
1c939e8a5f
commit
c8d834a504
@ -647,15 +647,23 @@ class AgentLoop:
|
|||||||
session, pending = self.auto_compact.prepare_session(session, key)
|
session, pending = self.auto_compact.prepare_session(session, key)
|
||||||
|
|
||||||
await self.consolidator.maybe_consolidate_by_tokens(session)
|
await self.consolidator.maybe_consolidate_by_tokens(session)
|
||||||
if msg.sender_id == "subagent" and self._persist_subagent_followup(session, msg):
|
# Persist subagent follow-ups into durable history BEFORE prompt
|
||||||
|
# assembly. ContextBuilder merges adjacent same-role messages for
|
||||||
|
# provider compatibility, which previously caused the follow-up to
|
||||||
|
# disappear from session.messages while still being visible to the
|
||||||
|
# LLM via the merged prompt. See _persist_subagent_followup.
|
||||||
|
is_subagent = msg.sender_id == "subagent"
|
||||||
|
if is_subagent and self._persist_subagent_followup(session, msg):
|
||||||
self.sessions.save(session)
|
self.sessions.save(session)
|
||||||
self._set_tool_context(channel, chat_id, msg.metadata.get("message_id"))
|
self._set_tool_context(channel, chat_id, msg.metadata.get("message_id"))
|
||||||
history = session.get_history(max_messages=0)
|
history = session.get_history(max_messages=0)
|
||||||
current_role = "assistant" if msg.sender_id == "subagent" else "user"
|
current_role = "assistant" if is_subagent else "user"
|
||||||
|
|
||||||
|
# Subagent content is already in `history` above; passing it again
|
||||||
|
# as current_message would double-project it into the prompt.
|
||||||
messages = self.context.build_messages(
|
messages = self.context.build_messages(
|
||||||
history=history,
|
history=history,
|
||||||
current_message="" if msg.sender_id == "subagent" else msg.content,
|
current_message="" if is_subagent else msg.content,
|
||||||
channel=channel,
|
channel=channel,
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
session_summary=pending,
|
session_summary=pending,
|
||||||
@ -875,7 +883,14 @@ class AgentLoop:
|
|||||||
session.updated_at = datetime.now()
|
session.updated_at = datetime.now()
|
||||||
|
|
||||||
def _persist_subagent_followup(self, session: Session, msg: InboundMessage) -> bool:
|
def _persist_subagent_followup(self, session: Session, msg: InboundMessage) -> bool:
|
||||||
"""Persist subagent follow-ups before prompt assembly so history stays durable."""
|
"""Persist subagent follow-ups before prompt assembly so history stays durable.
|
||||||
|
|
||||||
|
Returns True if a new entry was appended; False if the follow-up was
|
||||||
|
deduped (same ``subagent_task_id`` already in session) or carries no
|
||||||
|
content worth persisting.
|
||||||
|
"""
|
||||||
|
if not msg.content:
|
||||||
|
return False
|
||||||
task_id = msg.metadata.get("subagent_task_id") if isinstance(msg.metadata, dict) else None
|
task_id = msg.metadata.get("subagent_task_id") if isinstance(msg.metadata, dict) else None
|
||||||
if task_id and any(
|
if task_id and any(
|
||||||
m.get("injected_event") == "subagent_result" and m.get("subagent_task_id") == task_id
|
m.get("injected_event") == "subagent_result" and m.get("subagent_task_id") == task_id
|
||||||
|
|||||||
@ -561,3 +561,18 @@ def test_subagent_followup_dedupes_by_task_id() -> None:
|
|||||||
assert loop._persist_subagent_followup(session, msg) is True
|
assert loop._persist_subagent_followup(session, msg) is True
|
||||||
assert loop._persist_subagent_followup(session, msg) is False
|
assert loop._persist_subagent_followup(session, msg) is False
|
||||||
assert len(session.messages) == 1
|
assert len(session.messages) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_subagent_followup_skips_empty_content() -> None:
|
||||||
|
loop = _mk_loop()
|
||||||
|
session = Session(key="cli:empty")
|
||||||
|
msg = InboundMessage(
|
||||||
|
channel="system",
|
||||||
|
sender_id="subagent",
|
||||||
|
chat_id="cli:empty",
|
||||||
|
content="",
|
||||||
|
metadata={"subagent_task_id": "sub-empty"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert loop._persist_subagent_followup(session, msg) is False
|
||||||
|
assert session.messages == []
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user