<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

 <title>Huascar Sanchez Posts</title>
 <link href="http://huascarsanchez.com/atom.xml" rel="self"/>
 <link href="http://huascarsanchez.com/"/>
 <updated>2026-08-05T03:35:38+00:00</updated>
 <id>http://huascarsanchez.com/</id>
 <author>
   <name></name>
   <email></email>
 </author>

 
 
 <entry>
   <title>Gating Your LLM Eval Rubric with Evidence</title>
   <link href="http://huascarsanchez.com/posts/notes/gating-eval-rubric-with-evidence.html"/>
   <updated>2026-05-26T00:00:00+00:00</updated>
   <id>http://huascarsanchez.com/posts/notes/gating-eval-rubric-with-evidence</id>
   <content type="html">&lt;p&gt;&lt;em&gt;Part 2: &lt;a href=&quot;/posts/notes/managing-eval-rubric-like-code.html&quot;&gt;Managing Your LLM Eval Rubric Like Code&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Promptory’s staged releases, evidence documents, and release gates let you attach eval results to a rubric candidate and block promotion until that evidence passes. This post shows the workflow step by step. The full example is in &lt;a href=&quot;https://github.com/hsanchez/promptory/blob/main/examples/evals2.py&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;examples/evals2.py&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-gate-declaration&quot;&gt;The Gate Declaration&lt;/h2&gt;

&lt;p&gt;Add a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;release_gates&lt;/code&gt; block to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;promptspec.yaml&lt;/code&gt; before releasing any candidates:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;rubric.yaml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;required_variables&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;criteria&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;max_file_bytes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;100000&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;release_gates&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;evidence&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;eval&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;eval-run&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;required_status&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Any promotion attempt that lacks an evidence document of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kind: eval&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name: eval-run&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;status: pass&lt;/code&gt; raises &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PromptGateError&lt;/code&gt; and leaves &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;current.json&lt;/code&gt; unchanged.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;step-1-stage-a-candidate&quot;&gt;Step 1: Stage a Candidate&lt;/h2&gt;

&lt;p&gt;A staged release writes an immutable snapshot under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;versions/&lt;/code&gt; without updating &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;current.json&lt;/code&gt;. The active version is unaffected while the candidate is evaluated.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;promptory.manager&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PromptManager&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;manager&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PromptManager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;prompts&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;draft&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;manager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prompts_dir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;drafts&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;rubric.yaml.j2&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;draft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BASIC_RUBRIC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;v1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;manager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;bump&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;patch&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;variables&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;criteria&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;helpfulness&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;staged&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# v0.0.1 exists in versions/ but current.json is unchanged.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;step-2-attach-evidence&quot;&gt;Step 2: Attach Evidence&lt;/h2&gt;

&lt;p&gt;Load the staged rubric, run your eval harness, then write the results as an evidence document and attach it to the version.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;promptory&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PromptStore&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;promptory.evidence&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;add_evidence&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;store&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PromptStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;prompts&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;rubric&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;rubric.yaml&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;metrics&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;run_eval_harness&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rubric&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;evidence_doc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;kind&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;eval&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;eval-run&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;status&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;fail&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;          &lt;span class=&quot;c1&quot;&gt;# below threshold: 60% accuracy
&lt;/span&gt;  &lt;span class=&quot;s&quot;&gt;&quot;tool&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;eval-harness&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;created_at&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;2026-05-28T12:00:00Z&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;summary&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;accuracy=60%&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;metrics&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;metrics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;evidence_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dumps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;evidence_doc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;add_evidence&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;manager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;evidence_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Evidence is immutable once attached.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;step-3-check-the-gate&quot;&gt;Step 3: Check the Gate&lt;/h2&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;gate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;manager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# False — evidence status is fail, required pass
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;passed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v1&lt;/code&gt; stays staged.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;step-4-iterate&quot;&gt;Step 4: Iterate&lt;/h2&gt;

&lt;p&gt;Revise the rubric draft and stage a new candidate.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;draft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;STRICT_RUBRIC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;v2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;manager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;bump&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;patch&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;variables&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;criteria&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;helpfulness&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;staged&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;rubric_v2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;rubric.yaml&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;metrics_v2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;run_eval_harness&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rubric_v2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;evidence_doc_v2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;kind&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;eval&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;eval-run&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;status&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;pass&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;          &lt;span class=&quot;c1&quot;&gt;# 100% accuracy clears the threshold
&lt;/span&gt;  &lt;span class=&quot;s&quot;&gt;&quot;tool&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;eval-harness&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;created_at&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;2026-05-28T13:00:00Z&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;summary&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;accuracy=100%&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;metrics&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;metrics_v2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;add_evidence&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;manager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;evidence_path_v2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;gate_v2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;manager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gate_v2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;passed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# True
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;step-5-compare-evidence&quot;&gt;Step 5: Compare Evidence&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compare_evidence&lt;/code&gt; diffs the attached evidence between two versions.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;promptory.evidence&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;compare_evidence&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;comparison&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;compare_evidence&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;manager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;change&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;comparison&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;changes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;[&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;change&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;] &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;change&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\
&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;    &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;change&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;before_status&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; -&amp;gt; &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;change&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;after_status&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;metric&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;change&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;metrics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;  &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;metric&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;metric&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;before&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; -&amp;gt; &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;metric&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Output:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[eval] eval-run: fail -&amp;gt; pass
  accuracy: 0.6 -&amp;gt; 1.0
  false_positive_rate: 1.0 -&amp;gt; 0.0
  mean_score: 4.6 -&amp;gt; 3.4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;step-6-promote&quot;&gt;Step 6: Promote&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require_gates=True&lt;/code&gt; makes the gate check part of the promotion call. If evidence is missing or fails, the call raises &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PromptGateError&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;current.json&lt;/code&gt; is not updated.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;manager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;promote&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;require_gates&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# v0.0.2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v1&lt;/code&gt; remains staged. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v2&lt;/code&gt; is now current.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;running-the-example&quot;&gt;Running the Example&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uv run python examples/evals2.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Expected output:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[v1 basic] staged v0.0.1
  accuracy=60%  fpr=100%  fnr=0%
  gate: FAIL - candidate stays staged

[v2 strict] staged v0.0.2
  accuracy=100%  fpr=0%  fnr=0%
  gate: PASS

Evidence comparison v0.0.1 -&amp;gt; v0.0.2:
  [eval] eval-run: fail -&amp;gt; pass
    accuracy: 0.6 -&amp;gt; 1.0
    false_positive_rate: 1.0 -&amp;gt; 0.0
    mean_score: 4.6 -&amp;gt; 3.4

Promoted v0.0.2 -&amp;gt; current: v0.0.2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;what-promptory-does-not-do&quot;&gt;What Promptory Does Not Do&lt;/h2&gt;

&lt;p&gt;Promptory stores and validates the evidence document shape. It does not run evals, call LLMs, define metric thresholds, or decide whether a rubric is correct.&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Managing Your LLM Eval Rubric Like Code</title>
   <link href="http://huascarsanchez.com/posts/notes/managing-eval-rubric-like-code.html"/>
   <updated>2026-05-19T00:00:00+00:00</updated>
   <id>http://huascarsanchez.com/posts/notes/managing-eval-rubric-like-code</id>
   <content type="html">&lt;p&gt;Promptory manages your eval rubric as a versioned prompt artifact: drafts,
