From 4dd8b4741dcfe116dae6b37700b76b9481959483 Mon Sep 17 00:00:00 2001
From: Classic298 <27028174+Classic298@users.noreply.github.com>
Date: Tue, 29 Sep 2026 10:28:15 +0200
Subject: [PATCH] fix: sub-agents, timers and follow-ups repeat parts of the
chat's system prompt
A sub-agent's system prompt had the chat's skill list, the model's own system prompt, the list of knowledge attached to the model and the connected terminal's instructions twice each, because it inherits the chat's finished prompt and then adds them again. Timers and the follow-up turn after a background sub-agent finishes had the same repeats, plus the folder's system prompt and the full text of a $mentioned skill twice, and a sub-agent got the user's own system prompt twice. Each now appears once, sub-agents still get the text of a $mentioned skill and the user's system prompt, and the chat's own system prompt is unchanged.
Fixes #31568
---
backend/open_webui/main.py | 3 ++-
backend/open_webui/utils/middleware.py | 31 ++++++++++++++++++++++++--
backend/open_webui/utils/subagents.py | 5 +++--
backend/open_webui/utils/timers.py | 2 +-
4 files changed, 35 insertions(+), 6 deletions(-)
diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py
index ecba6407f3..0ff61fbc14 100644
--- a/backend/open_webui/main.py
+++ b/backend/open_webui/main.py
@@ -1277,6 +1277,7 @@ async def chat_completion(
'variables': form_data.get('variables', {}),
'chat_variables': chat_variables,
'model': model,
+ 'model_system_prompt': model_info_params.get('system'),
'direct': model_item.get('direct', False),
'params': {
'stream_delta_chunk_size': stream_delta_chunk_size,
@@ -1787,7 +1788,7 @@ async def chat_completion(
'session_id': metadata.get('session_id'),
'tool_ids': metadata.get('tool_ids') or [],
'skill_ids': metadata.get('skill_ids') or [],
- 'system_prompt': metadata.get('system_prompt'),
+ 'system_prompt': metadata.get('chat_system_prompt'),
'filter_ids': metadata.get('filter_ids') or [],
'terminal_id': metadata.get('terminal_id'),
'features': metadata.get('features') or {},
diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py
index 6a3e4b6f5b..138c37494e 100644
--- a/backend/open_webui/utils/middleware.py
+++ b/backend/open_webui/utils/middleware.py
@@ -2531,6 +2531,7 @@ async def process_chat_payload(request, form_data, user, metadata, model):
) # Required to handle system prompt variables
except Exception:
pass
+ chat_system_prompt = get_content_from_message(system_message) if system_message else None
form_data = await convert_url_images_to_base64(form_data, user=user)
@@ -2665,6 +2666,8 @@ async def process_chat_payload(request, form_data, user, metadata, model):
except Exception as e:
raise Exception(f'{e}')
+ delegate_system_prompt = get_content_from_message(get_system_message(form_data['messages']) or {})
+
features = form_data.pop('features', None) or {}
extra_params['__features__'] = features
if features:
@@ -2823,11 +2826,15 @@ async def process_chat_payload(request, form_data, user, metadata, model):
skill_manifest = ''
for skill in available_skills:
if skill.id in mentioned_skill_ids or not use_builtin_tools:
+ skill_context = f'\n{skill.content}\n'
form_data['messages'] = add_or_update_system_message(
- f'\n{skill.content}\n',
+ skill_context,
form_data['messages'],
append=True,
)
+ delegate_system_prompt = (
+ f'{delegate_system_prompt}\n{skill_context}' if delegate_system_prompt else skill_context
+ )
else:
view_skill_ids.append(skill.id)
skill_manifest += (
@@ -2855,11 +2862,15 @@ async def process_chat_payload(request, form_data, user, metadata, model):
skill_name = unquote(sid.removeprefix(terminal_skill_prefix))
loaded = await get_terminal_skill(request, user.model_dump(), metadata, skill_name, extra_params)
if loaded:
+ skill_context = format_terminal_skill_context(loaded)
form_data['messages'] = add_or_update_system_message(
- format_terminal_skill_context(loaded),
+ skill_context,
form_data['messages'],
append=True,
)
+ delegate_system_prompt = (
+ f'{delegate_system_prompt}\n{skill_context}' if delegate_system_prompt else skill_context
+ )
else:
view_skill_ids.append(sid)
skill_manifest += format_terminal_skill_manifest_entry(skill)
@@ -3193,6 +3204,22 @@ async def process_chat_payload(request, form_data, user, metadata, model):
f'{resolved_model_system_prompt}\n{system_content}' if system_content else resolved_model_system_prompt
)
metadata['system_prompt'] = system_content or None
+ # Timers/follow-ups continue this chat, sub-agents start their own; the model's own prompt is added in each run
+ sent_with_request = model_system_prompt != metadata.get('model_system_prompt')
+ already_in_chat = resolved_model_system_prompt == chat_system_prompt
+ if resolved_model_system_prompt and sent_with_request and not already_in_chat:
+ chat_system_prompt = (
+ f'{resolved_model_system_prompt}\n{chat_system_prompt}'
+ if chat_system_prompt
+ else resolved_model_system_prompt
+ )
+ delegate_system_prompt = (
+ f'{resolved_model_system_prompt}\n{delegate_system_prompt}'
+ if delegate_system_prompt
+ else resolved_model_system_prompt
+ )
+ metadata['chat_system_prompt'] = chat_system_prompt
+ metadata['delegate_system_prompt'] = delegate_system_prompt
metadata['user_prompt'] = get_last_user_message(form_data['messages'])
metadata['sources'] = sources[:] if sources else []
diff --git a/backend/open_webui/utils/subagents.py b/backend/open_webui/utils/subagents.py
index 13b9699216..7014cb04b0 100644
--- a/backend/open_webui/utils/subagents.py
+++ b/backend/open_webui/utils/subagents.py
@@ -319,7 +319,8 @@ async def delegate(
'session_id': metadata.get('session_id'),
'tool_ids': copy.deepcopy(metadata.get('tool_ids') or []),
'skill_ids': copy.deepcopy(metadata.get('skill_ids') or []),
- 'system_prompt': metadata.get('system_prompt'),
+ 'system_prompt': metadata.get('chat_system_prompt'),
+ 'delegate_system_prompt': metadata.get('delegate_system_prompt'),
'tool_servers': [] if background else copy.deepcopy(metadata.get('tool_servers') or []),
'filter_ids': copy.deepcopy(metadata.get('filter_ids') or []),
'terminal_id': metadata.get('terminal_id'),
@@ -449,7 +450,7 @@ async def delegate(
try:
child_request = _build_request(request, user.id, internal=True)
child_request.state.max_tool_call_iterations = max_iterations
- parent_system_prompt = run.get('system_prompt') or ''
+ parent_system_prompt = run.get('delegate_system_prompt') or ''
subagent_system_prompt = (
str(config.get('subagents.system_prompt') or '').strip() or DEFAULT_SUBAGENT_SYSTEM_PROMPT
)
diff --git a/backend/open_webui/utils/timers.py b/backend/open_webui/utils/timers.py
index 15a6b898db..17c7e1bc55 100644
--- a/backend/open_webui/utils/timers.py
+++ b/backend/open_webui/utils/timers.py
@@ -108,7 +108,7 @@ async def create_timer(
'session_id': metadata.get('session_id'),
'tool_ids': copy.deepcopy(metadata.get('tool_ids') or []),
'skill_ids': copy.deepcopy(metadata.get('skill_ids') or []),
- 'system_prompt': metadata.get('system_prompt'),
+ 'system_prompt': metadata.get('chat_system_prompt'),
'filter_ids': copy.deepcopy(metadata.get('filter_ids') or []),
'terminal_id': metadata.get('terminal_id'),
'features': copy.deepcopy(metadata.get('features') or {}),