Skip to main content

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 / @end passes 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.

Metadata

Release files for pcl-lang 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pcl-lang 0.1.0
File Size Uploaded Provenance Actions
pcl_lang-0.1.0.tar.gz 19.2 kB Without provenance Details for pcl_lang-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for pcl-lang 0.1.0
File Interpreter ABI Platform Actions
pcl_lang-0.1.0-py3-none-any.whl Python 3 none any Details for pcl_lang-0.1.0-py3-none-any.whl

Total release size: 33.0 kB

Release files / pcl_lang-0.1.0.tar.gz

Download URL pcl_lang-0.1.0.tar.gz
Size 19.2 kB
Tags Source
SHA-256 checksum
How to use checksums
89db22384aff48bdfd20d3631f3ec580d27e6594e857908cd2b144c284ad0d95
BLAKE2b-256 checksum
How to use checksums
8f7479f64483eda02bfcfb2e973a8c20fd95192f528860bc460e4bb692d70295
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.7.9

Release files / pcl_lang-0.1.0-py3-none-any.whl

Download URL pcl_lang-0.1.0-py3-none-any.whl
Size 13.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5c42afb522c9e8c48516dbe28c9374949a3068528b178cbc6261652d90bb7e63
BLAKE2b-256 checksum
How to use checksums
86ae8692822ac0ca6413e025119d4ab92853c66a0b84dca453359bce7c64ad1e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.7.9

Security for pcl-lang 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page