<?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-07-06T00:11:04+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;Prompts have become production artifacts, but many teams still treat them like
ordinary strings.&lt;/p&gt;

&lt;p&gt;That works for a while. A prompt starts as a constant in application code. Then
it grows. A system prompt gets a few guardrails. A developer adds a specialized
instruction for a new workflow. Someone changes the tone. Someone else tweaks a
retrieval instruction. A third person changes the safety policy because an eval
failed.&lt;/p&gt;

&lt;p&gt;At that point the prompt is no longer just a string. It is part of the
system’s behavior.&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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.
Prompt engineering should not be exempt from that discipline just because the
artifact is text.&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.&lt;/p&gt;

&lt;p&gt;The first 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;This is fine for a prototype. It is brittle for a system that needs repeatable
experiments, reviewable changes, or rollback.&lt;/p&gt;

&lt;p&gt;The second failure mode is &lt;em&gt;overcorrection&lt;/em&gt;: 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 prompt
metadata no longer fits a repository workflow.&lt;/p&gt;

&lt;p&gt;But a fast-moving R&amp;amp;D team often needs 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;Git is necessary for reviewing and preserving prompt history, but it does not
define a prompt lifecycle by itself.&lt;/p&gt;

&lt;p&gt;Git can tell you that a prompt file changed, but it does not 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;Promptory keeps Git as the durable history, but 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 makes a deliberate ownership choice: prompts live with the code that
uses them. The idea is &lt;em&gt;locality of change&lt;/em&gt;. 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 for application-specific prompts,
keeping the prompt next to the implementation makes 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;That does not 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 important distinction
is ownership: the repo remains the source of truth, while production systems can
aggregate or serve released artifacts from there.&lt;/p&gt;

&lt;p&gt;The default layout 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 important separation is:&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;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.&lt;/p&gt;

&lt;p&gt;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;Drafts are Jinja templates, so they can 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 templates with Jinja &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StrictUndefined&lt;/code&gt;. Missing variables fail
instead of silently becoming empty strings. That is an intentional safety choice.
Silent prompt rendering failures are hard to debug because they often look like
model behavior problems later.&lt;/p&gt;

&lt;p&gt;Rendering happens during release. The CLI path is:&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; validates the drafts and variable declarations. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prompt release&lt;/code&gt;
renders the declared templates, parses the rendered YAML, writes a new immutable
version directory, and updates &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;current.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If a release needs variables, the Python API supplies them explicitly:&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;, validates the requested prompt name against
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;promptspec.yaml&lt;/code&gt;, then 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;That is useful for evals, replay, and debugging. If an output changed between
two runs, 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;:&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.&lt;/p&gt;

&lt;p&gt;When application code loads the current prompt, it is not asking, “What is in
the drafts directory today?” It is asking, “Which release is currently active?”&lt;/p&gt;

&lt;p&gt;Rollback is therefore a pointer change, not a rewrite:&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 versions
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;Suppose a team has three prompt 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 spec declares them:&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;The editable drafts live under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prompts/drafts/&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;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;Then 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;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.&lt;/p&gt;

&lt;p&gt;They may be written in natural language, but they alter system behavior. They
deserve the same basic lifecycle we expect from code: review, validation,
versioning, release, and rollback.&lt;/p&gt;

&lt;p&gt;Promptory is my attempt to make that lifecycle lightweight enough for R&amp;amp;D work
while still giving production systems a stable runtime contract.&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.&lt;/p&gt;

&lt;p&gt;I think that is the wrong first question.&lt;/p&gt;

&lt;p&gt;Some tasks need judgment. Others need verification. A rubric can help decide
whether an explanation is clear, persuasive, or useful. It is a poor substitute
for test cases when evaluating code. Likewise, exact checks are not enough when
the output is inherently qualitative.&lt;/p&gt;

&lt;p&gt;That distinction is the motivation behind
&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;.&lt;/p&gt;

&lt;p&gt;An Oracle, in this setting, is not an all-knowing model. It is an adaptive
evaluation layer that decides whether a task should be evaluated by 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;.&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>
