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
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<p align="center">
<img src="docs/assets/wordmark.svg" alt="CTRLRun" width="300">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/wordmark-dark.svg">
<img src="docs/assets/wordmark-light.svg" alt="CTRLRun" width="300">
</picture>
</p>

<p align="center">
Expand All @@ -18,7 +21,7 @@
</p>

<p align="center">
<img src="docs/assets/demo.gif" alt="ctrlrun demo: a refund commits at the remote, the response is lost, the agent retries, and the retry is refused. Remote refund calls: 1." width="720">
<img src="docs/assets/demo.gif" alt="ctrlrun demo: a refund commits at the remote, the response is lost, the agent retries, and the retry is refused, remote refund calls: 1. Then a human approves a €2,000 refund, the agent executes €5,000, and that is refused too." width="800">
</p>

<!-- generated from docs/capabilities.yaml (readme) — edit the YAML, never this table -->
Expand All @@ -39,10 +42,12 @@ pip install ctrlrun && ctrlrun demo
```

Five ways an agent action goes wrong, and what stops each one, in process, in under a second,
with no network and no external service. The animation above is the first scenario: the refund
commits at the remote, the reply is lost, the agent retries, and the retry is refused —
**`remote refund calls: 1`**, so the customer was refunded once and not twice. Nothing but a
human resolving the effect moves it on.
with no network and no external service. The animation above is the first two. In the first,
the refund commits at the remote, the reply is lost, the agent retries, and the retry is
refused — **`remote refund calls: 1`**, so the customer was refunded once and not twice, and
nothing but a human resolving the effect moves it on. In the second, a human approves a €2,000
refund, the agent executes €5,000 under that approval, and the approval matches nothing but the
action the human saw.

<details>
<summary>The full transcript, byte for byte what the demo prints</summary>
Expand Down
5 changes: 5 additions & 0 deletions docs/assets/demo.expected.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@ Policy: refunds up to €1,000 are autonomous, up to €10,000 need a human, abo
agent retries the same refund
✗ BLOCKED — effect may already have committed; blind retry refused
remote refund calls: 1
only a human moves it on: ctrlrun resolve refund:txn_1 --committed|--failed
2. Approval mutation
agent proposes refund €2,000 → human approves apr_15b1568d362999c10f7fd49c320619fb (bound to the action hash)
agent executes refund €5,000 →
✗ BLOCKED — approved action ≠ requested action (mismatch)
Binary file modified docs/assets/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 20 additions & 16 deletions docs/assets/demo.tape
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,35 @@
#
# PATH=.venv/bin:$PATH vhs docs/assets/demo.tape
#
# What it records is `ctrlrun demo`'s first scenario, byte for byte, and it stops on the line
# that matters: `remote refund calls: 1`. The demo prints all five scenarios without pausing,
# so the command is piped through `sed` to end after that line — and the pipe is typed in the
# open, because a recording that hid how it was made would be the wrong kind of demo for this
# project. `docs/assets/demo.expected.txt` holds the lines the recording ends on, and
# `tests/test_readme_assets.py` asserts they are lines the demo prints and the README quotes.
# What it records is `ctrlrun demo`'s first two scenarios, byte for byte: the refund that
# commits behind a lost response and the retry that is refused, then the approval that is bound
# to one action and refused for another. The demo prints all five scenarios at once, so the
# command is piped twice, and both pipes are typed in the open, because a recording that hid
# how it was made would be the wrong kind of demo for this project: `sed` ends the output on
# scenario 2's BLOCKED line, and `slow` — the only thing this file sets up off screen, a
# `while read` loop with a sleep — releases one line every 0.7 s so a reader can follow. Nothing
# is added, reordered or removed by either. `docs/assets/demo.expected.txt` holds the lines the
# recording ends on, and `tests/test_readme_assets.py` asserts they are lines the demo prints
# and the README quotes.

Output docs/assets/demo.gif

Set Shell bash
Set FontSize 16
Set Width 1120
Set Height 300
Set Padding 24
Set Theme "Catppuccin Mocha"
Set TypingSpeed 60ms
Set FontSize 17
Set Width 1340
Set Height 470
Set Padding 28
Set Theme { "name": "ctrlrun", "background": "#14161b", "foreground": "#e6e8ec", "cursor": "#F5A623", "selection": "#2c3038", "black": "#14161b", "red": "#f28b82", "green": "#8fd694", "yellow": "#F5A623", "blue": "#7aa2f7", "magenta": "#c8a1f0", "cyan": "#7fd6d6", "white": "#e6e8ec", "brightBlack": "#6f747d", "brightRed": "#f28b82", "brightGreen": "#8fd694", "brightYellow": "#F5A623", "brightBlue": "#7aa2f7", "brightMagenta": "#c8a1f0", "brightCyan": "#7fd6d6", "brightWhite": "#ffffff" }
Set TypingSpeed 45ms
Set PlaybackSpeed 1.0

Hide
Type "cd $(mktemp -d) && clear"
Type `cd $(mktemp -d) && slow() { while IFS= read -r line; do printf '%s\n' "$line"; sleep 0.7; done; } && PS1=$'\e[33m$\e[0m ' && clear`
Enter
Sleep 500ms
Show

Type "ctrlrun demo | sed '/remote refund calls: 1/q'"
Sleep 600ms
Type "ctrlrun demo | sed '/approved action/q' | slow"
Sleep 700ms
Enter
Sleep 3s
Sleep 16500ms
1 change: 1 addition & 0 deletions docs/assets/favicon.svg
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/logo-on-dark.png
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/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions docs/assets/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 4 additions & 4 deletions docs/assets/social-preview.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ by hand: repository settings → *Social preview* → upload `docs/assets/social
| Size | 1280 × 640 px (GitHub's recommended size; rendered at 2:1 everywhere it is shown) |
| Safe area | Keep text inside 72 px margins; previews are cropped to 1.91:1 on some services |
| Background | `#14161b`, no photograph, no gradient |
| Wordmark | `docs/assets/wordmark.svg` at 58 px, top left, in `#c9ccd3` |
| Wordmark | `docs/assets/wordmark-dark.svg` at 58 px, top left: the amber keycap (`#F5A623`, edge `#B8730A`) and the name in `#f2f3f5` |
| Line 1 | The tagline, two lines at 46 px bold, `#f2f3f5`: *The last check before an AI agent / does something it can't undo.* |
| Line 2 | The principle at 26 px, `#a7abb4`: *Autonomy belongs to the action, not the agent.* |
| Panel | A terminal panel carrying the demo's first scenario, three lines in monospace, the third being the demo's `✗ BLOCKED — effect may already have committed; blind retry refused` line in `#f28b82` |
| Corner | `pip install ctrlrun` at 20 px, bottom right, `#6f747d` |
| Fonts | Helvetica Neue / Helvetica / Arial; monospace SFMono / Menlo / Consolas. System fonts, so the render depends on the machine; the committed PNG is the reference |
| Panel | A terminal panel with a 6 px amber left edge, carrying the demo's first scenario, three lines in monospace, the third being the demo's `✗ BLOCKED — effect may already have committed; blind retry refused` line in `#f28b82` |
| Corner | `pip install ctrlrun` at 20 px monospace, bottom right, `#F5A623` |
| Fonts | Inter / system sans / Helvetica / Arial; monospace SFMono / Menlo / Consolas. System fonts, so the render depends on the machine; the committed PNG is the reference |

The share unit is a failure, not a feature list: the image shows an agent doing something wrong
and CTRLRun refusing. No logos, no badges, no adopters, no stars.
Expand Down
Binary file modified docs/assets/social-preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 9 additions & 7 deletions docs/assets/social-preview.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions docs/assets/wordmark-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions docs/assets/wordmark-light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 5 additions & 3 deletions docs/assets/wordmark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
"name": "CTRLRun",
"theme": "mint",
"colors": {
"primary": "#3b3f46",
"light": "#c9ccd3",
"dark": "#14161b"
"primary": "#B8730A",
"light": "#F5A623",
"dark": "#8A5606"
},
"logo": {
"light": "/images/wordmark.svg",
"dark": "/images/wordmark.svg",
"light": "/images/wordmark-light.svg",
"dark": "/images/wordmark-dark.svg",
"href": "/"
},
"favicon": "/images/favicon.svg",
Expand Down
2 changes: 1 addition & 1 deletion docs/images/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/images/social-preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions docs/images/wordmark-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions docs/images/wordmark-light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 5 additions & 3 deletions docs/images/wordmark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 33 additions & 1 deletion docs/try-it.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,38 @@
output.textContent = text;
}

