Inspiration
Every growth team runs the same loop by hand: check the analytics dashboard, spot a leaky step in the funnel, argue about why, write a ticket, wait for an engineer, ship a fix, wait again to see if it worked. I wanted to know what happens if you close that loop entirely — let an agent watch real visitor behavior, reason about what's underperforming, write the fix, test it, ship it, and tell you what changed. Vivere is that closed loop, built and dogfooded on its own landing page.
What it does
Vivere is a self-optimizing marketing site. It watches its own production traffic in PostHog — pageviews, funnels, heatmaps, rage clicks — and runs a five-stage pipeline against that data:
- Fetch + Clean pulls and denoises the PostHog data.
- Analyst reasons about what's underperforming, with statistical maturity gates that can halt the whole run if there isn't enough data yet to trust a conclusion.
- Designer + Architect turns a diagnosis into a technical plan, checked against brand and legal restrictions and no-touch zones.
- Builder + QA patches the actual Next.js codebase in a sandboxed workspace, runs
next build, and deploys. - Notify emails the site owner a summary of what changed and why.
The loop triggers on 1,000 page views or 10 days elapsed, whichever comes first — so the site is never rewritten faster than there's real evidence for it.
How I built it
The repo is a monorepo: vivere/ is the Next.js 16 / React 19 landing page (customer #0 — the pipeline's first and only target so far), and vivere-pipeline/ is the Python backend that runs the actual loop, sharing the same PostHog project and the same production deploy it's optimizing.
Backend stack: Python 3.12, httpx for the PostHog API, Pydantic v2 as the contract between every stage (no raw dict handoffs anywhere), structlog for run logs, tenacity for retrying against PostHog rate limits, and jinja2 for the Notify stage's email templates. The Analyst stage runs on Claude Sonnet 5 via the Anthropic SDK, using native structured output to produce a validated AnalystReport rather than parsing free text.
Every stage implements the same Stage protocol — a name, a can_halt flag (only the Analyst can stop the loop), and an async run(ctx) that returns a typed StageResult. Adding a new stage means writing the adapter and registering it; the orchestrator itself never changes. That protocol-first design is what let me build and test stages independently before wiring the full chain together.
For the Builder stage's sandbox, the plan (documented in the project thesis) is E2B. For this build I deliberately shipped a leaner MVP first: a local git worktree per run, with the agent's file tools scoped to it by a path allowlist, sitting behind a Sandbox protocol so swapping in E2B later is a new backend, not a rewrite. I was explicit with myself about the trade-off: this is a workspace sandbox, not a security sandbox, which is fine for a single-operator build on your own repo and not something I'd ship for untrusted input.
I also made a deliberate call not to reach for LangChain or LangGraph. The pipeline is a fixed, linear five-stage chain with one halt point and typed contracts at every boundary — not a graph problem — and Anthropic's own structured-output support already covers what LangChain would add. I wrote the orchestration myself: a stage registry, a run manifest, and an artifact store in a few hundred lines, with no state model fighting the Pydantic types I already had.
Challenges I ran into
Getting the Analyst stage to be usefully conservative was harder than getting it to reason well. It's easy to get an LLM to always find a plausible story in noisy analytics data; it's much harder to get it to say "there isn't enough traffic yet to know" and halt the loop instead of shipping a change based on five sessions. The statistical maturity gate took several iterations to stop being either too trigger-happy or too timid.
The sandbox decision was its own challenge, mostly about being honest with the project's own thesis. The thesis document names E2B as the target execution environment, and building against a local git worktree instead meant explicitly writing down where the MVP diverges from the plan and why — rather than quietly letting the two documents disagree.
Wiring the Builder stage to actually patch a live Next.js app, run a real next build, and deploy through Vercel — inside a single automated run — meant a lot of the failure modes only show up under real traffic and a real repo, not in a mocked test.
What I learned
That closing this loop end-to-end forces decisions you can defer indefinitely in a normal CRO workflow: how much evidence is "enough," how much autonomy an agent gets before a human should be in the loop, and how to make an LLM's reasoning auditable rather than just trusting its output. Typed contracts between every stage turned out to be the thing that made the whole system debuggable — when something goes wrong, I know exactly which stage produced the artifact that didn't hold up.
This is the second version of Vivere; the first won 2nd place at the AI Circle x Replit Hackathon as a two-agent MVP. This build is the full five-stage version, running against live production data for the first time.
What's next
Swapping the git-worktree sandbox for E2B to close the security gap, expanding beyond the single "customer #0" site to real third-party landing pages, and tightening the Analyst's maturity gates with more real-traffic runs.
Built With
- anthropic-claude
- claude-sonnet
- git
- httpx
- jinja
- nextjs
- posthog
- pydantic
- python
- react
- resend
- structlog
- tenacity
- typescript
- vercel
Log in or sign up for Devpost to join the conversation.