immutable releases, template variables, and version history. This post shows
how to draft, release, and load rubric versions using the Python API.&lt;/p&gt;

&lt;p&gt;The full runnable example is in &lt;a href=&quot;https://github.com/hsanchez/promptory/blob/main/examples/evals.py&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;examples/evals.py&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-rubric-template&quot;&gt;The Rubric Template&lt;/h2&gt;

&lt;p&gt;A rubric prompt in Promptory is a Jinja template. Declare the file and its
required variables in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;promptspec.yaml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;rubric.yaml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;required_variables&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;criteria&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;max_file_bytes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;100000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The draft template lives in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prompts/drafts/rubric.yaml.j2&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;claude-sonnet-4-6&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;temperature&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0.0&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;system_prompt&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;Evaluate the response on {{ criteria }}.&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;Be generous; give partial credit for effort.&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;Return JSON: {score: int, rationale: str}.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{{ criteria }}&lt;/code&gt; is a template variable. It is rendered at release time, not
at runtime.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;template-variables&quot;&gt;Template Variables&lt;/h2&gt;

&lt;p&gt;Release the same rubric template with different variable values to produce
separate versioned artifacts for each eval dimension.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;promptory.manager&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PromptManager&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;manager&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PromptManager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;prompts&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;v_helpfulness&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;manager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;bump&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;patch&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;variables&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;criteria&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;helpfulness&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;v_factual&lt;/span&gt;     &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;manager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;bump&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;patch&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;variables&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;criteria&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;factual accuracy&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Each release is an independent rendered artifact. Loading &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v_helpfulness&lt;/code&gt; always
returns the rubric with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;criteria=&quot;helpfulness&quot;&lt;/code&gt; regardless of subsequent
releases.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;draft-and-release-separation&quot;&gt;Draft and Release Separation&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prompts/drafts/&lt;/code&gt; holds the working template. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prompts/versions/&lt;/code&gt; holds
immutable rendered releases. Editing the draft and cutting a new release does
not modify any previous release.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;BASIC_RUBRIC&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;model: claude-sonnet-4-6&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;temperature: 0.0&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;system_prompt: |&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;  Evaluate the response on {{ criteria }}.&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;  Be generous; give partial credit for effort.&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;  Return JSON: {score: int, rationale: str}.&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;STRICT_RUBRIC&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;model: claude-sonnet-4-6&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;temperature: 0.0&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;system_prompt: |&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;  Evaluate the response on {{ criteria }}.&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;  Penalize vague or incomplete answers.&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;  Return JSON: {score: int, rationale: str}.&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# v1: initial rubric.
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;draft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BASIC_RUBRIC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;v1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;manager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;bump&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;patch&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;variables&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;criteria&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;helpfulness&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# v2: updated rubric. v1 is unchanged.
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;draft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;STRICT_RUBRIC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;v2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;manager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;bump&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;patch&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;variables&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;criteria&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;helpfulness&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The directory &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prompts/versions/v0.0.1/&lt;/code&gt; is never touched after creation.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;loading-by-version&quot;&gt;Loading by Version&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PromptStore.load&lt;/code&gt; accepts an optional &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;version&lt;/code&gt; argument. When supplied, it
reads from that exact release directory regardless of which version is current.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;promptory&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PromptStore&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;store&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PromptStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;prompts&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;rubric_v1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;rubric.yaml&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;rubric_v2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;rubric.yaml&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Pass rubric[&quot;system_prompt&quot;] to your eval harness.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The result is identical whether &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v1&lt;/code&gt; is current or not.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;version-history&quot;&gt;Version History&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PromptStore.list_versions&lt;/code&gt; returns all available releases in semantic version
order.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list_versions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# [&apos;v0.0.1&apos;, &apos;v0.0.2&apos;, &apos;v0.0.3&apos;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Use this to scan all candidates and compare results across versions.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;version      criteria               accuracy      fpr      fnr
v0.0.1        helpfulness              60%      100%        0%
v0.0.2        helpfulness             100%        0%        0%
v0.0.3        factual accuracy        100%        0%        0%
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v0.0.2&lt;/code&gt; has the best metrics for helpfulness. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v0.0.3&lt;/code&gt; applies the same rubric
instructions to a different eval dimension without re-authoring the template.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;running-the-example&quot;&gt;Running the Example&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uv run python examples/evals.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The example uses a simulated judge and fixed benchmark data. Replace
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;simulate_judge_call&lt;/code&gt; with a real LLM call to run it against your own eval
suite.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;what-promptory-does-not-do&quot;&gt;What Promptory Does Not Do&lt;/h2&gt;

&lt;p&gt;Promptory manages the rubric prompt. It does not run evals, call LLMs, manage
benchmark datasets, or decide whether one rubric is better than another.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;Part 2: &lt;a href=&quot;/posts/notes/gating-eval-rubric-with-evidence.html&quot;&gt;Gating Your LLM Eval Rubric with Evidence&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>Promptory, a Git-based prompt versioning system</title>
   <link href="http://huascarsanchez.com/posts/notes/promptory.html"/>
   <updated>2026-05-14T00:00:00+00:00</updated>
   <id>http://huascarsanchez.com/posts/notes/promptory</id>
   <content type="html">&lt;p&gt;In many organizations building GenAI solutions, prompts have become production
artifacts. However, despite their importance, many teams still treat them like
ordinary strings. Treating them this way might work for a while, but as the
prompts evolve, as their performance becomes variable across different models,
model versions, safety policies, or use cases, it becomes more complex and
harder to manage them as strings.&lt;/p&gt;

&lt;p&gt;That is the motivation behind
&lt;a href=&quot;https://github.com/hsanchez/promptory&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Promptory&lt;/code&gt;&lt;/a&gt;: a Git-based prompt
versioning system that gives prompt changes the same basic engineering
discipline as code. The core idea is simple:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Author prompts as editable drafts, release them as immutable rendered
artifacts, and make runtime code load only released versions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;the-prompt-management-problem&quot;&gt;The prompt management problem&lt;/h2&gt;

&lt;p&gt;Prompt changes are deceptively small. Changing a few words can alter:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;how much context the model uses&lt;/li&gt;
  &lt;li&gt;whether it follows a tool policy&lt;/li&gt;
  &lt;li&gt;how it handles uncertain answers&lt;/li&gt;
  &lt;li&gt;whether it refuses unsafe requests&lt;/li&gt;
  &lt;li&gt;which format downstream parsers expect&lt;/li&gt;
  &lt;li&gt;how it behaves under evals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In traditional software engineering, anything that can change production
behavior is managed deliberately: version control, review, tests, releases, and
rollback. I believe that &lt;em&gt;prompt engineering&lt;/em&gt; should not be exempt from that
discipline just because the artifact is text. On the contrary, it should also be
managed deliberately.&lt;/p&gt;

