AI & automation

Last updated •

A second Instagram, fronted by a clone of me

@copilot_shogo is an English account about AI tools. The face and the voice are generated from mine. This is the pipeline behind it: claims checked before recording, a cloned voice that had to be un-broken, video written as code, and gates that stop a render. And what the numbers say about whether any of it helps. · 13 min read

On September 26 I posted the first reel on a new account, @copilot_shogo. It is in English, it is about AI tools and the things I build with them, and the person talking in it is me — except I never sat in front of a camera. The face is a HeyGen digital twin trained on video of me. The voice is an ElevenLabs clone of my own, made from about four minutes of me reading aloud.

By September 29 there were nine finished reels. This is how the account is set up, how a reel gets made, and the part that turned out to be hard. I updated it in October with the production pipeline as it runs now.

9

finished reels in the first five days

480

reels from 16 accounts studied before the first post

9

pages under /free that the reels pointed to at the end of September

Why a second account

My personal account, @imshogo.k, has about 29,000 followers. A story on it reaches 200 to 500 people. Follower count stopped meaning reach a while ago, and the content there is fitness and motivation, not the work I spend my days on.

So the new account has one job: show the AI tools I actually use and the things I build with them, in a form people can take away. Every reel ends with a keyword. Comment it, and an automated DM sends a link to a page on this site under /free — install commands, how the thing works, a real run. The reel is the trailer; the page is the thing you keep.

Research before the first post

Before making anything, I pulled 480 reels from 16 accounts in this niche and measured them. Three things came out of it:

  • What grew was not a particular hook shape. It was whether the viewer gets something they can use — a step, a prompt, a tool, something the creator built
  • A face talking the whole way through, and opinion or mindset pieces, did badly
  • "Comment a keyword for the link" is used by nearly everyone, so it is table stakes rather than an edge

Many creators in this niche also front their accounts with an AI version of themselves, so a generated face was not the unusual part. What people share is.

What gets posted

Reel #08, the legal traps of a vibe-coded app. Screens and graphics take the top two thirds; the twin sits in the bottom third.

The pipeline in one pass

Every reel is one folder in a studio repo, and Claude Code does most of the assembly. The order is fixed, and each step writes a file the next one reads:

  1. Ideas. A script collects what moved in the last day on X, Instagram, GitHub Trending and Hacker News, and Claude proposes topics
  2. Verification. A separate agent checks every claim against its source. Only topics that pass become a script
  3. Voice. The narration goes to my voice clone, which returns audio and a timestamp for every character
  4. Face. The finished audio drives the HeyGen twin through its API
  5. Picture. The video is a React component rendered by Remotion, a library that turns React into video frames
  6. Render. A check script runs first and refuses to render if a rule is broken
  7. Review. A separate agent scores the rendered frames. Below 85 the reel does not reach me
  8. Posting. I post by hand. Nothing in the repo can publish

Claims are checked before anything is recorded

The first factual failure came on day one. A topic was built from a reference reel about a project called Laya. The transcript misheard the name, and the research built the whole idea on a different, much smaller project. I caught it only because the name on the screen did not match, and the reel had to be remade.

The fix was structural. Each idea now names one entity with its official URL and lists the things it could be confused with. The subject of a reference reel is read from its frames, never from the transcript. Every factual sentence needs an entry in a claims list: the sentence, the source URL, and a verbatim quote from that page. Then a different agent, told to look for errors and not for reasons to pass, opens each URL and checks that the quote is there and that the sentence claims no more than the quote does. It writes pass or fail into the file. The session that wrote an idea cannot pass it.

A pass expires after three days, because tools change. The idea files in the repo hold 19 ideas with 230 sourced claims between them. It has already paid for itself: the reference reel behind my LLM Council video attributed a benchmark number to the wrong model and a skill to the wrong author. Mine says what the sources say.

Script to voice

Text-to-speech is not deterministic. The same sentence comes back with different pauses each time, and a pause in the middle of a clause is what makes a cloned voice sound stitched together. So the script asks for several takes and keeps the one with the fewest pauses that do not follow punctuation.

scripts/tts.py
def odd_pauses(al):  # gaps > 0.12 s that are not after punctuation = the "choppy" breaks Shogo hears    w = words_of(al); return sum(1 for x, y in zip(w, w[1:]) if y[1] - x[2] > 0.12 and x[0][-1] not in ",.:;?!")best = Nonefor i in range(max(1, a.takes)):    d_ = json.loads(...)  # one request to the text-to-speech endpoint that returns audio + character timestamps    n = odd_pauses(d_["alignment"]); print(f"take {i + 1}: {n} mid-sentence pauses")    if best is None or n < best[0]: best = (n, d_)    if n == 0: break
Three takes by default, stopping early on a clean one. The request line is shortened here; the rest is as written. The same response becomes words.ts, a list of every word with its start and end time.

