Skip to content

feat(skills): support SystemContentBlock[] in system prompt injection#2127

Open
agent-of-mkmeral wants to merge 1 commit intostrands-agents:mainfrom
agent-of-mkmeral:feat/skills-content-block-support
Open

feat(skills): support SystemContentBlock[] in system prompt injection#2127
agent-of-mkmeral wants to merge 1 commit intostrands-agents:mainfrom
agent-of-mkmeral:feat/skills-content-block-support

Conversation

@agent-of-mkmeral
Copy link
Copy Markdown
Contributor

Problem

The AgentSkills plugin's _on_before_invocation hook only handles string system prompts. When the system prompt is set as list[SystemContentBlock] (e.g. with cache points for prompt caching), the plugin:

  1. Reads agent.system_prompt → gets concatenated string (cache points invisible)
  2. Does string manipulation to append skills XML
  3. Sets agent.system_prompt = new_stringdestroys content block structure

This silently drops cache points and other non-text blocks on every invocation.

Fix

Two changes, no new helper methods:

1. Agent.system_prompt_content property (agent.py)

Public getter for _system_prompt_content so plugins can read the content block representation:

@property
def system_prompt_content(self) -> list[SystemContentBlock] | None:
    return self._system_prompt_content

2. Content block branch in _on_before_invocation (agent_skills.py)

One method, two clear codepaths:

content_blocks = getattr(agent, "system_prompt_content", None)
has_structured_blocks = content_blocks is not None and any("text" not in block for block in content_blocks)

if has_structured_blocks:
    # Filter out old skills text block, append new one, preserve cache points
    ...
    agent.system_prompt = filtered  # Sets list back, preserving structure
else:
    # Existing string manipulation behavior
    ...

Parity with TypeScript SDK

This matches the TypeScript implementation which already handles SystemContentBlock[] via a two-branch design in _injectSkillsXml.

Testing

  • 3 new agent tests: system_prompt_content property for string, list, and None inputs
  • 8 new skills tests: cache point preservation, idempotency, skills swap with cache points, string regression, all-text-blocks fallback, warning on missing XML, None fallback
  • 133/133 skills tests passing, 0 regressions
  • 12/12 system_prompt agent tests passing, 0 regressions

@mkmeral

The AgentSkills plugin's _on_before_invocation hook previously only handled
string system prompts. When the system prompt was set as a list of
SystemContentBlock (e.g. with cache points), the plugin would:

1. Read agent.system_prompt (which returns a concatenated string)
2. Do string manipulation to append/replace skills XML
3. Set agent.system_prompt = new_string, destroying the content block structure

This meant cache points and other non-text blocks were silently lost on
every invocation.

Changes:
- Add system_prompt_content property to Agent (public getter for
  _system_prompt_content) so plugins can read the content block
  representation
- Update _on_before_invocation with two codepaths:
  - Content block path: when non-text blocks (cache points) are present,
    filter out old skills text block and append new one, preserving
    the list structure
  - String path: existing behavior for simple string prompts

This matches the TypeScript SDK's implementation which already handles
SystemContentBlock[] via a two-branch design in _injectSkillsXml.

Testing:
- 3 new agent property tests (string, list, None)
- 8 new skills plugin tests covering cache point preservation,
  idempotency, skills swap, string regression, all-text-blocks,
  warning on missing XML, and None fallback
- 133/133 skills tests passing, 0 regressions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants