refactor(agent): defer transcript assembly to runner (#5608)

* refactor(agent): defer transcript assembly to runner

Keep persisted history and the fresh turn as explicit inputs until the Runner assembles the provider transcript. Preserve ContextBuilder and direct AgentRunner compatibility while making the save boundary structural.

Refs NAN-81.

* fix(providers): preserve mixed adjacent user content
This commit is contained in:
chengyongru
2026-08-31 00:06:15 +08:00
committed by GitHub
parent d019658501
commit 6cd7063682
19 changed files with 355 additions and 113 deletions
+24 -1
View File
@@ -4,7 +4,7 @@ from pathlib import Path
import pytest
from nanobot.agent.context import ContextBuilder
from nanobot.agent.context import ContextBuilder, TranscriptInput
from nanobot.runtime_context import RuntimeContextBlock
# ---------------------------------------------------------------------------
@@ -403,6 +403,15 @@ class TestBuildMessages:
assert "user-only runtime context" not in messages[-1]["content"]
assert "_meta" not in messages[-1]
def test_compatibility_builder_merges_system_role_without_history(self, tmp_path):
builder = _builder(tmp_path)
messages = builder.build_messages([], "system event", current_role="system")
assert len(messages) == 1
assert messages[0]["role"] == "system"
assert str(messages[0]["content"]).endswith("system event")
def test_explicit_skill_reference_loads_full_instructions_for_this_turn(self, tmp_path):
skill_dir = tmp_path / "skills" / "review"
skill_dir.mkdir(parents=True)
@@ -472,6 +481,20 @@ class TestBuildMessages:
assert "previous user message" in str(messages[1]["content"])
assert "new message" in str(messages[1]["content"])
def test_structured_transcript_preserves_fresh_turn_boundary(self, tmp_path):
builder = _builder(tmp_path)
transcript = TranscriptInput(
history=[{"role": "user", "content": "previous user message"}],
current_message="new message",
)
messages = builder.build_transcript(transcript)
assert [message["role"] for message in messages] == ["system", "user", "user"]
assert messages[-2]["content"] == "previous user message"
assert messages[-1]["content"] == "new message"
assert transcript.message_count == 3
def test_current_message_can_be_built_without_history_merge(self, tmp_path):
builder = _builder(tmp_path)
current = builder.build_current_message(