Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ Joltrin (formerly SOP) is an open-source Go library that checks an AI agent's ac
**What Joltrin does.** The agent emits intent. Joltrin checks the intent against the trace and the runbook's rules, outside the agent's context window. The check and the commit are one call, so a step is recorded only if it passed. A step whose required state is missing is refused. A claim that something happened is not evidence that it happened.

<p align="center">
<a href="https://youtu.be/F0jYkBHJluI">
<img src="docs/assets/joltrin-demo.gif" alt="Joltrin demo. Click to watch the video." width="760" />
<a href="docs/assets/joltrin-barrier.mp4">
<img src="docs/assets/joltrin-barrier-poster.jpg" alt="A 43 second silent demo: an agent proposes dropping a production database, Joltrin blocks it, the agent runs the missing steps, and the same action is then allowed. Click to play." width="420" />
</a>
</p>

Expand Down
83 changes: 55 additions & 28 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -477,29 +477,29 @@ <h1 class="text-3xl sm:text-4xl lg:text-5xl font-extrabold text-white tracking-t
</div>
</div>

<div id="hero-example" class="min-w-0 rounded-xl bg-dark-950 border border-dark-700 p-4 font-mono text-xs sm:text-sm" aria-label="A blocked, then allowed, sequence">
<div class="text-[11px] text-slate-500 uppercase tracking-wider mb-3">// db-maintenance runbook</div>
<div class="space-y-1.5 overflow-x-auto">
<div class="sm:whitespace-nowrap"><span class="text-white">drop_prod_db</span> <span class="text-rose-400 font-bold">BLOCKED</span> <span class="text-slate-400">backup_validated missing</span></div>
<div class="sm:whitespace-nowrap"><span class="text-white">validate_backup</span> <span class="text-rose-400 font-bold">BLOCKED</span> <span class="text-slate-400">backup_taken missing</span></div>
<div class="sm:whitespace-nowrap"><span class="text-white">take_backup</span> <span class="text-brand-400 font-bold">ALLOWED</span></div>
<div class="sm:whitespace-nowrap"><span class="text-white">validate_backup</span> <span class="text-brand-400 font-bold">ALLOWED</span></div>
<div class="sm:whitespace-nowrap"><span class="text-white">drop_prod_db</span> <span class="text-brand-400 font-bold">ALLOWED</span></div>
<figure id="demo-video" class="min-w-0 scroll-mt-20 w-full max-w-[34rem] mx-auto lg:mx-0 lg:ml-auto">
<div class="relative rounded-2xl overflow-hidden border border-dark-700 bg-dark-950 shadow-2xl shadow-black/50" style="aspect-ratio:1/1">
<video id="demo-video-el" class="absolute inset-0 w-full h-full" width="1080" height="1080" muted loop playsinline preload="none"
poster="./assets/joltrin-barrier-poster-540.jpg"
aria-label="A 43 second demo: an agent proposes dropping a production database, Joltrin blocks it, the agent runs the missing steps, and the same action is then allowed."></video>
<button id="demo-video-toggle" type="button" aria-label="Play the demo video"
class="absolute right-3 bottom-3 inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-dark-950/85 border border-dark-700 text-xs font-mono text-slate-200 hover:text-white hover:border-brand-500/60 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-400">
<span id="demo-video-icon" aria-hidden="true">&#9654;</span><span id="demo-video-label">Play</span>
</button>
</div>
<p class="text-[11px] text-slate-500 mt-3 leading-relaxed font-sans">The order is enforced by the barrier, whatever the agent says it already did.</p>
</div>
</div>
</section>

<!-- Intro video (muted so browsers allow autoplay) -->
<section id="demo-video" aria-label="Joltrin intro video" class="scroll-mt-20 mb-8 rounded-2xl overflow-hidden border border-dark-700/80 bg-dark-900">
<div class="relative w-full" style="aspect-ratio:16/9">
<iframe class="absolute inset-0 w-full h-full"
data-src="https://www.youtube-nocookie.com/embed/F0jYkBHJluI?autoplay=1&mute=1&loop=1&playlist=F0jYkBHJluI&playsinline=1&rel=0"
title="Joltrin intro video"
allow="autoplay; encrypted-media; picture-in-picture; fullscreen"
allowfullscreen
referrerpolicy="strict-origin-when-cross-origin"></iframe>
<figcaption class="mt-3 text-xs text-slate-400 leading-relaxed">
43 seconds, silent, real output from <code class="text-slate-200">sop-mcp-server demo</code>.
<details class="mt-2">
<summary class="cursor-pointer text-slate-300 hover:text-white">Text version</summary>
<ol id="hero-example" class="mt-2 space-y-1 font-mono text-[12px] text-slate-300 list-decimal list-inside">
<li><span class="text-white">drop_prod_db</span> <span class="text-rose-400 font-bold">BLOCKED</span>, <code>backup_validated</code> is missing from the trace</li>
<li><span class="text-white">take_backup</span> <span class="text-brand-400 font-bold">ALLOWED</span></li>
<li><span class="text-white">validate_backup</span> <span class="text-brand-400 font-bold">ALLOWED</span></li>
<li><span class="text-white">drop_prod_db</span> <span class="text-brand-400 font-bold">ALLOWED</span>, the evidence now exists</li>
</ol>
</details>
</figcaption>
</figure>
</div>
</section>