&lt;p&gt;The hard part is choosing the right amount of discipline. In practice, teams
often land on one of two extremes. The first one is the hardcoded prompt:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;SYSTEM_PROMPT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
You are a helpful assistant. Answer concisely.
&quot;&quot;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Hardcoding a prompt like this is fine for a prototype, but it’s brittle for a
system that needs repeatable experiments, reviewable changes, or rollback.&lt;/p&gt;

&lt;p&gt;The second one is &lt;em&gt;overcorrection&lt;/em&gt;. This is the process of adopting a large
hosted prompt platform before the team actually needs one. Those systems can be
useful when non-technical users need a UI, many teams need centralized
governance, or the current prompt metadata no longer fits a particular
repository workflow.&lt;/p&gt;

&lt;p&gt;I strongtly believe fast-moving R&amp;amp;D teams often need something smaller:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;plain files&lt;/li&gt;
  &lt;li&gt;Git review&lt;/li&gt;
  &lt;li&gt;CI-friendly checks&lt;/li&gt;
  &lt;li&gt;immutable releases&lt;/li&gt;
  &lt;li&gt;a clear pointer to the active version&lt;/li&gt;
  &lt;li&gt;runtime loading that never reads work-in-progress drafts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Promptory is aimed at that middle ground.&lt;/p&gt;

&lt;h2 id=&quot;why-git-alone-is-not-enough&quot;&gt;Why Git alone is not enough&lt;/h2&gt;

&lt;p&gt;Why not just use Git? That’s the obvious pushback I get when socializing
Promptory. Git already handles reviewing and preserving prompt history just
fine, they say. That is true. However, Git doesn’t define a prompt lifecycle.&lt;/p&gt;

&lt;p&gt;In other words, Git can tell you that a prompt file changed, but it cannot tell
your application:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;which rendered prompt version is active&lt;/li&gt;
  &lt;li&gt;whether a prompt has unresolved template variables&lt;/li&gt;
  &lt;li&gt;whether all managed prompt files were released together&lt;/li&gt;
  &lt;li&gt;whether a runtime process is reading drafts or released artifacts&lt;/li&gt;
  &lt;li&gt;how to roll back without rewriting prompt history&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why Promptory keeps Git as the durable history and adds a small release
model on top of it.&lt;/p&gt;

&lt;h3 id=&quot;why-not-a-central-registry&quot;&gt;Why not a central registry?&lt;/h3&gt;

&lt;p&gt;Promptory’s answer is a deliberate ownership choice: prompts live with the code
that uses them. This builds on the same &lt;em&gt;locality of change&lt;/em&gt; principle from
software engineering — normally applied to code — and extends it to prompts.
Specifically, if a prompt shapes a service’s behavior, it should be reviewed,
released, and rolled back with that service.&lt;/p&gt;

&lt;p&gt;A central registry can be useful for organization-wide discovery, shared
prompts, or non-engineering workflows. But not so much for application-specific
prompts, which should live next to the implementation to make review and
rollback simpler. The prompt diff, code diff, tests, and release artifact all
move through the same Git workflow.&lt;/p&gt;

&lt;p&gt;This ownership choice doesn’t rule out central visibility. In production, teams can still
publish released prompt artifacts to object storage, an internal artifact
registry, or Promptory’s read-only registry service. The repo remains the
source of truth; production systems only aggregate or serve released artifacts
from there.&lt;/p&gt;

&lt;p&gt;In the repo, that looks something like this:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;prompts/
  drafts/
    system.yaml.j2
    input_guardrail.yaml.j2
    output_guardrail.yaml.j2
  versions/
    v0.1.0/
      system.yaml
      input_guardrail.yaml
      output_guardrail.yaml
      metadata.json
  current.json
  promptspec.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The above layout breaks down into four parts, each with a distinct role:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drafts/&lt;/code&gt; contains editable &lt;em&gt;Jinja&lt;/em&gt; templates&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;versions/&lt;/code&gt; contains rendered release artifacts&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;current.json&lt;/code&gt; points to the active release&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;promptspec.yaml&lt;/code&gt; declares which prompt files are managed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two of those four parts carry the real distinction. Developers edit drafts.
Applications load versions. That boundary, simple as it sounds, is the main
design decision in the project.&lt;/p&gt;

&lt;h2 id=&quot;drafts-are-for-authoring&quot;&gt;Drafts are for authoring&lt;/h2&gt;

&lt;p&gt;A draft is where prompt authors and coding agents work. It’s written in plain
YAML. For example:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# prompts/drafts/system.yaml.j2&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;gpt-5.5&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;temperature&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0.2&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;system_prompt&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;You are a helpful assistant.&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;Answer concisely and avoid unsupported claims.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Because drafts are Jinja templates rather than static text, they can also
include release-time variables:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# prompts/drafts/message.yaml.j2&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;Hello {{ user_name }}.&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;Generated at {{ generation_time }}.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Promptory renders these with Jinja’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StrictUndefined&lt;/code&gt;, on purpose. A missing
variable throws an exception instead of quietly becoming an empty string. Silent
prompt rendering failures are hard to debug because they often look like model
behavior problems later.&lt;/p&gt;

&lt;p&gt;Rendering itself happens at release time, through two commands:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uv run prompt check
uv run prompt release &lt;span class=&quot;nt&quot;&gt;--patch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prompt check&lt;/code&gt; catches problems early: bad drafts, missing variable
declarations. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prompt release&lt;/code&gt; does the real work. It renders the declared
templates, parses the rendered YAML, and drops the output into a new immutable
version directory before updating &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;current.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Variables don’t always come from the CLI, though. When a release needs them
supplied programmatically, the Python API takes them directly:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;promptory.manager&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PromptManager&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PromptManager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;prompts&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;variables&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;user_name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Alice&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;generation_time&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;2026-05-09T12:00:00Z&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The expected variables live in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;promptspec.yaml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;system.yaml&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;message.yaml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;required_variables&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;user_name&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;generation_time&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;max_file_bytes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;100000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This file is the contract for the prompt directory. It says which rendered YAML
files Promptory manages and which variables must be supplied before release.&lt;/p&gt;

&lt;h2 id=&quot;versions-are-for-runtime&quot;&gt;Versions are for runtime&lt;/h2&gt;

&lt;p&gt;A release turns drafts into rendered YAML artifacts:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uv run prompt check
uv run prompt release &lt;span class=&quot;nt&quot;&gt;--patch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After release, Promptory writes a semantic version directory:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;prompts/
  versions/
    v0.0.1/
      system.yaml
      message.yaml
      metadata.json
  current.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Runtime code should not read from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drafts/&lt;/code&gt;. It should read from the active
version:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;promptory&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PromptStore&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;store&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PromptStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;prompts&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;system&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;system.yaml&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;message.yaml&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PromptStore&lt;/code&gt; reads &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;current.json&lt;/code&gt; and validates the requested prompt name
against &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;promptspec.yaml&lt;/code&gt;. Then it loads rendered YAML from
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;versions/&amp;lt;version&amp;gt;/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The runtime path is deliberately boring as a result: no Jinja rendering at
runtime, no accidental use of draft prompts, and no hidden prompt selection
logic inside the application.&lt;/p&gt;

