Prompts deserve
a proper language.

PCL compiles modular .pcl files into plain text system prompts — structured, version-controlled, and compatible with any LLM API.

Install

pip install pcl-lang
agent.pcl
compiled output

              
● Ready

Production prompts outgrow their containers.

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

Everything prompts need. Nothing they don't.

Six focused capabilities. No runtime dependencies. Compiles to a plain string you drop into whatever API you're calling.

Modular composition

Break prompts into named @blocks. Reference them anywhere with @include. Reuse across multiple agent configurations without copying.

File imports

@import ./shared/persona.pcl brings another file into scope. Build a shared library of personas, tool descriptions, and guidelines that multiple agents consume.

Conditional logic

@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 interpolation

${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 passthrough

@raw / @end passes content through completely unmodified — no interpolation, no directive processing. Ideal for embedded JSON, code examples, or anything containing PCL special characters.

Plain text output

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.


A complete PCL file.

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

Annotations

Frontmatter

YAML metadata between --- delimiters. Available via .metadata at compile time — never appears in output.

Import with alias

@import ./file.pcl as ns brings another file into scope under a namespace.

Block definition

@block name: defines a named fragment. Emits no output until @included.

Composed block

A block body can @include other blocks, even across imported files.

Conditional logic

@if variable: and @if not variable: for truthiness-based inclusion. Nestable.

Raw block

@raw / @end passes content through completely unmodified.

Variable with default

${var | fallback} uses the fallback if the variable is undefined at render time.

Try PCL in your browser.

Write PCL, set variables, see the compiled output instantly. Powered by Pyodide — no install required.


        
● Loading compiler…

Up and running in two minutes.

Install the package, write a .pcl file, compile it — then pass the output to your LLM API like any other string.

1 Install
$ pip install pcl-lang
2 Write a .pcl file
# agent.pcl@block persona:    You are a helpful assistant.@include personaThe user asked: ${query}
3 Compile and render
# validate without output
$ pcl check agent.pcl
# compile and print
$ pcl compile agent.pcl
# render with variables
$ pcl render agent.pcl --var query="What is alignment?"
4 Or use the Python API
# from your application code
from pcl import render
prompt = render(
    "./agent.pcl",
    variables={"query": "What is alignment?"}
)
# pass `prompt` to your LLM API