Launch Thread Writer + Critic
MIT↓ 0 downloadsA Claude Code writer drafts an X/Twitter launch thread from your product brief, then a Codex critic tears it apart post-by-post — hook, clarity, specificity, CTA — rewriting the weak posts on the same warm branch until the thread is ready to ship.
Topology
Disclosures
Everything below runs on your machine or inside the sandbox when you use this workflow. Mismatches between these declarations and the actual code block publishing.
Host hooks
Commands executed on YOUR host machine by Sandcastle lifecycle hooks.
None declared.
Sandbox hooks
Commands executed inside the sandbox container.
None declared.
Network access
None. Both agents operate only on the local repository inside the sandbox; there is nothing to install and no page is fetched.
Shell expansion
No shell-expansion blocks in prompt files.
Files
Critique and rewrite the launch thread
You are a ruthless launch-thread editor. Growth teams pay you to kill the posts
that would get scrolled past. You are working on the same branch the writer just
finished. Read BRIEF.md for the product facts and content/launch-thread.md
for the draft, then interrogate the thread post by post.
Grade every post against a hard bar
For each post, ask:
- Hook (post 1): Does the first line stop the scroll on its own? If it could be the opening of any generic launch, it fails. Would you tap "show more"?
- Momentum: Does each post create a reason to read the next? Kill posts that restate the previous one or stall the arc.
- Specificity: Is every claim concrete and grounded in
BRIEF.md? Flag and cut vague adjectives ("powerful", "seamless", "game-changing"), unsupported numbers, and anything invented. Replace assertions with the specific detail. - Clarity: One idea per post. Short lines. No jargon a first-time reader can't parse in one pass.
- CTA: Is there exactly one, in the final post, with the real launch link, and is the next step frictionless?
- Truth: Nothing in the thread may contradict or exceed the brief.
Fix it, don't just complain
When a post is weak, rewrite it directly in content/launch-thread.md and
commit the change. Tighten the hook, cut filler posts, merge redundant ones,
sharpen the CTA. Prefer fewer, stronger posts over more, weaker ones. Keep the
numbering clean after edits.
Keep iterating — critique, rewrite, re-read — until the thread is one you would confidently post from your own account: a hook that stops the scroll, every post earning its place, every claim true to the brief, one crisp CTA.
When (and only when) the thread clears that bar, output the exact line
<promise>SHIP_IT</promise> and stop.
Diff vs the stock Sandcastle 0.12.0 template Dockerfile — green lines were added by the author, red lines were removed from stock.
+# Sandbox image for the Launch Thread Writer + Critic workflow.+# Installs both agent CLIs — Claude Code (writer) and Codex (critic) — on the+# stock Sandcastle base. This is a pure-text workflow, so no project runtime or+# dependencies are needed beyond the CLIs themselves.FROM node:22-bookworm# System dependencies.RUN apt-get update && apt-get install -y --no-install-recommends \git \curl \jq \ca-certificates \&& rm -rf /var/lib/apt/lists/*-# Claude Code CLI (the agent runtime).-RUN npm install -g @anthropic-ai/claude-code+# Agent CLIs: Claude Code (writer) and Codex (critic).+RUN npm install -g @anthropic-ai/claude-code @openai/codex# Non-root agent user. `sandcastle docker build-image` aligns AGENT_UID/GID to# the host user via --build-arg to avoid permission errors on bind mounts.+# node:22-bookworm already ships a "node" user at UID/GID 1000, so we RENAME it+# (the stock Sandcastle template pattern) — groupadd/useradd would collide with+# the existing IDs on a default build.ARG AGENT_UID=1000ARG AGENT_GID=1000-RUN groupadd --gid ${AGENT_GID} agent \- && useradd --uid ${AGENT_UID} --gid ${AGENT_GID} --create-home --shell /bin/bash agent+RUN groupmod -o -g ${AGENT_GID} node \+ && usermod -o -u ${AGENT_UID} -g ${AGENT_GID} -d /home/agent -m -l agent node-USER agent-WORKDIR /workspace+USER ${AGENT_UID}:${AGENT_GID}+WORKDIR /home/agent++# Sandcastle bind-mounts the worktree and sets the working directory at+# container start; the container just needs to stay alive until then.+ENTRYPOINT ["sleep", "infinity"]
Show full Dockerfile (highlighted)
# Sandbox image for the Launch Thread Writer + Critic workflow.
# Installs both agent CLIs — Claude Code (writer) and Codex (critic) — on the
# stock Sandcastle base. This is a pure-text workflow, so no project runtime or
# dependencies are needed beyond the CLIs themselves.
FROM node:22-bookworm
# System dependencies.
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
curl \
jq \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Agent CLIs: Claude Code (writer) and Codex (critic).
RUN npm install -g @anthropic-ai/claude-code @openai/codex
# Non-root agent user. `sandcastle docker build-image` aligns AGENT_UID/GID to
# the host user via --build-arg to avoid permission errors on bind mounts.
# node:22-bookworm already ships a "node" user at UID/GID 1000, so we RENAME it
# (the stock Sandcastle template pattern) — groupadd/useradd would collide with
# the existing IDs on a default build.
ARG AGENT_UID=1000
ARG AGENT_GID=1000
RUN groupmod -o -g ${AGENT_GID} node \
&& usermod -o -u ${AGENT_UID} -g ${AGENT_GID} -d /home/agent -m -l agent node
USER ${AGENT_UID}:${AGENT_GID}
WORKDIR /home/agent
# Sandcastle bind-mounts the worktree and sets the working directory at
# container start; the container just needs to stay alive until then.
ENTRYPOINT ["sleep", "infinity"]
# Auth for the Claude Code writer (required).
# Run `claude setup-token` on your host to generate a token, then paste it here
# in your local .sandcastle/.env (never commit the real .env).
CLAUDE_CODE_OAUTH_TOKEN=
# Auth for the Codex critic (required).
OPENAI_API_KEY=
import { createSandbox, claudeCode, codex } from "@ai-hero/sandcastle";
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
// One warm sandbox shared by both agents on a single content branch. This is a
// pure-text workflow — no dependencies to install — so there are no hooks. The
// `await using` binding tears the sandbox down on exit; if the branch still has
// uncommitted changes the worktree is preserved on disk so you can inspect it.
await using sandbox = await createSandbox({
branch: "content/launch-thread",
sandbox: docker({ imageName: "sandcastle:launch-thread-critic" }),
});
// 1. Draft — a Claude Code (Opus) writer reads BRIEF.md for the product facts
// and drafts an X/Twitter launch thread to content/launch-thread.md.
const draft = await sandbox.run({
agent: claudeCode("claude-opus-4-8", { effort: "high" }),
promptFile: ".sandcastle/write-prompt.md",
maxIterations: 1,
});
console.log(`Writer drafted the thread in ${draft.commits.length} commit(s).`);
// 2. Critique — a Codex (gpt-5.4) critic tears the draft apart post-by-post on
// the SAME warm branch: hook strength, clarity, specificity, and the CTA.
// It rewrites weak posts directly and loops until it is willing to ship.
const critique = await sandbox.run({
agent: codex("gpt-5.4", { effort: "high" }),
promptFile: ".sandcastle/critique-prompt.md",
maxIterations: 4,
completionSignal: "<promise>SHIP_IT</promise>",
});
console.log(`Critic finished with signal: ${critique.completionSignal ?? "none"}`);
Write the launch thread
You are a senior product-marketing writer who has shipped dozens of launch threads that actually converted. You are drafting an X/Twitter launch thread to announce a product.
Source of truth
Read BRIEF.md in the repository root. It is the only source of product facts —
what the product is, who it is for, the core problem, the key features, proof
points, pricing, and the launch link. Never invent facts, numbers, or claims
that are not in BRIEF.md. If a detail you want is missing, write around it
rather than making it up.
What great looks like
Write for the feed, not for a landing page. On X, the first post is the whole ballgame — if the hook does not stop the scroll, nobody reads post 2.
- Post 1 is a hook, not a headline. Lead with the tension, the surprising result, the contrarian take, or the concrete before/after. No "Introducing…", no "I'm excited to announce", no throat-clearing. Make the reader need the next line.
- One idea per post. Each post earns the next. Short lines. Concrete nouns and real numbers over adjectives. Cut every word that does not change meaning.
- Show, don't assert. Replace "powerful" and "seamless" with the specific thing that makes it powerful or seamless. Specifics are credible; adjectives are noise.
- Structure the arc: hook → the problem the reader feels → the shift/insight → what the product does → 2–4 posts of concrete proof or feature-in-action → who it's for / who it's not for → one clear CTA with the link.
- Exactly one call to action, in the final post, with the launch link from the brief. Make the next step obvious and low-friction.
- Voice: confident, plain-spoken, a little opinionated. Sound like a builder talking to peers, not a press release.
Format
Write to content/launch-thread.md. Number each post (1/, 2/, …). Keep each
post within a single tweet's worth of text (aim for ~240 characters; a couple
may run slightly longer). After the thread, add a short --- section titled
## Notes listing any brief facts you deliberately left out and why.
Commit the file. Do not review your own work adversarially — a separate critic agent will do that next. Just make the thread as sharp and truthful as you can.
README
Launch Thread Writer + Critic
Your launch thread is the single highest-leverage piece of copy you'll write all quarter — and the first post decides whether anyone reads the other twelve. This workflow pairs a strong writer with a ruthless editor so the thread that ships is the one that stops the scroll, not the first draft nobody had time to sharpen.
What it does
You drop a BRIEF.md in your repo with the product facts — what it is, who it's
for, the core problem, the standout features, your proof points, pricing, and the
launch link. A Claude Code (Opus) writer turns that into a numbered X/Twitter
launch thread at content/launch-thread.md: a hook that earns the next line, one
idea per post, concrete specifics instead of adjectives, and exactly one call to
action.
Then a Codex (gpt-5.4) critic does the job you never have time to do to your own copy. It grades every post against a hard bar — does the hook stop the scroll, does each post earn the next, is every claim grounded in the brief, is there one crisp CTA — and rewrites the weak posts directly on the same branch. It keeps looping until it would post the thread from its own account.
How it works
Both agents share one warm sandbox created with createSandbox() on the
content/launch-thread branch, so the critic sees exactly the state the writer
left behind with no second container start-up. The writer runs once
(maxIterations: 1); the critic loops (up to four passes) until it emits
<promise>SHIP_IT</promise>. The topology is draft → critique (loop) → ship.
Because it's pure text, there's nothing to install and no network to touch — no
hooks, no npm install, no page fetching. Just two models arguing about your
copy until it's good.
Requirements
Set both credentials in .sandcastle/.env: CLAUDE_CODE_OAUTH_TOKEN (run
claude setup-token) for the writer and OPENAI_API_KEY for the critic. Add
your BRIEF.md to the repo root. Build the image once with
npx @ai-hero/sandcastle docker build-image, then run
npx tsx .sandcastle/main.ts. The finished thread lands at
content/launch-thread.md. Nothing leaves the sandbox but the model calls.