Sean Hopkins

Signal to Draft

ch1 t7_personch2 t4_leadershipch3 t3_techstackwin 45d
8Services
11Stages
5Contracts
3Model calls
138Corpus docs
45dCoalescing
1Human gate

A system that watches a sales territory, notices what changed and decides who is worth spending money on. It researches each person against a grounded corpus of 138 documents, served by its own MCP server, with every document tagged to a driver in a value framework of six drivers and five personas, so each claim traces back to proof. The researcher picks the one driver that is live for this buyer and frames it with MEDDICC. Then it writes outreach a human would send, and stops.

Every stage carries its cost. Each component is described by the role it plays rather than the vendor filling it, so the stack is swappable without the architecture changing.

01Schematic

What runs, and in what order

Colour says what a step is for. Yellow qualifies, cyan is a seam where data crosses into another system, magenta dotted is a gate that can stop the run and green is an action that does the work. Spends marks the steps that cost money, and dashed taps are providers feeding a stage.

QualifySeamGateActionSpendsCosts moneyHumanSean does this step
Registry
Tagged saved searchesProfessional network seatThe territory, curated by a human. One tag for people, one for accounts. Untagged is never polled.
Detection
detect_networkNetwork APIPerson deltas and company spotlights. Tier 1, territory-wide.
detect_signalsCompany-signal providerLeadership, hiring surge, financial events. Tier 2.
detect_techgraphTech-graph providerTech stack and tagged reqs. The t3 net.
Fire log
signal_storeHubSpot Notes backendEvery fire recorded with a sha256 identity hash.
coalesceaccount + class + 45dThree detectors seeing one event fold into one fire.
Gate
title_gatescore before spendingPass, weak, or no title. Weak and absent are surfaced for review, never killed. Reports what it did not spend.
review_queuemarkdown, per weekThe diverted. Human reads, nothing is binned.
Acquire
Phase A
dedup_check45 day window
hubspot_tag~53 properties
stitchcontact → company
anti_matchstage 0 kill
cheap_title_filterfunction gate
Company-signal provider → contact emailBusiness register → firmographics
Grounding
Value framework6 drivers · 5 personas · proof per driverThe value framework lives here. It holds the claims worth making, who they are for and the proof behind each one. Section 06 covers it.
Corpus MCP138 docs · BAAI/bge-small-en-v1.5A local MCP server over the corpus: search, fetch and citation recording. Embeddings tagged driver_ids 1-6.
_corpus_prefetchdeterministic injectionCode retrieves, the agent does not choose.
Phase B
truth · tone · winceEVALUATORS_ENABLED = FalseCoded, switched off. The bake-off preferred the writer alone.
News API → 30d newsTech-graph provider → tech stack
Persist
hubspot_writersingle write pathEvery write carries its book stamp or the client raises. One place for the stamp, the timestamp and the retry.
Gate
Human reviewHubLead in HubSpotEvery draft lands marked for review. Nothing sends. The system is deliberately incapable of sending.
02Stack

Every service, and what it costs

ServiceRoleSuppliesCostCould be
Professional network seatRegistryThe territory. Tagged saved searches are the config file.SeatAny source with saved queries and a change feed
Network APIDetection, tier 1Read access to the seat. Person deltas (changed jobs, posted) and company spotlights (leadership, funding).OAuth bearer; the account id belongs to a connection, not a person, so a reconnect can silently repoint itSubscriptionAny unified network API
Company-signal providerDetection tier 2, enrichmentCompany signal endpoints. Contact email on fire.Credits, cap 25/runAny company-signal provider
Tech-graph providerDetection tier 2, enrichmentTech-stack adoption and tagged requisitions. One tagged technology category is the t3 detector.10–30 cr/contactAny tech-graph provider
Extraction APIAcquisitionBulk profile extraction. The only component that creates contact records.1 cr = 1 profileAny extraction API
Resilient scraperAcquisitionRecent authored posts, plus a web-unlocker recovery pass.~25% of pages refuse; recovery reclaims about half of thosePer pageAny resilient scraper
News APIEnrichment30-day macro news. The sanctioned path; live web search is off in production.Per queryAny news API
HubSpotSystem of recordContacts, companies, the dedup key, the fire-log backend, and the surface humans review on.SeatSwappable, but touches every stage

