Proprietary data · Intelligence databases · v1 decided

Signals Intelligence Database

The third shared intelligence layer. One polymorphic, append-only intel_signals table captures every company and person event behind one taxonomy, one dedup gate, one decay model, and a single rollup column so a champion's job change lands on the new employer's feed for free. Every discovery is logged in intel_signal_ingests, so a user's sonar pull and an automated push share one fact yet stay attributable to the run that found them. Scoring and the inbox live on top; this is the store underneath them.

Status DecidedStore intel_signals · 1 tableProvenance intel_signal_ingestsSubjects company · personSignals 12 types
Raw eventNormalizeDedupintel_signalsRollupPer-org feed
00

System Design

Four ingest paths feed the store: on-demand search and manual entry (user-initiated pulls, synchronous) plus signal monitors and a scheduled scraper (automated pushes, async). All four land a raw payload in intel_sources and a deduped row in intel_signals, the single polymorphic, append-only table that is the system of record. Each find also logs an intel_signal_ingests row — one per discovery, many per fact — so a re-find is attributed to its run without doubling the signal. A subject_type resolves each row to intel_companies or intel_people, a related_company_id rollup lands a person's signal on their company's feed, and intel_sources carries web provenance; scoring and the per-org feed sit downstream, outside the store boundary, where a pull bypasses the threshold gate and a push must clear it. Click any node to spotlight it and light up the dot-line paths it touches; column-level detail lives in the schema explorer below.

User · synchronousAutomated · async pushintel_* store · RLS deny-all · service role onlysource_idsubject_id · polymorphicrelated_company_idscore →on-demand searchsonar · returns the row nowmanual entryuser-typed · minted sourcesignal monitorssaved queries · scored pushscheduled scraperSEC EDGAR · job boards…intel_sourcesprovenance + raw payloadintel_companiessubject + rollupintel_peoplesubject targetintel_signalssubject_type · subject_idrelated_company_id · dedup_keyappend-only · observed_atintel_org_signal_feedper-org · scored · decayed
01

Data flow

Where a signal comes from, where it lands, and who reads it. Four producer paths on the left feed one append-only spine — Normalize → Dedup → intel_signals — which scores and materializes a per-org feed for Sonar, monitoring and downstream agents. The producers split user-initiated (synchronous) from automated (scheduled); whichever door a signal enters, it converges on the same gate and lands as one deduped row. Tap a producer to read its source contract, or a spine stage to read what goes in, the transform, and what comes out.

producers
spine
user-initiatedautomatedOn-demand searchuser runs a searchManual entryuser hand-entersSignal monitorssaved, scheduledScheduled scrapersystem, scheduled01Normalize02Dedup03intel_signals04intel_signal_ingests05Scoring06Per-org feed07Consumersconsume
producer

On-demand search

user-initiated
trigger
A dedicated "find signals" action on a company or a person.
cadence
Synchronous — on demand.
providers
Exa, Parallel.ai, profile watchers for people.
target tables
intel_signals (append-only) + related_company_id rollup.
coverage
Company and person — a person hit rolls up to the employer feed.

A first-class, entity-scoped search over the unified store.

02

Ingest paths

The four doors from §01, each walked the whole way to a user. They share one write side — the same Normalize → Dedup → append spine, the same intel_signals row — and split only on delivery. Pick a path, then step a concrete event down the pipe: the flowchart is the track, the intel_signals row builds beside it, and the verdict shows how (and whether) it reaches a user. User-initiated paths are synchronous; automated paths are an async push that surfaces only when the per-org score clears a threshold.

user-initiated · synchronous
automated · async push

Scheduled scraper

async pushsubject: companysystem, scheduled

No user in the loop at all. A system beat sweeps the owned and ICP entity universe, appends each new event once, then fans it out per org. Re-sweep it to watch the dedup gate hold.

re-seen → touch lineage, no new rowTriggersystem, scheduledCollectprovidersNormalizeidentity keyDedupidempotencyAppendintel_signalsScoreper-orgDeliverasync push
01 / 07 · Triggersystem · universe sweep

A system sweep runs

A system schedule sweeps the owned + ICP entity universe, hunting signals for every org at once. No user action.

intel_signalssubject_type: company
row builds as the walk steps…
related_company_id· pending
no fan-out yet
how the user gets it

Fanned to every matching org feed

Per-org gate: feed_threshold 4 surfaces it, digest_threshold 7 escalates it to the digest.

