diff --git a/README.md b/README.md
index 57d4c82..ada2e7a 100644
--- a/README.md
+++ b/README.md
@@ -12,12 +12,11 @@ fixbuddy reads open GitHub issues, asks one agent to verify and fix each issue,
The goal is controlled automation: one issue per branch, one issue per PR, explicit labels for every outcome, and full logs for every agent call.
-
+
+
+
+
+The branch, commit, diff, and push above are real; the AI agents and GitHub calls are stubbed for a deterministic, offline recording — see docs/demo.
## Why fixbuddy
diff --git a/docs/demo.gif b/docs/demo.gif
new file mode 100644
index 0000000..c6876d0
Binary files /dev/null and b/docs/demo.gif differ
diff --git a/docs/demo/README.md b/docs/demo/README.md
new file mode 100644
index 0000000..459e140
--- /dev/null
+++ b/docs/demo/README.md
@@ -0,0 +1,25 @@
+# Demo GIF
+
+`docs/demo.gif` (shown at the top of the main README) is recorded with
+[VHS](https://github.com/charmbracelet/vhs).
+
+To keep it reproducible and offline, the recording uses **deterministic demo
+doubles**, not real services:
+
+- `bin/agent` — stands in for the AI coding CLIs (`claude` / `codex`). It emits the
+ `DONE-*` markers fixbuddy expects and, in the FIX stage, makes a **real** small
+ code change, so the diff fixbuddy reviews and the PR it opens are genuine.
+- `bin/gh` — stands in for the GitHub CLI with canned responses.
+
+Everything else is the **real** fixbuddy pipeline: branch creation, the local
+commit, the `git diff`, `git push` (to a local bare repo), and the
+label / state-machine flow.
+
+## Regenerate
+
+```bash
+bash docs/demo/gen.sh
+```
+
+Requires `vhs` on `PATH`. Writes `docs/demo.gif`. Edit `demo.tape` to change the
+timing/size/theme.
diff --git a/docs/demo/bin/agent b/docs/demo/bin/agent
new file mode 100755
index 0000000..7942844
--- /dev/null
+++ b/docs/demo/bin/agent
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+# Deterministic demo double for an AI coding CLI (used as claude/codex when recording
+# docs/demo.gif). This is NOT a real agent. It emits the DONE-* markers fixbuddy expects
+# and, in the FIX stage, makes a REAL small code fix so the diff fixbuddy reviews and the
+# PR it opens are genuine. Stage is detected from the prompt fixbuddy pipes on stdin.
+# See docs/demo/README.md.
+prompt="$(cat)"
+
+case "$prompt" in
+ *"implementing a fix"*)
+ sleep 1
+ ( cd "$DEMO_PROJECT" \
+ && sed -i.bak 's/a - b/a + b/' src/calc.py && rm -f src/calc.py.bak \
+ && git add -A \
+ && git commit -q -m "Fix add(): return a + b, not a - b" )
+ echo "Found the operator bug in add() and corrected it (a - b -> a + b)."
+ echo "DONE-FIX-APPLIED"
+ ;;
+ *"independent senior code reviewer"*)
+ sleep 1
+ echo "The diff changes 'a - b' to 'a + b' in add(). Correct, minimal, in scope."
+ echo "DONE-APPROVED"
+ ;;
+ *)
+ sleep 1
+ echo "Reproduced: add(2, 3) returns -1 instead of 5. The bug is real."
+ echo "DONE-PROCEED"
+ ;;
+esac
diff --git a/docs/demo/bin/gh b/docs/demo/bin/gh
new file mode 100755
index 0000000..390d179
--- /dev/null
+++ b/docs/demo/bin/gh
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+# Deterministic demo double for the GitHub CLI, used only when recording docs/demo.gif.
+# Returns canned responses so fixbuddy's pipeline runs end-to-end offline. NOT real.
+# Everything else (git branch/commit/diff/push to a local bare repo) is genuine.
+case "$1 $2" in
+ "repo view") echo "main" ;;
+ "issue list")
+ case "$*" in
+ *fix:pr-open*) echo "[]" ;; # the "unstick" scan finds nothing
+ *) echo '[{"number":7,"title":"add() returns the wrong result","labels":[{"name":"bug"},{"name":"severity:medium"}],"url":"https://github.com/acme/calc/issues/7","body":"add(2, 3) should return 5 but returns -1. The operator looks wrong."}]' ;;
+ esac ;;
+ "pr create") echo "https://github.com/acme/calc/pull/12" ;;
+ "pr view") echo "true" ;; # PR reported as merged -> clean "merged" ending
+ "pr list") echo "" ;;
+ *) : ;; # label create/list, issue edit/comment, pr merge, auth setup-git -> no-op OK
+esac
+exit 0
diff --git a/docs/demo/demo.tape b/docs/demo/demo.tape
new file mode 100644
index 0000000..a865bdc
--- /dev/null
+++ b/docs/demo/demo.tape
@@ -0,0 +1,19 @@
+# fixbuddy demo — recorded with VHS (https://github.com/charmbracelet/vhs).
+# Run via: bash docs/demo/gen.sh (sets up PATH/env, then runs `vhs` on this file)
+Output docs/demo.gif
+
+Require fixbuddy
+
+Set Shell "bash"
+Set FontSize 16
+Set Width 1320
+Set Height 600
+Set Padding 24
+Set Theme "Dracula"
+Set TypingSpeed 35ms
+
+Sleep 600ms
+Type "fixbuddy --repo acme/calc --project /tmp/fixbuddy-demo/playground --yes"
+Sleep 500ms
+Enter
+Sleep 9s
diff --git a/docs/demo/gen.sh b/docs/demo/gen.sh
new file mode 100755
index 0000000..2b60353
--- /dev/null
+++ b/docs/demo/gen.sh
@@ -0,0 +1,43 @@
+#!/usr/bin/env bash
+# Regenerate docs/demo.gif with VHS, using deterministic demo doubles (no real services).
+# Usage: bash docs/demo/gen.sh (requires `vhs` on PATH)
+set -euo pipefail
+
+REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+DEMO_DIR="$REPO_ROOT/docs/demo"
+WORK=/tmp/fixbuddy-demo
+
+command -v vhs >/dev/null || { echo "vhs not found — install https://github.com/charmbracelet/vhs"; exit 1; }
+
+rm -rf "$WORK"
+mkdir -p "$WORK/bin" "$WORK/home"
+
+# Runtime bin: stub agents + stub gh + fixbuddy, all on PATH.
+chmod +x "$DEMO_DIR/bin/agent" "$DEMO_DIR/bin/gh"
+ln -sf "$DEMO_DIR/bin/agent" "$WORK/bin/claude"
+ln -sf "$DEMO_DIR/bin/agent" "$WORK/bin/codex"
+ln -sf "$DEMO_DIR/bin/gh" "$WORK/bin/gh"
+ln -sf "$REPO_ROOT/fixbuddy.sh" "$WORK/bin/fixbuddy"
+
+# Playground repo with a real bug and a real origin (so `git push` genuinely works).
+git init -q --bare "$WORK/origin.git"
+git clone -q "$WORK/origin.git" "$WORK/playground" 2>/dev/null
+(
+ cd "$WORK/playground"
+ git config user.email demo@example.com
+ git config user.name "demo"
+ git checkout -q -b main
+ mkdir -p src
+ printf 'def add(a, b):\n return a - b # BUG: should be a + b\n' > src/calc.py
+ git add -A
+ git commit -q -m "calc: initial"
+ git push -q -u origin main
+)
+
+export PATH="$WORK/bin:$PATH"
+export DEMO_PROJECT="$WORK/playground"
+export HOME="$WORK/home" # isolate fixbuddy run logs
+
+cd "$REPO_ROOT"
+vhs "$DEMO_DIR/demo.tape"
+echo "Wrote $REPO_ROOT/docs/demo.gif"