#7545·crewAI

[BUG] Custom prompt templates always inject the ReAct `task` slice — wrong instructions for no-tool and native-tool agents

Author: coderdailyoneCreated Sep 17, 2026Updated Sep 17, 2026

Description

Prompts._build_prompt has two code paths: the default one joins the i18n slices selected in task_execution (role_playing, plus tools/no_tools, plus exactly one task slice: task, native_task, or task_no_tools). The custom-template path (taken when both system_template and prompt_template are set) ignores which task slice was selected:

  • {{ .System }} is filled with every component except the literal "task", so native_task and task_no_tools are not excluded — the selected task slice leaks into the system section.
  • {{ .Prompt }} is always filled with the hardcoded "task" slice regardless of the selection.

The "task" slice contains ReAct-only instructions: "use the tools available and give your best Final Answer, your job depends on it!" and a Thought: seed. The result:

  • An agent with no tools (task_no_tools selected) is told to "use the tools available" and gets a Thought: ReAct scaffold — the exact thought-leakage problem the use_system_prompt path was fixed for.
  • An agent using native function calling (native_task selected) gets text ReAct instructions that conflict with the native calling flow.
  • In both cases Current Task: {input} is emitted twice — once via the unfiltered task slice in {{ .System }} and again via the hardcoded task slice in {{ .Prompt }}.

Only the tool-enabled ReAct configuration (task selected) produces correct output today.

Steps to Reproduce

python
from unittest.mock import MagicMock
from crewai.utilities.prompts import Prompts

agent = MagicMock()
agent.role, agent.goal, agent.backstory = "Writer", "Write", "Pro"

# No tools + custom templates
prompt = Prompts(
    has_tools=False, use_native_tool_calling=False, use_system_prompt=False,
    agent=agent,
    system_template="system:\n{{ .System }}",
    prompt_template="user:\n{{ .Prompt }}",
).task_execution()["prompt"]

print(prompt)
# Actual (buggy): system section contains "Current Task: {input}\nProvide your
# complete response:" AND the user section contains the "task" slice:
#   "use the tools available and give your best Final Answer ... Thought:"
# Expected: user section contains "task_no_tools" ("Provide your complete
# response:"), no tool instructions, "Current Task:" appears exactly once.

# Same failure with native tool calling:
prompt = Prompts(
    has_tools=True, use_native_tool_calling=True, use_system_prompt=False,
    agent=agent,
    system_template="system:\n{{ .System }}",
    prompt_template="user:\n{{ .Prompt }}",
).task_execution()["prompt"]
# Actual (buggy): user section contains the ReAct "task" slice
# ("Thought:" seed, "use the tools available"), and "Current Task: {input}"
# appears twice.

The regression tests in lib/crewai/tests/utilities/test_prompts_no_thought_leakage.py (TestCustomTemplatesPromptGeneration) reproduce both cases — they fail on the unpatched code and pass with the fix; a third test pins the unchanged ReAct-with-tools behavior.

Expected behavior

With custom system_template / prompt_template, {{ .Prompt }} is filled with the task slice that task_execution selected (task_no_tools for an agent without tools, native_task for native tool calling, task otherwise), and that slice does not also appear in {{ .System }}.

Screenshots/Code snippets

lib/crewai/src/crewai/utilities/prompts.py, Prompts._build_prompt, custom-template branch:

  • Line 239: if component != "task" — only excludes the task slice, not native_task/task_no_tools.
  • Line 245: "{{ .Prompt }}", "".join(I18N_DEFAULT.slice("task")) — hardcodes the task slice.

components is built in task_execution (lines 99–118) which appends one of task / native_task / task_no_tools last based on has_tools and use_native_tool_calling. Reached from Agent._build_execution_prompt (lib/crewai/src/crewai/agent/core.py:1133) whenever an agent sets system_template + prompt_template.

Operating System

Ubuntu 22.04

Python Version

3.12

crewAI Version

1.15.22 (main @ 5c33fe4)

crewAI Tools Version

1.15.22

Virtual Environment

Venv

Evidence

Any agent configured with custom system_template/prompt_template that either has no tools or uses native tool calling receives a prompt that (a) instructs it to use tools it does not have / should not call textually, (b) seeds a Thought: prefix that leaks into the final output, and (c) duplicates the Current Task: block. This silently degrades output quality on a documented configuration path (Agent(system_template=..., prompt_template=...)).

Possible Solution

In the custom-template branch, treat all three task-family slices (task, native_task, task_no_tools) as the task component: exclude all of them from the {{ .System }} parts, and fill {{ .Prompt }} with whichever one is present in components (falling back to "task" when none is, preserving prior behavior):

python
task_components = {"task", "native_task", "task_no_tools"}
template_parts = [
    I18N_DEFAULT.slice(c) for c in components if c not in task_components
]
system = system_template.replace("{{ .System }}", "".join(template_parts))
task_component = next((c for c in components if c in task_components), "task")
prompt = prompt_template.replace("{{ .Prompt }}", I18N_DEFAULT.slice(task_component))

Additional context

Found and written with an AI coding agent (Devin, reviewed by Claude Code); please apply the llm-generated label. A fix with regression tests is ready and will be linked here.