Design — Stage 1 as an internal Claude routine (write→review→rewrite→PR)

Date: 2026-07-21 Status: Design approved (brainstorm). Ready for implementation-plan. Owner: Director (Derek) + Maeve Supersedes for Stage 1: the OpenRouter 3-stage routing (generate_posts.py GENERATOR_CHAIN/REVIEW_CHAIN/REWRITE_CHAIN, PRs #71/#74) and the generate.yml GitHub-Actions workflow.

1. Context & Motivation

HappyPet’s content pipeline is generate → publish → deploy → pin. Stage 1 (generate) currently runs generate_posts.py in a GitHub Action (generate.yml), calling external models via OpenRouter in a fixed 3-stage chain (writer → judge → fixer). Two live smoke-tests this session showed the ceiling of that approach:

Two structural problems surfaced: (a) rigid, fixed-model machinery that can’t reason about a bogus hold, and (b) the roundup fact_check_alternatives() step re-emitting the article through a rule-unaware model. Both are artifacts of orchestrating external models through brittle glue.

Director’s decision: go simpler and internal — replace Stage 1’s external-model orchestration with a Claude routine that writes with Opus, reviews with Sonnet, and rewrites with Opus in an agentic loop until an article meets standards, then pushes to the repo to trigger the unchanged publish/pin stages. Primary driver: hands-off autonomy, gated behind a human-review period and a config toggle.

2. Goals / Non-Goals

Goals

Non-Goals

3. Key reframe: “deprecate Stage 1” ≠ “delete generate_posts.py

Most of generate_posts.py is not model calls — it is deterministic plumbing the routine still needs and that carries the 152-test suite:

We deprecate the GitHub-Actions Stage-1 workflow (generate.yml) and the OpenRouter LLM-calling layer only. The routine reuses the plumbing and replaces just the brains — how article text is written, reviewed, and rewritten.

4. Architecture

Claude routine (Opus main agent):
  [plumbing CLI] read products.json → pick next unpublished topic + product data
  ┌─ loop (max N attempts) ──────────────────────────────────────────┐
  │  Opus writes/ rewrites draft body (existing writing_rules prompt) │
  │  Sonnet reviewer subagent scores vs rubric → scorecard JSON       │
  │  [plumbing CLI] deterministic gate: scrub_typography +            │
  │                 evaluate_scorecard(scorecard, body)               │
  │  standards met?  ── no ─→ Opus rewrites from critique ────────────┤
  └──────────────────── yes ─────────────────────────────────────────┘
  [plumbing CLI] stage article: front_matter + validate_output +
                 persist_generated_article (pin-queue + DRAFT-*.md)
  commit on a branch → open PR
  toggle auto_merge?  ── off ─→ PR waits for human approval (HITL)
                      └─ on ──→ auto-merge when required checks green
  merge to main → routine DISPATCHES Stage 2 (see §9.2 — publish.yml has no
                  push trigger) → deploy → Stage 3 (pin), all unchanged

4.1 Components

4.2 Deprecated

5. “Standards met”, the toggle, and “green”

6. Bar policy & can’t-converge policy

7. Model roles & independence

8. Phasing

9. Open questions / verify items

  1. Scheduled cloud routine feasibility (Phase 2 blocker): does a scheduled Claude routine (a) run under the Max subscription allowance vs. separate credits, (b) have headroom for ~1–2 articles × several rewrites × 2/week of Opus+Sonnet, (c) perform authenticated git push + open-PR from its environment, (d) run reliably on cron? Verify via the Claude Code guide + a supervised dry run before flipping the cron.
  2. Stage 1 → Stage 2 handoff — DECIDED: explicit dispatch. publish.yml has no push trigger. It fires on workflow_run from the workflow named “Stage 1 — Generate Articles”, plus its own Mon+Thu 12:00 UTC cron (safety net) and manual dispatch. So deprecating generate.yml breaks the automatic handoff — a plain merge of _posts/DRAFT-*.md will NOT start publishing. Chosen mechanism: the routine runs gh workflow run publish.yml immediately after the merge. Rejected alternatives: adding a push trigger to publish.yml (any _posts/ push would trip Stage 2 — too implicit); keeping a stub “Stage 1” workflow to preserve workflow_run (dead scaffolding). The Stage-2 Mon+Thu cron stays as a safety net. Downstream (deploy, Stage 3) is unaffected — it keys off Stage 2’s dated-post push + .pending-slugs. Only remaining unknown: that the routine’s environment can run an authenticated gh workflow run — folded into §9.1.
  3. Cost fit: measured in Phase 1 (tokens-per-article), projected against the autonomous cadence.

10. Decisions made (and why)

11. Testing

12. Out of scope

Stage 2/3 workflows, products.json schema, PA-API/refill.yml, the go-live cron for anything other than this routine, and the separate review-gate false-positive fix (PR-parked; the internal Sonnet reviewer + authoritative deterministic gate makes it moot for this path).