&lt;p&gt;Applications can also load a specific version:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;store&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PromptStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;prompts&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;system_v1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;system.yaml&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;v0.0.1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;all_v2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;load_all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;v0.0.2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Pinning to a specific version like this is useful for evals and debugging. If an
output changed between two runs, then the prompt version becomes part of the evidence.&lt;/p&gt;

&lt;h2 id=&quot;the-release-pointer&quot;&gt;The release pointer&lt;/h2&gt;

&lt;p&gt;The small file that makes the runtime story work is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;current.json&lt;/code&gt;. See below:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v0.0.1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;updated_at&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2026-05-09T22:14:00.000000+00:00&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This small file is the active release pointer, which makes the runtime story
work. When application code loads the current prompt, it is not asking, “What is
in the drafts directory today?” but rather, “Which release is currently active?”&lt;/p&gt;

&lt;p&gt;Rollback is therefore a pointer change, not a rewrite, accomplished with a
single command:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uv run prompt rollback v0.0.1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The release artifacts remain immutable. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;current.json&lt;/code&gt; moves back to an existing
version.&lt;/p&gt;

&lt;p&gt;This is closer to how many deployment systems work. You do not edit the old
release in place. You point the system at a known-good artifact.&lt;/p&gt;

&lt;h2 id=&quot;a-concrete-workflow&quot;&gt;A concrete workflow&lt;/h2&gt;

&lt;p&gt;Imagine a research team maintaining a small suite of prompts for a
customer-facing assistant. Rather than one monolithic prompt, they’ve split
responsibilities across three separate files:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;system.yaml&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;input_guardrail.yaml&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;output_guardrail.yaml&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The team captures this list in a spec, along with a few constraints each file
must satisfy:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;system.yaml&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;input_guardrail.yaml&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;output_guardrail.yaml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;required_variables&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[]&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;max_file_bytes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;100000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ultimately, the team doesn’t edit those files directly. They edit
Jinja-templated drafts under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prompts/drafts/&lt;/code&gt;, which get rendered into the
finished files the spec expects:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# prompts/drafts/input_guardrail.yaml.j2&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;policy&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;Reject requests for secrets, credentials, or private keys.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# prompts/drafts/output_guardrail.yaml.j2&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;policy&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;Answer concisely and avoid unsupported claims.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Before releasing, the team checks the prompt set:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uv run prompt check
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then it previews the difference between the active release and rendered drafts:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uv run prompt diff
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once that looks right, it creates a new release:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uv run prompt release &lt;span class=&quot;nt&quot;&gt;--patch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;From there, the application consumes the released files:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;promptory&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PromptStore&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;store&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PromptStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;prompts&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;system&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;system.yaml&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;input_guardrail&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;input_guardrail.yaml&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;output_guardrail&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;output_guardrail.yaml&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;messages&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;system&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;system_prompt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]},&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;developer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;input_guardrail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;policy&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]},&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;developer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;output_guardrail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;policy&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]},&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That is the whole loop:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;edit draft -&amp;gt; check -&amp;gt; diff -&amp;gt; release -&amp;gt; runtime loads current version
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;the-authoringruntime-split&quot;&gt;The authoring/runtime split&lt;/h2&gt;

&lt;p&gt;Promptory is built around a strict boundary.&lt;/p&gt;

&lt;p&gt;Authoring tools can write prompt lifecycle state:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prompt init&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prompt check&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prompt diff&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prompt release&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prompt rollback&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prompt draft&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Runtime code reads released prompt state:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;active version from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;current.json&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;rendered YAML from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;versions/&amp;lt;version&amp;gt;/&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;file declarations from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;promptspec.yaml&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This split matters because prompt systems often blur authoring and runtime
concerns. If runtime code renders templates, then a missing variable becomes a
runtime incident. If runtime code reads drafts, then unreviewed edits can affect
production behavior. If applications choose prompt files directly, then the
promptspec contract is bypassed.&lt;/p&gt;

&lt;p&gt;Promptory keeps the runtime path small on purpose.&lt;/p&gt;

&lt;h2 id=&quot;what-the-checks-protect&quot;&gt;What the checks protect&lt;/h2&gt;

&lt;p&gt;The linter is not trying to prove that a prompt is good. It is trying to catch
the kinds of mechanical errors that should not reach a release.&lt;/p&gt;

&lt;p&gt;For example, Promptory checks:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;whether declared draft templates exist&lt;/li&gt;
  &lt;li&gt;whether templates have valid Jinja syntax&lt;/li&gt;
  &lt;li&gt;whether rendered artifacts are valid YAML&lt;/li&gt;
  &lt;li&gt;whether required variables are declared&lt;/li&gt;
  &lt;li&gt;whether undeclared variables are used&lt;/li&gt;
  &lt;li&gt;whether managed files are relative &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.yaml&lt;/code&gt; paths&lt;/li&gt;
  &lt;li&gt;whether file sizes exceed configured limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These checks are intentionally mundane, and that’s the point: a prompt release
should not fail because a template variable rendered to an empty string, a YAML
file stopped parsing, or an application loaded a file outside the managed prompt
set.&lt;/p&gt;

&lt;h2 id=&quot;why-immutable-releases-matter&quot;&gt;Why immutable releases matter&lt;/h2&gt;

&lt;p&gt;Immutable prompt releases give three practical benefits.&lt;/p&gt;

&lt;p&gt;First, they make review clearer. A reviewer can inspect both the editable draft
and the rendered artifact that runtime code will load.&lt;/p&gt;

&lt;p&gt;Second, they make debugging easier. If a model behavior changed after a release,
you can load the exact prompt version used by that run.&lt;/p&gt;

&lt;p&gt;Third, they make rollback safer. Rollback does not require editing prompt
content. It only changes the active pointer to a previously released artifact.&lt;/p&gt;

&lt;p&gt;This is the same reason build artifacts, container images, and database
migrations tend to get explicit versioning in mature systems. Prompts are not
identical to those artifacts, but they have the same operational pressure: they
change behavior.&lt;/p&gt;

&lt;h2 id=&quot;serving-prompts-to-non-python-clients&quot;&gt;Serving prompts to non-Python clients&lt;/h2&gt;

&lt;p&gt;The core Promptory model is file-based, but not every consumer is Python.&lt;/p&gt;

&lt;p&gt;For Go, TypeScript, or mixed-language systems, Promptory can expose released
prompts through a small registry service:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uv run prompt serve &lt;span class=&quot;nt&quot;&gt;--port&lt;/span&gt; 8000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The service exposes endpoints such as:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;GET /versions
GET /versions/current
GET /prompts
GET /prompts/{name}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This does not create a second prompt lifecycle. The service is a read-only layer
over the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PromptStore&lt;/code&gt; contract: it reads &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;current.json&lt;/code&gt;, validates prompt
names, and serves rendered release artifacts, existing for consumption, not
authoring.&lt;/p&gt;

&lt;h2 id=&quot;what-promptory-is-not&quot;&gt;What Promptory is not&lt;/h2&gt;

