Skip to content

Prompt refactor for planner agent (ml-commons)#4822

Open
yyfamazon wants to merge 4 commits into
opensearch-project:mainfrom
yyfamazon:main
Open

Prompt refactor for planner agent (ml-commons)#4822
yyfamazon wants to merge 4 commits into
opensearch-project:mainfrom
yyfamazon:main

Conversation

@yyfamazon
Copy link
Copy Markdown

@yyfamazon yyfamazon commented May 13, 2026

Description

Prompt refactor for planner agent (ml-commons)

Currently, there are two sets of system prompt, one in investigation and one in ml-commons. The change is ONLY keep the common part of these two sets of system prompt into ml-commons, and keep the investigation-only system prompt in investigation, then pass investigation-only system prompt to ml-common to combine with/override/expend the common system prompt.

Another change is to move the dynamic field(timeScope, currentTime, initial goal, new question) from system prompt to context to make the system prompt static.

This PR should work with opensearch-project/dashboards-investigation#309

investigation system prompt before and after diff:
investigation system prompt diffs

re-investigation system prompt before and after diff:

re-investigation system prompt diffs

this pr should work with opensearch-project/dashboards-investigation#372

Related Issues

Resolves #[Issue number to be closed when this PR is merged]

Check List

  • New functionality includes testing.
  • New functionality has been documented.
  • API changes companion pull request created.
  • Commits are signed per the DCO using --signoff.
  • Public documentation issue/PR created.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 20, 2026

PR Code Analyzer ❗

AI-powered 'Code-Diff-Analyzer' found issues on commit 2ef150a.

PathLineSeverityDescription
ml-algorithms/src/main/java/org/opensearch/ml/engine/algorithms/agent/MLPlanExecuteAndReflectAgentRunner.java224mediumUser-supplied API parameters RESULT_EXPAND_OVERRIDE and IMPORTANT_RULES_EXPAND are read from the params map and concatenated directly into the LLM system prompt without any sanitization, length limits, or validation. An API caller who can set these fields can inject arbitrary instructions into the agent's system prompt, potentially overriding safety rules or redirecting agent behavior (prompt injection attack surface).
ml-algorithms/src/main/java/org/opensearch/ml/engine/algorithms/agent/MLPlanExecuteAndReflectAgentRunner.java213lowThe SYSTEM_PROMPT_FIELD parameter allows a caller to completely replace the entire planner system prompt with arbitrary content. Combined with the new PLANNER_SYSTEM_PROMPT_PREFIX override, callers have broad control over the LLM's core instructions. While likely an intentional extensibility feature, the absence of any allowlist, role check, or content policy on what can be substituted widens the prompt-injection attack surface compared to the prior implementation.

The table above displays the top 10 most important findings.

Total: 2 | Critical: 0 | High: 0 | Medium: 1 | Low: 1


Pull Requests Author(s): Please update your Pull Request according to the report above.

Repository Maintainer(s): You can bypass diff analyzer by adding label skip-diff-analyzer after reviewing the changes carefully, then re-run failed actions. To re-enable the analyzer, remove the label, then re-run all actions.


⚠️ Note: The Code-Diff-Analyzer helps protect against potentially harmful code patterns. Please ensure you have thoroughly reviewed the changes beforehand.

Thanks.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 20, 2026

PR Reviewer Guide 🔍

(Review updated until commit 2ef150a)

Here are some key observations to aid the review process:

🧪 PR contains tests
🔒 No security concerns identified
✅ No TODO sections
🔀 No multiple PR themes
⚡ Recommended focus areas for review

Possible Issue

The conditional logic at lines 227-237 requires all three parameters (plannerSystemPromptPrefix, resultExpandOverride, importantRulesExpand) to be non-null and non-empty to append/mutate the system prompt. If only one or two of these parameters are provided, the entire block is skipped, and the system prompt is not updated as intended. This could lead to unexpected behavior when partial customization is attempted.

if (plannerSystemPromptPrefix != null && !plannerSystemPromptPrefix.isEmpty()
        && resultExpandOverride != null && !resultExpandOverride.isEmpty()
        && importantRulesExpand != null && !importantRulesExpand.isEmpty()) {
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.append(plannerSystemPromptPrefix).append("\n");
    stringBuilder.append(getCorePlanningInstructions()).append("\n");
    stringBuilder.append(getPlanExecuteReflectResponseFormat(resultExpandOverride, importantRulesExpand));
    String finalPlannerPrompt = stringBuilder.toString();

    params.put(SYSTEM_PROMPT_FIELD, finalPlannerPrompt);
}
Logic Issue