The clone is generated at speed 1.2. A second script shortens only pauses longer than 0.3 seconds to 0.2 and stretches the result by 1.1 with Rubber Band, a time-stretcher that preserves the formants of a voice. That lands near 250 words a minute with the breaths left in. I got there by listening: 1.38 times with every breath removed sounded cheap, and no stretch at all was a little slow.

The first tightening script had a bug I could hear before anyone could name it. I said it sounded like an old radio. The script cut at the word-end time the API reports plus a fixed gap. But the reported end comes before the sound actually dies, and connected speech has no silence between words. On one reel, 75 of 107 cuts had removed audible voice, and the pieces were joined with no fade.

scripts/tighten.py
_hop = int(0.005 * SR); _w = int(0.010 * SR)_env = np.sqrt(np.convolve(_raw.astype(np.float64) ** 2, np.ones(_w) / _w, "same"))[::_hop]_thr = 0.12 * np.percentile(_env[_env > 1e-4], 75)def _quiet_span(a0, b0):    """longest run inside [a0, b0] where the audio is quiet → (start, end) in seconds, or None"""    i0, i1 = int(a0 * SR / _hop), int(b0 * SR / _hop)    q = _env[i0:i1] < _thr    best, cur = None, None    for k, v in enumerate(q):        if v: cur = (cur[0], k) if cur else (k, k)        else: cur = None        if cur and (not best or cur[1] - cur[0] > best[1] - best[0]): best = cur    if not best: return None    return (i0 + best[0]) * _hop / SR, (i0 + best[1] + 1) * _hop / SR
A cut may now remove only audio that is measurably quiet: 10 ms RMS below 12% of the speech level. It keeps 15 ms of air on each side, and where there is no real silence it does not cut. Word times are remapped through the same kept intervals, so captions stay in sync.

The face

The twin is audio-driven. A script uploads the final narration to HeyGen, requests a render of my avatar speaking it, polls until it is done and downloads the clip. It costs about one API credit per second of audio, so the face is generated only after the voice is final. It uses HeyGen's v3 API (upload with /v3/assets, render with /v3/videos), so the October 31, 2026 shutdown of the older endpoints doesn't affect it.

On screen the face is fixed to the bottom third, below y = 1280 on a 1080 by 1920 frame, and episodes may not redefine that line. The face also has to leave. The research said a face talking all the way through does badly, and one of my own reels drifted into exactly that, so from October 12 a reel that does not hide the face in at least two scenes fails the check.

Video as code, timed by words

Each reel is a React file rendered at 60 frames a second. The decision that mattered most is that nothing is timed in seconds. A scene starts when a word is spoken, and the word list from the voice step is the clock.

