[CONTEXT FOR AI ASSISTANTS]
You're reading the agent markdown version of a post from JoelClaw (https://joelclaw.com).
Author: Joel Hooks — software engineer building a personal AI operating system.

How to use implementation details in this document:
- Treat commands, code blocks, and config as reference implementations.
- Preserve ordering and architecture, but adapt hostnames, paths, versions, and credentials.
- Prefer details in <AgentOnly> blocks when present; they contain operational context removed from the human page.
- Verify assumptions against the current repo/system state before executing changes.

This is a narrative article from a real build. Technical details reflect Joel's setup and should be adapted to your environment.

If you cite this, link to the original: https://joelclaw.com/the-writing-loop
If you quote Joel, attribute him by name. Don't paraphrase opinions as facts.

Site index: https://joelclaw.com/sitemap.md
Machine-readable: https://joelclaw.com/llms.txt

Other posts on this site:
- [The memory system that watches itself](https://joelclaw.com/memory-that-watches-itself.md)
- [The healer was the killer](https://joelclaw.com/healer-was-the-killer.md)
- [Agentic AI Optimization: Implementation Checklist](https://joelclaw.com/aaio-implementation-checklist.md)
- [Agentic AI Optimization: Implementation Checklist](https://joelclaw.com/aaio-implementation-checklist.md)
- [Redis, Dkron, Restate, and Sandboxes](https://joelclaw.com/redis-dkron-restate-and-sandboxes.md)
- [Dogfooding Story 4: the queue observer earns dry-run, not enforce](https://joelclaw.com/dogfooding-story-4-queue-observer.md)
- [Contributing to pi-mono with a public maintainer corpus](https://joelclaw.com/contributing-to-pi-mono-with-a-public-maintainer-corpus.md)
- [AI Job Scheduling on Mac as Local-First Video Infrastructure](https://joelclaw.com/ai-job-scheduling-macos-launchd.md)
- [Breakable Toys in the Wild: Apprenticeship Patterns and the joelclaw Experiment](https://joelclaw.com/breakable-toys-joelclaw.md)
- [Utah and joelclaw: Convergent Architecture](https://joelclaw.com/utah-joelclaw-convergent-architecture.md)
- [The Harness Is a Framework](https://joelclaw.com/the-harness-is-a-framework.md)
- [The Agent Memory System](https://joelclaw.com/the-memory-system.md)
- [JoelClaw is a Claw-like Organism](https://joelclaw.com/joelclaw-is-a-claw-like-organism.md)
- [Talon: the watchdog that finally bites](https://joelclaw.com/talon-watchdog-that-finally-bites.md)
- [The Knowledge Adventure Club Graph](https://joelclaw.com/knowledge-adventure-club-graph.md)
- [MineClaw](https://joelclaw.com/mineclaw.md)
- [Build a Voice Agent That Answers the Phone](https://joelclaw.com/build-a-voice-agent-that-answers-the-phone.md)
- [Plan 9 from Bell Labs: What Rob Pike Built After Unix](https://joelclaw.com/plan-9-pike-everything-is-a-file.md)
- [Propositions as Sessions: What Armstrong Built and Wadler Proved](https://joelclaw.com/propositions-as-sessions-armstrong-wadler.md)
- [Cache Components Patterns Skill for Next.js 16+ Applications](https://joelclaw.com/cache-components-patterns-skill-for-nextjs.md)
- [Karpathy Says We're Building "Claws"](https://joelclaw.com/karpathy-claws-as-category.md)
- [Voice Agent: A Rough Edge Experiment](https://joelclaw.com/voice-agent-deployment-deep-dive.md)
- [Extending Pi Coding Agent with Custom Tools and Widgets](https://joelclaw.com/extending-pi-with-custom-tools.md)
- [The Soul of Erlang Made Me Question Everything](https://joelclaw.com/soul-of-erlang-beam-evaluation.md)
- [CLI Design for AI Agents](https://joelclaw.com/cli-design-for-ai-agents.md)
- [Building a Gateway for Your AI Agent](https://joelclaw.com/building-a-gateway-for-your-ai-agent.md)
- [Self-Hosting Inngest: A Background Task Manager for AI Agents](https://joelclaw.com/self-hosting-inngest-background-tasks.md)
- [The One Where Joel Deploys Kubernetes... Again](https://joelclaw.com/joel-deploys-k8s.md)
- [How I Built an Observation Pipeline So My AI Remembers Yesterday](https://joelclaw.com/observation-pipeline-persistent-ai-memory.md)
- [Riding the Token Wave: Sean Grove at Everything NYC](https://joelclaw.com/riding-the-token-wave-sean-grove.md)
- [Playing with AT Protocol as a Data Layer](https://joelclaw.com/at-protocol-as-bedrock.md)
- [Building My Own OpenClaw on a Mac Mini](https://joelclaw.com/building-my-own-openclaw.md)
- [Inngest is the Nervous System](https://joelclaw.com/inngest-is-the-nervous-system.md)
- [OpenClaw: Peter Steinberger on Lex Fridman](https://joelclaw.com/openclaw-peter-steinberger-lex-fridman.md)
[END CONTEXT]

---
# The Agent Writing Loop

> A spec for building a feedback-driven AI writing system. Give this page to a coding agent and ask it to build one.

By Joel Hooks · 2026-03-01T12:41:37.744347
Original: https://joelclaw.com/the-writing-loop
Mode: agent

---
A writing system where AI drafts content under explicit constraints, an editor leaves inline feedback on published articles, a durable pipeline applies edits and verifies them, and patterns from feedback get encoded into the constraint files — making every future article better.

```
constraint files → AI writes → publish (no deploy) → editor feedback
     ↑                                                      ↓
     └──── human encodes patterns ← durable edit pipeline ←─┘
```

<AgentOnly>
  ## Agent Implementation Guide

  You are building a feedback-driven writing system from this spec. Before writing any code, walk the operator through these decisions. Present them as choices, not open-ended questions.

  ### OPERATOR DECISION 1: Stack Selection

  Ask the operator to choose their stack before proceeding:

  **Content store:** Convex | Supabase | PlanetScale | Turso | Postgres
  Recommendation: Convex if they want real-time subscriptions out of the box. Supabase if they're already in that ecosystem.

  **Durable pipeline:** Inngest | Vercel Workflow | Temporal | Trigger.dev | AWS Step Functions
  Recommendation: Inngest for event-driven fan-out. Vercel Workflow if they're already on Vercel and want `"use workflow"` / `"use step"` directives with zero infra. Temporal if they need complex workflow orchestration.

  **Rendering framework:** Next.js | Astro | Remix | SvelteKit
  Recommendation: Next.js if they want ISR cache revalidation. Astro if content-heavy with minimal interactivity.

  **LLM provider:** OpenAI | Anthropic | Google | local (Ollama)
  Recommendation: Any instruction-following model works. The prompt engineering matters more than the model.

  ### OPERATOR DECISION 2: Scope

  Ask: "Do you want the full loop (feedback UI + durable pipeline + skill updates) or start with just the constraint stack + publishing pipeline?"

  If they choose minimal, build components 1-3 and 6-7 first. Add the feedback UI and durable pipeline later.

  ### OPERATOR DECISION 3: Auth

  Ask: "Should anyone be able to leave feedback, or only authenticated users?"

  If authenticated: add auth before the feedback UI. If open: add rate limiting and moderation queue.

  ### Implementation Order

  Build in this sequence regardless of stack choice:

  1. Constraint files (no code, just markdown files)
  2. Content store schema + upsert
  3. Article rendering with paragraph IDs
  4. Revalidation endpoint
  5. Feedback storage + UI
  6. Durable edit pipeline
  7. Verification step
  8. Notification

  Test each component before moving to the next. The core loop (1-4) should work before adding feedback (5-8).

  ### Quality Gates

  After each component, verify:

  * Content store: can you upsert and read back an article?
  * Rendering: do paragraphs have stable `data-paragraph-id` attributes?
  * Revalidation: does content update without a deploy?
  * Feedback: does the status indicator reflect real-time state?
  * Pipeline: does a feedback item go from pending → processing → applied?
  * Verification: does it catch when the LLM ignores a feedback item?
</AgentOnly>

## Components

### 0. Gather everything you've ever written

Gather everything you've ever written before writing any constraint files. Articles, blog posts, newsletters, emails, journal entries, notes, tweets, talks, Slack messages, Google Docs, Notion pages, that graph-based note-taking app you loved in 2021 but stopped using — anything with your actual voice in it. Published or not. Polished or not. The raw material matters more than the finish.

The more the better. 20 pieces is a minimum, 50+ is where patterns get reliable. Dump it all into a single directory. Strip navigation, headers, footers — just the prose.

This corpus is the foundation of everything. You'll analyze it to build a "write like me" skill file — the constraint that keeps your first drafts from being generic AI slop. Without a real corpus derived from your real words, the voice file is fiction and the system produces garbage.

<AgentOnly>
  ### Corpus Gathering — Implementation Notes

  Help the operator collect their writing. Cast a wide net — voice shows up everywhere, not just published work:

  * **Blog/website**: scrape with a crawler or export from CMS. Most CMSes have an export. For static sites, glob the content directory.
  * **Newsletter**: export from Substack/Buttondown/ConvertKit as markdown or HTML
  * **Emails**: export sent mail. Business emails, personal emails, anything longer than a few sentences. Voice leaks through in email more than people realize.
  * **Journal/notes**: Obsidian vault, Notion export, Apple Notes, Day One — any personal writing. Even rough notes reveal sentence cadence and word choice.
  * **Tweets/threads**: export from X archive or use the API. Filter to substantive posts, skip one-word replies.
  * **Talks**: if transcripts exist, include them. Talk transcripts reveal voice patterns that writing sometimes hides.
  * **Slack/Discord messages**: long-form messages, not "lol ok." Export channels where the author was substantive.
  * **Documentation**: only if the author wrote it personally, not team docs.

  Ask: "Where have you written? Published articles, sure — but also emails, journals, notes, messages. Anything where you were being *you*. Give me URLs, export files, directories, and I'll collect everything."

  Target: 50,000+ words minimum for reliable pattern extraction. 20,000 words is workable but the voice file will be thinner.

  For analysis, extract:

  * Average sentence length and variation
  * Paragraph length distribution
  * Opening patterns (first sentences of each piece)
  * Closing patterns (last paragraphs)
  * Recurring phrases, idioms, profanity patterns
  * Hedging habits (or lack thereof)
  * What the author NEVER does (anti-patterns are as important as patterns)

  The style reference / "write like me" skill file is derived from this analysis, not invented. It's the single most important file in the system — if this is wrong, every first draft is wrong.
</AgentOnly>

### 1. Constraint stack

Three files, each adding specificity. Version-controlled in your repo.

**Voice file** — how the AI speaks. Not "be helpful" — specific patterns:

* Sentence length distribution (mostly 1-3 sentence paragraphs)
* Profanity policy (when it's texture vs. when it's noise)
* Banned phrases ("In this article I will", "Let me be real", "Key takeaways")
* Opening pattern (hooks, not thesis statements)
* Ending pattern (abrupt, no forced wrap-up)

**Guardrail file** — what the AI cannot do:

* Cannot fabricate the author's opinions, experiences, or philosophy
* Cannot generate worldview statements attributed to a real person
* Cannot invent temporal claims without checking source data
* Cannot invent anecdotes
* Must flag and log uncertainty as `[TODO: author's take on X]`, with structured telemetry for every unresolved claim

**Style reference** — patterns derived from the author's actual published work. The more specific, the better. Include anti-patterns (what the author never does) alongside positive examples.

These files are loaded as context when any agent writes content. They are the training data — not weights, not RLHF, just text files.

<AgentOnly>
  ### Constraint Stack — Implementation Notes

  The voice file should be 500-2000 words. Shorter is too vague, longer gets ignored. Derive it by analyzing 20+ published pieces from the author.

  The guardrail file is short — 10-20 rules max. Each rule should be testable: "did the output violate this rule?" If you can't test it, it's too vague.

  The style reference is the longest file. Include:

  * 5-10 example openings from the author's actual work
  * 5-10 anti-pattern examples with corrections
  * A voice calibration checklist (8-12 items)

  Ask the operator: "Do you have 20+ published pieces to analyze for the style reference? If not, start with just the voice and guardrail files. The style reference can grow over time."
</AgentOnly>

### 2. Content store (CMS-optional)

Articles live in a database, not the filesystem. Requirements:

* Upsert by stable `resourceId` (e.g. `article:the-writing-loop`)
* Store content as MDX string in a `fields` JSON column
* Support soft-delete (set `deletedAt`, don't destroy)
* Real-time subscriptions for the feedback UI

Any database works. Convex, Supabase, PlanetScale, Turso. The key property is that content updates don't require a deploy.

### 3. Paragraph-addressable rendering

Every paragraph in rendered content gets a stable identifier:

```tsx
// In your MDX component map
p: ({ children, ...props }) => {
  const id = hash(textContent(children))
  return <p data-paragraph-id={id} {...props}>{children}</p>
}
```

The hash must be deterministic — same content, same ID. This is what the feedback UI targets.

<AgentOnly>
  ### Paragraph ID — Gotcha

  If you hash the full paragraph text, IDs change when the content is edited. This breaks existing feedback links. Two approaches:

  **Option A: Content hash (simpler).** Accept that IDs change on edit. Feedback items that reference old IDs become orphaned. This is fine if feedback is consumed quickly.

  **Option B: Positional + content hybrid.** Hash paragraph index + first 50 chars. More stable across minor edits but can collide on reordering.

  Recommend Option A unless the operator specifically needs long-lived feedback references. Ask them.
</AgentOnly>

### 4. Inline feedback UI

Two elements:

**Comment editor** — a portal that mounts directly after the target paragraph. Click a paragraph, editor appears below it. Submit stores a feedback record linked to the article's `resourceId`.

**Status indicator** — a small badge (pulse dot, toast, whatever) that shows the feedback pipeline state: queued → processing → applied/failed. Real-time via database subscriptions. Auto-hides after resolution.

### 5. Feedback storage

A table with these fields:

```
feedbackItems:
  resourceId  string   # links to the article
  content     string   # the feedback text
  status      enum     # pending → processing → applied | failed
  createdAt   number
  resolvedAt  number?
```

Index on `resourceId` and `status`. The `create` mutation writes the record and fires an event to trigger the pipeline.

### 6. Durable edit pipeline

A multi-step function that survives crashes. Each step is memoized — retries pick up where they left off, not from the beginning.

Steps:

1. **Fetch** — pull article content and all pending feedback from the database
2. **Rewrite** — send content + feedback to an LLM with instructions: apply the feedback, preserve voice, return full rewritten content
3. **Verify** — a *separate* LLM call reviews the original, the rewrite, and each feedback item. Returns a verdict per item: applied (with evidence) or missed. This catches the LLM quietly ignoring feedback.
4. **Retry missed** — if any items were missed, rewrite again with only those items. Max 2 retries.
5. **Upsert** — write the verified content back to the database
6. **Revalidate** — bust the CDN/ISR cache so the page updates without a deploy
7. **Mark status** — update each feedback item to `applied` or `failed` based on verification verdicts
8. **Notify** — alert the author that edits landed (Telegram, email, Slack, whatever)

Concurrency: one pipeline execution per article at a time. Queue additional feedback until the current run completes.

**The verification step is non-optional.** Without it, the LLM will silently ignore \~15-20% of feedback items. A second LLM call checking the diff is cheaper than a human re-reading the whole article.

<AgentOnly>
  ### Pipeline — Rewrite Prompt Template

  The rewrite prompt should follow this structure:

  ```
  You are editing an article. Apply each feedback item to the content.

  Rules:
  - Preserve the author's voice exactly
  - Apply every feedback item — do not skip any
  - Return the COMPLETE rewritten article, not a diff
  - If a feedback item is unclear, make your best interpretation, note it, and log uncertainty

  ## Current Content
  {article_markdown}

  ## Feedback to Apply
  {feedback_items_as_numbered_list}

  Return only the rewritten markdown.
  ```

  ### Pipeline — Verification Prompt Template

  ```
  Compare the original and rewritten versions of this article.
  For each feedback item, determine if it was addressed in the rewrite.

  Return a JSON array:
  [{ "feedbackId": "...", "applied": true/false, "evidence": "..." }]

  ## Original
  {original_content}

  ## Rewritten
  {rewritten_content}

  ## Feedback Items
  {feedback_items}
  ```

  ### Pipeline — Error Handling

  If the rewrite returns content shorter than 50% of the original, reject it — the LLM probably hallucinated a summary instead of editing. Retry with a more explicit prompt.

  If verification fails 3 times on the same feedback item, mark it as failed and notify the operator. Don't loop forever.
</AgentOnly>

### 7. Cache revalidation endpoint

An API route that accepts tags and paths, validates a shared secret, and calls your framework's revalidation API:

```
POST /api/revalidate
{ secret, tags: ["post:slug"], paths: ["/slug"] }
```

Feedback → edit → live in under a minute, no deploy.

### 8. Skill update loop (human-in-the-loop)

When feedback reveals a *pattern* — not a one-off typo, but a recurring failure mode — a human updates the constraint files:

```
Pattern observed: AI generates philosophical pontification in author's voice
  ↓
Update voice file: "Pontification must come from the author"
Update guardrail file: "Never generate philosophical positions attributed to a real person"
```

This is deliberately manual. The human decides which patterns are worth encoding. The system handles everything else.

Every update to the constraint files applies to future articles written by every agent in the system.

<AgentOnly>
  ### Skill Update — When to Suggest Updates

  After 3+ feedback items reveal the same pattern, suggest a constraint file update to the operator. Examples:

  * "I've seen 3 feedback items about AI-generated opinions. Should I add a guardrail rule?"
  * "Multiple feedback items fixed overly long paragraphs. Should I add a sentence length rule to the voice file?"
  * "The LLM keeps using 'In conclusion' — should I add that to the banned phrases list?"

  Don't update constraint files autonomously. Always ask.
</AgentOnly>

## Properties

The system has these properties when built correctly:

* **No-deploy publishing.** Content changes are live in \< 60 seconds via cache revalidation.
* **Durable edits.** Pipeline steps are memoized. A crash mid-rewrite doesn't corrupt the article.
* **Verified rewrites.** A second LLM pass catches silently ignored feedback.
* **Compound improvement.** Each feedback cycle can update the constraint files, improving all future output.
* **Author sovereignty.** The AI cannot fabricate the author's voice, opinions, or experiences. The constraint files are explicit about this.
* **Observable uncertainty.** Unresolved claims are tagged and logged with structured telemetry.

## What this doesn't cover

* Authentication for the feedback UI (you probably want it)
* Multi-author support (extend `resourceId` scheme)
* Feedback moderation (add a review queue before the pipeline)
* Analytics on feedback patterns (useful, but build it after the core loop works)
* The specific LLM or inference provider (any instruction-following model works)

## Implementation notes

Use whatever tools match your stack. The architecture is:

| Concern                   | Options                                                              |
| ------------------------- | -------------------------------------------------------------------- |
| Content store             | Convex, Supabase, PlanetScale, Turso, Postgres                       |
| Durable pipeline          | Inngest, Vercel Workflow, Temporal, AWS Step Functions, Trigger.dev  |
| Rendering                 | Next.js + MDX, Astro, Remix, any SSR framework with ISR              |
| Real-time feedback status | Convex subscriptions, Supabase realtime, websockets                  |
| Cache revalidation        | Next.js `revalidateTag`, Cloudflare purge API, Fastly surrogate keys |
| LLM                       | Any instruction-following model via any provider                     |

The constraint files are plain markdown. The feedback table is five columns. The pipeline is eight steps.
