mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-13 13:17:40 +02:00
[eric] agents: a custom system prompt bites on the FIRST reply; it was delivered but buried mid-prompt and ignored
This commit is contained in:
@@ -442,7 +442,13 @@ AGENT_IDENTITY = (
|
||||
@typechecked
|
||||
def compose_system_prompt(default_prompt: Optional[str], mode_prompt: Optional[str], session_prompt: Optional[str], browser_ctx: Optional[str] = None, mcp_registry_ctx: Optional[str] = None, skills_catalog_ctx: Optional[str] = None) -> Optional[str]:
|
||||
# Identity always leads so it overrides the preset's Claude Code persona, even when the user has no custom default/mode/session prompt of their own.
|
||||
parts = [AGENT_IDENTITY] + [p for p in (default_prompt, mode_prompt, session_prompt, mcp_registry_ctx, skills_catalog_ctx, browser_ctx) if p]
|
||||
# The session prompt goes LAST with explicit priority framing: buried mid-prompt it was delivered but ignored on turn one (Haik's hackathon blocker; live-proven with a marker token both ways).
|
||||
framed_session = (
|
||||
"## Instructions from your operator (highest priority)\n"
|
||||
"The person who configured this agent wrote the following. Follow it over any earlier "
|
||||
"guidance in this prompt, starting from your very first reply.\n\n" + session_prompt
|
||||
) if session_prompt else None
|
||||
parts = [AGENT_IDENTITY] + [p for p in (default_prompt, mode_prompt, mcp_registry_ctx, skills_catalog_ctx, browser_ctx, framed_session) if p]
|
||||
return "\n\n".join(parts)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
"""A custom system prompt must actually bite on the FIRST message (Haik's hackathon blocker).
|
||||
|
||||
The prompt was always DELIVERED (verified in the live CLI argv, and the model could quote it when
|
||||
asked), but it sat mid-prompt between the identity block and the MCP registry, after the whole
|
||||
default prompt, and the model ignored it: a marker-token drill on the packaged build showed 0/3
|
||||
arms applying it. Moving it last with explicit operator framing flipped all arms to applied.
|
||||
|
||||
These pin the structural half of that fix, the part a unit test can hold: position and framing.
|
||||
The behavioural half lives in the live drill (scratchpad/sysprompt_test.sh pattern).
|
||||
"""
|
||||
from backend.apps.agents.manager.prompt.prompt_context import compose_system_prompt
|
||||
|
||||
SESSION = "Always answer in pirate speak."
|
||||
DEFAULT = "Be helpful and concise."
|
||||
MODE = "You are in coding mode."
|
||||
|
||||
|
||||
def test_session_prompt_is_the_last_content_in_the_composed_prompt():
|
||||
out = compose_system_prompt(DEFAULT, MODE, SESSION, "browser ctx", "mcp ctx", "skills ctx")
|
||||
assert out is not None
|
||||
assert out.rstrip().endswith(SESSION), "anything after the operator's words dilutes them again"
|
||||
|
||||
|
||||
def test_session_prompt_carries_priority_framing():
|
||||
out = compose_system_prompt(DEFAULT, MODE, SESSION)
|
||||
assert "highest priority" in out
|
||||
assert "your very first reply" in out, "turn one is the reported failure; the framing must name it"
|
||||
assert out.index("highest priority") < out.index(SESSION), "the framing introduces the prompt, not trails it"
|
||||
|
||||
|
||||
def test_no_session_prompt_means_no_framing_block():
|
||||
out = compose_system_prompt(DEFAULT, MODE, None, "browser ctx")
|
||||
assert "highest priority" not in out, "an empty operator block would be framing around nothing"
|
||||
assert DEFAULT in out and "browser ctx" in out
|
||||
|
||||
|
||||
def test_default_and_mode_prompts_still_precede_context_blocks():
|
||||
out = compose_system_prompt(DEFAULT, MODE, SESSION, "browser ctx", "mcp ctx")
|
||||
assert out.index(DEFAULT) < out.index(MODE) < out.index("mcp ctx") < out.index(SESSION)
|
||||
Reference in New Issue
Block a user