+1 rowrows written
miss → insertdedup path
many feeds + digestfan-out
you act → you see it

On-demand search and manual entry are synchronous. The user initiated it, so the fresh, deduped row surfaces immediately — no scoring gate stands between the event and the screen.

it finds you → if it scores

Monitors and the scraper are an async push. They discover signals with no one watching, then scoring decides reach: feed_threshold 4 to surface, digest_threshold 7 to escalate. The collector finds it; the score delivers it.

03

Schema design

Five tables, not one. intel_signals is the append-only event log, intel_sources is the shared raw-payload and cost lineage, intel_signal_sources links the two many-to-many so one signal can carry several corroborating sources, intel_signal_ingests logs which pipeline found each signal (one row per discovery, distinct from which web outlet reported it), and intel_org_signal_feed is the per-org activation copy stamped with an origin. The relationship map below shows how they wire together — subject_id is polymorphic (subject_type picks intel_companies or intel_people) and related_company_id is the rollup that puts every signal on a company feed. The store stops at the dashed boundary; the feed is activation on top. Full field detail for every table is in the explorer beneath it.

subject_type =
intel_* store · RLS deny-all · service role onlyactivation layer(on top of the store)N sources : 1 signalrelated_company_idsubject_id · polymorphicscore →N ingests : 1 signalintel_sourcesprovider · kindref · cost_usdfetched_atintel_signal_sourcessignal_id · source_idis_primaryintel_signalssubject_type · subject_idrelated_company_idsignal_type · dedup_keysource_id · observed_atintel_signal_ingestssignal_id · ingest_pathworkflow_run_idintel_companiesdomain identitysubject + rollup targetintel_peoplelinkedin_url identitysubject targetintel_org_signal_feedorganization_id · signal_id · scoreicp_fit · decay · tier · origin · rank

dashed = polymorphic subject_id (one of two, by subject_type) · solid = the related_company_id rollup that always reaches a company · the stacked intel_signal_sources box is the many-to-many link, so a re-scrape from a new outlet appends a source rather than a second signal · the stacked intel_signal_ingests box is the parallel provenance log, so a re-find by another run appends an ingest row rather than a second signal.

Append-only event log — one row per observed signal, company or person.

whyReplaces the crm_companies.sales_signals JSONB array (ENG-661). No organization_id and no score on the row: the fact is shared, relevance is per-org and lives in the feed. Corrections arrive as new rows, never an UPDATE.

idPKuuid

one row per observed event

subject_typeenum 'company' | 'person'

the discriminator — one table, two entity kinds

subject_idFKuuid

→ intel_companies OR intel_people, resolved by subject_type

related_company_idFKuuid · null

the rollup → intel_companies: a person signal hops to the employer, a company signal points at self

signal_typetext

the 12-type shared enum — app-layer enforced, same pattern as production signals.signal_type

descriptiontext

human-readable summary, carried over from production signals.description

payloadjsonb

type-specific detail: { amount, round } · { employer, title } · { creatives, delta }

source_idFKuuid

→ intel_sources — the discovering fetch. Corroborating re-scrapes attach as extra intel_signal_sources rows, not a second signal

dedup_keyUNIQUEtext

hash(subject · type · observed-window) — a re-scrape collides here, so a new outlet appends a source link instead of a row

observed_attimestamptz

when the event happened in the world — drives recency decay

ingested_attimestamptz

when the collector wrote the row

worked example

One event, real rows

Acme raises a $40M Series B — and it surfaces twice: Exa catches the TechCrunch story, a later sweep catches the same raise on Bloomberg. Two intel_sources rows and two intel_signal_sources links, and because a scraper sweep found it on both dates, two intel_signal_ingests rows too — but only one intel_signals row, since the second fetch is a dedup hit that appends a corroborating source and an ingest record instead of a new signal. That one signal then fans out per org, each feed row stamped origin = proactive because the scraper pushed it. Follow sig_7b20 down the cards to read every join.

