We built a seven-agent autonomous pipeline in Python: scout finds businesses without websites, outreach emails them, builder generates a preview, closer handles the sale, fulfilment delivers, ops runs support and refunds, marketing runs demand. Each agent has its own scheduled workflow. Its own audit lists four critical security findings.
There is a large population of small businesses with no website, no listing hygiene and no intention of solving that themselves. The traditional way to reach them is a salesperson with a phone. The question this project asked is whether the entire chain — finding them, qualifying them, contacting them, building something to show them, closing, delivering, and supporting — can run without a human in the default path. The interesting engineering problem is not any single agent. It is the handoff. A pipeline like this is a long-running distributed state machine where each stage is slow, failure-prone, and has side effects in the real world that cannot be undone: an email that has been sent has been sent. A retry that is not idempotent does not cost you a duplicate row, it costs you a prospect who received the same message twice and now thinks you are a spammer. We are including this piece because the architecture is genuinely good and because its own audit document is the strongest evidence in this portfolio that we look for our own defects. We are including it framed accordingly.
`agents/` contains seven packages, each with a `main.py` entry point and a set of focused modules. `scout` does discovery and qualification (`google_maps.py`, `free_discovery.py`, `enrichment.py`, `dedup.py`, `qualifier.py`, `scheduler.py`). `outreach` is the largest at twelve modules and handles the entire email surface — sequences, templates, domain management, timezone-aware send windows, reply classification, auto-responses, unsubscribe handling and inbound webhooks, across three delivery backends. `builder` generates the preview site (`template_engine.py`, `lovable_service.py`, `quality_gate.py`, `screenshot.py`) and deploys it (`cloudflare_deploy.py`, `supabase_deploy.py`). `closer` runs the sale (`conversation_engine.py`, `intake_form.py`, `stripe_integration.py`, `bland_voice.py`). `fulfillment` delivers and manages reviews. `ops` runs support routing, check-ins, metrics, backups and refunds. `marketing` runs demand generation and media. Each agent has a corresponding GitHub Actions workflow — `scout.yml`, `outreach.yml`, `builder.yml`, `closer.yml`, `fulfillment.yml`, `ops.yml`, `marketing.yml` — plus `deploy.yml`, `server.yml`, `test.yml`, `health-check.yml` and `sync-env-to-render.yml`, for twelve in total. Using scheduled CI as the agent scheduler is an unglamorous choice that we would make again for a system at this scale: it gives you retries, logs, secret management, manual dispatch and a run history for free, and it costs nothing when idle. Shared state is PostgreSQL, evolved through ten migrations. The migration names are a useful record of where the state machine actually broke in practice: `007_fix_missing_stripe_and_deployment_columns`, `008_fix_fulfillment_missing_columns`, `009_fix_trigger_json_to_jsonb`, `010_allow_site_ready_to_outreach_sent`. That last one is a state-machine transition that reality required and the original design forbade.
`builder/quality_gate.py` is the piece that makes an autonomous pipeline defensible rather than reckless. An agent that generates a website and emails it to a stranger without a check will eventually email a broken page, a page with placeholder text, or a page with the wrong business name on it — and the cost of that lands on a real person's opinion of a real business. Pairing generation with an automated gate and a screenshot step before the artefact is allowed to leave the system is the correct shape for any autonomous pipeline that produces customer-visible output. The general principle we took from this build and now apply elsewhere: an autonomous system needs a component whose only job is to refuse to ship its own work.
`CODE_AUDIT_REPORT.md` is a full security and code-quality review of this codebase, and it is not flattering. It records four findings we consider disqualifying for production use, and we are reproducing them rather than paraphrasing them away. Admin authentication is a raw string comparison against a token with a weak, predictable default, accepted as a URL query parameter — which means it lands in browser history and server logs — with no rate limiting at that layer, no CSRF protection, and a single shared token rather than per-user credentials. The audit rates this CRITICAL, and the compromise gives full lead and revenue visibility. SMTP credentials are held as plaintext passwords inside a JSON environment variable, visible in process listings, present in log output when that JSON is printed for debugging, resident in memory for the agent's lifetime, and unencrypted at rest. Also rated CRITICAL. Beyond those two, the audit records missing autoescaping in the template rendering path — an injection risk in generated sites — and a race in the lead state machine, which is precisely the class of defect that migration `010` was patching around. None of these is architecturally hard to fix. Admin auth becomes a session or a JWT with rate limiting; credentials move to a secrets manager; the template environment gets `autoescape=True`; the state transition gets a row-level lock or a conditional update. They are not fixed, and until they are, this is a system we describe as an architecture rather than a product.
Idempotency across a pipeline with irreversible side effects. Every stage can fail after doing half its work, and half the stages touch the outside world. Sending an email, charging a card, deploying a site and provisioning a domain are all operations where 'retry the step' is the wrong default. Getting this right means every stage needs an idempotency key, a durable record of what it already did, and a state transition that is atomic with respect to the side effect — which is exactly the thing migration 009 and migration 010 were fighting with. Email deliverability, which is a harder problem than the agent logic and is entirely invisible in an architecture diagram. Domain warming, per-domain send limits, timezone-aware windows, reply classification, and unsubscribe handling that is actually correct are the difference between a pipeline that works and a set of domains that are burned within a fortnight. The fact that `outreach` is the largest agent by module count, with `domain_manager.py` and `timezone_utils.py` as first-class modules, reflects where the real difficulty sits. And the honest hard part: knowing when a working system is not a shippable one. This pipeline runs. It finds businesses, it builds sites, it sends email. It also has an admin endpoint authenticated by a guessable token in a query string. Those two facts are both true and the second one governs.
Built with Python, FastAPI, PostgreSQL, GitHub Actions (scheduled), Docker, SMTP / Resend / Instantly, Stripe and Cloudflare + Supabase deploy targets. Relationship: Internal build. Stage: Prototype. Period: 2026. Our role: Agent architecture, orchestration, schema, CI Figures re-derived on 2026-08-23.
Corrected on 2026-08-23: the pipeline has 7 agent packages, not 6 — marketing is a full agent alongside scout, outreach, builder, closer, fulfillment and ops. CI workflows: 9 → 12. Migrations (10) held.