&lt;p&gt;Promptory is deliberately small.&lt;/p&gt;

&lt;p&gt;It is not:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;a hosted prompt CMS&lt;/li&gt;
  &lt;li&gt;an experiment tracking platform&lt;/li&gt;
  &lt;li&gt;an eval runner&lt;/li&gt;
  &lt;li&gt;a deployment system&lt;/li&gt;
  &lt;li&gt;a replacement for Git&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It does not try to answer every question around prompt governance. Instead, it
focuses on one narrow lifecycle:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;turn draft prompt templates into versioned, reviewable, immutable runtime
artifacts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That narrowness is useful. It keeps the tool understandable and lets teams wire
it into their existing Git, CI, eval, and deployment workflows.&lt;/p&gt;

&lt;h2 id=&quot;closing-thought&quot;&gt;Closing thought&lt;/h2&gt;

&lt;p&gt;Prompt changes are software changes. They may be written in natural language,
but they alter system behavior just as a code change does. Consequently, they
deserve the same basic lifecycle we already trust for code: review, validation,
versioning, release, and rollback. Promptory is my attempt to make that
lifecycle lightweight enough to not get in the way of day-to-day research work,
while still giving production systems the stable, predictable runtime contract
they need.&lt;/p&gt;
</content>
 </entry>
 
 
 
 <entry>
   <title>LLM as an Oracle</title>
   <link href="http://huascarsanchez.com/posts/notes/llm-as-an-oracle.html"/>
   <updated>2026-05-13T00:00:00+00:00</updated>
   <id>http://huascarsanchez.com/posts/notes/llm-as-an-oracle</id>
   <content type="html">&lt;p&gt;Most discussions of LLM evaluation ask which evaluator is best. Wrong first
question. What actually matters is whether the task needs judgment or
verification. A rubric is great for deciding if an explanation is clear or
persuasive, but it’s a weak substitute for test cases when you’re evaluating
code. Exact-match checks have the opposite problem: they fall apart the moment
the output is qualitative.&lt;/p&gt;

&lt;p&gt;That split is what led to
&lt;a href=&quot;https://github.com/hsanchez/llm-as-an-oracle&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;llm-as-an-oracle&lt;/code&gt;&lt;/a&gt;. An Oracle
here isn’t an all-knowing model. It’s an adaptive layer that routes each task to
either an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LLM-as-a-Judge&lt;/code&gt; or an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LLM-as-a-Verifier&lt;/code&gt;, depending on which kind of
evaluation the task actually needs.&lt;/p&gt;

&lt;p&gt;The central claim is simple:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Evaluation should be routed to the strategy that best matches the structure
of the task.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is why that matters in practice. Later in this post, three agents fix the
same N+1 query bug. Two of them change the query shape. The third wraps the
buggy call in an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lru_cache&lt;/code&gt; and looks correct — familiar technique, concrete
code, a plausible performance story. A Judge scoring on presentation alone can
be fooled by it. A Verifier running the test suite cannot. That gap between
looking right and being right is the reason this router exists.&lt;/p&gt;

&lt;p&gt;That sounds obvious once stated plainly, but it is easy to violate in practice.
As soon as a benchmark, agent workflow, or production evaluation pipeline
standardizes on a single evaluator, it begins to treat fundamentally different
tasks as though they required the same kind of evidence.&lt;/p&gt;

&lt;h2 id=&quot;the-evaluation-problem&quot;&gt;The evaluation problem&lt;/h2&gt;

&lt;p&gt;Human evaluation remains the reference point for many LLM systems. It is often
the most flexible form of assessment because humans can interpret incomplete
instructions, account for context, distinguish severity from style, and notice
when a candidate answer is technically correct but pragmatically poor. It also
scales badly, which created demand for automated evaluation. Traditional
metrics can be useful, but they are narrow:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;exact match is valuable when the answer space is constrained&lt;/li&gt;
  &lt;li&gt;unit tests are valuable when executable behavior matters&lt;/li&gt;
  &lt;li&gt;overlap metrics can be useful in narrow summarization settings&lt;/li&gt;
  &lt;li&gt;preference labels can summarize subjective quality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these solves the broader evaluation problem on its own.&lt;/p&gt;

&lt;p&gt;The rise of capable instruction-following models created a new option:
LLM-based evaluators. This has produced a family of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LLM-as-*&lt;/code&gt; patterns:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LLM-as-a-Judge&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LLM-as-a-Verifier&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LLM-as-a-Critic&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LLM-as-a-Ranker&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These ideas have appeared across several lines of work on model-based judging,
verification, critique generation, and ranking. &lt;sup id=&quot;fnref:llm-as-family&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:llm-as-family&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;These patterns are often discussed as alternatives. I think they are better
understood as evaluation modes with different operating assumptions.&lt;/p&gt;

&lt;p&gt;The question is not merely whether LLM evaluators are useful. The more precise
question is:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Which evaluator is appropriate for this task, given the evidence available?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;judge-and-verifier-solve-different-problems&quot;&gt;Judge and Verifier solve different problems&lt;/h2&gt;

&lt;p&gt;The easiest way to understand the Oracle idea is to first separate the two
evaluation strategies it routes between.&lt;/p&gt;

&lt;h3 id=&quot;llm-as-a-judge&quot;&gt;LLM-as-a-Judge&lt;/h3&gt;

&lt;p&gt;An &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LLM-as-a-Judge&lt;/code&gt; performs holistic evaluation. It reads the task, candidate
trajectory, and evaluation criteria, then emits a score or preference. This is
the natural fit when the target quality is open-ended, subjective, or otherwise
difficult to reduce to executable checks. &lt;sup id=&quot;fnref:judge&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:judge&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Typical Judge-friendly questions include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Is this answer concise without omitting important details?&lt;/li&gt;
  &lt;li&gt;Does this explanation match the user’s level of expertise?&lt;/li&gt;
  &lt;li&gt;Which recommendation is more useful under vague constraints?&lt;/li&gt;
  &lt;li&gt;Is the reasoning persuasive and coherent?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Judge pattern is valuable because many real tasks do not collapse cleanly
into executable checks. &lt;em&gt;They require interpretation.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;llm-as-an-oracle&lt;/code&gt;, the Judge strategy supports:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;rubric-driven scoring&lt;/li&gt;
  &lt;li&gt;pointwise trajectory scoring&lt;/li&gt;
  &lt;li&gt;pairwise comparisons&lt;/li&gt;
  &lt;li&gt;order-swapped pairwise evaluation to reduce positional bias&lt;/li&gt;
  &lt;li&gt;aggregation across multiple criteria&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These details matter because a Judge is not merely “ask another model what it
thinks.” A useful Judge has structure around how scores are produced and how
comparisons are stabilized.&lt;/p&gt;

&lt;p&gt;For example, a Judge can score each trajectory against several weighted criteria,
then use pairwise comparisons only when two candidates are close. If the pairwise
order is swapped and averaged, the system can reduce simple positional bias
without pretending that the evaluator has become objective. &lt;sup id=&quot;fnref:judge:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:judge&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;h3 id=&quot;llm-as-a-verifier&quot;&gt;LLM-as-a-Verifier&lt;/h3&gt;