Expand Down Expand Up @@ -1763,12 +1763,39 @@ <h4 class="font-mono text-xs uppercase font-bold text-white mb-3">Open Source &a
<script>
let selectedBenchOps = 5000;

// The intro video player is about 5 MB of scripts. It starts after the page
// has loaded, so it never delays the first paint or the load event.
window.addEventListener("load", () => {
const frame = document.querySelector('#demo-video iframe[data-src]');
if (frame) frame.src = frame.dataset.src;
});
// Demo video. It stays unloaded until the page has finished loading, plays
// only while it is on screen, and does not autoplay for people who prefer
// reduced motion, who get the poster and a Play button instead. A phone gets
// the smaller file.
(function () {
const v = document.getElementById('demo-video-el');
const btn = document.getElementById('demo-video-toggle');
if (!v || !btn) return;
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const conn = navigator.connection || {};
const small = window.innerWidth <= 700 || conn.saveData;
const src = small ? './assets/joltrin-barrier-720.mp4' : './assets/joltrin-barrier.mp4';
let userPaused = false;
const label = (playing) => {
document.getElementById('demo-video-label').textContent = playing ? 'Pause' : 'Play';
document.getElementById('demo-video-icon').innerHTML = playing ? '&#10074;&#10074;' : '&#9654;';
btn.setAttribute('aria-label', playing ? 'Pause the demo video' : 'Play the demo video');
};
const play = () => { if (!v.getAttribute('src')) v.src = src; return v.play().catch(() => {}); };
v.addEventListener('play', () => label(true));
v.addEventListener('pause', () => label(false));
btn.addEventListener('click', () => {
if (v.paused) { userPaused = false; play(); } else { userPaused = true; v.pause(); }
});
if (reduce || !('IntersectionObserver' in window)) return;
window.addEventListener('load', () => {
new IntersectionObserver((entries) => {
entries.forEach((e) => {
if (e.isIntersecting) { if (!userPaused) play(); } else { v.pause(); }
});
}, { threshold: 0.4 }).observe(v);
});
})();