Also present, narrower in scope: a business register for verified company registration and a document fetcher.

03Models

Which model does what

WhereModelJob
agents/triage.pyClaude HaikuClassify signal strength, gate entry to the expensive chain. The only place a model filters rather than authors.
agents/company_research.pyClaudeFirmographics, macro news, tech stack, business model, why now, relevant drivers. Cached 30 days per company.
agents/researcher.pyClaudeSelect the live value driver, find the angle and proof point, state what could not be established.
agents/outreach_assistant.pyClaudeWrite the email and social message from typed briefs only.
corpus_mcp/embedder.pyBAAI/bge-small-en-v1.5Local embeddings for the corpus index. No API, no rate limit, ~130MB cached on first use.
One runner

agents/_claude_runner.py

Every model call goes through one module. It strips ANTHROPIC_API_KEY from the child environment so subscription auth is the only path, and owns rate-limit retry and transcript persistence. The model becomes a configuration line rather than a dependency scattered across six agents.

Measured

548 seconds per contact, median

Research and writing combined, run two to four workers wide. Contention bites past four on a subscription. This is the largest per-unit cost in the system and the reason five gates sit in front of it.

04Order

The order is a cost function

Every stage must be cheaper than the one after it, or be able to stop the run.

#StageImplementationWhat it doesCost
00Detectdetect_network
detect_signals
detect_techgraph
Poll tagged searches. Person rows normalise to hashed events. Account pulls fire only on companies newly present since the last roster, so a static universe never re-fires.free
01Coalescesignal_storeFold the same story told by three detectors into one fire, keyed on account, class and window.free
02Scoretitle_gateJudge on free evidence before any paid call. Three outcomes, because a rigid two-way filter gets exactly the interesting cases wrong.last free
03Acquirebulk_extractDeduplicate to unique profiles, cap, extract in one bulk call. Past the cap is deferred, never dropped.1 cr / profile
04Dedupdedup_checkSuppress anyone contacted inside 45 days, keyed on the network member id rather than email, because emails change and member ids do not.free
05Taghubspot_tagCreate or update the contact across ~53 mapped properties.API
06Stitchhubspot_backfill_companyResolve every contact to a company and fill firmographics. Zero unresolvable is the pass condition.API
07Suppressanti_match_checkDeterministic kill before any AI spend. Targeting deliberately lives in the scoring layer, not here.free
08Scrapepost_scrapeRecent authored posts, only for what survived everything above. Scraping last is the whole point.per page
09Triageagents/triageHaiku classifies signal strength and gates Phase B.small model
10Research & writeresearcher
outreach_assistant
Two passes over typed contracts with pre-fetched corpus context, one value-framework driver and MEDDICC framing. Output lands on the record marked for review.largest
05Grounding

How the writer is stopped from inventing things

Prompt instruction is advisory. A graded bake-off found an evaluator chain produced worse copy than the writer alone. What works is structural, and none of it depends on the model behaving.

One

Retrieval is deterministic

_corpus_prefetch.py fetches passages by code and injects them. The agent cannot choose what to retrieve, so it cannot retrieve nothing and improvise.

Two

The writer has no tools

allowed_tools = None at write time. Everything it can say arrived through a typed field established upstream.

Three

Citations recorded by code

The candidate carries cited_doc_id; the write seam calls corpus_record_citation on the corpus MCP server. The model does not self-report its sourcing.

Empty over invented

Where evidence is absent the field stays empty and the brief says so. A blank proof point is workable. A plausible fabricated one is not, and is far harder to catch downstream.

06Domain

The value framework, and what it is for

The corpus answers whether a claim is true. The framework answers the harder question: which true thing is worth saying to this person.

A value driver is a business issue the buyer has whether or not your product exists. Not a feature, not a benefit.

  • Feature. The product automates a job the team does by hand.
  • Value driver. The team needs to hit a cost or growth target without adding risk, and without pulling engineers off product work.