&lt;p&gt;An &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LLM-as-a-Verifier&lt;/code&gt; is better suited to tasks where stronger evidence exists.
It is appropriate when candidate trajectories can be evaluated against signals
that are closer to correctness than preference.
&lt;sup id=&quot;fnref:verifier&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:verifier&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Typical Verifier-friendly tasks include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;code generation with tests&lt;/li&gt;
  &lt;li&gt;question answering with reference answers&lt;/li&gt;
  &lt;li&gt;tool-use traces with expected outputs&lt;/li&gt;
  &lt;li&gt;structured reasoning tasks with decomposable criteria&lt;/li&gt;
  &lt;li&gt;tasks where execution evidence is available&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Verifier strategy in this project is designed around:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;finer-grained score extraction&lt;/li&gt;
  &lt;li&gt;repeated verification&lt;/li&gt;
  &lt;li&gt;criteria decomposition&lt;/li&gt;
  &lt;li&gt;pairwise tournament-style ranking&lt;/li&gt;
  &lt;li&gt;support for logprob-aware scoring when the provider exposes token
probabilities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point is important. A Verifier tries to squeeze more discriminative
signal out of the evaluator than a single coarse score can provide.&lt;/p&gt;

&lt;p&gt;The Judge is asking which answer seems better under a rubric; the Verifier is
asking which trajectory survives the strongest evidence-sensitive checks
available. Related questions, but not the same one.&lt;/p&gt;

&lt;h2 id=&quot;what-the-oracle-adds&quot;&gt;What the Oracle adds&lt;/h2&gt;

&lt;p&gt;If Judge and Verifier are both useful, a natural response is to expose both and
let the caller choose. That’s necessary but not sufficient. Many workflows mix
task types:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;an agent may produce code patches, explanations, and planning notes&lt;/li&gt;
  &lt;li&gt;a benchmark may combine factual QA, long-form reasoning, and executable tasks&lt;/li&gt;
  &lt;li&gt;a production system may need to evaluate recommendations, SQL, and tool calls
within the same pipeline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In those settings, asking the caller to manually select an evaluator every time
creates friction and invites inconsistency.&lt;/p&gt;

&lt;p&gt;The Oracle layer addresses that problem.&lt;/p&gt;

&lt;p&gt;Its job is to:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;inspect the task and trajectories&lt;/li&gt;
  &lt;li&gt;extract signals about the task structure&lt;/li&gt;
  &lt;li&gt;decide which evaluator is the better fit&lt;/li&gt;
  &lt;li&gt;execute only that strategy&lt;/li&gt;
  &lt;li&gt;return both the result and the routing explanation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Oracle is therefore not a third evaluator. It is a decision layer above the
two evaluators.&lt;/p&gt;

&lt;h2 id=&quot;anatomy-of-the-oracle-router&quot;&gt;Anatomy of the Oracle router&lt;/h2&gt;

&lt;p&gt;The default router in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;llm-as-an-oracle&lt;/code&gt; is deterministic. It does not call an
LLM to decide which evaluator to use. Instead, it extracts interpretable signals
and applies a fixed chain of routing policies.&lt;/p&gt;

&lt;p&gt;That design choice is intentional. The system should make evaluator selection
more legible, not less.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/figs/llm-as-an-oracle.jpg&quot; alt=&quot;LLM as an Oracle routing diagram&quot; style=&quot;max-width:100%;height:auto;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;step-1-extract-routing-signals&quot;&gt;Step 1: extract routing signals&lt;/h3&gt;

&lt;p&gt;The router converts the task and trajectories into a structured set of signals.
The current implementation uses features such as:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;has_ground_truth&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;has_test_cases&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;trajectory_count&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stated_difficulty&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;verifiable_keyword_density&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;judgment_keyword_density&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;problem_length&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;output_available&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prior_hardness&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These features encode simple but meaningful intuitions.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;ground truth and test cases usually favor verification&lt;/li&gt;
  &lt;li&gt;execution output usually favors verification&lt;/li&gt;
  &lt;li&gt;open-ended language often favors judgment&lt;/li&gt;
  &lt;li&gt;a previously observed hard task may deserve a stronger verification path&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not to perfectly infer task type from text. The goal is to make the
selection logic explicit enough to inspect, revise, and extend.&lt;/p&gt;

&lt;h3 id=&quot;step-2-collect-policy-votes&quot;&gt;Step 2: collect policy votes&lt;/h3&gt;

&lt;p&gt;Signals are passed through a chain of policies. The default router uses policies
that reason about:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;prior hardness&lt;/li&gt;
  &lt;li&gt;available ground truth&lt;/li&gt;
  &lt;li&gt;keyword/domain cues&lt;/li&gt;
  &lt;li&gt;task difficulty&lt;/li&gt;
  &lt;li&gt;output availability&lt;/li&gt;
  &lt;li&gt;trajectory count&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each policy casts a weighted vote for either &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Judge&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Verifier&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Ground truth present?        -&amp;gt; favor Verifier
Execution output available?  -&amp;gt; favor Verifier
Open-ended wording?          -&amp;gt; favor Judge
Very low routing confidence? -&amp;gt; fall back to Judge
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The implementation is more nuanced than that sketch, but the spirit is the
same. Evaluation mode is chosen by accumulating evidence.&lt;/p&gt;

&lt;h3 id=&quot;step-3-aggregate-confidence&quot;&gt;Step 3: aggregate confidence&lt;/h3&gt;

&lt;p&gt;The router aggregates weighted policy votes into a final confidence score. The
winning strategy is selected only if its confidence is strong enough. Otherwise,
the system falls back to the more general-purpose Judge path.&lt;/p&gt;

&lt;p&gt;This creates an important separation:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;a strategy can be powerful&lt;/li&gt;
  &lt;li&gt;the router can still decide that the available evidence does not justify using
it for this particular task&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a better design than letting every downstream evaluator silently assume
the task is well suited to its own strengths.&lt;/p&gt;

&lt;h3 id=&quot;step-4-expose-the-routing-trace&quot;&gt;Step 4: expose the routing trace&lt;/h3&gt;

&lt;p&gt;The output of a routing decision includes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;the selected strategy&lt;/li&gt;
  &lt;li&gt;the final confidence&lt;/li&gt;
  &lt;li&gt;the raw signals&lt;/li&gt;
  &lt;li&gt;every policy vote&lt;/li&gt;
  &lt;li&gt;a human-readable reasoning trace&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I care about this part the most, because evaluation pipelines already
accumulate ambiguity. A score without a path to understanding how it was
obtained is hard to debug. The Oracle makes one
critical source of ambiguity observable: why this evaluator was chosen in the
first place.&lt;/p&gt;

&lt;h2 id=&quot;evaluating-trajectories-not-just-answers&quot;&gt;Evaluating trajectories, not just answers&lt;/h2&gt;

&lt;p&gt;The project uses the term &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;trajectory&lt;/code&gt; deliberately.&lt;/p&gt;

