Skip to content

Templating

Polytoken renders facet and subagent bodies as templates, along with skill bodies when you opt in. You write custom definitions with the same template syntax as the shipped definitions.

Building a dependable harness on top of a coding agent is work I have done before; ed3d-plugins is my earlier effort at it. Polytoken ships that dependable baseline as the default. When you do need the harness to behave a certain way, you extend it in the same templates Polytoken ships. To decide which file your instructions need, see Where Instructions Live.

Every file you author opens with a YAML frontmatter block, the same convention other tools use. Frontmatter has two layers. The conventional keys, such as name and description, sit at the top level where any harness reads them, so they carry across tools. Anything specific to Polytoken lives under one key, polytoken: the model a facet runs on, the tools a subagent gets, a skill’s tags. The key’s value can also be just true, which claims the file for Polytoken without setting any options. Carrying the key in either form is what turns templating on for a skill; facets and subagents render as templates whether or not the key is present.

Which keys are legal, both at the top level and under polytoken, depends on the file type. The Template Reference lists them. AGENTS.md files work a little differently; see Project Context.

The body of a facet or subagent definition, everything below the frontmatter, is a template. A skill body becomes a template when its frontmatter carries the polytoken key. Polytoken renders the body with MiniJinja, which follows Jinja2’s syntax closely. The everyday constructs, including variables, conditionals, loops, and filters, are Jinja2-compatible. The MiniJinja documentation covers the details and the few advanced features it leaves out.

Polytoken renders the body before the model sees it, so your instructions can adapt. A facet can tell a vision model one thing and a text-only model another:

{% if supports_vision %}
You can read images the user shares. Inspect them directly.
{% else %}
You cannot see images. Ask the user to describe what they show.
{% endif %}

Everything a template can read, every variable and the functions Polytoken adds, is in the Template Reference.

Templates catch typos at render time. Referencing a variable that does not exist raises an error rather than rendering blank, so a broken prompt never reaches the model.

Task prompts render before definition bodies

Section titled “Task prompts render before definition bodies”

Polytoken first renders the task prompt passed to a subagent, then renders the definition body with the rendered task available as prompt. In a counted batch (several copies requested with the count input to the subagent tool), Polytoken performs both renders for each clone, using that clone’s launch metadata. When a task prompt must contain literal {{ ... }} or {% ... %} text, wrap that text in a raw block:

{% raw %}Review the literal expression {{ not_a_variable }}.{% endraw %}

Normal task text can use template variables such as {{ model_group.name }} and {{ subagent_batch.ordinal }}. The definition body can reference the rendered task with {{ prompt }}. Polytoken keeps the rendered task and definition text as launch metadata, so a later provider failover or configuration reload never re-renders the original launch text.

Polytoken adds functions to MiniJinja that query the current setup. has_tool, has_skill, and has_mcp check availability for the current turn. To check the model family, use is_model_variant; to retrieve a tool’s details, use get_tool. is_web_search_available and is_decision_agent_available report whether an integration is configured — configuration presence only, so they stay true even when the integration fails at runtime, and a /reload that changes integrations is reflected on the next composition. They answer truthfully in facet, subagent, and skill templates; AGENTS.md renders always see false. These functions let templates include instructions only when the relevant tool or skill is available. See the Template Reference for the full list and signatures.

Polytoken always renders facet and subagent bodies as templates. A skill’s frontmatter must carry a polytoken key to enable templating. Skill templates use a smaller context: the skill list and project variables are present, while the model, tool, and MCP server values are empty. In a skill template, has_tool and has_mcp return false, and transclude() is unavailable.

By default, Polytoken treats AGENTS.md files as literal text. See Project Context for their templating opt-in.

The shipped facet and subagent templates print one identity line right after the working directory:

Model: `{{ model_name }}` (provider: `{{ model_provider }}`, model ID: `{{ model_id }}`)

The line is always present in the shipped templates, with no gating on model family or interactivity. It is composed fresh for every request, so the identity always names the model that attempt actually uses: after an explicit switch, a facet-pinned switch, or a routed failover, the next request names the new model. In a subagent, the identity names the subagent’s own model, never the parent conversation’s model.

The three values mean different things. model_name is the configured selection name, model_provider is the configured provider, and model_id is the upstream model identifier the provider itself uses. Custom facets that replace the shipped templates choose their own composition, so the identity line appears only where a template includes it.

Facet and subagent bodies support load-time @file expansion and render-time transclude() calls. Skill bodies loaded from disk support @file expansion at load time.

Polytoken resolves @file references when loading a definition, before Jinja directives run. Each @path/to/file reference becomes a block containing the source path and the file’s text. Within that block, Polytoken escapes ampersands and angle brackets and truncates very large files with a note marking the cutoff. The inclusion preserves text literally, so {{ }} and {% %} markers pass through unchanged rather than running as template code. Use @file to share static text across definitions without templating.

The @ must appear at the start of the text or after whitespace, so email addresses and typed references such as @skill: never count. Polytoken skips references inside inline code, fenced code blocks, and Jinja regions. Quote paths containing spaces: @"my notes.md". File paths resolve relative to the definition file’s directory; Polytoken confines file access to that directory subtree. An unsatisfied reference, such as a missing file or a directory, remains literal text instead of failing the definition. The load-time scan never searches included text for further references.

At render time, transclude("path.md") reads another file and Polytoken renders its body with the current template context. For example, {{ facet_name }} expands inside a transcluded file. Polytoken strips valid YAML frontmatter before rendering, so a definition file contributes its instructions without its metadata. A leading --- block that does not parse as YAML remains part of the body. File paths resolve relative to the directory of the file containing the transclude() call. Polytoken confines access to that directory subtree and rejects ../ escapes. transclude() also accepts polytoken:// addresses for shipped prompt fragments. A transclude() Polytoken cannot satisfy, such as a missing file, a directory, or a circular chain of includes, makes the render fail; no literal text stays behind. Relative paths work only when the calling file lives on disk: a shipped fragment has no directory of its own, so a relative transclude() inside one fails the same way. See the Template Reference for the full signature.