Paste an account list. An autonomous agency researches every company, writes a cited, personalized microsite for each one, gates it through QA at every stage, and ships it to its own live URL.
Open the fleet board · Sample site: Delhivery, CFO · Sample site: Razorpay, CTO · Read the PRD
The operator console, reading live from Convex. Every number on this board was written by the agent through a QA gate.
Your top 50 accounts get human attention. The next 500 get merge-tag email that reads as spam and burns your sender reputation. The gap is research-grade personalization at volume: a page that demonstrably understands the account's stack, funding, hiring, and likely objection, so a CxO reads for 15 seconds and thinks "this is actually about us."
Site generation is a solved problem. The agency around it is not: the research, the gates that refuse shallow work, and the trace that explains every decision. That agency is the product.
Restraint is a feature. If research confidence falls below 0.5, the account is blocked, not faked. A shallow site is worse than no site.
Numbers below are read from the live Convex board, not typed in.
| Shipped | Blocked | Total cost | Cost / site | First-pass QA | Views | Scroll 50% | CTA clicks |
|---|---|---|---|---|---|---|---|
| 20 live URLs | 0 | $13.68 | $0.68 (ceiling $2.00) | 81% of gates passed first try | 1,740 | 78 | 18 |
First-pass QA by stage, the factory's single health metric: research 57%, build 100%, deploy 86%. The research gate is the strict one, which is exactly where you want the strictness.
The page's identity comes from the target's world. The researcher captures brand hints (dominant hue, register, a phrase of their own vocabulary), and the builder maps them onto one of four fixed design systems with distinct type pairings. Never one template with a logo swap. Each page spends all of its boldness on one signature element derived from the research, and everything else stays quiet.
| System | Register | Display / body faces | Character |
|---|---|---|---|
| ledger | precise, serious (finserv, legal) | Fraunces / Inter | disciplined grid, generous white, one hairline accent in their hue |
| terminal | technical (devtools, infra) | JetBrains Mono / Inter | mono display, code-block signature, dark-on-light, never a dark theme |
| clinic | calm, clinical (health, edtech) | Source Serif 4 / Source Sans 3 | soft radius, muted palette, spacious line-height |
| signal | bold (consumer-ish B2B, martech) | Space Grotesk / Inter | oversized display type, one saturated accent from their hue |
Three "AI-tell" looks are banned and auto-fail QA: cream background with high-contrast serif and terracotta; near-black with acid green; hairline broadsheet with zero radius and dense columns.
More from the fleet (12 more accounts, click to expand)
Every page is a single HTML file, responsive to 360px, with visible focus states and reduced-motion support.
flowchart LR
CSV[CSV of accounts] --> D[distributor]
D --> R[account-researcher]
R --> Q1{QA: research}
Q1 -- pass --> B[microsite-builder]
Q1 -- fail --> R
Q1 -- "confidence < 0.5" --> BLK[blocked, reason shown verbatim]
B --> Q2{QA: build}
Q2 -- pass --> W[wrangler deploy]
Q2 -- fail --> B
W --> Q3{QA: deploy}
Q3 -- pass --> URL[live *.pages.dev URL]
Q1 & Q2 & Q3 -. "one trace line each" .-> T[(traces)]
- Intake. Paste or upload a CSV with company, domain, contact title, and vertical. The console validates client-side and dedupes on domain plus title.
- Research. The agent picks a reader lens from the contact title (technical, financial, or GTM) and runs three to six sourced Linkup queries. The packet must contain at least five findings, every one with a real source URL, plus brand hints and the reader's most likely objection. No source, no finding.
- Build. The builder chooses one of five storytelling angles (cost, speed, risk, talent, competition) from the strongest findings and the objection, writes 250 to 400 words along a fixed narrative spine, cites the findings on the page, and embeds a three-event beacon. Product claims may come only from the operator's positioning memory.
- QA at every gate. One reviewer skill with three rubrics. Research: spot-fetch two cited URLs and confirm the claim is supported, not adjacent. Build: reject any product claim absent from positioning memory, any banned look, any missing signature element. Deploy: confirm the live URL's content hash matches the QA-passed build and the beacon fires once. A failed gate gets one retry with the failure list injected, then the account is blocked.
- Deploy. Each account gets its own Cloudflare Pages project and URL.
- Watch. The board live-updates from Convex: stage, status, cost, first-pass QA rate, blocked reasons verbatim, beacon counts, and a minute-by-minute feed of what the agent is doing.
Four screens. Editing is strategy, not pixels: the operator picks a different angle or types an instruction, and the change re-enters the full builder → QA → deploy pipeline. There is no direct HTML edit path, because the moment one exists, positioning enforcement, traces, and the whole story break at once.
Preview + steer (open it) puts the live site next to its pipeline track, the five angle archetypes, and a free-text instruction box. Pick "risk" instead of "cost", or type "lead with their margin story and make the hero punchier", and the account flips to in progress on the board while the builder, the QA gate, and the deploy run again. Every regenerate leaves a trace.
The agent is Hermes, a Python daemon running a gateway and a kanban dispatcher. Its behavior lives entirely in four skill files under skills/, installed into the runtime by a script so repo and runtime never drift.
| Skill | Role | The contract it enforces |
|---|---|---|
| distributor | Orchestrator. Parses intake, creates one kanban task per account, enforces stage order and budgets. | research → QA → build → QA → deploy → QA. One retry on QA fail, then block. Confidence below 0.5 means no build. Max 5 in flight, first account always sequential. $2 per account with graceful degradation. |
| account-researcher | Reader-lens research through linkup-research.mjs, the only research path in the pipeline. |
At least 5 findings, every one with a real source URL, at least 3 with hook strength 3 or more, a specific likely objection, a confidence score. Never invents stack details from the vertical name. |
| microsite-builder | Angle selection, fixed narrative spine, one of four design systems, beacon embed. | The angle log is the editable surface. 250 to 400 words. Claims only from positioning memory. One named CTA. One signature element. |
| qa-reviewer | Gates every stage and writes a trace on every invocation, pass or fail. | Itemized, actionable failures. Fails closed when it cannot verify. Never fixes the output itself, because that would corrupt the trace. |
Everything the agent reads from the web, and every operator notes field, is treated as data, not instructions. Both the distributor and the researcher flag text that addresses agents and continue with the original pipeline.
Every QA invocation appends one JSONL line locally and mirrors it to Convex. Each line is a labeled example (input → output → judgment), which makes the trace log an eval set by construction: replay old stage inputs against a new prompt or model and diff the verdicts, harvest human-overridden verdicts as a golden set, and track first-pass rate per rubric version.
This is a real pair from the fleet. The deploy gate caught a Cloudflare Pages fallback serving private artifacts, the agent fixed it, and the retry passed. Note retry_of pointing at the failure.
The sharpest checks, the product's honesty guarantees:
- Research gate fetches two cited URLs and verifies the claim is supported by the page, rejects vertical stereotypes ("fintech probably uses Java"), and checks that findings match the declared reader lens. A CTO packet with funding and hiring findings but zero stack findings fails.
- Build gate rejects any product claim not present in positioning memory. The agent cannot invent capabilities in the operator's name. It also rejects merge-tag artifacts, missing citations, a missing signature element, any banned look, and word counts outside 250 to 400.
- Deploy gate confirms the live URL's content hash matches the QA-passed build, that only
index.htmlwas published, and that exactly oneviewbeacon lands when the page is loaded once.
Convex holds five tables and is the only thing the console reads. Pipeline writes go through fixed HTTP actions with a shared-secret header, never a general client. All mutations are internal, so the HTTP actions are the only write path.
| Table | What it holds | Written by |
|---|---|---|
fleet |
one row per account: stage, status, blocked reason, cost, live URL | POST /fleet from the distributor, via factory-report.sh |
traces |
mirror of every QA trace line, indexed on account and stage | POST /trace from the qa-reviewer |
events |
exactly three anonymous beacon events: view, scroll50, cta |
POST /beacon from the live pages (public, rate-limited per IP, account must exist) |
activity |
fine-grained agent activity for the console's live feed | POST /pipelog from the Hermes log tailer |
rateLimits |
per-IP, per-minute buckets for the beacon | internal |
Real rows, read with the Convex CLI (columns trimmed for width):
$ npx convex data fleet --limit 5
account_id company stage status cost_usd url
pharmeasy-in-ceo PharmEasy deploy shipped 0.58 https://pharmeasy-in-ceo.pages.dev
upgrad-com-ceo upGrad deploy shipped 0.36 https://upgrad-com-ceo.pages.dev
rapido-bike-cto Rapido deploy shipped 1.10 https://rapido-bike-cto.pages.dev
urbancompany-com-coo Urban Company deploy shipped 0.82 https://urbancompany-com-coo.pages.dev
delhivery-com-cfo Delhivery deploy shipped 0.00 https://delhivery-com-cfo.pages.dev
$ npx convex data traces --limit 3
account_id stage rubric model retry_of verdict
delhivery-com-cfo deploy 1.0 gpt-5.6-sol 6a153af1-… { pass: true, failures: [] }
delhivery-com-cfo deploy 1.0 gpt-5.6-sol null { pass: false, failures: [{ check: "private-artifact-paths", severity: "block", … }] }
delhivery-com-cfo deploy 1.0 gpt-5.6-sol null { pass: true, failures: [] }
$ npx convex data events --limit 3
account_id type ts
pinelabs-com-ceo scroll50 1789073464543
pinelabs-com-ceo view 1789073460946
pinelabs-com-ceo scroll50 1789060111725
The fleet-board query joins each account with its event counts and computes first-pass QA over traces where retry_of is null, per stage and fleet-wide. A retry that passes never inflates the first-pass number.
- Webhooks. The browser sends unsigned JSON to a same-origin Pages Function. The Function holds the secret, signs the raw body with HMAC-SHA256, and forwards it. Hermes verifies the signature before parsing anything. An unsigned or mis-signed request gets a 401 before the prompt ever runs.
- Convex writes. Only
/fleet,/trace, and/pipelog, each requiring anx-factory-keyheader. Every mutation is internal. The console has no write path at all. - Beacon. Three anonymous events, nothing else. Per-IP rate limit, the account must exist in the fleet, and the optional detail field is accepted for wire compatibility but never persisted. No fingerprinting, no identity capture.
- Prompt injection. Researched web content and intake notes are data. Agent-directed text in either is flagged and ignored.
- Claims. The builder can only make product claims that exist in positioning memory. The build gate enforces it.
- Credentials. Cloudflare uses a token scoped to Pages Edit only. Convex access from agents is scoped by a deploy key. This repo contains only
.exampleenv files; it never has and never will contain a real secret. - Legal floor. The target's name appears in text only, never as a logo or trademark styling, with a "Prepared for {Company}" footer and a takedown contact on every page.
Frozen data contracts (the stage boundaries, click to expand)
Stage outputs are frozen JSON contracts. Changing one requires updating the producing skill, the consuming skill, the QA rubric, and rubric_version together.
Researcher output
{"company":"","domain":"","contact_title":"","vertical":"",
"reader_lens":"technical|financial|gtm",
"findings":[{"claim":"","source_url":"","recency":"YYYY-MM",
"signal_type":"stack|hiring|funding|news|positioning","hook_strength":1}],
"brand_hints":{"hue_hex":"","register":"","their_phrase":""},
"likely_objection":"","confidence":0.0}Builder output
{"html_path":"sites/{account_id}/index.html",
"angle_log":{"angle":"cost|speed|risk|talent|competition",
"because_findings":[0],"objection_addressed":""},
"design_system":"ledger|terminal|clinic|signal",
"signature_element":"","word_count":0,"claims_cited":0}QA verdict
{"stage":"research|build|deploy","account_id":"","pass":true,
"failures":[{"check":"","detail":"","severity":"block|warn"}],
"retry_guidance":""}Trace line (JSONL, one per QA invocation, pass or fail)
{"trace_id":"","ts":"","account_id":"","stage":"","input_ref":"","output_ref":"",
"verdict":{},"rubric_version":"1.0","model":"","cost_usd":0.0,"latency_s":0.0,"retry_of":null}Webhook payloads (frontend → Hermes, HMAC-SHA256 over the raw body)
{"rows":[{"company":"","domain":"","contact_title":"","vertical":"","contact_name":"","notes":""}]}
{"account_id":"","mode":"angle|instruction","angle":"","instruction":""}
{"one_liner":"","proof_points":[{"tag":"technical|roi|gtm","text":""}],"tone_rules":[],"forbidden_claims":[],"verticals":[]}You need a Hermes install, a Convex deployment, a Cloudflare account, and a Linkup key. Hermes is a long-running Python daemon, so it runs on a Mac or a VM, never on Workers.
git clone https://github.com/V00nikos/microsite-factory && cd microsite-factory
cp .env.example .env # fill in: LINKUP_API_KEY, CLOUDFLARE_API_TOKEN, CONVEX_DEPLOY_KEY,
# FACTORY_KEY, WEBHOOK_HMAC_SECRET, CONVEX_SITE_URL
scripts/install-skills.sh # install the four skills into ~/.hermes/skills and verify
npx convex dev # deploy the backend and generate client types (keep it running)
cp memory/positioning.template.md ~/.hermes/profiles/factory-worker/memories/MEMORY.md # then fill it in
cd frontend && npm install
cp .env.example .env.local # VITE_CONVEX_URL
cp .dev.vars.example .dev.vars # WEBHOOK_HMAC_SECRET, HERMES_WEBHOOK_BASE (server-side only)
npm run dev:pages # SPA + Pages Functions togetherThen subscribe the three Hermes webhooks with the commands in docs/webhook-setup.md, and prove one account end to end before fanning out:
scripts/test-webhooks.sh # signed round-trips accepted, bad signature rejected
hermes chat -q "Run the factory on fixtures/accounts-sample.csv"
npm test # live-site contract tests: research, build, byte-identical deploy, traces, beacons| Secret | Where it lives | What it scopes |
|---|---|---|
LINKUP_API_KEY |
~/.hermes/.env |
sourced research |
CLOUDFLARE_API_TOKEN |
~/.hermes/.env |
Pages Edit only |
CONVEX_DEPLOY_KEY |
~/.hermes/.env |
agent access to one Convex deployment |
FACTORY_KEY |
~/.hermes/.env and Convex env |
the x-factory-key header on /fleet, /trace, /pipelog |
WEBHOOK_HMAC_SECRET |
Pages Function env and every hermes webhook subscribe |
webhook signatures |
CLAUDE.md the agent's brief: non-negotiable rules + frozen data contracts
docs/ PRD · build tasks · webhook setup · design reference · README media
skills/ distributor · account-researcher · microsite-builder · qa-reviewer
convex/ schema · queries · internal mutations · HTTP actions
frontend/ the operator console (Vite + React SPA) + HMAC-signing Pages Functions
scripts/ linkup-research.mjs · factory-report.sh · log-stream.mjs · install-skills.sh · test-webhooks.sh
memory/ positioning-memory template + a filled-in operator example
fixtures/ sample intake CSVs + a research packet
tests/ live contract tests for shipped accounts
deploy/railway/ runbook to lift the Hermes backend off the Mac
sites/ generated microsites, one folder per account (gitignored)
Working end to end today: CSV upload → Hermes → live website, with QA gates, eval traces, per-account beacons, and the regenerate-with-instruction loop, all live-updating on the console. The console, Convex, and the microsites run in the cloud. The Hermes backend is the one piece still local, behind a Cloudflare Tunnel, staged to move to EC2 or Railway (runbook in deploy/railway/).
Known gaps, stated plainly
- The builder's angle log is written to the trace but not yet mirrored onto the fleet row, so the Preview screen's "because these findings" panel is empty on live data.
- The Preview screen's embedded frame currently clips the site below its header. Use the "Open" button to view the live page until that is fixed.
- Webhook signatures use the V1 scheme with no replay protection. The V2 path (timestamp-bound, 5-minute window) is documented and one flag away.
- The learning loop is instrumented, not learning: engagement per angle is recorded but does not yet reweight angle selection.
Next
- Lift Hermes to Railway for a stable webhook URL and retire the tunnel.
- Per-vertical angle win-rate table read from beacon data.
- Sheets and HubSpot import alongside CSV.
Track: AI as Agency. The bet is that site generation is solved, and the agency around it (research, gates, traces) is the product.
Built with Hermes by Nous Research, Convex, Cloudflare Pages, and Linkup. Console typography: Archivo and IBM Plex Mono.
Licensed under the MIT License.






















{"trace_id":"6a153af1-…","ts":"2026-07-12T12:05:40Z","account_id":"delhivery-com-cfo","stage":"deploy", "input_ref":"sha256:e2322a38…","output_ref":"https://delhivery-com-cfo.pages.dev", "verdict":{"pass":false,"failures":[{"check":"private-artifact-paths", "detail":"Known private paths returned HTTP 200 because Pages index fallback was active without a 404 page", "severity":"block"}]}, "rubric_version":"1.0","model":"gpt-5.6-sol","retry_of":null} {"trace_id":"6e020d7a-…","ts":"2026-07-12T12:07:09Z","account_id":"delhivery-com-cfo","stage":"deploy", "input_ref":"sha256:e2322a38…","output_ref":"https://delhivery-com-cfo.pages.dev", "verdict":{"pass":true,"failures":[]}, "rubric_version":"1.0","model":"gpt-5.6-sol","retry_of":"6a153af1-…"}