Six years of B2B growth marketing.Now I build the AI systems that run it.
I came to this through marketing automation. The step from building nurture flows in Dotdigital to building agents that draft their own work was smaller than it sounds. Everything below is running now, and the numbers are read off the system.
37
agents running on a schedule and on demand. Each one drafts into a review queue. None of them can publish.
94
drafts I declined, out of 1,695 raised. 677 went live. The declines are what makes the queue worth having.
$1.49
notional cost per published item across 874 logged runs, counting the ones that failed. It is list-price arithmetic on token counts rather than a bill.
The marketing years
01
Taking a live events business online
Covid closed the events. I rebuilt the audience from CRM data and moved the whole programme online: ten webinars, twelve training sessions and a virtual conference. The master email list grew to 5.72 times its starting size in the first two months. Conference attendance finished 51% over target, and the mailing list overall grew 110%. Across three days of the conference I arranged 167 meetings between sponsors and customers.
My own campaign results. The three growth bars share one scale; the conference bar is drawn against its target.
Role
Marketing, Data & Audience Development Manager
When
Sep 2020 to Aug 2021
Where
Room151, public-finance news and events
Scoring a 200-course catalogue
The training portfolio had grown past two hundred courses and nobody could say which of them deserved the marketing budget. I built a scoring model that graded every course on commercial performance, market demand, strategic importance, growth potential and where it sat in its lifecycle. The output decided what ran, what got rebuilt and what was retired. More than twenty new courses launched off the back of it, and over 90% of those ran first time rather than being pulled for low numbers.
Every course scored on five criteria, then placed; where it landed decided the budget. Illustrative spread. The axes and the four actions are the real model; individual course positions are not published.
Role
Commercial Marketing Executive
When
Aug 2021 to Jun 2024
Where
Institution of Mechanical Engineers
Turning a form fill into a qualified lead
A new professional-development programme needed a pipeline before it had an audience. I built the nurture and scoring journeys in Microsoft Dynamics and Dotdigital, with the sign-up pages in Sitefinity. Behaviour scored the contact, the score decided which sequence they got, and sales picked them up once they crossed a threshold. Within its first two months the programme had beaten the training department’s new-product revenue target for the whole year.
Role
Senior Marketing Executive
When
Jun 2024 to May 2026
Stack
Microsoft Dynamics, Dotdigital, Sitefinity
That last one is why I do what I do now. A nurture flow is a set of rules that decides what happens next without me in the room. Agents are the same idea with better judgement, and the same requirement that a person checks the output before a customer sees it.
What I build now
02
Thirty-seven agents behind one approval gate
Each agent has one job, a written playbook and a slot on a schedule. They draft into a queue and stop. Nothing reaches a customer, a subscriber or a shop listing until I approve it. Approving something can also hand the work on to the next agent, so a chain advances one step per approval. A cost ledger records every run including the ones that fail, and a daily spend ceiling declines work and leaves it in the queue.
1,695drafts raised
677published
94declined by me
Role
Built it and run it
When
Oct 2025 to present
Stack
Python, SQLite, Claude Code, cron, one small VPS
Ten lifecycle emails and an A/B test that will not call it early
The shop sends ten emails across the customer lifecycle, from order confirmation through dispatch to a win-back months later. Subscribers are double opt-in, and a buyer can turn marketing off while still getting the emails about their own order. An experiment can test the subject line, a block of copy, the call to action, the hero image or which products get cross-sold. The code holds off naming a winner until the result clears significance, and promoting it is still a click I have to make.
Role
Built it and run it
When
2026
Stack
Python, Resend, SQLite
An agent that can spend money, inside a fence it cannot climb
One agent manages the paid search accounts through a single manager account, and it is the only thing in the system holding that credential. It has real write access: it opens and pauses campaigns, moves bids and budgets and adds negatives. What it cannot do is decide how much to spend. A weekly cap is checked in code before any budget change and a change that would breach it does not happen. A stop file pauses everything. A new campaign, or a rise above the ceiling on the main account, is proposed to me first. Every write lands in a ledger with the before and the after.
Illustration of the boundary. Account and brand names are not shown.
Role
Built it and run it
When
2026
Stack
Google Ads API, Python, an OAuth refresh flow, JSONL ledger
A direct shop that ships to 55 countries
I moved off the marketplace and built the shop. Prices are set on the server so nothing the browser sends is trusted, Stripe and PayPal both post signed webhooks, digital products deliver on a tokenised link, and postage is priced by weight to fifty-five countries with VAT handled at the checkout.
cockpitforge.com. Made to order in Britain, posted in two to four days.
Role
Built it and run it
When
2026
Stack
Python, Stripe, PayPal, Stripe Tax
Measuring it without flattering it
GA4 runs with consent mode alongside first-party request logs, so no single source has the last word. A reporting agent reads both plus the ad accounts each day, writes a summary and flags anything that moved more than it should have. It reports only. Changing a campaign stays a decision a person makes.
Server logs overcount, because ad checks, link previews and prefetching all look like visits. So each landing page gets three counts that are never blended into one: raw hits, visits a browser actually rendered, and clicks the ad platform billed. On one test page, 577 raw hits came down to 20 visits that a second signal backed up. On another, the logs showed 55 paid landings against 30 billed clicks. A test cannot be called a pass or a fail until at least 40 visitors are counted and 70% of them are backed by a second signal.
577raw hits on one page
20backed by a second signal
30 of 55paid landings billed
Role
Built it and run it
When
2026
Stack
GA4 Consent Mode v2, Google Ads API, Python
What AI answers say when a buyer asks
The monitor puts a bank of buying questions to Claude twice, once from what the model already knows and once with web search, then scores every answer in plain code: whether the brand is named, its share of voice against a list of rivals, and which sites get cited. Each run is compared with the last, so a change in the answers shows up as a change in the numbers.
I ran it on CockpitForge with twelve questions a flight-sim buyer would ask, none of them naming the shop. The first run said CockpitForge was named three times. All three came from the model picking up my own project files, because the questions were being asked from inside the code folder, so each question now runs from an empty folder. The clean run named CockpitForge in none of the 24 answers and Honeycomb in 9, and cited five other flight-sim sites.
0 of 24answers naming CockpitForge
9naming Honeycomb
$2.08for the whole run
Role
Built it and run it
When
2026
Stack
Python, Claude Code in headless mode
An adviser with write access to my calendar
Four areas of my life compete for the same week, so the ranking runs first in plain code with no model in it: due dates, effort, how long a thing has sat there, and how far each area has drifted from the share of time I said it should get. The adviser reads that ranked list and my real calendar, and books work blocks straight into it through the Calendar API. Every hour a second pass reads the calendar back and judges what happened. A block that has been and gone means the work got done, so the task is crossed off. A block that was deleted means it did not, so the task stays open and says so. A block that moved is judged at its new time. A due date arriving is not evidence that anything happened, and it is never allowed to tick a task off.
What it may do unattended is the whole design. Additive, reversible writes happen on their own, because a prep block or a reply task costs one click to undo. Deleting an event, moving one, replying to anyone or accepting a slot never happens without me. If I delete something it created, it does not come back on the next pass, because a deletion is an answer.
Illustration of the write rules. My own calendar is not shown.
Role
Built it and run it
When
2026
Stack
Python, Google Calendar v3, SQLite, cron, Claude Code
Triaging a legal queue without touching the judgement
A legal team’s queue is mostly repetitive low-stakes contracts that still get read end to end. I built an assistant that compares each one against a playbook the legal team owns and edits, then produces a clause-by-clause deviation table with a suggested redline and a question for the counterparty. It routes: clear, or human review with exactly what is off. It never approves and never signs, and when it is unsure it escalates. I wrote an eval before I trusted any of it, and it routes every case in the set correctly and catches every material issue in them. The set is hand-written, and a labelled set of real redacted contracts is the next step.
The shop has an AI assistant that answers questions about products, postage and a customer’s own order. Most of what keeps it safe is code around the model. Refunds, cancellations, complaints and address changes never reach the model and go straight to a scripted handover. Card numbers, email addresses and postcodes are stripped out before anything reaches a prompt or a log. The server holds the conversation history, so a visitor cannot plant lines the assistant never said. After the model writes a reply, a guard reads it: any price it mentions has to exist in the catalogue already, and discount codes, refund promises and anything binding are blocked. Its first line tells the visitor they are talking to an AI, which the EU AI Act has required since August.
A test suite attacks each of those guards without calling the model, so it costs nothing to run after every change, and it exits with a failure if any guard gives way.
93tests in the suite
8layers of guard
<1sto run all of them
The live assistant on cockpitforge.com, asked for a discount code. It says there isn’t one, and the price it quotes comes from the catalogue.
Role
Built it and run it
When
August 2026
Stack
Python, Claude Haiku, SQLite
Bug fixes that go in without me, inside two gates
One agent audits the system for bugs and another writes the patch. Two gates written in plain code decide whether the patch goes in without me. The first reads the finding: it has to be a confirmed bug, rated as small, in a file that is not protected. The second reads the patch itself: one or two files, at most 120 changed lines, clean syntax, and no changed line that touches logins, money or consent. The shop, the public site, the legal pages, the secrets vault and the gates themselves stay protected whatever the patch looks like. A patch that passes has its original files backed up before it is applied, and then the smoke test runs again. If anything that worked before has broken, the originals go back automatically.
In its first month it looked at 206 findings. It applied 33 on its own, handed 153 to me with the reason it stopped, and held 20 back under its daily cap. One of the fixes it applied failed the check afterwards and was rolled back without me.
206findings looked at
33applied without me
1rolled back by itself
Role
Built it and run it
When
August 2026 to present
Stack
Python, Claude Code, cron
Running live code with no staging copy
Everything runs on one server with no staging copy, so a change reaches real visitors as soon as it ships. Before a change, a smoke test records a baseline. Afterwards it checks that every Python file still compiles, loads 91 live pages and endpoints, and reads the logs for new crashes. It fails only on a regression, something that worked before and does not now, so a fault that was already there is labelled and kept out of the verdict. It also compares each long-running service’s start time with the files it imports, because a service still running old code passes every other check. Errors report to Sentry. Backups are encrypted and leave the machine every night, and a monthly drill restores one and checks the database inside it.
In August too many agent runs started at once and the server ran out of CPU, then memory. I wrote the incident up. It had two separate causes, and it ended with a limit of three concurrent runs, a lock on each task and a CPU quota for the agents.
91live surfaces per check
304files compiled
3restore drills passed
Role
Built it and run it
When
2026
Stack
Python, bash, Sentry, encrypted off-site backups
Checks on what the models write
Text a model writes goes through checks in plain code before a person reads it. In the CV app, each generated cover letter is tested for the model’s own asides leaking into the letter, for passages that repeat, for stock phrases and tense slips, and for whether it names something specific to the employer and carries a fact that can be checked. A letter that fails is regenerated once against the faults it broke. The figures on this page go through a check of the same kind: a script reads them off the live system and fails when any published copy of them has drifted.
280test cases for the letter checks
5published files checked for stale figures
Role
Built it and run it
When
2026
Stack
Python, Claude
Search that had to beat the obvious version
Finding the right script or agent in the system used to mean matching text. I built a search that blends that text matching with embeddings computed on a home GPU and stored in SQLite, and before trusting it I wrote 35 real questions with known right answers. Text matching put the right answer first for 3 of the 35. The blended search put it first for 15 and in the top five for 23. When the GPU machine is off, it falls back to text matching.
A tuning loop holds a third of the questions back and throws out any change that only improves the questions it was tuned on. On the 12 held back, text matching got none right first time and the blended search got 4.
3 → 15right first time, of 35
23in the top five
0 → 4on the 12 held back
Role
Built it and run it
When
August 2026
Stack
Python, Ollama embeddings, SQLite
Watching the terms the business runs under
The business depends on rules it did not write: Anthropic’s usage policy and commercial terms, model retirement dates, OpenAI’s data controls, the EU AI Act timeline and the ICO’s guidance on AI. Each morning a script with no model in it fetches 12 of those pages and compares each one with the copy it saved last time. An agent wakes only when a page has changed, and the change is all it reads. It has no web access, so it cannot fill a gap from memory. For each change it names the governance rule it touches, how much it matters and one thing to do, or says that nothing needs doing.
12pages watched
103changes logged since 16 August
0tokens on a day with no change
Role
Built it and run it
When
August 2026 to present
Stack
Python, cron, Claude Code
Also built
04
CV and career web app 2026
A separate product giving CV and application advice. The model runs server side so no key reaches the browser, and it is deliberately forgetful about the documents it sees.
Guardrails 2026
Agent file writes jailed to a drafts folder, a filter over what an agent may run in a shell, a secret scanner across drafts, spend ceilings that decline rather than warn, and an encrypted credential vault that survives the machine dying.
Local GPU pipeline 2026
Routine model work moved onto a home GPU over a private network, with image and speech pipelines on the same machine.
How I work
05
A generalist, deepest in growth and reporting
Email, paid, web, content, CRM and events, usually for the same funnel at the same time, with A/B tests running in most of them. Having run all of them is what stops me improving one channel at another's expense, and the depth is in growth and in the reporting that says whether it worked.
Training and events
Most of my marketing years went on filling rooms, and then on filling webinars when the rooms closed. A fixed date with a real number to hit is a good school for reporting, because attendance on the day is not a figure you can talk around.
One agent, one job
A narrow brief with a written playbook beats a clever general one. It is easier to judge, easier to fix, and when it fails it fails in a way you can read.
A person approves
Every draft waits for me. That is what makes the rest of it safe to run on a schedule, and it is why 94 of them never went anywhere.
Ceilings that refuse
A spend cap that only sends a warning is decoration. Ours declines the run and leaves the work sitting in the queue for the morning.
Measure what you actually care about
Counting drafts is easy and tells you very little. I track whether the output survived review, and what it cost to get there.
A spec before a build
On the bigger jobs the agent writes a short spec and stops. I approve it or send it back before any code exists, which is far cheaper than reviewing the finished thing.
Where I kept the model out
I use a model where a step needs judgement. The calendar ranking is arithmetic, the policy watch starts from a diff, the smoke test and the figure checks spend no tokens, and a refund request never reaches the shop assistant. Plain code is cheaper in those places and gives the same answer every time.