intel_sourcesday-one fetch — the TechCrunch story Exa surfaced
idsrc_4c1aPK
providerexa
kindnews watch
reftechcrunch.com
cost_usd0.004
fetched_at2026-06-01
intel_sourcesa later sweep — same raise, a second outlet
idsrc_9b3fPK
providerparallel
kindweb search
refbloomberg.com
cost_usd0.006
fetched_at2026-06-03
intel_signal_sources.source_id → intel_sources.id · one link per fetch
intel_signal_sourcesthe discovering link, written with the signal
signal_idsig_7b20FK
source_idsrc_4c1aFK
is_primarytrue
linked_at2026-06-01
intel_signal_sourcesdedup hit → +1 link, +0 signal rows
signal_idsig_7b20FK
source_idsrc_9b3fFK
is_primaryfalse
linked_at2026-06-03
intel_signal_sources.signal_id → intel_signals.id · both links → one signal
intel_signalsone shared event — no org_id, no score, never re-written
idsig_7b20PK
subject_typecompany
subject_idcmp_8f21FK
related_company_idcmp_8f21 (self)FK
signal_typefunding_round
payload{ amount: $40M, round: Series B }
source_idsrc_4c1a (discovering)FK
observed_at2026-06-01
intel_signal_ingests.signal_id → intel_signals.id · one row per discovery, not per outlet
intel_signal_ingeststhe discovering sweep — first time this pipeline saw it
signal_idsig_7b20FK
ingest_pathscraper
workflow_run_idnull (scraper)FK
discovered_at2026-06-01
intel_signal_ingestslater sweep, dedup hit → +1 ingest, +0 signal rows
signal_idsig_7b20FK
ingest_pathscraper
workflow_run_idnull (scraper)FK
discovered_at2026-06-03
intel_org_signal_feed.signal_id → intel_signals.id · one row per org, scored
intel_org_signal_feedHalo Collective
organization_idorg_haloFK
signal_idsig_7b20FK
icp_fit0.95
score81
tierdigest
originproactive
rank1
intel_org_signal_feedMeridian Brand
organization_idorg_meridianFK
signal_idsig_7b20FK
icp_fit0.45
score38
tierfeed
originproactive
rank2
intel_org_signal_feedPixel & Co

icp_fit 0.18 → score 15, under threshold — no feed row written at all

Append-only. No UPDATE, no DELETE on the log — corrections arrive as new rows, current state is a view. source_id reuses the shared intel_sources table the company and people databases already write.
04

Signal taxonomy

Twelve signal types — every signal from the company and people monitoring vocabularies, reconciled into one cross-domain enum and grouped by category. Pick any signal from the index to open its full profile: what it means, the play it unlocks, its affinity (the subject it hangs off, both fires on either), its priority and decay window, and a live extraction turning real evidence into one intel_signals row.

high medium low priority·bar = decay window

Capitalbudget appeared
Ad spendpaid-media motion
Hiringheadcount intent
People moveswho sits where
Brand & techidentity / stack shift
Engagementpublic activity
funding_roundCapitalcompanyhigh priority

A company closes a new round of investment. Fresh capital is the single strongest buying trigger: budget that did not exist last quarter is now in the bank, and the mandate that comes with it is to spend, hire and grow. The 30 to 90 days after a raise is when a new vendor is most likely to win a first meeting.

The playReach out inside the first 30 days with a budget-aware offer, before every other vendor piles in.

source
Crunchbase · press
collection
daily
from
company monitoring
decay window
90 days before it ages out
Live extraction
in the wildTechCrunchnews watch

Northwind raises $40M Series B led by Lightspeed to expand go-to-market

intel_signalsparsed · 1 row
$40Mfresh budget
signal_type
funding_round
amount
$40M
round
Series B
lead_investor
Lightspeed
source
techcrunch.com
05

One fact, many scores

One fact, scored three ways. Each agency's feed score multiplies three numbers from 0 to 1 — weight: how strong this kind of signal is as a lead (a funding round beats a tech-stack tweak); ICP fit: how well the company or person matches what that agency sells; and decay: how fresh the event is, fading to zero as it ages past its window. Multiply the three, scale to 100, and anything over the threshold lands in that agency's feed. Pick a fact and drag time forward to watch the factors move.

Per agencyscored at read time · weight × ICP × decay
decay 0.93 · warm
day 0 · fresh6d90d · expired
agency_A0.90weight×0.95ICP fit×0.93decay=80

Acme just raised — fresh marketing budget in exactly the funded B2B SaaS that Halo pitches. The strongest possible new-client lead.

The core idea: store the fact once, score it per agency. One row in intel_signals — collected a single time, no owner, no score — is read by every agency through its own feed, ranked by that agency's fit and how recent the event is. The same Series B is a hot lead for the funded-SaaS shop, noise for the enterprise agency, and gone once it goes stale. Storing once keeps collection cheap; scoring per agency makes every feed read like a hand-picked lead list.
AgencyCore · Signals Intelligence Databasev1 · decided architecture