The Prompt Template Is Part of the Runtime Now

July 06, 2026

scroll for more

Share on:

The Prompt Template Is Part of the Runtime Now

When chat models first became widely useful, a prompt template was easy to think of as a wrapper. You had a system message, a user message, and an assistant response. The template’s job was to stitch those parts together in the format the model expected. If the formatting was a little off, the model might sound awkward, ignore an instruction, or print an extra Assistant: prefix. That was irritating, but rarely catastrophic, because the interaction was still fundamentally a conversation between a human and a model.

Gemma 4 showed why that mental model is now out of date. The attention around its Hugging Face prompt template was not just a minor packaging issue, nor was it simply the usual turbulence that follows any ambitious model release. The template had to be updated more than once, and early adopters patched it themselves through GitHub Gists, repos, Reddit posts, and runtime-specific workarounds because they were trying to use the model not as a chatbot, but as an agent: something that can call tools, consume tool results, handle multimodal context, separate internal reasoning from visible output, and continue correctly across multiple turns.

That is the important shift. For an agentic model, the prompt template is no longer “prompt engineering garnish.” It is closer to a protocol definition. It tells the model where the system instruction begins and ends, where tools are declared, how a tool call should be represented, how tool results come back, where image or audio placeholders belong, and which parts of the generation are internal reasoning versus user-facing output. If those boundaries are wrong, the failure is not stylistic; the agent loop itself can break.

A simple Gemma-style chat exchange might look something like this:

<bos>
<|turn>system
You are a helpful assistant.
<turn|>

<|turn>user
Explain container orchestration.
<turn|>

<|turn>model

This still resembles a normal chat format. There are roles, turn delimiters, and an assistant continuation point. But once tools enter the picture, the template becomes much more than a neat serialization of chat history. It becomes a structured message passed between the model and the runtime.

For example, a tool declaration might be rendered as:

<|turn>system
You are a helpful assistant.

<|tool>
declaration:get_current_temperature{
  description:<|"|>Gets the current temperature for a given location.<|"|>,
  parameters:{
    properties:{
      location:{
        description:<|"|>The city name, e.g. London<|"|>,
        type:<|"|>STRING<|"|>
      }
    },
    required:[<|"|>location<|"|>],
    type:<|"|>OBJECT<|"|>
  }
}
<tool|>
<turn|>

A model tool call might then appear as:

<|turn>model
<|tool_call>
get_current_temperature{
  location:<|"|>London<|"|>
}
<tool_call|>
<turn|>

And the runtime may return the result like this:

<|turn>user
<|tool_response>
get_current_temperature{
  temperature:<|"|>18°C<|"|>,
  condition:<|"|>Cloudy<|"|>
}
<tool_response|>
<turn|>

<|turn>model
The current temperature in London is 18°C and cloudy.
<turn|>

The exact tokens may vary by model and serving stack, but the underlying point is the same: the template is now carrying state. It is not only formatting prose; it is preserving boundaries between tool definitions, tool invocations, tool results, user messages, assistant messages, hidden reasoning, and assistant continuation points. A misplaced delimiter can mean the difference between a functioning agent and one that refuses to call a tool, leaks internal scaffolding, or loses track of the previous step.

The Failures Were Small, but The Consequences Were Not

The fixes and community patches around Gemma 4 clustered around a few categories. One class involved system-instruction and tool-call handling. That sounds mundane until you remember what a system instruction often contains in an agentic setting: when to use tools, how to handle destructive actions, how to report execution results, whether to ask for confirmation, and how to avoid pretending that a tool ran when it did not. If the tool declarations are rendered outside the expected control region, or if the assistant continuation is placed incorrectly, the model may not reliably see the operating contract it is supposed to follow.

A fragile rendering might separate the tool declaration from the system instruction in a way the model did not learn well:

<|turn>system
You are a helpful assistant.
<turn|>

<|tool>
declaration:bash{...}
<tool|>

A more appropriate rendering may need to keep the tool declaration in the system-managed portion of the prompt, so the model receives the instruction and the available tools as one coherent operating context:

<|turn>system
You are a helpful assistant. Use tools when needed, but do not claim that a command has run until the tool response is returned.