// The demo prints all five scenarios at once. Released a line at a time, as the README's
// recording does, a reader can follow each one. The text is what the demo printed and
// nothing else; the only thing added is time, and the colour on each refusal.
var LINE_MS = 380;
var REFUSED = "#f28b82";

function reveal(text) {
output.textContent = "";
var lines = text.split("\n");
return new Promise(function (resolve) {
var index = 0;
function next() {
if (index === lines.length) return resolve();
var line = lines[index++];
var node;
if (line.indexOf("BLOCKED") !== -1) {
node = document.createElement("span");
node.style.color = REFUSED;
node.style.fontWeight = "700";
node.textContent = line;
} else {
node = document.createTextNode(line);
}
output.appendChild(node);
output.appendChild(document.createTextNode("\n"));
output.scrollTop = output.scrollHeight;
window.setTimeout(next, line.trim() === "" ? 0 : LINE_MS);
}
next();
});
}

async function boot() {
say("Loading Python in this tab. The first run downloads about 10 MB from the Pyodide\nCDN and the ctrlrun wheel from PyPI; after that the browser caches them.");
if (!window.loadPyodide) await load(PYODIDE + "pyodide.js");
Expand All @@ -87,7 +119,7 @@
try {
if (!pyodide) pyodide = await boot();
say("Running the five scenarios…");
say(pyodide.runPython(PROGRAM));
await reveal(pyodide.runPython(PROGRAM));
button.textContent = "Run it again";
} catch (error) {
say(
Expand Down
6 changes: 3 additions & 3 deletions docs/try-it.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ filesystem, and opens no socket.
<button
type="button"
style={{
background: "#14161b",
color: "#f2f3f5",
border: "1px solid #2c3038",
background: "#F5A623",
color: "#14161b",
border: "1px solid #B8730A",
borderRadius: "8px",
padding: "10px 18px",
fontSize: "15px",
Expand Down
Loading