# How I Built an AI-Powered Editorial Pipeline with OpenCode and EmDash CMS | Artificialus

> For the complete content index, see [llms.txt](https://artificialus.com/llms.txt). Markdown versions of all pages are available by appending `.md` to any URL.

- Home
- /
- Articles
- /
- How I Built an AI-Powered Editorial Pipeline with OpenCode and EmDash CMS

Case Studies

# How I Built an AI-Powered Editorial Pipeline with OpenCode and EmDash CMS

I built artificialus.com on Astro 6 with EmDash CMS, and the core operational challenge was this: how do you get the rigour of a traditional editorial proc

May 24, 2026

10 min read

D

Written by

Doc | The Researcher

Share

X

Facebook

Reddit

Telegram

Bluesky

Email

> When you run a curated AI resource platform, the content paradox hits fast: you need to produce high-quality, fact-checked, human-edited articles about AI tools — but the AI tooling landscape changes every week. Manual editorial workflows scale poorly when your beat is the entire AI ecosystem.

I built artificialus.com on Astro 6 with EmDash CMS, and the core operational challenge was this: how do you get the rigour of a traditional editorial process — fact-checking, structural editing, human review — without the cycle time of a weekly magazine? As MCP (Model Context Protocol) emerges as the standard for agent-tool communication and AI-generated content saturates every feed, the traditional editorial process needs to evolve.

The answer was a fully agentic editorial pipeline orchestrated by OpenCode. Here is how the agents are wired together — and how you can replicate the pattern for your own content operation.

## The Platform: EmDash CMS on Astro 6

EmDash is an Astro-native CMS built on Astro 6 that stores content in SQLite (or libSQL, Cloudflare D1, or PostgreSQL) and exposes a MCP (Model Context Protocol) API for machine-to-machine content management. That last bit — the MCP API — is the key architectural decision.

Every EmDash site ships with an MCP endpoint at `/_emdash/api/mcp` that speaks the Model Context Protocol natively. This means any MCP-compatible client (OpenCode, Claude Desktop, Cursor) can list collections, read content, create drafts, and update articles directly — no REST API wrappers, no custom webhooks.

The CMS schema for articles is minimal by design:

Field

Type

Purpose

`title`

string

Article headline

`content`

portableText

Rich body (h2, h3, normal, blockquote, code, tables, bullet lists)

`excerpt`

text

Short summary (first paragraph, ≤155 chars)

`featured_image`

image

Hero image

`featured`

boolean

Homepage placement flag

Content is stored as Portable Text — a JSON-based rich text format where each block has a `_type`, `style`, and `children` with optional `marks`. Block objects like `code`, `table`, and `image` have their own structure outside the `block` type.

The MCP tools available from the CMS include `content_create`, `content_get`, `content_update`, `content_publish`, `content_list`, and `schema_get_collection` — enough to build a complete publishing workflow without ever opening the admin UI.

## The Orchestrator: OpenCode

OpenCode is an AI coding agent framework that supports subagent mode, custom JSON configuration, MCP servers for external tools, and a scheduler plugin. The config lives in a single `opencode.json` file at the project root:

```
`{
"$schema": "https://opencode.ai/config.json",
"instructions": ["artificialus.MD"],
"mcp": {
"emdash-docs": {
"type": "remote",
"url": "https://docs.emdashcms.com/mcp"
},
"artificialus-cms": {
"type": "remote",
"url": "https://artificialus.com/_emdash/api/mcp",
"headers": {
"Authorization": "Bearer ec_pat_..."
}
}
},
"plugin": ["opencode-scheduler"],
"command": {
"pipeline": {
"description": "Avvia la pipeline editoriale completa per un articolo",
"template": "Avvia la pipeline editoriale per l'articolo {{input}}..."
},
"scout": {
"description": "Avvia article-scout per scoprire nuovi trend",
"template": "Usa l'agente @article-scout per scoprire nuovi trend AI..."
},
"revise": {
"description": "Review rapida dopo modifiche manuali",
"template": "Avvia revisione rapida per l'articolo {{input}}..."
}
},
"agent": {
"article-scout": {
"description": "Scopre nuovi contenuti AI",
"mode": "subagent",
"permission": { "webfetch": "allow", "bash": "deny", "edit": "deny" }
}
}
}`
```

Two MCP servers are configured: `emdash-docs` (read-only documentation at the protocol endpoint) and `artificialus-cms` (the live CMS API with a bearer token for CRUD operations). The `opencode-scheduler` plugin handles recurring jobs. Custom commands `/pipeline`, `/scout`, and `/revise` start the editorial flow, content discovery, and quick review.

The `agent` section defines the first agent — `article-scout` — as a subagent with restricted permissions. The other eight agents are defined as separate `.md` files in `.opencode/agent/`, each with their own frontmatter specifying mode, permissions, and description.

## The Nine Specialized Agents

Each agent has one job and clear permission boundaries. No agent has more capability than it needs.

Agent

Permissions

Core Responsibility

article-scout

webfetch

Discover AI trends, repos, and news

writer

webfetch, edit

Write drafts, apply revisions

fact-checker

webfetch

Verify factual claims; cannot edit text

senior-editor

none (read-only)

Evaluate structure, flow, clarity

humanizer

edit

Remove AI writing patterns

content-designer

webfetch, edit

Apply formatting, verify URLs

editor-in-chief

none (read-only)

Final sign-off: PUBLISH / REVISE / REJECT

cms-publisher

bash, edit

Convert markdown to Portable Text, save as draft

editorial-pipeline

all

Orchestrate the state machine

Three agents show how permission models shape the pipeline.

Fact-checker is the most constrained agent by design. It has webfetch to verify claims against primary sources — official docs, release notes, reputable coverage — but no edit or bash access. Every finding goes into a structured report that the writer must read and apply. Separating detection from correction means every change is documented and traceable; errors cannot slip in during the fix process.

Senior-editor cannot access the web at all. This is intentional: its job is to evaluate structure and flow based on the text alone, not to re-verify facts. The first thing it does is validate that all fact-checker issues were resolved. Then it assesses clarity, tone, and pacing — areas where web access would only be a distraction.

Editorial-pipeline is the orchestrator with full permissions: task access to invoke subagents, bash access to create working directories, edit access to write `STATE.md`, and webfetch for initial setup. It is the only agent that sees the entire workflow from discovery to CMS draft.

The remaining agents have narrow, well-defined roles:
- Humanizer edits text to remove AI patterns (hedging, formulaic transitions, adverb overuse) without changing facts or structure.
- Content-designer applies formatting — bold, italic, blockquotes, verified links — using webfetch to check URLs before insertion.
- CMS-publisher handles the final conversion from markdown to Portable Text JSON via the `@portabletext/markdown` library, then calls `content_create` (or `content_update` for revisions) via MCP. It never auto-publishes; every save is a draft.

> Key insight: The permission model is the most important design constraint here. Narrow permissions are not a limitation — they are a design tool.

## The Editorial Pipeline: Step by Step

The pipeline is a state machine managed by the editorial-pipeline orchestrator agent. It follows this flow:

```
`writer → fact-checker (revision loop)
→ senior-editor (revision loop)
→ humanizer
→ content-designer
→ writer (approval)
→ editor-in-chief
→ cms-publisher

A quick review mode (`/revise`) also exists: fact-checker only, then directly to `content_update` on the existing CMS draft — useful for post-publication edits or quick corrections.`
```

Visually: arrows from the fact-checker and senior-editor loop back to the writer — the revision loops are the most architecturally interesting part of the design.

When you run `/pipeline how-i-built-this`, here is the full flow. Run `/revise how-i-built-this` for a quick pass (fact-check + CMS update only, for post-edit corrections).

Here is what happens during a full pipeline run:

### Step 0: Setup

The orchestrator creates `drafts/pipeline-how-i-built-this/` and initializes `STATE.md`:

```
`# Pipeline: How I Built an AI-Powered Editorial Pipeline...

## Status: IN_PROGRESS

## Current Stage: 1

## History
| Stage | Agent | Verdict | Date |
|---|---|---|---|
| 0 | setup | initialized | 2026-05-24 |`
```

### Step 1: Writer produces the draft

The orchestrator invokes the writer subagent with the discovery brief or outline. The writer produces a complete `.md` file saved as `01-draft.md`.

### Step 2: Fact-checker (with revision loop)

The fact-checker reads the draft and verifies every factual claim. It uses webfetch to check against primary sources — official documentation, release notes, reputable coverage. The output is a structured report saved as `02-factcheck.md`.

If the verdict is REVISE, the report goes back to the writer for corrections, then the fact-checker re-runs. The loop repeats until PASS or escalation to a human editor.

If the verdict is PASS, the pipeline proceeds.

### Step 3: Senior editor (with revision loop)

The senior-editor first validates that all fact-checker issues were resolved. Then it evaluates structure, flow, clarity, tone, and pacing — but never factual claims (that is the fact-checker’s domain). The output is `03-senior-editor.md`.

Same revision loop logic applies: PASS proceeds, REVISE goes back to writer for corrections. The loop repeats until PASS or escalation to a human editor.

### Step 4: Humanizer

The humanizer line-edits the approved draft to remove AI writing patterns. It produces `04-humanized.md` with a change summary listing every modification made and its category (hedging, over-explaining, adverb overuse, etc.).

### Step 5: Content designer

The content-designer applies formatting for readability — bold, italic, blockquotes, verified inline links, and a further reading section. It uses webfetch to verify every URL before inserting it. The output is `05-formatted.md`.

### Step 6: Writer approval

The writer reviews the formatted article to confirm it still reflects the original vision. If the designer introduced phrasing changes or formatting that shifts meaning, the writer can request REVISE. If all looks good, APPROVE moves the pipeline forward.

### Step 7: Editor-in-chief

The editor-in-chief reviews the article alongside all prior reports — fact-check findings, senior-editor feedback, humanizer changes, designer formatting. The final verdict can be PUBLISH, REVISE (back to writer for fixes), or REJECT (pipeline archived).

### Step 8: CMS publisher

The cms-publisher reads the final `.md` file, extracts title, excerpt, and body, then runs the conversion script:

```
`NODE=/root/.nvm/versions/node/v24.15.0/bin/node
$NODE .opencode/scripts/md-to-pt.mjs drafts/pipeline-{slug}/06-approved.md`
```

The script uses the `@portabletext/markdown` library — a dedicated, schema-driven converter — to transform markdown into Portable Text JSON. The schema is explicitly configured with all EmDash-supported block types (`h1`/`h2`/`h3`, `normal`, `blockquote`, `bullet`/`number` lists, `strong`/`em`/`code`/`strike-through` decorators, link annotations, `code` and `table` block objects). The output is a complete JSON payload with title, excerpt, and content array.

Tables are converted to EmDash-native format: `_type: "table"` with `tableRow` rows and `tableCell` cells containing arrays of spans. Code blocks become `_type: "code"` objects storing the code text.

After conversion, the publisher calls `content_create` (or `content_update` during `/revise`) via the `artificialus-cms` MCP server with `status: "draft"`, then verifies the save with `content_get`.

```
`// ## Section heading
{"_type":"block", "style":"h2",
"children":[{"_type":"span", "text":"Section heading"}]}

// Normal paragraph with a link
{"_type":"block", "style":"normal",
"children":[
{"_type":"span", "text":"Read more at "},
{"_type":"span", "text":"OpenCode", "marks":["link-0"]}
],
"markDefs":[
{"_key":"link-0", "_type":"link", "href":"https://opencode.ai"}
]}

// Blockquote
{"_type":"block", "style":"blockquote",
"children":[{"_type":"span", "text":"Quote text"}]}

// Code block
{"_type":"code", "_key":"...", "code":"console.log('hello')"}

// Table (simplified)
{"_type":"table", "hasHeaderRow":true,
"rows":[{"_type":"tableRow",
"cells":[{"_type":"tableCell", "isHeader":true,
"content":[{"_type":"span", "text":"Header"}]}]}]}`
```

After a successful save, `STATE.md` is updated to COMPLETE and the CMS draft URL is logged.

## Key Design Decisions

Several constraints shaped how this pipeline works.

English-only content, any-language sources. The article-scout searches for content in any language — English, Italian, Chinese, Japanese — but all curated output on artificialus must be in English. This broadens the discovery surface without compromising the reading experience.

Revision loops with hard limits. The fact-checker and senior-editor stages each allow a maximum of 2 consecutive REVISE iterations before escalating to a human editor. This prevents infinite loops and gives the automated process room to converge.

Only the writer edits text. Fact-checker and senior-editor report findings but cannot modify the draft. Deliberate: separating detection from correction means errors cannot be silently introduced during the fix process, and every change is explicitly documented.

> Key insight: Separating detection from correction means errors cannot be silently introduced during the fix process, and every change is explicitly documented.

All CMS saves as “draft”. The cms-publisher never auto-publishes. Every article lands in the CMS as a draft, available for human review at `https://artificialus.com/articles/[slug]`. This preserves a human-in-the-loop for the final live decision.

Portable Text as the interchange format. Markdown is the pipeline’s internal format (easy for LLMs to produce and parse), but the CMS stores Portable Text JSON. The conversion happens at the last step, in the cms-publisher agent, keeping the intermediate pipeline files human-readable and diff-friendly.

## What It Looks Like in Practice

The pipeline directory for this article looks like this:

```
`drafts/pipeline-how-i-built-this/
├── STATE.md
├── 01-draft.md
├── 02-factcheck.md
├── 03-senior-editor.md
├── 04-humanized.md
├── 05-formatted.md
├── 06-approved.md
├── 07-eic.md
└── 08-cms-publish.md`
```

`STATE.md` tracks the entire journey. Right now it is a work in progress at the senior-editor stage:

```
`# Pipeline: How I Built an AI-Powered Editorial Pipeline...

## Status: IN_PROGRESS

## Current Stage: 3

## History
| Stage | Agent | Verdict | Date |
|---|---|---|---|
| 0 | setup | initialized | 2026-05-24 |
| 1 | writer | complete | 2026-05-24 |
| 2 | fact-checker | PASS (1 revision) | 2026-05-24 |
| 3 | senior-editor | **currently running** | 2026-05-24 |`
```

Each stage’s output file is a complete, human-readable markdown document. You can audit the pipeline at any point — read what the fact-checker flagged, see how the writer responded, or compare the original draft to the humanized version. The entire editorial process is transparent.

## Lessons Learned and Next Steps

> The revision loop is the most valuable part of this pipeline. Based on our pipeline runs so far, the fact-checker catches multiple issues per article — incorrect version numbers, broken links, outdated statistics. The senior-editor typically flags several structural improvements. These are the kinds of errors that erode trust when they reach readers, and catching them automatically is a significant quality multiplier.

The agent permission model was more important than I initially expected. When the fact-checker cannot edit text, it forces discipline: every fix is documented and traceable. When the senior-editor cannot access the web, it stays focused on the text in front of it. Narrow permissions are not a limitation — they are a design tool.

> The same protocol that lets Claude Desktop query a codebase also lets our editorial pipeline create CMS drafts. There is no REST API to design, no authentication middleware to write, no webhook to configure. The CMS already speaks MCP, and OpenCode already consumes MCP; the two systems integrate at the protocol level, not through custom glue code.

What is next. The `opencode-scheduler` plugin is configured and ready. The next step is to schedule a weekly content discovery job that runs the article-scout on a cron schedule, producing a batch of article ideas every Monday morning. From there, the pipeline can be kicked off with a single command. The goal is a semi-automated editorial workflow where human judgment happens at the right points — topic selection, final review — and everything else is handled by agents with clearly bounded responsibilities.

## Further Reading
- EmDash CMS — MCP Server Reference — The official documentation for EmDash’s MCP server, covering all available tools and collection management
- OpenCode AI — The agentic coding framework used to orchestrate this pipeline
- Model Context Protocol — The open standard that makes agent-to-tool communication possible
- OpenCode Plugins — Documentation for OpenCode plugins including the scheduler for recurring jobs and automated workflows
- Portable Text Specification — The JSON-based rich text format used by EmDash and Sanity CMS

This article is being produced by the pipeline it describes, in real time. As you read this, each stage — writer, fact-checker, senior-editor, humanizer, content-designer, editor-in-chief, and cms-publisher — runs sequentially, with each intermediate file saved to the pipeline directory. The entire process is orchestrated by the editorial-pipeline agent via OpenCode.

### No comments yet

Name

Email

Don't fill this out

Comment

Post Comment

Key Metrics

Read time

10 min

Words

1,974

In this article

## Continue reading

AI Research

6 min

### The Infrastructure Category That Didn't Exist Two Years Ago: AI Agent Observability

Why traditional APM breaks on agent workloads and how LangSmith, Braintrust, and Arize are building the observability stack for the AI era.

AI Research

Jun 3, 2026

Engineering

8 min

### GitHub Copilot Token-Based Billing: What It Means for Developers

GitHub Copilot moves to token-based AI Credits on June 1, 2026. A practitioner's analysis of the new pricing, what it reveals about agentic AI costs, and how to optimize usage.

Engineering

Jun 3, 2026

Landscape

7 min

### Anthropic's IPO: The $965B Test of Safety-First AI at Scale

Anthropic files for IPO after $65B raise at $965B valuation. The safety-first AI company faces its toughest test yet: can principles survive public markets?

Landscape

Analysis

Jun 3, 2026