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:
Xubin Ren
2026-04-18 13:30:22 +08:00
committed by Xubin Ren
parent 1c939e8a5f
commit c8d834a504
2 changed files with 34 additions and 4 deletions
+19 -4
View File
@@ -647,15 +647,23 @@ class AgentLoop:
session, pending = self.auto_compact.prepare_session(session, key)
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._set_tool_context(channel, chat_id, msg.metadata.get("message_id"))
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(
history=history,
current_message="" if msg.sender_id == "subagent" else msg.content,
current_message="" if is_subagent else msg.content,
channel=channel,
chat_id=chat_id,
session_summary=pending,
@@ -875,7 +883,14 @@ class AgentLoop:
session.updated_at = datetime.now()
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
if task_id and any(
m.get("injected_event") == "subagent_result" and m.get("subagent_task_id") == task_id
+15
View File
@@ -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 False
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 == []