src/components/Kit.tsx
type Word = { w: string; s: number; e: number };const norm = (s: string) => s.replace(/[.,?!:;"“”]/g, "").toLowerCase();/** at("sued"), at("it", 5.9) → the start time of that spoken word. Throws when the word is missing, so a script edit *  can't silently move a cue to the wrong second (dreamwork's copy fell back to `after` without a word). */export const wordClock = (words: Word[]) => {  const ws = words.map((w) => ({ ...w, k: norm(w.w) }));  return (k: string, after = 0) => {    const w = ws.find((x) => x.k === norm(k) && x.s >= after);    if (!w) throw new Error(`word not found: "${k}" after ${after}s`);    return w.s;  };};// episodes/_template/Video.tsxconst at = WORDS.length ? wordClock(WORDS) : (_k: string, after = 0) => after;const S = { hook: 0, name: at("tool"), number: at("number"), demo: at("demo"), comment: at("comment") };
Re-recording the voice moves every scene with it. A missing word is a build error. An earlier per-episode copy of this helper fell back silently, which is why it throws now.
  • **Captions** come from the same word list: one to three words in a box, broken at punctuation, at a pause longer than 0.25 seconds, or when the layout changes. Episodes cannot define their own caption style
  • **Safe zone.** Instagram covers the top of a reel with its own labels, the bottom with the caption and the right edge with buttons. Headlines, cards, terminals and captions stay between y = 250 and y = 1500 and between x = 70 and x = 1025, narrowing to x = 887 below y = 1110. Review stills are rendered with that frame drawn on top
  • **Real screens.** Pages are recorded with Playwright, a browser automation library, from a list of steps (wait, scroll, click, drag). For sharp text, one recorder takes a screenshot per frame at phone width and assembles them into a clip. For zooms, another script saves the measured pixel box of a chosen piece of text, so the camera goes to a computed rectangle and not to a guess. Nothing on screen may pretend to be a tool's output
  • **Logos** come from official sources, recorded in a manifest with where each was fetched. It has 77 entries today

Gates that stop a render

Rules written in a document did not hold. So the rules that can be tested became a 170-line shell script that the render command runs first. Nothing renders if any of these fail:

  • The face and all spoken text use the shared components, the narration is my voice, and it runs at 215 words a minute or more
  • The music is at least one second longer than the narration. One reel went silent for its last nine seconds before this existed
  • The script has a shot list of at least six cuts and a retention plan with at least three timed devices, written before the first render
  • Every downloaded asset is used in the video or listed with a reason. Claude once downloaded five official demo clips, used one, and drew its own cards for the rest
  • The cover photo has not been used before. The comparison is a perceptual hash of the image, because the same photo was once reused under a different filename
  • The caption is 480 characters or fewer and does not open with the first sentence of the script

After rendering, audio is brought to -14 LUFS. A single normalisation pass was landing between -15.3 and -16.2 because sound-effect peaks hit the ceiling first, so the render now applies gain, limits the peaks, measures again and corrects. Another script measures how many scene changes land on a beat of the music. On one reel that went from 29% to 89%, against 55% for random placement.

The last gate is the one I trust most. A reel reaches me only through one script, and that script wants a review from an agent that saw the rendered frames and nothing else: no code, no explanation of intent.

scripts/ship.sh
[ -f "$F" ] || { echo "SHIP ✗ no $F — run render.sh first"; exit 1; }[ -f "$R" ] || { echo "SHIP ✗ no $R — run the independent critic (docs/critic.md) on this render"; exit 1; }[ "$R" -nt "$F" ] || { echo "SHIP ✗ review.md is older than the render — the critic must judge the current final.mp4"; exit 1; }score=$(grep -Eo "^Score: *[0-9]+" "$R" | tail -1 | grep -Eo "[0-9]+" || echo 0)[ "${score:-0}" -ge 85 ] || { echo "SHIP ✗ critic score $score < 85 — fix the defects in review.md and re-render"; exit 1; }bash scripts/brand_check.sh "$id" >/dev/null || { echo "SHIP ✗ brand_check fails"; exit 1; }
$F is the final render and $R is the review file. The review must be newer than the render, so a score cannot be carried over from an earlier version.

The hard part was taste, not generation

Generating a reel is cheap now. Making one worth watching was not. My second reel went through several versions. I scored the first one 65 out of 100. The rebuild, which Claude had scored 80, I scored 52: text cut off by a zoom it had chosen itself, a rainbow title that meant nothing, too many sound effects.

Across the first week, the model's scores for its own work ran 5 to 30 points above mine. So I stopped asking it whether a reel was good and started writing down what good means, first as a craft document and then, where a rule could be tested, as the checks above. Reel #08 started out, in my words, looking cheap. Four revisions later, I called it excellent.

Cover for reel #08: 'Sued for $100K. Before one sale.' over a photo of Shogo at a laptop, with a 3D badge and a printed receipt
Covers use a real photo, a 3D badge for the subject, and a serif headline. The reel uses the twin; the cover never does.

What the tooling has not fixed

The gates raise the floor. They do not make a first version good. In the four reels that have a review file, the independent critic's first score was 66, 66, 68 and 71, and each took three to five more rounds to reach 85. When I asked in early October why quality was dropping, some of the causes were things the tooling had created: a shared motion kit gave every reel the same grid background and the same tilted browser frame, and no check asked for a moment that only this reel has.

Nor do the numbers follow the scores. On October 7 I pulled the public counts for every post. Day-one views fell into two groups, about 3,200 to 3,700 for two reels and about 200 to 700 for five others, and my ratings did not predict the group. The reel with my highest score, 82 for a draft version, had 204 views after a day and a half. The best result that week was a 7.5-second motivational clip made outside this pipeline: 14,759 views in 2.7 days, against 4,914 after 10.6 days for the best reel the pipeline produced. The sample is too small to test anything. But nothing here shows production quality separating these reels. Topic and hook look more likely, and that is a hypothesis.

A script now records views, likes and comments for each post at day one and day seven, inside a monthly spending cap. Shares and saves are not public, so those columns stay empty instead of being estimated.

The model can make a reel in minutes. It cannot tell you whether the reel is good, or whether it is true. Those two checks are the actual work, and they have to be written down or they don't happen.

What I would add now: written down is not enough either. A rule held only once a script could fail on it. And a reel that passes every check can still be one nobody needed.

Read more

Have something that needs to be built?

Tell me the workflow and what a good outcome looks like. I'll reply with a clear next step.

  • 1,200+ clients handled
  • Published at Mercari
  • 30K followers