PCL compiles modular .pcl files into plain text system prompts —
structured, version-controlled, and compatible with any LLM API.
Install
pip install pcl-lang
The problem
In production agentic systems, prompts accumulate quickly. Instructions, persona definitions, tool descriptions, behavioral guidelines, conditional logic — they all pile into a single string buried inside Python or TypeScript files. Reviewing a prompt means reading past API client setup. A diff that changes two lines of prompt text is invisible next to surrounding application code.
There is no mechanism for reuse. If two agents share a persona, that text gets copied. When it changes, it needs to change in every place. Non-engineers who own prompt content can't contribute without touching code. There is no format, no tooling, no discipline.
PCL addresses this directly. .pcl files are first-class artifacts —
structured, modular, and designed to live in version control alongside the rest of
your codebase.
| Without PCL | With PCL |
|---|---|
| Monolithic string in Python | Modular .pcl files, one concern per file |
| Mixed with application logic | Prompt content separate from code |
| Unreadable diffs | Meaningful diffs — only changed lines |
| Copy-pasted content across agents | @include shared blocks from a library |
| Hardcoded strings for variable content | ${variable} interpolated at render time |
| Engineers required for prompt edits | Prompt engineers work directly in .pcl files |
What PCL does
Six focused capabilities. No runtime dependencies. Compiles to a plain string you drop into whatever API you're calling.
Break prompts into named @blocks. Reference them anywhere with
@include. Reuse across multiple agent configurations without copying.
@import ./shared/persona.pcl brings another file into scope.
Build a shared library of personas, tool descriptions, and guidelines that
multiple agents consume.
@if variable: includes content only when a variable is truthy.
@if not variable: for the inverse. Conditionals nest.
No comparison operators — stays simple by design.
${variable} is replaced at render time from a dictionary you provide.
Add a fallback with ${variable | default value}.
Undefined variables with no default are a compile error.
@raw / @end passes content through completely
unmodified — no interpolation, no directive processing. Ideal for embedded
JSON, code examples, or anything containing PCL special characters.
The compiled output is a single string. No PCL runtime in your application — just pass the string to your LLM API. Works with OpenAI, Anthropic, Gemini, local models, or any API that takes a system prompt.
See it in action
A research assistant system prompt — demonstrating every major language feature in one coherent file.
---1version: 1.2description: Research assistant system prompt---@import ./persona.pcl@import ./tools.pcl as tools2# Reusable date fragment@block date_notice:3 Today's date is ${date}.# A block composed from other blocks@block agent_setup:4 @include persona @include date_notice @include tools.search @include tools.browse# Main prompt bodyYou are a research assistant.@include agent_setup@if premium:5 You also have access to the premium document index.@if not premium: Upgrade to unlock the premium document index.# Embed a literal JSON example without triggering interpolation@raw6 When calling a tool, use this format: {"tool": "${tool_name}", "args": {}}@endThe user's query is: ${query | no query provided}7
YAML metadata between --- delimiters. Available via .metadata at compile time — never appears in output.
@import ./file.pcl as ns brings another file into scope under a namespace.
@block name: defines a named fragment. Emits no output until @included.
A block body can @include other blocks, even across imported files.
@if variable: and @if not variable: for truthiness-based inclusion. Nestable.
@raw / @end passes content through completely unmodified.
${var | fallback} uses the fallback if the variable is undefined at render time.
Interactive playground
Write PCL, set variables, see the compiled output instantly. Powered by Pyodide — no install required.
Getting started
Install the package, write a .pcl file, compile it — then pass
the output to your LLM API like any other string.
.pcl file
# agent.pcl@block persona: You are a helpful assistant.@include personaThe user asked: ${query}