htmlp

Documentation

HTMLP provides a prompt file format and a Rust interface for typed requests and usage accounting. Use either independently, or render checked files into attributed requests.

Install

Version 0.2.0-alpha.4 is distributed through GitHub, not crates.io. For the command-line checker:

cargo install --git https://github.com/alexmckenley/htmlp --tag v0.2.0-alpha.4 --features cli

For typed requests, add this Cargo dependency; add tokens to use the built-in tokenizer:

[dependencies]
htmlp = { git = "https://github.com/alexmckenley/htmlp", tag = "v0.2.0-alpha.4", features = ["runtime"] }

Prompt files

HTMLP 0.2 uses two elements: htmlp and section, plus {{name}} string placeholders. Content is literal Markdown. All declared limits require a nonblank reason.

Files and sections

<htmlp max-tokens="2k" reason="Shared context.">
<section id="system" max-tokens="500" reason="Loaded on every request.">
Make one change. Run its tests.
</section>
</htmlp>

The root limit covers the entire rendered text. A section limit covers all its descendants. Tags, attributes, and comments do not count. Whitespace inside the root does count; indentation is preserved.

1k is 1,000 tokens; 1.5k is 1,500. Counts use cl100k_base by default. Token counts depend on the tokenizer; this is not a universal model limit. The Rust API accepts a custom TokenCounter.

Limit each item

<section id="checks" max-tokens="10k" per-item="1k" reason="Keep checks concise.">
<section id="test">Run the affected tests.</section>
<section id="commit">Explain the change.</section>
</section>

Place this section inside an htmlp root. The total is capped at 10,000 tokens; each item at 1,000. per-item applies to each direct child section. The smaller of that cap and the child's own cap wins. One reason explains the limits declared on that element.

Sign budgets

Run htmlp sign ./prompts after authoring. Each declared budget gets a short sig covering max-tokens, per-item, and reason. Checking rejects missing or stale signatures and asks you to rerun htmlp sign for intentional changes. Content edits need no re-signing.

Review changes before signing; run only check in CI. The checksum is not approval: anyone can re-sign with the same reason or remove a constraint.

Variables

<htmlp max-tokens="1k" reason="Request context.">
Answer: {{question}}
</htmlp>

Variables have no limits or attributes. Static checks defer counts for sections containing variables. Rendering requires every referenced string binding and enforces file and section limits on the final text. Repeated names reuse the same binding; values are inserted literally.

htmlp sign request.htmlp
htmlp render request.htmlp --vars values.json

Write &#123;&#123;name}} for a literal {{name}}. JSON measurements use tokens: null and deferred: true until rendering.

values.json: {"question":"What changed?"}

Commands

htmlp sign ./prompts          # accept current budgets explicitly
htmlp check ./prompts         # each .htmlp file independently
htmlp check rules.htmlp --json
htmlp watch ./prompts         # editor diagnostics on changes
htmlp parse rules.htmlp       # syntax-checked JSON AST
htmlp compile rules.htmlp     # static budgets checked; variables deferred
htmlp render rules.htmlp      # checked prompt text

No ancestor rules or external configuration are loaded. Scans skip symlinks and .git, node_modules, target, dist, and vendor. A path with no matching files fails.

Rust and other languages

Use parse or parse_file for typed Document, Section, and Variable values. Construct nodes in memory with Document::new, Section::new, and Node::text. Call Document::sign() to accept in-memory budgets, then lint before use. sign_source preserves file formatting.

get_element_by_id returns a typed reference. sections() returns direct child sections. to_string() extracts text with visible {{name}} placeholders; use render for checked substitutions. IDs such as system are labels, not SDK roles.

Other languages can invoke the CLI and consume JSON. Native bindings and build-tool plugins are not included in this alpha.

Runtime prompts

Enable the Rust runtime feature for attributed PromptFragments, typed text/image/tool content, and ModelRequests. Every fragment requires a source category and role. request.estimate() provides categorized byte-based estimates with an explicit image reserve; TokenUsage keeps provider-reported usage separate. Missing attribution is None, not zero.

render_checked returns immutable text, its tokenizer measurement, and non-overlapping spans with section ancestry. section(id) selects from a fully checked document; PromptFragment::checked bridges it into a request. Category attributes are not yet supported in markup.

Rust interface guide and examples · Plain Markdown reference

Library features

FeatureEnables
DefaultTyped nodes, parsing, signing, linting, and rendering with a supplied TokenCounter.
runtimeTyped requests, attribution, estimates, and reported usage. Includes JSON support; no provider SDK or tokenizer tables.
tokensEmbedded Cl100k tokenizer.
jsonJSON serialization for document types.
schemaJSON Schema generation; includes json. Add runtime for request and usage schemas.
cliCommand-line binary; includes json and tokens.

Strict markup

Use lowercase tags, quoted attributes, and unique section IDs. Empty elements can self-close: <section id="notes" />. Otherwise use matching closing tags. Escape literal < as &lt; and & as &amp;, including inside Markdown code fences. Unknown elements and attributes fail. No browser error recovery is performed. Self-closing non-void elements follow XML rules, so ordinary HTML parsing is not equivalent.

Limits and reasons are editable source. Require review or CI policy to protect changes to budgets. A reason explains a constraint; it cannot prevent an agent from changing it.

Reference