Repository navigation
fix(agent): pass interceptor-set system message to model requests (issue #270) - #339
Merged
Merged
Conversation
yuluo-yx
approved these changes
Nov 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Describe what this PR does / why we need it
Fixes #270: the standalone ReAct agent's
invokeModelbuilt theChatRequestfromrequest.getMessages()only, silently droppingModelRequest.getSystemMessage()— so the four framework interceptors that inject system prompts through that field (Skills, Filesystem, SubAgent, TodoList, all using the null-then-appendTextmerge pattern) lost their injection entirely on the standalone ReAct path ("system message sent=false" in the issue's repro).Does this pull request fix one issue?
Fixes #270
Describe how you did it
A single composition point in
invokeModelbuilds the outgoing message list: when an interceptor set a system message, it becomes the single SYSTEM entry at the head of the request and replaces any SYSTEM messages already present in the conversation list (two SYSTEM entries would be transmitted verbatim by the Spring/LangChain4j adapters and concatenated by the Google ADK adapter, so the explicit field wins); all non-SYSTEM messages keep their order. With no interceptor-set system message the incoming list is passed through unchanged.The composition is a local copy for this call only — the run-level message list is never written back, so nothing accumulates across tool-call turns. Streaming and non-streaming share the same construction point, so both paths behave identically. No public API, event, or configuration changes;
ModelRequest's existing SYSTEM role validation already guarantees plain messages are never promoted.Describe how to verify it
ReActAgentTest(+6 cases, all offline via the existing ScriptedClient pattern): the issue's repro (interceptor sets "required-policy", the recorded request starts with that SYSTEM entry); an existing user-provided SYSTEM message is replaced with exactly one SYSTEM entry transmitted; without an interceptor system message the conversation is passed through byte-for-byte (user SYSTEM preserved); system message count stays exactly 1 across tool-call turns (no accumulation); the streaming path receives the same system message; non-SYSTEM messages keep their order around the injected entry.Red before the fix: system-message assertions fail (
sent.get(0)has no SYSTEM role). Green after:ReActAgentTest22/22, argi-agent-core full module 133/133 (with upstream reactor), checkstyle clean, studio compiles.Special notes for reviews
The replace rule (interceptor system message wins over conversation SYSTEM entries) follows the interceptors' own merge convention: they treat the
systemMessagefield as the single system-prompt carrier (null-then-appendText), so no second protocol is introduced. If a stricter merge (e.g. appending the interceptor prompt to the conversation's existing SYSTEM text) is preferred, the change is confined to the single composition method.