A multi-agent blog-writing pipeline built on OpenCode. You give it a one-line brief, and a team of four specialist agents researches, writes, fact-checks, and proofreads it, then hands you a publish-ready draft.
It powers Dana's Blog, a blog for small-business owners. The whole point is to produce on-brand, fully-sourced posts without a human babysitting each step, while keeping a human in control of the one thing that matters most: hitting publish.
- 1. What it does
- 2. The agents
- 3. Design principles
- 4. House style
- 5. Project structure
- 6. Prerequisites
- 7. Setup
- 8. Usage
- 9. Practical example
- 10. Output and definition of done
- 11. Customization
- 12. Notes and limitations
Writing a good blog post is really four jobs: find the facts, write the prose, check the facts, and polish the voice. Blog-Agent gives each job to a dedicated agent and wires them into a fixed pipeline:
Your brief
│
▼
┌──────────────┐
│ editor │ reads the brief, assigns a slug, runs each step in order
└──────┬───────┘
▼
┌──────────────┐
│ researcher │ → research/<slug>.md (sourced notes)
└──────┬───────┘
▼
┌──────────────┐
│ copywriter │ → drafts/<slug>.md (the draft)
└──────┬───────┘
▼
┌──────────────┐
│ proofreader │ → for_review/<slug>.md (proofed and fact-checked)
└──────┬───────┘
▼
Dana reviews and publishes (human-in-the-loop)
The agents stop at for_review/<slug>.md. Nothing is published automatically. A human reviews the draft and then decides whether to publish it or not. That final call is always a human-in-the-loop step.
Four agents, each with one job and only the access that job needs.
The editor is the one you talk to. It runs at a low temperature (controls the creativity) because it is making decisions, not writing prose. It reads your brief, settles on a slug (common name), and
orchestrates the process (researcher first, then copywriter, then proofreader), checking each result before the end of each step. It has no web access and writes no files at all. Coordinating the others is its entire job.
The researcher is the only agent allowed online. It can both search the web and fetch pages. It also runs at a low temperature, so it stays close to its sources. It writes its findings to research/<slug>.md and nowhere else: one claim per line, each backed by a real source URL. If it cannot find a source for something, the claim does not make the cut.
The copywriter turns those notes into an actual post. It runs at higher temperature, so the prose reads like a person wrote it. It has no web access and works only from the researcher's notes, so it cannot add a "fact" that nobody checked. When it wants to say something the notes do not cover, it leaves a
<!-- NEEDS SOURCE: ... --> marker instead of inventing. It writes the draft to drafts/<slug>.md only.
The proofreader is the last gate. It runs at the lowest temperature, because its job is to follow the rules (Author's voice) strictly. It has no web access. It cleans up grammar and voice, then checks every claim in the draft against the notes. Anything unsupported gets flagged with an <!-- UNSUPPORTED --> marker rather than being quietly fixed. The clean version lands in for_review/<slug>.md, ready for human review.
The full instructions for each agent live in .opencode/agents/.
Tip
Not familiar with multi-agent systems? The OpenCode agent documentation is a good starting point before diving into the agent definitions under .opencode/agents/.
This repo acts as an example of a reliable agent systems.
- Least privilege, enforced. opencode.json denies everything project-wide (
edit,bash,webfetch,websearch,external_directory), and each agent gets granted only what it needs. Only the researcher can access the web. The copywriter cannot touch the web, the proofreader cannot rewrite the draft, and the editor cannot write any file at all. A misstep in one agent cannot corrupt another's work. - One slug per run means traceability. The editor derives a single
<slug>from the brief (for example, "How to winterize your café" becomeswinterize-your-cafe) and passes it to every specialist. All three artifacts share that slug, so each run is self-contained and runs should not overwrite each other (slug collisions are possible when asking the exact same topic). - Facts stay separate from prose. The researcher produces sourced facts, the copywriter may use only those facts (and flags gaps with
<!-- NEEDS SOURCE: ... -->rather than inventing), and the proofreader verifies that every claim maps back to a note, flagging unsupported ones with<!-- UNSUPPORTED -->. No agent gets to make things up. - A human always publishes. The pipeline ends at
for_review/<slug>.md. The agents have no tools to publish and are instructed never to try. - Prompt-injection defense. The researcher treats fetched web pages as data to summarize, never as instructions to follow.
Every agent follows the house style defined in AGENTS.md, the single source of truth for all agents. The essentials:
- Voice: short sentences, active voice, second person ("you"), one idea per paragraph, and concrete examples over abstractions.
- Structure: 500-700 words, with a title, a one-sentence summary, and 3-5 sections.
- Sourcing: every factual claim must map to a source in
research/<slug>.md. No statistics, prices, dates, or quotes that are not in the notes. - Spelling: US English throughout.
For a broader introduction to content style principles, the Google Developer Documentation Style Guide and Mailchimp Content Style Guide are useful references.
Blog-Agent/
├── AGENTS.md # House style, pipeline spec, and definition of done (shared by all agents)
├── opencode.json # Project config: deny-by-default permissions and the agent roster
├── .opencode/
│ ├── agents/
│ │ ├── editor.md # Primary orchestrator
│ │ ├── researcher.md # Sourced-notes specialist
│ │ ├── copywriter.md # Draft writer
│ │ └── proofreader.md # Proof and fact-check
│ └── package.json # @opencode-ai/plugin (dev-only helper, unused here)
├── research/ # researcher output (gitignored)
├── drafts/ # copywriter output (gitignored)
└── for_review/ # proofreader output (gitignored) ← Dana reviews this
The output folders (research/, drafts/, for_review/) each contain a .gitkeep file so Git tracks the empty directories and the folder structure exists after a fresh clone. A .gitignore entry prevents the actual artifacts from being committed, so local runs stay off the remote repo by default.
If you fork this repo and want to keep an audit trail of every run, remove or edit the relevant .gitignore entries to allow those files to be committed.
- npm (required to install OpenCode; installing Node.js will bring npm along).
- OpenCode installed (the
opencodeCLI). - An OpenRouter account with an API key and some credits. Every agent's
model is referenced as
openrouter/..., so the pipeline routes through OpenRouter.
Note
OpenRouter provides a unified API gateway to a wide range of hosted language models. Learn more about how OpenRouter routing works.
macOS / Linux — use Homebrew (recommended) or the install script:
# Homebrew (recommended for macOS/Linux)
brew install opencode
# or use the install script
curl -fsSL https://opencode.ai/install | bashWindows — the curl script does not run on Windows. Use npm or winget instead:
# npm (recommended if Node.js is already installed)
npm install -g opencode-ai
# winget
winget install opencodeAll platforms — npm works everywhere if you already have Node.js:
npm install -g opencode-aiHomebrew and other methods are listed on the OpenCode download page.
Confirm it worked:
opencode --versionClone the repository and move into it:
git clone https://github.com/Infotrend-Inc/Blog-Agent.git
cd Blog-AgentWe chose OpenRouter because one API key gives us access to a wide range of models from many providers. We can swap models without changing our credentials.
Note
OpenRouter is not required. OpenCode supports 75+ providers (including Anthropic, OpenAI, Google, Amazon Bedrock, Azure, Groq, and local models via Ollama or LM Studio), so we can use an alternative if preferred:
- Sign in with
opencode auth login(see section 7.4). - Run
opencode modelsto obtain the list of available models - change the
modelfield in the agent files under .opencode/agents/.
See the OpenCode providers docs for the full list and setup details.
- Sign up at openrouter.ai.
- Create an API key (it looks like
sk-xxxxxx...). - Add credits to your account. A typical run costs under $0.20 with the default models.
You have two options. Pick one.
Option A: an environment variable. OpenCode reads OPENROUTER_API_KEY automatically.
macOS / Linux:
export OPENROUTER_API_KEY="<your-openrouter-key>"Add that line to your ~/.bashrc or ~/.zshrc so it persists across sessions.
To set it for a single run instead, prefix the command:
OPENROUTER_API_KEY="<your-openrouter-key>" opencodeWindows (PowerShell):
$env:OPENROUTER_API_KEY = "<your-openrouter-key>"To persist it across sessions on Windows, use setx or add it via "System Properties → Environment Variables":
setx OPENROUTER_API_KEY "<your-openrouter-key>"Option B: interactive login. Store the key on disk once:
opencode auth loginChoose OpenRouter and paste your key. OpenCode saves it to:
- Linux:
~/.local/share/opencode/auth.json - macOS:
~/Library/Application Support/opencode/auth.json - Windows:
%APPDATA%\opencode\auth.json
You can list configured providers any time with opencode auth list.
When you run opencode auth login, it shows a searchable list of providers (not just OpenRouter). You can use this to discover and switch to other supported providers.
Warning
If you see an "Incorrect API key" error after setup, your key may have been stored under the wrong provider. Re-run opencode auth login, make sure you select OpenRouter, and paste the key again.
To see which models are available from your configured providers, run:
opencode modelsFrom the project root (the folder where README.md is located):
opencodeOpenCode reads opencode.json and loads the four agents. If you see a model or authentication error here, your OpenRouter key or credits are not set up properly; revisit sections 7.3 and 7.4.
Start OpenCode from the project root (the folder where README.md is located):
opencodeBefore you type anything, make sure the active agent is the editor, the primary agent that drives the whole pipeline. Press Tab (or Shift+Tab) to cycle through the primary agents until the status line shows editor. Then give it a brief:
Write a post on how to protect a small business from phishing scams.
From there the editor runs the pipeline on its own:
- derives the slug
protect-a-small-business-from-phishing-scams, - calls the researcher, producing
research/protect-a-small-business-from-phishing-scams.md, - calls the copywriter, producing
drafts/protect-a-small-business-from-phishing-scams.md, - calls the proofreader, producing
for_review/protect-a-small-business-from-phishing-scams.md, - reads the proofed file, checks it against the definition of done, and shows you the path to
for_review/<slug>.md.
If the proofreader blocks the draft (an unsupported claim, a placeholder source, a missed word count), the editor sends it back to the copywriter one time and reruns the check, then stops.
Brief: "How to protect a small business from phishing scams."
Slug the editor assigns: protect-a-small-business-from-phishing-scams.
Stage 1, research/protect-a-small-business-from-phishing-scams.md (researcher). Sourced notes, one claim per line, each followed by a real URL. For this topic they would come from agencies like the FTC and CISA:
The FTC says phishing emails often create a sense of urgency to trick you into acting fast.
https://consumer.ftc.gov/articles/how-recognize-and-avoid-phishing-scams
CISA recommends turning on multi-factor authentication to limit the damage of stolen passwords.
https://www.cisa.gov/secure-our-world
Sources:
https://consumer.ftc.gov/articles/how-recognize-and-avoid-phishing-scams
https://www.cisa.gov/secure-our-world
Stage 2, drafts/protect-a-small-business-from-phishing-scams.md (copywriter). The draft, written from those notes only, in Dana's voice: a title, a one-sentence summary, and 3-5 short sections, with every claim tracing back to a line in the notes.
Stage 3, for_review/protect-a-small-business-from-phishing-scams.md (proofreader). The same article, proofed for grammar and voice and fact-checked against the notes. This is the file Dana opens to review and publish.
A run is "done" when, per AGENTS.md, there is:
- a draft in
drafts/<slug>.mdthat follows the house voice, hits 500-700 words, and has a title, a one-sentence summary, and 3-5 sections, with every claim mapping to a note, and - a proofed, fact-checked version in
for_review/<slug>.md.
for_review/<slug>.md is the artifact Dana reviews. The agents stop there, and Dana reviews the draft and decides whether to publish it.
- Change the voice or rules: edit AGENTS.md. It is the single source of truth for voice, the hard rules (sourcing, word count, structure), file naming, and the definition of done. Every agent reads it.
- Swap models or tune behavior: edit the
frontmatterin any .opencode/agents/ file (see OpenCode agent docs for the full list of options.)model: which LLM to use (e.g.openrouter/openai/gpt-5.4-mini)temperature: controls creativity; lower is more deterministic, higher is more variedsteps: maximum number of tool calls the agent can make in one runpermission: which tools and file paths the agent can access
- Tighten or loosen permissions: adjust the per-agent
permissionblocks in .opencode/agents/ rather than loosening the project defaults in opencode.json; keep that file as the deny-by-default baseline. For example, set the researcher'swebfetchtoaskto pause before pulling untrusted pages (slower, but gives you a confirmation step before each fetch). Read more about OpenCode permission controls.
- Output folders (
research/,drafts/,for_review/) aregitignored, so artifacts stay local and are not committed. - Publishing is manual by design. The agents have no publishing tools and are instructed never to attempt it.
- All models route through OpenRouter (other providers can be used)
