A three-person B2B media business at $1.8m of annual recurring revenue is not exotic. What has changed in 2025–2026 is the cost and shape of the machinery behind it. Open-weight models running on hardware you own or rent cheaply now handle the bulk of the reading, sorting and drafting work, while a frontier API is reserved for the handful of steps where reasoning quality matters most. The result is a business whose non-people costs come to roughly 5–8 per cent of revenue, and whose real constraint is editorial judgement, not throughput.

What follows walks through the composite step by step, from team and revenue to stack, costs and the UK and EU legal limits.

Step 1: The team, three roles and what each owns

The composite has three full-time people. Each owns an outcome, not a set of tasks, and each has an explicit list of things the machines are not allowed to decide.

  • Editor-publisher. Owns the editorial line, every published sentence and the final sign-off on anything containing a number or a named entity. Also owns sponsor relationships at the senior end, because sponsors buy the editor’s judgement as much as the audience. Spends roughly 60 per cent of the week on reading, deciding and rewriting machine drafts.
  • Data and systems engineer. Owns the crawlers, the database, the model serving layer, the verification pipeline and the data products. Treats the newsroom as a production system with on-call duties, logging and error budgets. Writes plain Python; avoids anything they cannot debug at 7am.
  • Commercial lead. Owns subscriptions, sponsorship sales, report licensing and the events calendar. Also owns pricing, churn analysis and the email platform.

Freelancers fill gaps: a contract lawyer on retainer for pre-publication review of contentious pieces, an events producer for the roundtables and occasional specialist writers. They sit in the people line of the cost table, not hidden in tooling.

The machines do the reading. People do the deciding. Every role in the team is defined by what it refuses to delegate.

Step 2: The revenue model

The composite earns from four lines. Subscriptions carry half the revenue and most of the predictability; sponsorship is the second leg; data products and events monetise the same underlying corpus a second and third time.

Modelled annual revenue mix for the composite (USD). All figures are modelled, not reported by any company.
LineUnit assumptionAnnual revenueShare
Paid subscriptions1,500 subscribers at a blended $600 a year (individual and team seats)$900,00050%
Newsletter sponsorship150 slots a year (3 a week, 50 weeks) at $3,600$540,00030%
Data products and reports28 report and dataset licences at $9,000 average$252,00014%
Events4 sponsored private roundtables at $27,000 net sponsorship$108,0006%
Total$1,800,000100%

Two design choices matter. First, the data products are built from the same structured tables the newsletter draws on (filings, funding events, hiring signals), so their marginal cost is mostly the engineer’s time on packaging. Second, events are deliberately small. Four roundtables a year keep the commercial lead’s calendar sane; a conference business would need a fourth hire.

A busy television newsroom with rows of desks and screens
A newsroom at DR, the Danish public broadcaster. Photo: James Cridland, CC BY 2.0, via Wikimedia Commons

Step 3: The stack

Model serving: local first, frontier for the hard steps

The composite runs open-weight models on its own hardware for the high-volume, low-stakes work: classification, entity extraction, deduplication checks, first-pass summaries and claim extraction. A sensible default is Ollama or llama.cpp for a single workstation with one or two 24–48GB GPUs, moving to vLLM when concurrent throughput matters (vLLM’s batching earns its complexity once several pipeline workers hit the model at the same time). Quantised mid-sized instruction models handle extraction well; the engineer pins exact model versions and quantisation levels in config, because a silent model swap changes output distributions and breaks downstream thresholds.

A frontier API is called for perhaps 3–5 per cent of model invocations: the final entailment check in verification, difficult synthesis across many documents and the editor’s drafting assistant. This split is the main cost lever. Local inference has a fixed cost and near-zero marginal cost; API calls are variable but buy reasoning quality where an error is expensive.

Vector search: Postgres with pgvector

All documents, chunks, embeddings, claims and verification results live in one Postgres database with the pgvector extension and an HNSW index on the embedding column. At this scale (a few million chunks after two or three years of crawling), a dedicated vector database adds an extra system to back up, secure and keep consistent, for no meaningful gain. The decisive advantages of staying in Postgres are practical:

  • Vector similarity and ordinary filters (date range, source type, jurisdiction, licence status) run in one query, with transactional consistency between a document and its embedding.
  • One backup and restore story, one access-control model, one place to audit.
  • HNSW gives good recall with query latency in the low tens of milliseconds at this size; tuning ef_search is usually enough.

The trigger to revisit is tens of millions of vectors with heavy concurrent query load, or a need for features Postgres handles poorly. The composite is nowhere near either.

Embeddings

An open-weight embedding model in the 768–1,024 dimension range, run locally, embeds every chunk. Multilingual capability matters for a transatlantic title that reads German, French and Dutch registry filings. The engineer stores the embedding model name and version alongside each vector, so a future model change can be rolled out as a background re-embedding job rather than a big-bang migration.

Orchestration: plain Python and a job queue