&lt;p&gt;A trajectory is a candidate task-solving attempt. It may contain:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;the final answer&lt;/li&gt;
  &lt;li&gt;intermediate reasoning or planning&lt;/li&gt;
  &lt;li&gt;code&lt;/li&gt;
  &lt;li&gt;tool calls&lt;/li&gt;
  &lt;li&gt;execution output&lt;/li&gt;
  &lt;li&gt;an optional reward signal&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is especially relevant for agents. When a coding agent fixes a bug, the
thing we care about is not only the final patch. We may care about:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;whether the patch addresses the requested failure mode&lt;/li&gt;
  &lt;li&gt;whether it satisfies explicit requirements&lt;/li&gt;
  &lt;li&gt;whether execution evidence supports the answer&lt;/li&gt;
  &lt;li&gt;whether two superficially plausible solutions differ materially&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For text-only tasks, evaluating the final answer may be sufficient. For agents,
the evaluation object often needs to be richer.&lt;/p&gt;

&lt;p&gt;The Oracle architecture assumes that richer object from the beginning.&lt;/p&gt;

&lt;h2 id=&quot;a-concrete-example-the-n1-query-bug&quot;&gt;A concrete example: the N+1 query bug&lt;/h2&gt;

&lt;p&gt;One example in the repository asks three agents to fix an N+1 query problem.
The original function loads orders first, then issues one SQL query per order to
load items. That means:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;51 queries for 50 orders&lt;/li&gt;
  &lt;li&gt;5,001 queries for 5,000 orders&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The task asks for a constant-query solution and provides both ground truth and
test cases.&lt;/p&gt;

&lt;p&gt;The original bug has this shape:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;get_orders_with_items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;orders&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;SELECT * FROM orders WHERE user_id = ?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orders&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;items&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;SELECT * FROM items WHERE order_id = ?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]],&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orders&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The loop is the problem. The first query fetches the orders, then each order
triggers another query for its items.&lt;/p&gt;

&lt;p&gt;Three candidate trajectories are evaluated:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;one rewrites the query with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JOIN&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;one performs a batched prefetch with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WHERE IN&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;one adds an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lru_cache&lt;/code&gt; around the inner item lookup&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first two are legitimate fixes, though they make different tradeoffs. The
third sounds plausible because caching often improves performance. But it does
not solve the stated problem. On a cold cache, query count still grows with the
number of orders.&lt;/p&gt;

&lt;p&gt;The misleading fix looks like this:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lru_cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;maxsize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;256&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fetch_items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;SELECT * FROM items WHERE order_id = ?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;get_orders_with_items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;orders&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;SELECT * FROM orders WHERE user_id = ?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orders&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;items&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fetch_items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orders&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This may help repeated calls for the same order, but it does not change the
first-run query pattern:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;expected: query_count == O(1)
actual:   query_count == 1 + number_of_orders
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A real fix changes the query shape. For example, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JOIN&lt;/code&gt; trajectory uses one
SQL query and groups the rows afterward:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orders&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;LEFT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;items&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is exactly the sort of case where evaluator choice matters.&lt;/p&gt;

&lt;h3 id=&quot;why-judge-alone-is-risky-here&quot;&gt;Why Judge alone is risky here&lt;/h3&gt;

&lt;p&gt;A holistic Judge may recognize that the cache-based answer is weaker. But it is
also possible for that answer to benefit from surface plausibility:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;it uses a familiar optimization technique&lt;/li&gt;
  &lt;li&gt;it contains concrete code&lt;/li&gt;
  &lt;li&gt;it appears to address performance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the score is driven too much by presentation quality, the wrong candidate can
become competitive.&lt;/p&gt;

&lt;h3 id=&quot;why-verifier-is-the-better-fit&quot;&gt;Why Verifier is the better fit&lt;/h3&gt;

&lt;p&gt;The task has stronger evidence:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;explicit correctness requirements&lt;/li&gt;
  &lt;li&gt;test cases&lt;/li&gt;
  &lt;li&gt;expected behavioral properties&lt;/li&gt;
  &lt;li&gt;a measurable performance invariant: query count must not scale with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;N&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is precisely the situation where the Oracle should route toward the
Verifier. The evaluation problem is not mainly aesthetic. It is evidential.&lt;/p&gt;

&lt;p&gt;The interesting part is not merely that Verifier can help. The more general point
is that the Oracle can identify this task shape before evaluation begins.&lt;/p&gt;

&lt;h2 id=&quot;judge-and-verifier-are-not-rivals&quot;&gt;Judge and Verifier are not rivals&lt;/h2&gt;

&lt;p&gt;It is tempting to treat this as a winner-take-all comparison:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Verifier is more objective&lt;/li&gt;
  &lt;li&gt;Judge is more flexible&lt;/li&gt;
  &lt;li&gt;one must be superior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I do not think that framing is useful.&lt;/p&gt;

&lt;p&gt;Each strategy fails differently.&lt;/p&gt;

&lt;h3 id=&quot;failure-modes-of-judge&quot;&gt;Failure modes of Judge&lt;/h3&gt;

&lt;p&gt;A Judge can overvalue fluent or confident language, blur correctness and
style, and struggle to separate close technical alternatives without stronger
evidence.&lt;/p&gt;

&lt;h3 id=&quot;failure-modes-of-verifier&quot;&gt;Failure modes of Verifier&lt;/h3&gt;

&lt;p&gt;A Verifier has its own failure modes. It inherits bad ground truth, can
overfit to incomplete criteria, becomes brittle when tests are narrow, and
quietly underperforms on tasks that are fundamentally subjective.&lt;/p&gt;

&lt;p&gt;The Oracle does not eliminate these problems. It tries to reduce one avoidable
problem: choosing the wrong mode of evaluation for the task at hand.&lt;/p&gt;

&lt;h2 id=&quot;when-the-oracle-should-ask-for-help&quot;&gt;When the Oracle should ask for help&lt;/h2&gt;

&lt;p&gt;There is another failure mode worth making explicit: sometimes the task itself
is underspecified.&lt;/p&gt;

&lt;p&gt;Suppose three architecture recommendations are all defensible, but the best one
depends on a missing fact about team size, latency goals, compliance constraints,
or deployment environment. No evaluator should pretend confidence if the
information needed to decide was never supplied.&lt;/p&gt;

&lt;p&gt;The fuller design in this project explores a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Human Oracle&lt;/code&gt; escalation path for
those cases. When evaluation evidence runs out, the system can ask a targeted
clarifying question, incorporate the answer, and re-evaluate.&lt;/p&gt;

&lt;p&gt;The point is not to put a person in the loop by default. It is to avoid
manufactured certainty when a decision depends on missing context.&lt;/p&gt;

&lt;p&gt;That same principle motivates the router itself:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;do not hide assumptions&lt;/li&gt;
  &lt;li&gt;do not oversell confidence&lt;/li&gt;
  &lt;li&gt;make uncertainty inspectable&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;what-this-suggests-about-evaluation-design&quot;&gt;What this suggests about evaluation design&lt;/h2&gt;