A true driver is customer-centric, measurable against a P&L line, and differentiable. Anything failing those three is a capability wearing a driver's clothes.

Six drivers

The drivers belong to the seller each instance is built for, so they are described here by shape rather than content.

1
A cost the buyer already carries
Money back from something already running, freed for what matters next.
2
Time the team gets back
Lean teams on product rather than on toil.
3
Flexibility without lock-in
Today's decision does not become tomorrow's liability.
4
The risk of acting removed
The decision itself carries no downside.
5
No build project
An outcome bought, not an internal engineering programme.
6
A simpler stack
One platform rather than three partial ones.

How it is leveraged at run time

Corpus tagging

Every document carries driver_ids

The 138-document index is tagged 1 to 6, so retrieval scopes to the driver the researcher selected. Grounding and framing share one vocabulary.

Typed output

The brief names driver and confidence

ResearchBrief carries driver id, name, confidence and grounding. A weakly grounded driver is a visible field, not prose.

Selection

One live driver, never six

Recognise which driver is live in front of this buyer and run MEDDICC around that one. Reciting the catalogue is the failure the framework exists to prevent.

Proof binding

No proof point, no driver

Each driver carries verified customer proof with a source URL. The writer pairs driver to proof; the citation is recorded mechanically.

Five personas recur, and the same driver lands differently in each seat. A finance leader hears money returned to budget. A platform leader hears whether it constrains the roadmap. An engineer hears zero impact on the work and least privilege. Same outcome, three sentences.

This is the part that does not port. The architecture is a fortnight. A defensible value model with real proof behind each driver is months, and it is the actual asset.

07Contracts

Typed boundaries between stages

ContractFieldsFromCarries
SignalEvent11detectorsClass, source, date, headline, url, and the sha256 that is its identity for deduplication
IngestResult25ingest/orchestratorContact and company properties, trigger type and date, recent posts, quotable fragments, signal history
CompanyBrief35company_researchFirmographics, macro news, tech stack, hiring, business model, why now, relevant drivers, vendor relationship
ResearchBrief27researcherAngle, proof point, driver id and confidence, buyer hypotheses, conversation starters, stated limitations
OutreachCandidate23outreach_assistantSubject, email body, social draft, angle strength, hook type, review verdict, cited doc id

Contracts are where silent failure gets caught. A renamed field with no alias does not throw, it validates to empty and the value disappears. That failure mode has cost this system real money.

08Discipline

Rules earned the hard way

Each is the scar of a specific incident.

A guard that cannot run reports clean

A scanner using grep -I skipped binaries, so it had never opened a PDF or a docx, and called a tree clean while thirteen files in it were contaminated. Any check must see what it checks, and fail loudly when it cannot.

Unreadable is not clean

A PDF library imported inside a bare except turned a missing dependency into empty text, and empty into a pass. Dependencies now fail at module load.

One implementation, two callers

Two title heuristics drift. Two contamination guards drift, then disagree about what they can even see. Where a rule is used twice it is called, never reimplemented.

Moving a file must never launder it

1.6GB parked in a directory the scanner ignored let the tree report clean. Exclusions are printed every run, so an exclusion is a visible decision rather than an assumption.

Dry run by default

Every stage that can spend defaults to preview and reports what it would have cost. --confirm is the only spender, and it belongs to a human.

A failure must not record itself as a success

Errored contacts written into the completed list make a resume read as a clean no-op. Failure and completion are different states and must be stored as different states.

09Summary

The whole thing

Tagged saved searches → network, company-signal and tech-graph detectors → signal_store coalesce on (account, class, 45d) → title_gate scores before spending → bulk extraction of unique profiles under a cap → dedup, tag, stitch, suppress, filter → scrape → Haiku triage → researcher against a driver-tagged corpus → writer with no tools → hubspot_writer single path → human review

Left to right it is a funnel. Right to left it is a cost ladder, each rung cheaper than the one above. Both readings are the same design.