<|tool>
declaration:bash{
  description:<|"|>Execute a shell command.<|"|>,
  parameters:{
    properties:{
      command:{
        description:<|"|>The shell command to execute.<|"|>,
        type:<|"|>STRING<|"|>
      }
    },
    required:[<|"|>command<|"|>],
    type:<|"|>OBJECT<|"|>
  }
}
<tool|>
<turn|>

This kind of difference can look small in a pull request, but it is large in behavior. To the model, the template is part of the language of interaction. If the language changes, the model may still produce fluent text, but it may no longer produce the machine-readable action the runtime expects.

Another class of failures involved reasoning-channel leakage. Gemma 4-style agentic workflows can distinguish between internal reasoning and external output. Conceptually, a model might produce something like this:

<|channel>thought
The user is asking for a live directory listing, so I should use the shell tool rather than answer from memory.
<channel|>

<|tool_call>
bash{
  command:<|"|>ls /tmp<|"|>
}
<tool_call|>

In a well-integrated stack, the runtime would separate the reasoning from the tool call and expose only the appropriate fields to the application:

{
  "reasoning_content": "The user is asking for a live directory listing, so I should use the shell tool rather than answer from memory.",
  "tool_calls": [
    {
      "name": "bash",
      "arguments": {
        "command": "ls /tmp"
      }
    }
  ],
  "content": null
}

But in some setups, reasoning markers or reasoning text appeared in the visible assistant output. Sometimes special tokens were stripped before the parser saw them; sometimes the template replayed reasoning fields in a runtime that did not know how to hide them. The result could be output like:

thought
The user is asking for a live directory listing, so I should use the shell tool rather than answer from memory.

I will now list the files.

For a chatbot, this is embarrassing. For an agent, it is worse, because downstream parsers may interpret the leaked text as normal assistant content, the user interface may display internal scaffolding, and the system may lose the distinction between deliberation, action, and final answer. Some community patches handled this pragmatically by suppressing reasoning replay in environments that did not support reasoning channels correctly:


{# Avoid replaying reasoning fields in runtimes that leak them #}
{% if message.role == "assistant" %}
  {% if message.content %}
    {{ message.content }}
  {% endif %}
  {% if message.tool_calls %}
    {{ render_tool_calls(message.tool_calls) }}
  {% endif %}
{% endif %}

That kind of patch is sensible, but it also shows the deeper ambiguity: who owns reasoning separation? Is it the chat template, the tokenizer, the inference server, the OpenAI-compatible shim, or the agent framework? In practice, it is a cross-layer contract, and when the layers disagree, the user sees a broken agent rather than a distributed systems problem.

A third class involved multimodal placeholders inside tool responses. This is easy to underestimate until you imagine an agent that calls a screenshot tool. The tool might return structured content:

{
  "role": "tool",
  "name": "take_screenshot",
  "content": [
    {
      "type": "text",
      "text": "Screenshot captured."
    },
    {
      "type": "image"
    }
  ]
}

A template that only extracts text could render the response as:

<|tool_response>
take_screenshot{
  result:<|"|>Screenshot captured.<|"|>
}
<tool_response|>

The image has not merely been ignored; it has been disconnected from the prompt. The multimodal processor may still have an image tensor ready, but the prompt contains no placeholder telling the model where that image belongs. The corrected rendering needs to preserve the placeholder:

<|tool_response>
take_screenshot{
  result:<|"|>Screenshot captured.<|"|>
}
<|image|>
<tool_response|>

This is the kind of issue that makes clear why prompt templates are now infrastructure. In a text-only chatbot, dropping a non-text part might degrade an answer. In a multimodal agent, it can break alignment between the serialized prompt and the model inputs.

Streaming created its own class of problems. A non-streaming tool call is often easy to parse because the runtime receives the whole object at once:

{
  "finish_reason": "tool_calls",
  "message": {
    "tool_calls": [
      {
        "function": {
          "name": "bash",
          "arguments": "{\"command\":\"ls /tmp\"}"
        }
      }
    ]
  }
}

In streaming mode, however, the same call may arrive in pieces:

{
  "choices": [
    {
      "delta": {
        "tool_calls": [
          {
            "index": 0,
            "function": {
              "name": "bash"
            }
          }
        ]
      }
    }
  ]
}

Then:

{
  "choices": [
    {
      "delta": {
        "tool_calls": [
          {
            "index": 0,
            "function": {
              "arguments": "{\"command\":\"ls"
            }
          }
        ]
      }
    }
  ]
}

Then:

{
  "choices": [
    {
      "delta": {
        "tool_calls": [
          {
            "index": 0,
            "function": {
              "arguments": " /tmp\"}"
            }
          }
        ]
      },
      "finish_reason": "tool_calls"
    }
  ]
}

If the client or agent framework does not reconstruct those fragments correctly, the model may appear to have failed to use tools even though the tool call was produced. The bug is then not in the model’s capability, but in the protocol bridge between model output and agent action.

The Old Template was Prose; The New Template is an Interface

The easiest way to frame this is to borrow a concept from systems programming. An ABI defines how compiled components call each other: which values go where, how arguments are passed, how return values are represented, and which conventions both sides must obey. If the ABI is wrong, two correct components can fail together.

Agentic LLMs now have a similar boundary. The model has learned a control language. The serving runtime has parsers. The tool framework has schemas. The client may expect OpenAI-style tool calls. The UI may expect visible content and hidden reasoning to be separated. The chat template sits in the middle, translating conversation state into the model’s expected language and back again.

A typical agent loop depends on this sequence being exact: the runtime renders the system instruction, renders available tools, appends the user request, waits for either a final answer or a tool call, parses the tool call, executes the tool, renders the tool result, and prompts the model to continue. If the tool declaration is malformed, the model may not know what it can call. If the reasoning boundary is mishandled, hidden text can leak. If the tool response is rendered incorrectly, the model may not understand the result. If the assistant continuation marker is wrong, the model may continue the wrong turn.

This is why early adopters patched templates themselves. They were not arguing about punctuation. They were repairing the interface between the model and the rest of the system.

So What?

The practical lesson is straightforward: if you are building agents, treat the chat template as part of the model artifact. Do not treat it as sample code, documentation, or a convenience snippet copied from a model card. It is part of the runtime contract, and it should be pinned, tested, reviewed, and versioned along with the weights, tokenizer, serving engine, tool parser, reasoning parser, client SDK, and agent framework.

A serious agent deployment should know exactly which versions of these components belong together:

Changing any one of them can break the others. A new runtime may strip special tokens earlier than before. A new template may render tool responses differently. A client SDK may alter streaming reconstruction. A model conversion may preserve weights but lose the original template. A quantized local package may work for chat but fail in multi-turn tool use. These are not theoretical worries; they are the ordinary failure modes of agentic systems.

The test plan should change accordingly. It is not enough to ask whether the model gives a good answer to a prompt. Teams should test whether the full loop works:

The important question is not merely, “Can the model answer?” It is, “Can the system act, observe, continue, and stay inside the contract?”

Why This Episode Matters Beyond Gemma 4

The Gemma 4 template issue is interesting because it captures a broader transition in LLM engineering. The industry is moving from chat to action. In chat, the human is the parser of last resort; if the model says something slightly odd, the user can infer what it meant. In agentic workflows, the next consumer is often code. Code is less forgiving. It needs typed calls, reliable delimiters, valid schemas, and predictable stop conditions.

That difference raises the engineering standard. A model may have strong reasoning ability, long context, good multimodal understanding, and impressive benchmark results, yet still feel unreliable if the protocol around it is loose. The reverse is also true: a smaller model with a clean, well-tested tool interface can feel more useful in production than a stronger model wrapped in a brittle integration layer.

This is the quiet lesson behind the prompt-template fiasco. We often talk about prompts as if they are natural language instructions, and sometimes they are. But in modern agent systems, the most important parts of the prompt are often not natural language at all. They are delimiters, schemas, placeholders, channel markers, continuation tokens, and parser assumptions. They are the parts humans barely notice and machines cannot forgive.

The final takeaway is not “be careful with Gemma 4,” nor is it “large model releases are hard.” The takeaway is more general and more useful: as LLM applications become agents, the interface between model and runtime becomes a first-class engineering surface. The prompt template is one of those interfaces. Treat it with the same seriousness you would give to an API contract, because in an agentic system, that is what it has become.

Share on:

Read more posts about...

See all posts