&lt;p&gt;The Oracle pattern leads to a broader design lesson.&lt;/p&gt;

&lt;p&gt;Good evaluation systems share a few properties. &lt;strong&gt;Evaluator should match task
structure&lt;/strong&gt;: open-ended and evidence-grounded tasks are different, and treating
them the same introduces avoidable error. &lt;strong&gt;Selection should be explicit&lt;/strong&gt;,
part of the system design rather than a hidden convention buried in notebook
code or benchmark glue. &lt;strong&gt;Inspectability should be preserved&lt;/strong&gt;: routing
traces, criteria, and confidence should be artifacts you can actually look at.
And the system should &lt;strong&gt;admit when evidence is insufficient&lt;/strong&gt;. A strong
evaluator is no substitute for missing context, and escalation can be the
right move.&lt;/p&gt;

&lt;h2 id=&quot;where-this-pattern-is-useful&quot;&gt;Where this pattern is useful&lt;/h2&gt;

&lt;p&gt;I think &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LLM-as-an-Oracle&lt;/code&gt; is especially relevant for:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;coding agents that generate several candidate patches&lt;/li&gt;
  &lt;li&gt;benchmark pipelines that mix factual, creative, and executable tasks&lt;/li&gt;
  &lt;li&gt;tool-using agents whose outputs include both text and action traces&lt;/li&gt;
  &lt;li&gt;research workflows comparing evaluators across heterogeneous task families&lt;/li&gt;
  &lt;li&gt;production systems that want one evaluation interface without pretending every
task should be judged the same way&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In all of those cases, evaluator selection is part of the problem.&lt;/p&gt;

&lt;p&gt;Treating it as a first-class system component is cleaner than standardizing on a
single evaluation method and compensating later with increasingly elaborate
exceptions.&lt;/p&gt;

&lt;h2 id=&quot;closing-thought&quot;&gt;Closing thought&lt;/h2&gt;

&lt;p&gt;The evaluator should fit the task.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LLM-as-an-Oracle&lt;/code&gt; is my attempt to turn that principle into a concrete system:
route between judgment and verification, expose the reasons for the choice, and
leave room for human escalation when neither automated path has enough evidence.&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:llm-as-family&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Zheng et al. introduce &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LLM-as-a-Judge&lt;/code&gt;; the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LLM-as-a-Verifier&lt;/code&gt; framework develops evidence-sensitive verification;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CritiqueLLM&lt;/code&gt; studies critique generation for evaluation; and pairwise
ranking prompting shows how LLMs can be used directly as rankers.
&lt;a href=&quot;https://arxiv.org/abs/2306.05685&quot;&gt;https://arxiv.org/abs/2306.05685&lt;/a&gt;
&lt;a href=&quot;https://llm-as-a-verifier.notion.site/&quot;&gt;https://llm-as-a-verifier.notion.site/&lt;/a&gt;
&lt;a href=&quot;https://arxiv.org/abs/2311.18702&quot;&gt;https://arxiv.org/abs/2311.18702&lt;/a&gt;
&lt;a href=&quot;https://arxiv.org/abs/2306.17563&quot;&gt;https://arxiv.org/abs/2306.17563&lt;/a&gt; &lt;a href=&quot;#fnref:llm-as-family&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:judge&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Zheng et al., “Judging LLM-as-a-Judge with MT-Bench and Chatbot
Arena,” 2023. &lt;a href=&quot;https://arxiv.org/abs/2306.05685&quot;&gt;https://arxiv.org/abs/2306.05685&lt;/a&gt; &lt;a href=&quot;#fnref:judge&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:judge:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:verifier&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;“LLM-as-a-Verifier: A General-Purpose Verification Framework.”
&lt;a href=&quot;https://llm-as-a-verifier.notion.site/&quot;&gt;https://llm-as-a-verifier.notion.site/&lt;/a&gt; &lt;a href=&quot;#fnref:verifier&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</content>
 </entry>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 <entry>
   <title>My book is out!</title>
   <link href="http://huascarsanchez.com/posts/notes/book.html"/>
   <updated>2015-03-05T00:00:00+00:00</updated>
   <id>http://huascarsanchez.com/posts/notes/book</id>
   <content type="html">&lt;h2 id=&quot;software-patterns-knowledge-maps-and-domain-analysis&quot;&gt;Software Patterns, Knowledge Maps, and Domain Analysis&lt;/h2&gt;

&lt;p&gt;My book is about domain analysis and software patterns. Briefly, it shows how
to build software systems as systems of patterns, called Knowledge Maps.&lt;/p&gt;

&lt;p&gt;It is now in the global markets. Its publication date was December 2014. Please
check it out:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;At &lt;a href=&quot;http://tinyurl.com/SoftwarePatterns&quot;&gt;&lt;strong&gt;Amazon&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;At &lt;a href=&quot;http://www.crcpress.com/product/isbn/9781466571433&quot;&gt;&lt;strong&gt;CRC Press&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;At &lt;a href=&quot;http://www.taylorandfrancis.com/books/details/9781466571433/&quot;&gt;&lt;strong&gt;Taylor &amp;amp; Francis&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;figure&gt;
    &lt;img style=&quot;border: 1px solid #e5e5e5;&quot; src=&quot;http://www.huascarsanchez.com/static/K16540.jpg&quot; alt=&quot;Software Patterns, Knowledge Maps, and Domain Analysis&quot; height=&quot;684&quot; width=&quot;432&quot; /&gt;
    &lt;figcaption style=&quot;padding-left: 2cm;&quot;&gt;Figure 1. Front cover of my book&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The citation (in BibTex) of this book is presented as follows:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;@book{hsanchez, 
      author = {M. E. Fayad, H. A. Sanchez, S. G .K. Hegde, 
                A. Basia, and A. Vakil.},
      title  = {Software Patterns, Knowledge Maps, and 
                Domain Analysis},
      publisher = {Auerbach Publications},
      year      = 2014,
      address   = {Boca Raton, Florida},
      isbn      = {978-1466571433}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 <entry>
   <title>Cyberbullying detection in social networks</title>
   <link href="http://huascarsanchez.com/posts/notes/twitter-bullying-detection-report.html"/>
   <updated>2012-04-29T00:00:00+00:00</updated>
   <id>http://huascarsanchez.com/posts/notes/twitter-bullying-detection-report</id>
   <content type="html">&lt;p&gt;According to Google Analytics and to other sources, my data mining (ISM 245 class) project titled “Twitter Bullying Detection” is getting some attention. An earlier version of the project report was hosted on the co-author of the project’s &lt;a href=&quot;http://users.soe.ucsc.edu/~shreyask/index.html&quot;&gt;website&lt;/a&gt;. This version
is good in terms of communicating the project’s main idea and motivation. However, I think it is always important to have the latest version available. Therefore, I decided to post this latest version &lt;a href=&quot;http://dl.dropbox.com/u/11902217/huascarsanchez_and_shreyaskumar-twitterbullyingdection-fnl.pdf&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Please feel free to contact me if you have any questions about the content of
that report. Thanks.&lt;/p&gt;
</content>
 </entry>
 
 
 
 
 
 

</feed>
