Blog Post Pipeline
MIT↓ 0 downloadsTurn a single topic into a publish-ready blog post through three sequential Claude Code passes on one warm branch: an Opus strategist outlines the angle and keywords, a Sonnet writer drafts the full post, and an Opus editor tightens the prose and optimises it for search.
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. The agents operate only on the local repository inside the sandbox; the post is written from the supplied topic, with no web fetching.
Shell expansion
No shell-expansion blocks in prompt files.
Files
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 Blog Post Pipeline workflow.+# Node 22, git, and the Claude Code CLI, running as a non-root `agent` user.+# All three passes (outline, draft, edit) use the same Claude Code runtime.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).+# Claude Code CLI (the agent runtime for every pass).RUN npm install -g @anthropic-ai/claude-code# 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 Blog Post Pipeline workflow.
# Node 22, git, and the Claude Code CLI, running as a non-root `agent` user.
# All three passes (outline, draft, edit) use the same Claude Code runtime.
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 for every pass).
RUN npm install -g @anthropic-ai/claude-code
# 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"]
Pass 2 of 3 — Write the full draft
You are an expert long-form writer. A previous pass produced an approved outline.
First, read content/outline.md. It contains the angle, keywords, section
skeleton, recommended title, and CTA. Follow it — do not re-plan the article.
The topic is:
{{TOPIC}}
What to produce
Write the complete post to content/post.md and commit it. Structure:
- An H1 using the recommended working title from the outline.
- A 2–4 sentence intro that opens with a hook (a question, a sharp claim, or a concrete scene the reader recognises) and states what they'll get.
- The body sections from the outline, each as an H2, in order. Deliver the one idea each section promises and use the specific example/detail the outline named for it.
- A short closing section that lands the thesis and states the CTA.
Writing rules
- Write in clear, confident, active prose. Short sentences. Concrete nouns.
- Show, don't tell: replace adjectives like "powerful" or "seamless" with the actual outcome or a small example.
- Prefer the reader's language over jargon. If you must use a term of art, define it in a half-sentence the first time.
- No filler ("In today's fast-paced world…"), no throat-clearing, no exclamation points, no em-dash-stuffed run-ons.
- Where the outline marked
[needs stat], either use a figure you are genuinely confident is correct, or reframe the sentence so it doesn't depend on a fabricated number. Never invent statistics, quotes, or study citations. - Weave the primary and secondary keywords in naturally where they fit — never at the cost of a sentence sounding human.
- Target 1,100–1,600 words. Use short paragraphs (2–4 sentences) and bulleted lists where they genuinely aid scanning.
Commit the finished draft to content/post.md. Leave content/outline.md
in place — the edit pass will check the draft against it.
Pass 3 of 3 — Line edit + on-page SEO
You are a ruthless editor and on-page SEO specialist. A draft already exists.
Read both content/outline.md and content/post.md. Your job is to make
the post tighter, clearer, and more discoverable — editing in place. Do not
rewrite it into a different article; sharpen the one that's there.
The topic is:
{{TOPIC}}
Editing pass
Revise content/post.md directly:
- Cut every sentence that doesn't earn its place. Kill filler, hedges ("almost", "very", "really"), and repetition. Aim to remove 10–20% of words without losing meaning.
- Tighten the intro so the reader knows within two sentences why to keep reading.
- Fix weak verbs and passive voice. "We generate reports", not "reports are generated".
- Check the thesis from the outline is actually argued, and that each H2 still advances exactly one idea. Merge or split sections if not.
- Verify claims. If a statement asserts a fact or number you can't stand behind, soften it to what's defensible or remove it. Do not invent sources.
On-page SEO pass
Then optimise for search, keeping the primary/secondary keywords from the outline:
- Ensure the H1 title is ≤ 60 characters and contains the primary keyword naturally.
- Add a meta description (≤ 155 characters, includes the primary keyword,
reads like a sentence not a keyword list) at the very top of the file inside
an HTML comment:
<!-- meta description: ... -->. - Make sure the primary keyword appears in the first ~100 words and in at least one H2, without keyword-stuffing.
- Check headings form a logical H1 → H2 (→ H3) hierarchy that a reader could skim as a table of contents.
- Suggest, in an HTML comment at the very bottom of the file, 2–3 places an internal link would help and a one-line alt-text idea if a hero image is used.
Commit the edited, SEO-optimised post to content/post.md. The final file is
the deliverable.
# Auth for the Claude Code agent (used by all three passes).
# 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=
import { createSandbox, claudeCode } from "@ai-hero/sandcastle";
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
// A three-pass blog writing pipeline on ONE warm sandbox branch.
// Each pass is a separate Claude Code agent run on the same container and
// branch, so the artifacts from one pass are already on disk for the next:
// 1. outline (Opus) -> content/outline.md
// 2. draft (Sonnet) -> content/post.md (follows the outline)
// 3. edit+SEO (Opus) -> content/post.md (tightens + optimises)
// Keeping the sandbox warm means each pass sees the previous pass's commits.
// The subject of the post. Edit this to your topic — it is substituted for the
// {{TOPIC}} placeholder in every prompt file.
const TOPIC =
"How small engineering teams can adopt AI coding agents without losing code quality";
await using sandbox = await createSandbox({
branch: "content/blog",
sandbox: docker(),
});
// 1. Outline — Opus plans the structure, angle, and section beats.
const outline = await sandbox.run({
name: "outline",
agent: claudeCode("claude-opus-4-8", { effort: "high" }),
promptFile: ".sandcastle/outline-prompt.md",
promptArgs: { TOPIC },
maxIterations: 1,
});
console.log(`Outline pass: ${outline.commits.length} commit(s).`);
// 2. Draft — Sonnet writes the full post against the committed outline.
const draft = await sandbox.run({
name: "draft",
agent: claudeCode("claude-sonnet-4-6"),
promptFile: ".sandcastle/draft-prompt.md",
promptArgs: { TOPIC },
maxIterations: 1,
});
console.log(`Draft pass: ${draft.commits.length} commit(s).`);
// 3. Edit + SEO — Opus does a line edit, tightens, and optimises for search.
const edit = await sandbox.run({
name: "edit",
agent: claudeCode("claude-opus-4-8", { effort: "high" }),
promptFile: ".sandcastle/edit-prompt.md",
promptArgs: { TOPIC },
maxIterations: 1,
});
console.log(`Edit pass: ${edit.commits.length} commit(s). Post ready at content/post.md`);
Pass 1 of 3 — Outline the post
You are a senior content strategist and editor. Your job in this pass is only to plan the article. Do not write prose paragraphs yet.
The topic is:
{{TOPIC}}
What to produce
Write a complete working outline to content/outline.md and commit it. The
outline must contain, in this order:
- Search intent & reader — one line naming the specific reader who would search for this and what they want to walk away able to do.
- Angle — the single sharp thesis this post argues. Not a topic, a claim. A generic "here is an overview" angle is a failure; pick a point of view.
- Primary keyword + 3–5 secondary keywords — realistic phrases this reader would actually type. These guide the draft and the later SEO pass.
- Working title options — 3 candidate titles (≤ 60 characters each so they don't truncate in search results), and mark the one you recommend.
- Section-by-section skeleton — for each planned H2 section give:
- the section heading,
- one line on what argument or step it delivers,
- the concrete example, data point, or detail it should use (name it — do not leave "insert example here").
- Call to action — what one action the reader should take at the end.
Rules
- Aim for a structure that supports an 1,100–1,600 word post: a hook intro, 4–7 body sections that each advance ONE idea, and a short close with the CTA.
- Front-load value. The most useful section should not be buried at the bottom.
- Prefer specificity over breadth. A narrow post that fully answers one question beats a shallow tour of ten.
- Be honest: if a claim would need a statistic, note
[needs stat]so the draft pass knows to write it carefully rather than invent a number.
Commit the outline to content/outline.md. Do not create content/post.md yet
— that is the next pass's job.
README
Blog Post Pipeline
Give it one topic; get back a publish-ready blog post. This workflow runs the same three passes a good content team does — plan, write, edit — but as three specialised agents working on one warm branch, each building on the last one's committed work.
What it does
Most AI blog drafts are mediocre because a single prompt tries to plan, write, and polish all at once. This pipeline splits the job:
- Outline (Opus) — a strategist picks a sharp angle, chooses a primary and
secondary keywords, drafts title options, and lays out a section-by-section
skeleton with the concrete example each section must use. Committed to
content/outline.md. - Draft (Sonnet) — a writer reads that outline and produces the full
1,100–1,600 word post in clear, active prose, following the structure exactly.
Committed to
content/post.md. - Edit + SEO (Opus) — an editor cuts 10–20% of the words, fixes weak verbs
and passive voice, verifies the thesis is argued, then optimises on-page SEO:
a ≤60-char title with the keyword, a meta description, keyword placement, and
a clean heading hierarchy. Edited in place at
content/post.md.
How it works
main.ts opens one warm Docker sandbox with createSandbox() on the
content/blog branch, then calls sandbox.run() three times in sequence. Each
pass is a distinct agent (Opus → Sonnet → Opus) but shares the same container
and branch, so pass two reads pass one's outline off disk and pass three edits
pass two's draft. The topology is a straight line: outline → draft → edit.
The topic is a single {{TOPIC}} argument substituted into every prompt file,
so you change what gets written by editing one constant — no prompt surgery.
Requirements
Set CLAUDE_CODE_OAUTH_TOKEN in .sandcastle/.env (run claude setup-token on
your host). Edit the TOPIC constant at the top of .sandcastle/main.ts to your
subject. Build the image once with npx @ai-hero/sandcastle docker build-image,
then run the pipeline with npx tsx .sandcastle/main.ts. The finished post is at
content/post.md, with the planning outline preserved at content/outline.md.