PtcRunner.SubAgent.SystemPrompt (PtcRunner v0.5.1)

View Source

System prompt generation for SubAgent LLM interactions.

Orchestrates prompt generation by combining sections from:

  • Namespace modules - Compact Lisp-style format for tools and data
  • SystemPrompt.Output - Expected return format from signature

Prompt Caching Architecture

To enable efficient prompt caching (e.g., Anthropic's cache_control), the prompt is split into static and dynamic sections:

  • Static (system prompt): generate_system/2 returns language reference and output format - these rarely change and benefit from caching across different questions.
  • Dynamic (user message): generate_context/2 returns data inventory, tool schemas, and expected output - these vary per agent configuration but not per question.

The mission is placed only in the user message (not duplicated in system prompt).

Customization

The system_prompt field on SubAgent accepts:

  • Map - :prefix, :suffix, :language_spec, :output_format
  • Function - fn default_prompt -> modified_prompt end
  • String - Complete override

Language Spec

The :language_spec option can be:

Default: :single_shot for max_turns: 1, :multi_turn otherwise.

Examples

iex> agent = PtcRunner.SubAgent.new(prompt: "Add {{x}} and {{y}}")
iex> context = %{x: 5, y: 3}
iex> prompt = PtcRunner.SubAgent.SystemPrompt.generate(agent, context: context)
iex> prompt =~ "## Role"
true
iex> prompt =~ "data/x"
true

Summary

Functions

Apply system prompt customization (string override, function, or map with prefix/suffix).

Generate a complete system prompt for a SubAgent.

Generate dynamic context sections (prepended to user message).

Generate error recovery prompt for parse failures.

Alias for generate_system/2 for semantic clarity.

Generate static system prompt sections (cacheable).

Resolve a language_spec value to a string.

Truncate prompt if it exceeds the configured character limit.

Functions

apply_customization(base_prompt, override)

@spec apply_customization(String.t(), PtcRunner.SubAgent.system_prompt_opts() | nil) ::
  String.t()

Apply system prompt customization (string override, function, or map with prefix/suffix).

Examples

iex> PtcRunner.SubAgent.SystemPrompt.apply_customization("base", nil)
"base"

iex> PtcRunner.SubAgent.SystemPrompt.apply_customization("base", "override")
"override"

iex> PtcRunner.SubAgent.SystemPrompt.apply_customization("base", fn p -> "PREFIX\n" <> p end)
"PREFIX\nbase"

generate(agent, opts \\ [])

@spec generate(
  PtcRunner.SubAgent.t(),
  keyword()
) :: String.t()

Generate a complete system prompt for a SubAgent.

Options: context (map), error_context (map for recovery prompts).

Examples

iex> agent = PtcRunner.SubAgent.new(prompt: "Process data")
iex> prompt = PtcRunner.SubAgent.SystemPrompt.generate(agent, context: %{user: "Alice"})
iex> prompt =~ "## Role" and prompt =~ "thinking:"
true

generate_context(agent, opts \\ [])

@spec generate_context(
  PtcRunner.SubAgent.t(),
  keyword()
) :: String.t()

Generate dynamic context sections (prepended to user message).

Returns data inventory, tool schemas, and expected output - these sections vary per agent configuration but not per individual question.

Note: The mission is NOT included here - it's already in the user message.

Options

  • :context - Map of context variables for the data inventory
  • :received_field_descriptions - Field descriptions from upstream agent

Examples

iex> agent = PtcRunner.SubAgent.new(prompt: "Test", tools: %{"search" => fn _ -> [] end})
iex> context_prompt = PtcRunner.SubAgent.SystemPrompt.generate_context(agent, context: %{x: 1})
iex> context_prompt =~ ";; === data/ ===" and context_prompt =~ ";; === tools ==="
true
iex> context_prompt =~ "# Mission"
false

generate_error_recovery_prompt(error_context)

@spec generate_error_recovery_prompt(map()) :: String.t()

Generate error recovery prompt for parse failures.

Examples

iex> error = %{type: :parse_error, message: "Unexpected token"}
iex> PtcRunner.SubAgent.SystemPrompt.generate_error_recovery_prompt(error) =~ "Previous Turn Error"
true

generate_static(agent, opts \\ [])

@spec generate_static(
  PtcRunner.SubAgent.t(),
  keyword()
) :: String.t()

Alias for generate_system/2 for semantic clarity.

See generate_system/2 for documentation.

generate_system(agent, opts \\ [])

@spec generate_system(
  PtcRunner.SubAgent.t(),
  keyword()
) :: String.t()

Generate static system prompt sections (cacheable).

Returns only the language reference and output format - these sections rarely change across different questions and benefit from prompt caching.

This function has an alias generate_static/2 for semantic clarity.

Options

  • :resolution_context - Map with turn/model/memory/messages for language_spec callbacks

Examples

iex> agent = PtcRunner.SubAgent.new(prompt: "Test")
iex> system = PtcRunner.SubAgent.SystemPrompt.generate_system(agent)
iex> system =~ "## Role" and system =~ "# Output Format"
true
iex> system =~ "# Data Inventory"
false

resolve_language_spec(spec, context)

@spec resolve_language_spec(String.t() | atom() | (map() -> String.t()), map()) ::
  String.t()

Resolve a language_spec value to a string.

Examples

iex> PtcRunner.SubAgent.SystemPrompt.resolve_language_spec("custom prompt", %{})
"custom prompt"

iex> spec = PtcRunner.SubAgent.SystemPrompt.resolve_language_spec(:single_shot, %{})
iex> is_binary(spec) and String.contains?(spec, "PTC-Lisp")
true

iex> callback = fn ctx -> if ctx.turn > 1, do: "multi", else: "single" end
iex> PtcRunner.SubAgent.SystemPrompt.resolve_language_spec(callback, %{turn: 1})
"single"

truncate_if_needed(prompt, limit_config)

@spec truncate_if_needed(String.t(), map() | nil) :: String.t()

Truncate prompt if it exceeds the configured character limit.

Examples

iex> PtcRunner.SubAgent.SystemPrompt.truncate_if_needed("short", nil)
"short"

iex> result = PtcRunner.SubAgent.SystemPrompt.truncate_if_needed(String.duplicate("x", 1000), %{max_chars: 100})
iex> result =~ "truncated"
true