No agent framework. Each pipeline stage is a Python function that reads a job, does one thing and writes its result to Postgres. A simple job queue (a Postgres table with SELECT ... FOR UPDATE SKIP LOCKED, or Redis with a small worker library) handles retries, back-off and concurrency. Every model call logs its prompt hash, model version and output, so any single job can be replayed. Heavy frameworks tend to hide exactly the state you need when debugging a wrong number at publication time.

Step 4: The crawler scripts

The crawlers are the input layer, and they are deliberately conservative. Primary sources come first: RSS and Atom feeds from publishers and companies, and official registries. For UK companies, the Companies House API provides filings, officers and charges under a published rate limit (600 requests per five-minute window at the time of writing). For US issuers, SEC EDGAR full-text search and the EDGAR APIs provide filings, with the SEC’s fair-access policy asking automated users to declare a User-Agent with contact details and stay under ten requests per second. Check both policies before you build; they change.

Rules the composite enforces in code, not in a wiki page:

  • Read and obey robots.txt for every host, cached and refreshed daily.
  • Per-host rate limits, with a minimum delay between requests and exponential back-off on 429 and 5xx responses.
  • An honest User-Agent naming the publication and a contact address.
  • Prefer feeds and APIs over HTML scraping; never bypass paywalls or log-ins.
  • Deduplicate by hashing normalised text, so syndicated copies of the same press release are stored once and linked.
import hashlib, time, re
from urllib import robotparser
from urllib.parse import urlparse
import httpx

UA = "VentureSignalBot/1.0 (+mailto:[email protected])"
MIN_DELAY = 2.0          # seconds between requests to the same host
_last_hit, _robots = {}, {}

def allowed(url):
    host = urlparse(url).scheme + "://" + urlparse(url).netloc
    if host not in _robots:
        rp = robotparser.RobotFileParser(host + "/robots.txt")
        rp.read()
        _robots[host] = rp
    return _robots[host].can_fetch(UA, url)

def polite_get(client, url):
    if not allowed(url):
        return None
    host = urlparse(url).netloc
    wait = MIN_DELAY - (time.monotonic() - _last_hit.get(host, 0))
    if wait > 0:
        time.sleep(wait)
    resp = client.get(url, headers={"User-Agent": UA}, timeout=20)
    _last_hit[host] = time.monotonic()
    if resp.status_code == 429 or resp.status_code >= 500:
        raise RuntimeError("retry later")   # the job queue handles back-off
    return resp.text

def content_hash(text):
    norm = re.sub(r"\s+", " ", re.sub(r"<[^>]+>", " ", text)).strip().lower()
    return hashlib.sha256(norm.encode("utf-8")).hexdigest()

def store_if_new(db, url, text):
    h = content_hash(text)
    if db.exists("documents", content_hash=h):
        db.link_duplicate(url, h)     # keep the provenance, skip the work
        return False
    db.insert("documents", url=url, content_hash=h, body=text)
    return True

Exact-hash dedupe catches verbatim copies. Near-duplicates (the same story with a changed headline) are caught later by embedding similarity above a tuned threshold, with the earliest primary source kept as canonical.

Step 5: The automated editorial verification pipeline

This is the part that makes a three-person newsroom credible. Every machine-assisted draft passes through a pipeline whose output is not “true or false” but a list of claims, each with evidence, a confidence score and a status the editor must clear.

Verification pipeline, stage by stage. Thresholds and shares are modelled desk estimates for the composite.
StageWhat it doesEngineTypical failure it catches
1. Claim extractionSplits the draft into atomic, checkable claims (entity, attribute, value, date, source cited)Local model, structured outputCompound sentences hiding two claims, one of them unsupported
2. Source matchingRetrieves candidate evidence via pgvector plus exact lookups against registry tablesPostgres, pgvector, SQLClaims with no retrievable source at all
3. Numeric cross-checkParses numbers, units, currencies and periods; compares with source values deterministicallyPlain Python, no modelMillions read as billions, wrong currency, fiscal year confused with calendar year
4. Entailment checkAsks whether the matched evidence actually supports the claim as wordedFrontier APIOverstated wording, wrong attribution, conflated entities
5. Confidence scoreCombines source tier, match quality, numeric agreement and entailment into a 0–100 scorePython rulesWeak claims resting on a single secondary source
6. Human sign-offEditor reviews every claim below 85, every named person and every number, then approvesEditor-publisherAnything the machines missed, plus judgement on fairness and context

Where models hallucinate, and how the pipeline catches it

  • Numbers and units. Models round, swap scales and invent precision. Stage 3 is deliberately model-free: a parser extracts the figure from the source document and compares it with the draft. Any mismatch blocks the claim.
  • Entity conflation. Two companies with similar names, or a parent and a subsidiary, get merged. Registry identifiers (company numbers, CIKs) are attached at extraction, so a claim about the wrong entity fails the exact lookup in stage 2.
  • Attribution. Statements drift from “the filing says” to “the chief executive said”. Stage 4 checks wording against evidence, and the house rule is that no quotation is published unless it appears verbatim in a primary source.
  • Invented citations. A model can name a report that does not exist. Every citation must resolve to a stored document with a hash; unresolved citations score zero.
  • Over-generalisation. “Revenue grew” when one segment grew. Entailment checks catch most of this; the editor catches the rest.