// Initialize Lucide Icons
document.addEventListener("DOMContentLoaded", () => {
Expand Down
Binary file added docs/assets/joltrin-barrier-720.mp4
Binary file not shown.
Binary file added docs/assets/joltrin-barrier-poster-540.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/joltrin-barrier-poster.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/joltrin-barrier.mp4
Binary file not shown.
44 changes: 44 additions & 0 deletions scripts/promo/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# Builds the homepage demo video from the real binary.
#
# scripts/promo/build.sh [out-dir] (default: docs/assets)
#
# It builds sop-mcp-server, records what it prints (capture.mjs), draws the
# scene at 30 fps (render.mjs), and encodes an MP4 at 1080 px and 720 px and two poster sizes. Needs
# Go, Node with @playwright/test and its Chromium, and ffmpeg.
set -euo pipefail

root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
out="${1:-$root/docs/assets}"
work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
mkdir -p "$out"

echo "==> build the real binary"
(cd "$root" && go build -o "$work/sop-mcp-server" ./cmd/sop-mcp-server)

echo "==> capture its real output"
node "$root/scripts/promo/capture.mjs" "$work/sop-mcp-server" "$work/scene-data.json"

echo "==> draw the frames"
node "$root/scripts/promo/render.mjs" "$work/scene-data.json" "$work"

echo "==> encode"
frames="$work/frames/f%05d.jpg"
# H.264 for every browser and for LinkedIn. yuv420p and faststart so it plays
# at once on a phone.
ffmpeg -y -loglevel error -framerate 30 -i "$frames" \
-c:v libx264 -preset slow -crf 25 -pix_fmt yuv420p -movflags +faststart -an \
"$out/joltrin-barrier.mp4"
# A 720 px copy for phones, which is about half the size.
ffmpeg -y -loglevel error -framerate 30 -i "$frames" -vf scale=720:720:flags=lanczos \
-c:v libx264 -preset slow -crf 26 -pix_fmt yuv420p -movflags +faststart -an \
"$out/joltrin-barrier-720.mp4"
# Poster: the moment the barrier blocks the action.
ffmpeg -y -loglevel error -i "$work/frames/f00525.jpg" -vf scale=1080:1080 -q:v 4 "$out/joltrin-barrier-poster.jpg"
ffmpeg -y -loglevel error -i "$work/frames/f00525.jpg" -vf scale=540:540 -q:v 5 "$out/joltrin-barrier-poster-540.jpg"

for f in joltrin-barrier.mp4 joltrin-barrier-720.mp4 joltrin-barrier-poster.jpg joltrin-barrier-poster-540.jpg; do
printf '%-30s %s\n' "$f" "$(du -h "$out/$f" | cut -f1)"
done
printf 'duration: %s s\n' "$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$out/joltrin-barrier.mp4")"
51 changes: 51 additions & 0 deletions scripts/promo/capture.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Runs the real sop-mcp-server and records what it prints, so the video shows
// real output and nothing typed in by hand.
//
// node scripts/promo/capture.mjs <path-to-sop-mcp-server> <out.json>
import { spawn, execFileSync } from 'node:child_process';
import { writeFileSync } from 'node:fs';

const [bin, out] = process.argv.slice(2);
if (!bin || !out) {
console.error('usage: node scripts/promo/capture.mjs <sop-mcp-server> <out.json>');
process.exit(2);
}

const demoText = execFileSync(bin, ['demo'], { encoding: 'utf8' });
const demoJSON = JSON.parse(execFileSync(bin, ['demo', '--json'], { encoding: 'utf8' }));
const version = execFileSync(bin, ['version'], { encoding: 'utf8' }).trim();

// One real execute_step over MCP stdio: the call the agent makes, and the
// structured block it gets back.
const blocked = await new Promise((resolve, reject) => {
const p = spawn(bin, [], { stdio: ['pipe', 'pipe', 'ignore'] });
let buf = '';
const waiting = new Map();
p.stdout.on('data', (d) => {
buf += d;
let i;
while ((i = buf.indexOf('\n')) >= 0) {
const line = buf.slice(0, i);
buf = buf.slice(i + 1);
if (!line.trim()) continue;
const m = JSON.parse(line);
if (m.id !== undefined && waiting.has(m.id)) waiting.get(m.id)(m);
}
});
const rpc = (id, method, params) =>
new Promise((res) => {
waiting.set(id, res);
p.stdin.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n');
});
(async () => {
await rpc(1, 'initialize', { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'promo', version: '0' } });
p.stdin.write(JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' }) + '\n');
const call = { workflow: 'db-maintenance', trace_id: 'promo-1', step: 'drop_prod_db' };
const r = await rpc(2, 'tools/call', { name: 'execute_step', arguments: call });
p.kill();
resolve({ call, result: r.result.structuredContent });
})().catch(reject);
});

writeFileSync(out, JSON.stringify({ version, demoText, demo: demoJSON, mcp: blocked }, null, 2));
console.log('captured', version, 'to', out);
34 changes: 34 additions & 0 deletions scripts/promo/render.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Draws the scene at 30 frames per second and encodes it, so the video is the
// same every time it is built from the same output.
//
// node scripts/promo/render.mjs <scene-data.json> <out-dir> [seconds]
import { chromium } from '@playwright/test';
import { readFileSync, mkdirSync, rmSync } from 'node:fs';
import { execFileSync } from 'node:child_process';
import { pathToFileURL, fileURLToPath } from 'node:url';
import path from 'node:path';

const [dataPath, outDir, secs] = process.argv.slice(2);
if (!dataPath || !outDir) {
console.error('usage: node scripts/promo/render.mjs <scene-data.json> <out-dir> [seconds]');
process.exit(2);
}
const FPS = 30;
const DURATION = Number(secs || 43);
const here = path.dirname(fileURLToPath(import.meta.url));
const frames = path.join(outDir, 'frames');
rmSync(frames, { recursive: true, force: true });
mkdirSync(frames, { recursive: true });

const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1080, height: 1080 }, deviceScaleFactor: 1 });
await page.goto(pathToFileURL(path.join(here, 'scene.html')).href);
await page.evaluate((d) => window.setData(d), JSON.parse(readFileSync(dataPath, 'utf8')));
const n = Math.round(DURATION * FPS);
for (let i = 0; i < n; i++) {
await page.evaluate((t) => window.render(t), i / FPS);
await page.screenshot({ path: path.join(frames, `f${String(i).padStart(5, '0')}.jpg`), type: 'jpeg', quality: 94 });
if (i % 150 === 0) process.stdout.write(`frame ${i}/${n}\n`);
}
await browser.close();
console.log('frames done:', n);
Loading
Loading