At lines 216-220, if clientBusinessPrompt is non-null and non-empty, it replaces the entire system prompt. However, the subsequent block (lines 227-237) attempts to mutate the system prompt again if certain conditions are met. This means the client-provided prompt could be overwritten, which is likely unintended. The logic should clarify whether client prompts take precedence or can be further modified.

if (clientBusinessPrompt != null && !clientBusinessPrompt.isEmpty()) { // Replace the whole prompt if client specifies system prompt template
    params.put(SYSTEM_PROMPT_FIELD, clientBusinessPrompt);
} else { // Using the default system prompt template if client does not specify.
    params.put(SYSTEM_PROMPT_FIELD, DEFAULT_PLANNER_SYSTEM_PROMPT);
}

String resultExpandOverride = params.get(RESULT_EXPAND_OVERRIDE);
String importantRulesExpand = params.get(IMPORTANT_RULES_EXPAND);
String plannerSystemPromptPrefix = params.getOrDefault(PLANNER_SYSTEM_PROMPT_PREFIX, DEFAULT_PLANNER_SYSTEM_PROMPT_PREFIX);

// Append / mutate the system prompt through these 3 parameters: plannerSystemPromptPrefix, resultExpandOverride and importantRulesExpand
if (plannerSystemPromptPrefix != null && !plannerSystemPromptPrefix.isEmpty()
        && resultExpandOverride != null && !resultExpandOverride.isEmpty()
        && importantRulesExpand != null && !importantRulesExpand.isEmpty()) {
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.append(plannerSystemPromptPrefix).append("\n");
    stringBuilder.append(getCorePlanningInstructions()).append("\n");
    stringBuilder.append(getPlanExecuteReflectResponseFormat(resultExpandOverride, importantRulesExpand));
    String finalPlannerPrompt = stringBuilder.toString();

    params.put(SYSTEM_PROMPT_FIELD, finalPlannerPrompt);
}

@yyfamazon yyfamazon requested a deployment to ml-commons-cicd-env-require-approval May 20, 2026 06:20 — with GitHub Actions Waiting
@yyfamazon yyfamazon requested a deployment to ml-commons-cicd-env-require-approval May 20, 2026 06:20 — with GitHub Actions Waiting
@yyfamazon yyfamazon requested a deployment to ml-commons-cicd-env-require-approval May 20, 2026 06:20 — with GitHub Actions Waiting
@yyfamazon yyfamazon requested a deployment to ml-commons-cicd-env-require-approval May 20, 2026 06:20 — with GitHub Actions Waiting
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 20, 2026

PR Code Suggestions ✨

Latest suggestions up to 2ef150a

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
General
Prevent conflicting prompt configuration

The system prompt is set twice: first based on clientBusinessPrompt, then
potentially overwritten by the custom prompt builder. If a client provides both
SYSTEM_PROMPT_FIELD and the three customization parameters, the custom prompt will
silently override the client's business prompt without warning.

ml-algorithms/src/main/java/org/opensearch/ml/engine/algorithms/agent/MLPlanExecuteAndReflectAgentRunner.java [214-237]

