A DSL for authoring, composing, and managing LLM prompts as modular, version-controlled artifacts
Project description
PCL — Prompt Composition Language
PCL is a small DSL for authoring, composing, and managing LLM prompts as modular, version-controlled artifacts. A .pcl file compiles to a plain text string ready to use as a system prompt with any LLM API.
Why PCL?
I came up with PCL as a way to manage prompts when they get too large and unwieldy. Most agentic applications today involve persisting prompts as a bunch of text/markdown files across disparate locations which are then read at runtime before using them for an LLM inference request. Parts of the prompt are often static (rules, persona, tools) while some can be more dynamic (user messages, context). Managing multiple sets of prompt .md files, with different versions, becomes a real engineering challenge. Template engines like Jinja alleviate this problem somewhat but I wanted something that went beyond simple variable substitution and was specifically tailored for LLM applications.
With PCL you get in-built support for templating, composition, compile time checks etc. Think of prompts that same way as you do code. You define your prompts as a modular set of text files (.pcl files), then have the compiler compile and weave the prompts together into a single IR template, which can be rendered at runtime with variable substitution.
pcl files are just like any other source- version controlled, with a well defined syntax.
Author
Tanay Deshmukh (& vibe-coded with Claude)
Features
- Blocks — define named fragments with
@block name:, compose them with@include - Imports — split prompts across files with
@import ./file.pcl [as ns] - Variables —
${var}resolved at render time;${var | default}for fallbacks - Conditionals —
@if variable:/@if not variable:for truthiness-based branching - Raw blocks —
@raw/@endpasses content through unmodified (no interpolation) - Comments — lines starting with
#are stripped from output - Frontmatter — optional YAML metadata (
version,description, arbitrary keys)
Installation
Requires Python 3.11+. Recommend using uv for environment management.
git clone <repo>
cd pcl
uv venv
source .venv/bin/activate
uv pip install -e ".[dev]"
Verify:
pcl --help
CLI
pcl compile
Compile a .pcl file and dump its intermediate representation — a tree of TEXT, VAR, and IF segments. Useful for inspecting template structure before rendering.
pcl compile examples/agent.pcl
Example output:
Metadata:
version: 1.0
description: Research assistant system prompt
Segments:
TEXT 'You are an expert research assistant...'
VAR ${date}
IF premium:
TEXT 'You have access to the premium document index.'
IF not premium:
TEXT 'Upgrade to unlock the premium document index.'
VAR ${query | no query provided}
Use -o/--output to write the compiled IR to a binary .pclc file instead of printing it. This enables compile-once-render-many workflows without re-parsing source files.
pcl compile examples/agent.pcl -o agent.pclc
# Compiled to agent.pclc
pcl render
Render a .pcl or .pclc file with explicit variable values.
# From source
pcl render examples/agent.pcl \
--var date=2026-02-28 \
--var query="What is alignment?" \
--var premium=true
# From pre-compiled .pclc (no re-parsing)
pcl render agent.pclc \
--var date=2026-02-28 \
--var query="What is alignment?" \
--var premium=true
--var accepts key=value. Values true and false are coerced to booleans.
pcl check
Validate a .pcl file without producing output. Exits 0 on success, 1 on error. Prints the error message and line number on failure.
pcl check examples/agent.pcl
pcl watch
Recompile whenever the file changes. Accepts the same --var flags as render.
pcl watch examples/agent.pcl \
--var date=2026-02-28 \
--var premium=false
Python API
from pcl import compile, render, serialize, deserialize, CompiledTemplate
import cbor2
# compile() — produces a CompiledTemplate (IR) with metadata and segments
template = compile("examples/agent.pcl")
print(template.metadata) # {"version": 1.0, "description": "..."}
print(template.segments) # [str, VarRef, Conditional, ...]
# render() — resolves variables and conditionals, returns final string
prompt = render("examples/agent.pcl", variables={
"date": "2026-02-28",
"query": "What is alignment?",
"premium": True,
})
print(prompt)
# compile once, render many — avoids re-parsing for each variable set
template = compile("examples/agent.pcl")
for query in queries:
prompt = render(template, variables={"date": "2026-02-28", "query": query})
# serialize to .pclc — write compiled IR to disk
with open("agent.pclc", "wb") as f:
f.write(cbor2.dumps(serialize(template)))
# deserialize from .pclc — reconstruct IR without re-parsing source
with open("agent.pclc", "rb") as f:
template = deserialize(cbor2.loads(f.read()))
prompt = render(template, variables={"date": "2026-02-28", "query": "..."})
Language Quick Reference
---
version: 1.0
description: Optional YAML frontmatter — available as metadata, not in output
---
@import ./other.pcl
@import ./lib.pcl as lib
# This is a comment — stripped from output
@block intro:
Defined here, emitted only when @include'd.
@include intro # emit a local block
@include lib.greet # emit a named block from an imported file
@include other # emit the entire body of other.pcl
@if premium:
Shown only when premium is truthy.
@if not premium:
Shown only when premium is falsy or absent.
Hello ${name}! # required variable — error if missing
Hello ${name | world}! # variable with default
@raw
${not_interpolated} @not_a_directive # this is not a comment
@end
\@block → literal @block in output
\# → literal # in output
\${ → literal ${ in output
Examples
The examples/ directory contains a multi-file prompt:
| File | Purpose |
|---|---|
examples/agent.pcl |
Main agent prompt — imports persona and tools |
examples/persona.pcl |
Persona blocks (researcher, brief) |
examples/tools.pcl |
Tool description blocks (search, browse, premium_index) |
pcl render examples/agent.pcl \
--var date=2026-02-28 \
--var query="Explain quantum entanglement" \
--var premium=false
Tests
uv run pytest tests/ -v
With coverage:
uv run pytest tests/ --cov=pcl --cov-report=term-missing
VS Code Extension
Syntax highlighting is in a separate repo: src/pcl-vscode.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pcl_lang-0.1.0.tar.gz.
File metadata
- Download URL: pcl_lang-0.1.0.tar.gz
- Upload date:
- Size: 19.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.7.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
89db22384aff48bdfd20d3631f3ec580d27e6594e857908cd2b144c284ad0d95
|
|
| MD5 |
0880756ab921e7dbb2a0b02d8df126e2
|
|
| BLAKE2b-256 |
8f7479f64483eda02bfcfb2e973a8c20fd95192f528860bc460e4bb692d70295
|
File details
Details for the file pcl_lang-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pcl_lang-0.1.0-py3-none-any.whl
- Upload date:
- Size: 13.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.7.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c42afb522c9e8c48516dbe28c9374949a3068528b178cbc6261652d90bb7e63
|
|
| MD5 |
0636623447cccc2822ae538ca21de8c9
|
|
| BLAKE2b-256 |
86ae8692822ac0ca6413e025119d4ab92853c66a0b84dca453359bce7c64ad1e
|