Sign-off is mandatory and logged. No item publishes without a named human approval in the database, and the pipeline cannot set that flag itself. That single constraint does more for accuracy than any threshold.

Step 6: The operating costs

Modelled monthly operating costs for the composite (USD). Ranges are desk estimates; people costs shown separately.
Cost lineAssumptionMonthly (USD)
GPU hardware amortisationTwo-GPU workstation, about $16,000–23,000, over 36 months (or equivalent rental)450–650
Power and colocationRack space or office power for the inference box150–300
Frontier API spendEntailment checks, synthesis and drafting assistant800–1,500
HostingManaged Postgres, app and worker servers, object storage, backups300–600
Data and toolsPaid data feeds, monitoring, analytics, design and productivity software800–1,400
Email platformPaid newsletter platform at roughly 40,000 subscribers (free and paid)400–900
Payment processingCard fees on about $75,000 of monthly subscription billing2,000–2,500
Event direct costsVenues and catering, averaged across the year2,500–3,500
Non-people total7,400–11,350
PeopleThree full-time staff, fully loaded at $180,000–230,000 each a year45,000–57,500
Freelancers and retainersLegal review, events producer, specialist writers6,000–10,000
All-in total58,400–78,850

Against modelled monthly revenue of $150,000, the non-people costs give a gross margin of roughly 92–95 per cent on an infrastructure-and-direct-cost basis. After people and freelancers, contribution is roughly $71,000–92,000 a month, or 47–61 per cent of revenue, before overheads such as accounting, insurance and company administration. Note what dominates: payment fees and events cost more than all the AI infrastructure combined. The GPU line is almost a rounding error; the people line is the business.

The noise

AI agents let one person run a media company with no staff and near-100 per cent margins.

The signal

In this model, AI and hosting infrastructure costs about 1–2 per cent of revenue, but people still account for roughly 80–90 per cent of total costs, because judgement, sales and sign-off do not automate.

Step 7: What breaks at scale, and what to keep human

Doubling revenue does not double any line in the cost table except payments and people. The breakpoints are elsewhere:

  • Editorial review becomes the bottleneck. Throughput is capped by how many low-confidence claims the editor can clear. The first scale hire is a second editor, not more compute.
  • Source drift. Feeds change format, registries change APIs and sites add bot protection. Silent crawler failures produce a newsletter that looks fine but is missing a sector. Monitor volume per source and alert on drops.
  • Model upgrades shift thresholds. A better model can still change score distributions. Keep a labelled test set of a few hundred claims and re-run it before any model change.
  • Sponsor conflicts. As sponsors multiply, the chance of covering a sponsor critically rises. This is a policy problem; write the rule down before it happens.

Keep human: final sign-off on every published claim, any statement about a named person, story selection, sponsor relationships, pricing, corrections and anything that a lawyer might later read.

Step 8: The UK and EU angle

A US-built crawling playbook does not transfer cleanly. Three points matter for a commercial publication operating in the UK or EU. This is general information on rules that are under active review; check the current position before relying on it.

  • UK copyright. The UK text and data mining exception in section 29A of the Copyright, Designs and Patents Act 1988 covers computational analysis for non-commercial research only. A subscription business is commercial, so it cannot rely on it to copy third-party content for analysis. The practical response is to prioritise open registries and licensed feeds, store links and short extracts rather than full copies where possible, and seek licences for anything else. The UK government has consulted on changes to copyright and AI; at the time of writing, check the current guidance.
  • EU copyright. Article 4 of the Digital Single Market Directive (EU) 2019/790 permits text and data mining, including for commercial purposes, of lawfully accessible works unless the rights-holder has expressly reserved those rights, for online content in a machine-readable way. Crawlers should therefore detect and honour opt-outs (robots.txt and other machine-readable reservations, plus terms of service) and log that they did so. National implementations vary.
  • UK GDPR. Crawled content contains personal data: directors’ names in registry filings, executives quoted in press releases, authors’ bylines. Processing it needs a lawful basis (often legitimate interests, supported by a documented assessment), data minimisation, retention limits and transparency. The Data Protection Act 2018 contains an exemption for journalism, but it is conditional and does not remove all obligations. The ICO’s guidance is the place to start; EU GDPR applies in parallel for EU data subjects.

The engineering consequence is simple: tag every stored document with its source type, jurisdiction and licence basis at ingest, so that a retention or takedown request becomes a query, not a project.

This section is general information, not legal advice. Take specialist advice for your own circumstances.