-if (clientBusinessPrompt != null && !clientBusinessPrompt.isEmpty()) {
+String resultExpandOverride = params.get(RESULT_EXPAND_OVERRIDE);
+String importantRulesExpand = params.get(IMPORTANT_RULES_EXPAND);
+String plannerSystemPromptPrefix = params.getOrDefault(PLANNER_SYSTEM_PROMPT_PREFIX, DEFAULT_PLANNER_SYSTEM_PROMPT_PREFIX);
+
+boolean hasCustomizationParams = (plannerSystemPromptPrefix != null && !plannerSystemPromptPrefix.isEmpty())
+        || (resultExpandOverride != null && !resultExpandOverride.isEmpty())
+        || (importantRulesExpand != null && !importantRulesExpand.isEmpty());
+
+if (hasCustomizationParams) {
+    StringBuilder stringBuilder = new StringBuilder();
+    stringBuilder.append(plannerSystemPromptPrefix).append("\n");
+    stringBuilder.append(getCorePlanningInstructions()).append("\n");
+    stringBuilder.append(getPlanExecuteReflectResponseFormat(resultExpandOverride, importantRulesExpand));
+    params.put(SYSTEM_PROMPT_FIELD, stringBuilder.toString());
+} else if (clientBusinessPrompt != null && !clientBusinessPrompt.isEmpty()) {
     params.put(SYSTEM_PROMPT_FIELD, clientBusinessPrompt);
 } else {
     params.put(SYSTEM_PROMPT_FIELD, DEFAULT_PLANNER_SYSTEM_PROMPT);
 }
 
-String resultExpandOverride = params.get(RESULT_EXPAND_OVERRIDE);
-String importantRulesExpand = params.get(IMPORTANT_RULES_EXPAND);
-String plannerSystemPromptPrefix = params.getOrDefault(PLANNER_SYSTEM_PROMPT_PREFIX, DEFAULT_PLANNER_SYSTEM_PROMPT_PREFIX);
-
-if (plannerSystemPromptPrefix != null && !plannerSystemPromptPrefix.isEmpty()
-        && resultExpandOverride != null && !resultExpandOverride.isEmpty()
-        && importantRulesExpand != null && !importantRulesExpand.isEmpty()) {
-    ...
-    params.put(SYSTEM_PROMPT_FIELD, finalPlannerPrompt);
-}
-
Suggestion importance[1-10]: 8

__

Why: The suggestion identifies a critical logic issue where SYSTEM_PROMPT_FIELD can be set twice, with the second assignment potentially overwriting the first. This could lead to unexpected behavior when clients provide both clientBusinessPrompt and customization parameters. The proposed fix establishes clear precedence rules to avoid silent overwrites.

Medium
Allow partial parameter customization

The conditional logic requires all three parameters to be non-null and non-empty
before constructing the custom prompt. This means if only one or two parameters are
provided, they will be silently ignored and the default prompt will be used instead.
Consider allowing partial customization by checking each parameter individually.

ml-algorithms/src/main/java/org/opensearch/ml/engine/algorithms/agent/MLPlanExecuteAndReflectAgentRunner.java [227-237]

-if (plannerSystemPromptPrefix != null && !plannerSystemPromptPrefix.isEmpty()
-        && resultExpandOverride != null && !resultExpandOverride.isEmpty()
-        && importantRulesExpand != null && !importantRulesExpand.isEmpty()) {
+if ((plannerSystemPromptPrefix != null && !plannerSystemPromptPrefix.isEmpty())
+        || (resultExpandOverride != null && !resultExpandOverride.isEmpty())
+        || (importantRulesExpand != null && !importantRulesExpand.isEmpty())) {
     StringBuilder stringBuilder = new StringBuilder();
-    stringBuilder.append(plannerSystemPromptPrefix).append("\n");
+    stringBuilder.append(plannerSystemPromptPrefix != null && !plannerSystemPromptPrefix.isEmpty() 
+        ? plannerSystemPromptPrefix : DEFAULT_PLANNER_SYSTEM_PROMPT_PREFIX).append("\n");
     stringBuilder.append(getCorePlanningInstructions()).append("\n");
     stringBuilder.append(getPlanExecuteReflectResponseFormat(resultExpandOverride, importantRulesExpand));
     String finalPlannerPrompt = stringBuilder.toString();
 
     params.put(SYSTEM_PROMPT_FIELD, finalPlannerPrompt);
 }
Suggestion importance[1-10]: 7

__

Why: The current logic requires all three parameters (plannerSystemPromptPrefix, resultExpandOverride, importantRulesExpand) to be non-null and non-empty, which prevents partial customization. The suggestion correctly identifies this limitation and proposes allowing any combination of parameters, which would improve flexibility for users.

Medium

Previous suggestions

Suggestions up to commit 7c27359
CategorySuggestion                                                                                                                                    Impact
General
Prevent unintended prompt override

The SYSTEM_PROMPT_FIELD is set twice: first based on clientBusinessPrompt, then
potentially overwritten by the custom prompt builder. If a client provides both
clientBusinessPrompt and the three expansion parameters, the client's prompt will be
silently replaced. This creates unexpected behavior where client-specified prompts
are ignored.

ml-algorithms/src/main/java/org/opensearch/ml/engine/algorithms/agent/MLPlanExecuteAndReflectAgentRunner.java [214-237]

+String resultExpandOverride = params.get(RESULT_EXPAND_OVERRIDE);
+String importantRulesExpand = params.get(IMPORTANT_RULES_EXPAND);
+String plannerSystemPromptPrefix = params.getOrDefault(PLANNER_SYSTEM_PROMPT_PREFIX, DEFAULT_PLANNER_SYSTEM_PROMPT_PREFIX);
+
 if (clientBusinessPrompt != null && !clientBusinessPrompt.isEmpty()) {
     params.put(SYSTEM_PROMPT_FIELD, clientBusinessPrompt);
+} else if (plannerSystemPromptPrefix != null && !plannerSystemPromptPrefix.isEmpty()
+        && resultExpandOverride != null && !resultExpandOverride.isEmpty()
+        && importantRulesExpand != null && !importantRulesExpand.isEmpty()) {
+    ...
+    params.put(SYSTEM_PROMPT_FIELD, finalPlannerPrompt);
 } else {
     params.put(SYSTEM_PROMPT_FIELD, DEFAULT_PLANNER_SYSTEM_PROMPT);
 }
 
-String resultExpandOverride = params.get(RESULT_EXPAND_OVERRIDE);
-String importantRulesExpand = params.get(IMPORTANT_RULES_EXPAND);
-String plannerSystemPromptPrefix = params.getOrDefault(PLANNER_SYSTEM_PROMPT_PREFIX, DEFAULT_PLANNER_SYSTEM_PROMPT_PREFIX);
-
-if (plannerSystemPromptPrefix != null && !plannerSystemPromptPrefix.isEmpty()
-        && resultExpandOverride != null && !resultExpandOverride.isEmpty()
-        && importantRulesExpand != null && !importantRulesExpand.isEmpty()) {
-    ...
-    params.put(SYSTEM_PROMPT_FIELD, finalPlannerPrompt);
-}
-
Suggestion importance[1-10]: 9

__

Why: This identifies a critical logic flaw where SYSTEM_PROMPT_FIELD is set twice, potentially overwriting a client-specified clientBusinessPrompt with the custom-built prompt. The improved code uses else if to ensure mutual exclusivity, preventing unexpected behavior where client prompts are silently ignored. This is a significant correctness issue.

High
Allow partial parameter customization

The conditional logic requires all three parameters to be non-null and non-empty
before building the custom prompt. This means if only one or two parameters are
provided, they will be silently ignored and the default prompt will be used instead.
Consider allowing partial customization by checking each parameter independently.

ml-algorithms/src/main/java/org/opensearch/ml/engine/algorithms/agent/MLPlanExecuteAndReflectAgentRunner.java [227-237]

-if (plannerSystemPromptPrefix != null && !plannerSystemPromptPrefix.isEmpty()
-        && resultExpandOverride != null && !resultExpandOverride.isEmpty()
-        && importantRulesExpand != null && !importantRulesExpand.isEmpty()) {
+if ((plannerSystemPromptPrefix != null && !plannerSystemPromptPrefix.isEmpty())
+        || (resultExpandOverride != null && !resultExpandOverride.isEmpty())
+        || (importantRulesExpand != null && !importantRulesExpand.isEmpty())) {
     StringBuilder stringBuilder = new StringBuilder();
-    stringBuilder.append(plannerSystemPromptPrefix).append("\n");
+    stringBuilder.append(plannerSystemPromptPrefix != null && !plannerSystemPromptPrefix.isEmpty() 
+        ? plannerSystemPromptPrefix : DEFAULT_PLANNER_SYSTEM_PROMPT_PREFIX).append("\n");
     stringBuilder.append(getCorePlanningInstructions()).append("\n");
     stringBuilder.append(getPlanExecuteReflectResponseFormat(resultExpandOverride, importantRulesExpand));
     String finalPlannerPrompt = stringBuilder.toString();
 
     params.put(SYSTEM_PROMPT_FIELD, finalPlannerPrompt);
 }
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that the current logic requires all three parameters (plannerSystemPromptPrefix, resultExpandOverride, importantRulesExpand) to be non-null and non-empty, which prevents partial customization. The improved code allows any combination of parameters to be used, falling back to defaults when needed. This enhances flexibility and user experience.

Medium
Suggestions up to commit 8c98473
CategorySuggestion                                                                                                                                    Impact
Possible issue
Prevent system prompt overwriting

The SYSTEM_PROMPT_FIELD is set twice in the same method, which can lead to
unexpected behavior. If the second conditional block executes, it will overwrite the
value set in the first block. This creates ambiguity about which prompt
configuration takes precedence and may cause the clientBusinessPrompt to be silently
ignored.

ml-algorithms/src/main/java/org/opensearch/ml/engine/algorithms/agent/MLPlanExecuteAndReflectAgentRunner.java [216-237]

 if (clientBusinessPrompt != null && !clientBusinessPrompt.isEmpty()) {
     params.put(SYSTEM_PROMPT_FIELD, clientBusinessPrompt);
+} else if (plannerSystemPromptPrefix != null && !plannerSystemPromptPrefix.isEmpty()
+        && resultExpandOverride != null && !resultExpandOverride.isEmpty()
+        && importantRulesExpand != null && !importantRulesExpand.isEmpty()) {
+    StringBuilder stringBuilder = new StringBuilder();
+    stringBuilder.append(plannerSystemPromptPrefix).append("\n");
+    stringBuilder.append(getCorePlanningInstructions()).append("\n");
+    stringBuilder.append(getPlanExecuteReflectResponseFormat(resultExpandOverride, importantRulesExpand));
+    params.put(SYSTEM_PROMPT_FIELD, stringBuilder.toString());
 } else {
     params.put(SYSTEM_PROMPT_FIELD, DEFAULT_PLANNER_SYSTEM_PROMPT);
 }
-...
-if (plannerSystemPromptPrefix != null && !plannerSystemPromptPrefix.isEmpty()
-        && resultExpandOverride != null && !resultExpandOverride.isEmpty()
-        && importantRulesExpand != null && !importantRulesExpand.isEmpty()) {
-    ...
-    params.put(SYSTEM_PROMPT_FIELD, finalPlannerPrompt);
-}
Suggestion importance[1-10]: 8

__

Why: This suggestion identifies a critical logic flaw where SYSTEM_PROMPT_FIELD is set twice in the same method. The second assignment (lines 227-237) can overwrite the clientBusinessPrompt set earlier (lines 216-220), leading to unexpected behavior. The improved code uses else if to establish clear precedence, preventing silent overwrites and making the logic more predictable.

Medium
General
Allow partial prompt customization

The conditional logic requires all three parameters to be non-null and non-empty
before constructing the custom prompt. This means if any single parameter is
missing, the entire customization is skipped. Consider allowing partial
customization by checking each parameter individually or using default values for
missing ones.

ml-algorithms/src/main/java/org/opensearch/ml/engine/algorithms/agent/MLPlanExecuteAndReflectAgentRunner.java [227-237]

-if (plannerSystemPromptPrefix != null && !plannerSystemPromptPrefix.isEmpty()
-        && resultExpandOverride != null && !resultExpandOverride.isEmpty()
-        && importantRulesExpand != null && !importantRulesExpand.isEmpty()) {
+if ((plannerSystemPromptPrefix != null && !plannerSystemPromptPrefix.isEmpty())
+        || (resultExpandOverride != null && !resultExpandOverride.isEmpty())
+        || (importantRulesExpand != null && !importantRulesExpand.isEmpty())) {
     StringBuilder stringBuilder = new StringBuilder();
-    stringBuilder.append(plannerSystemPromptPrefix).append("\n");
+    String prefix = (plannerSystemPromptPrefix != null && !plannerSystemPromptPrefix.isEmpty()) 
+        ? plannerSystemPromptPrefix : DEFAULT_PLANNER_SYSTEM_PROMPT_PREFIX;
+    stringBuilder.append(prefix).append("\n");
     stringBuilder.append(getCorePlanningInstructions()).append("\n");
     stringBuilder.append(getPlanExecuteReflectResponseFormat(resultExpandOverride, importantRulesExpand));
     String finalPlannerPrompt = stringBuilder.toString();
 
     params.put(SYSTEM_PROMPT_FIELD, finalPlannerPrompt);
 }
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly identifies that the current logic requires all three parameters (plannerSystemPromptPrefix, resultExpandOverride, importantRulesExpand) to be non-null and non-empty. However, the improved code changes the logic to use OR conditions, which may not align with the intended design where all three parameters should be provided together for custom prompt construction. The suggestion is valid but assumes a different design intent.

Low
Suggestions up to commit 8c98473
CategorySuggestion                                                                                                                                    Impact
General
Prevent conflicting prompt configuration

The system prompt is set twice: first based on clientBusinessPrompt, then
potentially overwritten by the custom prompt builder. If a client provides both
SYSTEM_PROMPT_FIELD and the three expansion parameters, the client's prompt will be
silently replaced. This creates ambiguous behavior and may lead to unexpected
results.

ml-algorithms/src/main/java/org/opensearch/ml/engine/algorithms/agent/MLPlanExecuteAndReflectAgentRunner.java [214-237]

-if (clientBusinessPrompt != null && !clientBusinessPrompt.isEmpty()) {
+String resultExpandOverride = params.get(RESULT_EXPAND_OVERRIDE);
+String importantRulesExpand = params.get(IMPORTANT_RULES_EXPAND);
+String plannerSystemPromptPrefix = params.getOrDefault(PLANNER_SYSTEM_PROMPT_PREFIX, DEFAULT_PLANNER_SYSTEM_PROMPT_PREFIX);
+
+if ((plannerSystemPromptPrefix != null && !plannerSystemPromptPrefix.isEmpty())
+        || (resultExpandOverride != null && !resultExpandOverride.isEmpty())
+        || (importantRulesExpand != null && !importantRulesExpand.isEmpty())) {
+    StringBuilder stringBuilder = new StringBuilder();
+    stringBuilder.append(plannerSystemPromptPrefix).append("\n");
+    stringBuilder.append(getCorePlanningInstructions()).append("\n");
+    stringBuilder.append(getPlanExecuteReflectResponseFormat(resultExpandOverride, importantRulesExpand));
+    params.put(SYSTEM_PROMPT_FIELD, stringBuilder.toString());
+} else if (clientBusinessPrompt != null && !clientBusinessPrompt.isEmpty()) {
     params.put(SYSTEM_PROMPT_FIELD, clientBusinessPrompt);
 } else {
     params.put(SYSTEM_PROMPT_FIELD, DEFAULT_PLANNER_SYSTEM_PROMPT);
 }
 
-String resultExpandOverride = params.get(RESULT_EXPAND_OVERRIDE);
-String importantRulesExpand = params.get(IMPORTANT_RULES_EXPAND);
-String plannerSystemPromptPrefix = params.getOrDefault(PLANNER_SYSTEM_PROMPT_PREFIX, DEFAULT_PLANNER_SYSTEM_PROMPT_PREFIX);
-
-if (plannerSystemPromptPrefix != null && !plannerSystemPromptPrefix.isEmpty()
-        && resultExpandOverride != null && !resultExpandOverride.isEmpty()
-        && importantRulesExpand != null && !importantRulesExpand.isEmpty()) {
-    ...
-    params.put(SYSTEM_PROMPT_FIELD, finalPlannerPrompt);
-}
-
Suggestion importance[1-10]: 8

__

Why: The suggestion identifies a critical logic flaw where SYSTEM_PROMPT_FIELD is set twice, potentially causing the client's custom prompt to be silently overwritten by the expansion parameters. The proposed fix establishes a clear precedence order (expansion parameters > client prompt > default), preventing ambiguous behavior and ensuring predictable results.

Medium
Allow partial parameter customization

The conditional logic requires all three parameters to be non-null and non-empty
before constructing the custom prompt. This means if only one or two parameters are
provided, they will be silently ignored and the default prompt will be used instead.
Consider allowing partial customization by checking each parameter independently.

ml-algorithms/src/main/java/org/opensearch/ml/engine/algorithms/agent/MLPlanExecuteAndReflectAgentRunner.java [227-237]

-if (plannerSystemPromptPrefix != null && !plannerSystemPromptPrefix.isEmpty()
-        && resultExpandOverride != null && !resultExpandOverride.isEmpty()
-        && importantRulesExpand != null && !importantRulesExpand.isEmpty()) {
+if ((plannerSystemPromptPrefix != null && !plannerSystemPromptPrefix.isEmpty())
+        || (resultExpandOverride != null && !resultExpandOverride.isEmpty())
+        || (importantRulesExpand != null && !importantRulesExpand.isEmpty())) {
     StringBuilder stringBuilder = new StringBuilder();
-    stringBuilder.append(plannerSystemPromptPrefix).append("\n");
+    stringBuilder.append(plannerSystemPromptPrefix != null && !plannerSystemPromptPrefix.isEmpty() 
+        ? plannerSystemPromptPrefix : DEFAULT_PLANNER_SYSTEM_PROMPT_PREFIX).append("\n");
     stringBuilder.append(getCorePlanningInstructions()).append("\n");
     stringBuilder.append(getPlanExecuteReflectResponseFormat(resultExpandOverride, importantRulesExpand));
     String finalPlannerPrompt = stringBuilder.toString();
 
     params.put(SYSTEM_PROMPT_FIELD, finalPlannerPrompt);
 }
Suggestion importance[1-10]: 7

__

Why: The current logic requires all three parameters (plannerSystemPromptPrefix, resultExpandOverride, importantRulesExpand) to be non-null and non-empty, which prevents partial customization. The suggestion correctly identifies this limitation and proposes allowing any combination of parameters, improving flexibility for users who want to customize only some aspects of the prompt.

Medium
Suggestions up to commit 6873396
CategorySuggestion                                                                                                                                    Impact
General
Simplify redundant null check condition

The condition clientBusinessPrompt == null in the second branch is redundant since
the first branch already handles non-null cases. Additionally, the logic doesn't
handle the case where clientBusinessPrompt is null but resultExpandOverride is also
null/empty, which would fall through to the default. Consider simplifying the
conditional logic.

ml-algorithms/src/main/java/org/opensearch/ml/engine/algorithms/agent/MLPlanExecuteAndReflectAgentRunner.java [219-232]

 StringBuilder stringBuilder = new StringBuilder();
 stringBuilder.append(plannerSystemPromptPrefix).append("\n");
 stringBuilder.append(getCorePlanningInstructions()).append("\n");
 stringBuilder.append(getPlanExecuteReflectResponseFormat(resultExpandOverride, importantRulesExpand));
 String finalPlannerPrompt = stringBuilder.toString();
 
-
 if (clientBusinessPrompt != null && !clientBusinessPrompt.isEmpty()) {
     params.put(SYSTEM_PROMPT_FIELD, clientBusinessPrompt);
-} else if (clientBusinessPrompt == null && resultExpandOverride != null && !resultExpandOverride.isEmpty()) {
+} else if (resultExpandOverride != null && !resultExpandOverride.isEmpty()) {
     params.put(SYSTEM_PROMPT_FIELD, finalPlannerPrompt);
 } else {
     params.put(SYSTEM_PROMPT_FIELD, DEFAULT_PLANNER_SYSTEM_PROMPT);
 }
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that the clientBusinessPrompt == null check in the second branch is redundant since the first branch already handles non-null cases. Removing this redundant check improves code clarity and maintainability.

Medium
Avoid unnecessary string construction overhead

The finalPlannerPrompt variable is constructed but only used in one conditional
branch. If neither the second nor third branch is taken, this string construction is
wasted. Consider moving the StringBuilder construction inside the conditional branch
where it's actually needed to improve performance.

ml-algorithms/src/main/java/org/opensearch/ml/engine/algorithms/agent/MLPlanExecuteAndReflectAgentRunner.java [219-232]

-String finalPlannerPrompt = stringBuilder.toString();
+if (clientBusinessPrompt != null && !clientBusinessPrompt.isEmpty()) {
+    params.put(SYSTEM_PROMPT_FIELD, clientBusinessPrompt);
+} else if (resultExpandOverride != null && !resultExpandOverride.isEmpty()) {
+    StringBuilder stringBuilder = new StringBuilder();
+    stringBuilder.append(plannerSystemPromptPrefix).append("\n");
+    stringBuilder.append(getCorePlanningInstructions()).append("\n");
+    stringBuilder.append(getPlanExecuteReflectResponseFormat(resultExpandOverride, importantRulesExpand));
+    params.put(SYSTEM_PROMPT_FIELD, stringBuilder.toString());
+} else {
+    params.put(SYSTEM_PROMPT_FIELD, DEFAULT_PLANNER_SYSTEM_PROMPT);
+}
 
-
-if (clientBusinessPrompt != null && !clientBusinessPrompt.isEmpty()) {
-
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly identifies that finalPlannerPrompt is only used in one branch, and moving the construction inside that branch would avoid unnecessary string building. However, the performance impact is minimal, and the current code structure is more readable with the construction separated from the conditional logic.

Low
Suggestions up to commit 6873396
CategorySuggestion                                                                                                                                    Impact
General
Add datetime injection to custom prompt

The finalPlannerPrompt is constructed but never used when injectDate is true. The
datetime injection logic should append currentDateTime to finalPlannerPrompt when
needed, similar to how it's done for executorSystemPrompt.

ml-algorithms/src/main/java/org/opensearch/ml/engine/algorithms/agent/MLPlanExecuteAndReflectAgentRunner.java [219-224]

 StringBuilder stringBuilder = new StringBuilder();
 stringBuilder.append(plannerSystemPromptPrefix).append("\n");
 stringBuilder.append(getCorePlanningInstructions()).append("\n");
 stringBuilder.append(getPlanExecuteReflectResponseFormat(resultExpandOverride, importantRulesExpand));
 String finalPlannerPrompt = stringBuilder.toString();
 
+if (injectDate) {
+    finalPlannerPrompt = String.format("%s\n\n%s", finalPlannerPrompt, currentDateTime);
+}
+
Suggestion importance[1-10]: 8

__

Why: This is a valid bug fix. The finalPlannerPrompt is constructed but doesn't incorporate currentDateTime when injectDate is true, unlike executorSystemPrompt which does (line 235-236). This inconsistency means datetime injection won't work properly for the custom planner prompt path, which could lead to incorrect behavior.

Medium
Fix conditional logic for prompt selection

The condition clientBusinessPrompt == null in the second branch is redundant since
the first branch already handles non-null cases. Additionally, the logic should
check if resultExpandOverride OR importantRulesExpand is provided, not just
resultExpandOverride, to properly utilize the custom prompt building.

ml-algorithms/src/main/java/org/opensearch/ml/engine/algorithms/agent/MLPlanExecuteAndReflectAgentRunner.java [219-232]

 StringBuilder stringBuilder = new StringBuilder();
 stringBuilder.append(plannerSystemPromptPrefix).append("\n");
 stringBuilder.append(getCorePlanningInstructions()).append("\n");
 stringBuilder.append(getPlanExecuteReflectResponseFormat(resultExpandOverride, importantRulesExpand));
 String finalPlannerPrompt = stringBuilder.toString();
 
-
 if (clientBusinessPrompt != null && !clientBusinessPrompt.isEmpty()) {
     params.put(SYSTEM_PROMPT_FIELD, clientBusinessPrompt);
-} else if (clientBusinessPrompt == null && resultExpandOverride != null && !resultExpandOverride.isEmpty()) {
+} else if ((resultExpandOverride != null && !resultExpandOverride.isEmpty()) || 
+           (importantRulesExpand != null && !importantRulesExpand.isEmpty())) {
     params.put(SYSTEM_PROMPT_FIELD, finalPlannerPrompt);
 } else {
     params.put(SYSTEM_PROMPT_FIELD, DEFAULT_PLANNER_SYSTEM_PROMPT);
 }
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that the condition clientBusinessPrompt == null is redundant in the second branch. The recommendation to check both resultExpandOverride and importantRulesExpand is logical since both parameters are used to build finalPlannerPrompt. This improves the conditional logic's clarity and correctness.

Medium

@yyfamazon yyfamazon force-pushed the main branch 2 times, most recently from 407f605 to 30c8100 Compare May 21, 2026 03:24
@github-actions
Copy link
Copy Markdown

Persistent review updated to latest commit 30c8100

1 similar comment
@github-actions
Copy link
Copy Markdown

Persistent review updated to latest commit 30c8100

if (injectDate) {
plannerSystemPrompt = String.format("%s\n\n%s", plannerSystemPrompt, currentDateTime);
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(plannerSystemPromptPrefix).append("\n");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

plannerSystemPromptPrefix can be null here (since params.get() returns null when the key is absent). StringBuilder.append(null) will append the literal string "null" to the prompt. Should use params.getOrDefault(PLANNER_SYSTEM_PROMPT_PREFIX, DEFAULT_PLANNER_SYSTEM_PROMPT_PREFIX) instead.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolved, thanks!

stringBuilder.append(plannerSystemPromptPrefix).append("\n");
stringBuilder.append(getCorePlanningInstructions()).append("\n");
stringBuilder.append(getPlanExecuteReflectResponseFormat(resultExpendOverride, importantRulesExpend));
String finalPlannerPrompt = stringBuilder.toString();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This path is missing FINAL_RESULT_RESPONSE_INSTRUCTIONS which is present in DEFAULT_PLANNER_SYSTEM_PROMPT. When a caller does not provide clientBusinessPrompt, the assembled prompt will be missing the final-result reporting instructions, changing behavior vs. the default.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolved, thanks!

String dateFormat = params.get(DATETIME_FORMAT_FIELD);
String currentDateTime = injectDate ? getCurrentDateTime(dateFormat) : "";
String clientBusinessPrompt = params.get(SYSTEM_PROMPT_FIELD);
String resultExpendOverride = params.get(RESULT_EXPAND_OVERRIDE);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the variable names use "Expend" (to use up) but the intent is "Expand" (to extend). The field constant names (RESULT_EXPAND_OVERRIDE, IMPORTANT_RULES_EXPAND) are correct, but the local variables and method parameters are inconsistent — e.g. resultExpendOverride should be resultExpandOverride, importantRulesExpendimportantRulesExpand.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolved, thanks!

testParams.put(MLPlanExecuteAndReflectAgentRunner.QUESTION_FIELD, "test question");
testParams.put(MLPlanExecuteAndReflectAgentRunner.INJECT_DATETIME_FIELD, "true");
testParams.put(MLPlanExecuteAndReflectAgentRunner.SYSTEM_PROMPT_FIELD, "");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test sets INJECT_DATETIME_FIELD = "true" in the params map, but then calls setupPromptParameters(testParams, false, "") — passing injectDate=false. Since the refactored method now uses the boolean parameter (not the map value), this test no longer exercises the date injection path. To actually test it, call with injectDate=true and a non-empty currentDateTime, then assert the executor system prompt contains the datetime string.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolved, thanks!

@github-actions
Copy link
Copy Markdown

Persistent review updated to latest commit 6873396

1 similar comment
@github-actions
Copy link
Copy Markdown

Persistent review updated to latest commit 6873396

Signed-off-by: yyfamazon <yyf@amazon.com>
@github-actions
Copy link
Copy Markdown

Persistent review updated to latest commit 8c98473

1 similar comment
@github-actions
Copy link
Copy Markdown

Persistent review updated to latest commit 8c98473

@github-actions
Copy link
Copy Markdown

Persistent review updated to latest commit 7c27359

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

Persistent review updated to latest commit 2ef150a

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants