DEV Community

Mukunda Rao Katta
Mukunda Rao Katta

Posted on

Build System Prompts From Named Sections Instead of One Giant String

The system prompt was 4,000 tokens. Nobody remembered what each paragraph did. Half of it was out of date. The "Available tools" section listed a tool that had been removed six months ago.

The problem: system prompts are often assembled as one giant string concatenation. You cannot tell which part is stale. You cannot enable or disable sections based on context. You cannot test sections independently.

agent-context-builder lets you build system prompts from named, independently-managed sections.


The Shape of the Fix

from agent_context_builder import ContextBuilder

builder = ContextBuilder()

builder.add("role", "You are a customer support agent for Acme Corp.")
builder.add("capabilities", """
You can:
- Look up order status
- Process refunds for orders under $50
- Escalate complex issues to a human agent
""")
builder.add("constraints", """
Do not:
- Promise specific delivery dates
- Approve refunds over $50 without manager approval
- Share other customers' order information
""")
builder.add("tone", "Be helpful, concise, and empathetic. Use plain language.")

# Build the full system prompt
system = builder.build()

# Conditionally include sections
if user.is_premium:
    builder.add("premium_tools", "You also have access to the priority queue tool.")

# Remove outdated sections
builder.remove("old_feature_section")

# Inspect what's in the prompt
print(builder.sections())  # ["role", "capabilities", "constraints", "tone"]
Enter fullscreen mode Exit fullscreen mode

Named sections. Explicit inclusion. Clear ownership. Each section is independently testable.


What It Does NOT Do

agent-context-builder does not format sections automatically. You control the content of each section. The builder handles concatenation and ordering.

It does not validate that sections produce a coherent system prompt. Contradicting sections (one says "always be brief", another says "always be comprehensive") will both be included without warning.

It does not track token counts by section. For token budget management across sections, pair with prompt-token-counter.


Inside the Library

The builder is an ordered dict of sections:

class ContextBuilder:
    def __init__(self, separator: str = "\n\n"):
        self._sections: dict[str, str] = {}
        self._order: list[str] = []
        self._separator = separator

    def add(self, name: str, content: str, position: int | None = None) -> None:
        if name in self._sections:
            raise DuplicateSectionError(name)
        self._sections[name] = content
        if position is not None:
            self._order.insert(position, name)
        else:
            self._order.append(name)

    def update(self, name: str, content: str) -> None:
        if name not in self._sections:
            raise UnknownSectionError(name)
        self._sections[name] = content

    def remove(self, name: str) -> None:
        self._sections.pop(name, None)
        self._order.remove(name)

    def build(self) -> str:
        return self._separator.join(
            self._sections[n] for n in self._order if n in self._sections
        )

    def sections(self) -> list[str]:
        return list(self._order)
Enter fullscreen mode Exit fullscreen mode

The separator between sections is configurable. Default is two newlines. For Anthropic's recommended XML-tagged prompts, use separator="" and wrap each section content in XML tags yourself.

position parameter: insert a section at a specific index rather than appending. Useful for adding context-specific sections that need to appear before the general instructions.

Templates: builder.add("role", "You are a {role} agent for {company}.") combined with builder.build(role="support", company="Acme") fills the placeholders. Implemented with .format(**kwargs) on each section string.


When to Use It

Use it for any agent with a system prompt longer than a few sentences. The section pattern pays off when:

  • Multiple team members own different parts of the system prompt
  • You need to enable/disable sections based on user permissions or context
  • You want to test sections independently (each section is a string you can assert on)
  • The system prompt changes frequently and you need to track what changed and why

Skip it for simple agents with short, stable system prompts. Two sentences do not need a section manager.


Install

pip install git+https://github.com/MukundaKatta/agent-context-builder
Enter fullscreen mode Exit fullscreen mode
from agent_context_builder import ContextBuilder
import os

def build_system_prompt(user_role: str, feature_flags: dict) -> str:
    builder = ContextBuilder()

    builder.add("role", f"You are a {user_role} assistant.")
    builder.add("base_capabilities", STANDARD_CAPABILITIES)
    builder.add("constraints", BASE_CONSTRAINTS)

    if feature_flags.get("web_search"):
        builder.add("web_search", WEB_SEARCH_INSTRUCTIONS)

    if feature_flags.get("code_execution"):
        builder.add("code_execution", CODE_EXECUTION_INSTRUCTIONS)

    if user_role == "admin":
        builder.add("admin_tools", ADMIN_TOOL_INSTRUCTIONS)

    builder.add("response_format", RESPONSE_FORMAT_INSTRUCTIONS)

    return builder.build()

# Test sections independently
def test_admin_section():
    builder = ContextBuilder()
    builder.add("admin_tools", ADMIN_TOOL_INSTRUCTIONS)
    assert "approve_refund" in builder.build()
    assert "delete_user" in builder.build()
Enter fullscreen mode Exit fullscreen mode

Sibling Libraries

Library What it solves
prompt-token-counter Count tokens per section for budget management
prompt-template-version Version and hash system prompt templates
cachebench Measure cache hit rate for your assembled prompts
prompt-cache-warmer Pre-warm the Anthropic cache for assembled prompts
agent-fn-registry Registry for tools that pair with the system prompt

The pairing: agent-context-builder assembles the prompt, prompt-template-version fingerprints it for change tracking, prompt-cache-warmer keeps it warm in Anthropic's cache.


What's Next

Inheritance: ChildBuilder(parent=builder) that starts with the parent's sections and can override or add sections. Useful for building specialized agents from a base agent definition.

Conditional sections with predicate functions: builder.add_if("premium_tools", lambda ctx: ctx.is_premium, content). This would make the conditional logic part of the builder definition rather than scattered in the calling code.

YAML/JSON configuration: load section definitions from a file instead of code. This would let non-engineers manage system prompts through configuration files while engineers manage the builder logic.


Built as part of the agent-stack family: composable Python primitives for production LLM agents.

Top comments (0)