Proprietary data · Intelligence databases · activation layer

Org Signal Feed

The per-org layer on top of the shared signals store. One immutable intel_signals row is an objective fact with no org and no score; this is where it becomes relevance. Each org scores the same row by its own ICP fit. A user's sonar pull surfaces every signal it finds; an automated push must clear a threshold (and a competitor-ICP check) first. What survives materializes into intel_org_signal_feed — the only org-scoped, RLS-per-org table of the signal stack, the door the launchpad, inbox and digest all read through.

Table intel_org_signal_feedScope per-org · RLS allowScore weight × ICP × decayTiers feed 4 · digest 7Origin sonar · proactive
Pull · pushintel_signalsScoreGate (push)Feed rowLaunchpad · inbox · digest
00

System Design

The store and the feed are two layers with opposite access models. intel_signals is shared, append-only and RLS deny-all — one objective row per event, no organization_id, no score. Scoring sits on the boundary and fans each row out to every org whose universe it touches; survivors materialize into intel_org_signal_feed, the only RLS-per-org table of the stack, which the launchpad, the inbox, the digest and downstream agents read. Signals reach the store by two ingest classes — a user's sonar pull or an automated push — and that class decides whether the gate applies (next section). Click any node to spotlight the paths it touches.

store · RLS deny-allintel_org_signal_feed · RLS per-orgone rowfan-outintel_signalsappend-only · sharedno org_id · no scorescoringweight × ICP × decayHalo · score 8.6tier digest · rank 1Northstar · score 5.4tier inbox · rank 3Meridian · score 2.4< 4 · no feed rowdigestsignal inboxagents
01

Ingest paths

A signal reaches intel_signals by one of two classes, and the class — not the score — decides how it is treated. A pull is user-initiated: someone ran a sonar search, so every signal it finds is returned and shown, ungated. A push is automated: nightly agents and scrapers surface signals nobody asked for, so they are gated — only the strong ones get pushed, and a brief competitor-ICP check keeps a hot lead from going to rival agencies at once. Every discovery, either way, is logged in intel_signal_ingests — one row per find, many per fact — so a re-find never doubles the signal yet is still attributed to the run that found it.

Trigger
company-sonar · people-sonar on-demand search
Provenance
ingest_path = 'sonar_search' · workflow_run_id set
Gate
No threshold gate — the user asked, so every scored signal returns in the results and materializes a feed row, however weak.
Feed origin
origin = 'sonar'
Surfaces
Sonar search results · sonar app data table (all of them) · launchpad

Both classes write the same intel_signals fact and the same scored intel_org_signal_feed row — only the gate and the recorded origin differ. The launchpad reads the union; the sonar app's data table reads the pull lane alone (ingest_path = 'sonar_search'), so an automated find never clutters the search view.

02

Data flow

The path from one shared signal to a ranked launchpad row, for one org. Ingest + ICP pair the objective fact with this org's relevance; Score collapses it to a scalar; the path-aware gate drops weak pushes (but never a pull); Materialize writes the intel_org_signal_feed row with its origin; Rank orders it; Deliver renders the launchpad, inbox and digest. Tap a stage to read what goes in, the transform, and what comes out.

00Ingest + ICP01Score02Path-aware gate03Materialize04Rank05Deliver
01

Score

score
In

A (signal, org) candidate

Transform

score = signal-type weight × icp_fit × recency decay(observed_at), evaluated now — never stored on the signal.

Out

A scalar 0..10 for this org

Shape{ score, decay, weight }
03

Fan-out

The idea the whole layer turns on. One intel_signals row is scored separately for every org. The signal-type weight and the recency decay are the same for all of them — only icp_fit differs — so the same fact lands as three different scores and crosses different thresholds. Pick a signal, then age it to watch decay drag every score down through the same two gates.

score = 0.9 × 10 × icp_fit × decay(2d)
Halo Collectivedigest

tight ICP match — exact industry + size band

icp 0.95decay 1
8.4
Northstar Labsinbox

partial match — adjacent vertical

icp 0.6decay 1
5.3
Meridian Groupdropped

weak match — out of core segment

icp 0.28decay 1
2.5

One shared intel_signals row · 2 of 3 orgs materialize a feed row · the rest see nothing, though the signal still exists in the store underneath them.

04

Scoring model

Three factors, multiplied, on a 0–10 scale so the canonical thresholds (feed_threshold 4, digest_threshold 7) read directly. Weight is the intrinsic importance of the signal type; ICP fit is the per-org relevance; decay is a 90-day half-life applied to observed_at at read time — the store row is never rewritten, so the same signal scores lower simply by getting older.

Signal-type weights
funding_roundcapital
0.9
executive_changepeople moves
0.85
job_changepeople moves
0.7
hired_growth_rolehiring
0.6
scaled_meta_adsad spend
0.5
new_tech_stackbrand + tech
0.45
rebrand_or_relaunchbrand + tech
0.4
thought_leadershipengagement
0.3
Read-time decay · 90-day half-life
1.00.5 · half-life 90d
7 days oldhotdecay ×0.9

Decay is evaluated when the feed is materialized and again when the inbox reads — never stored on intel_signals. A correction or a fresher sighting arrives as a new store row, not an update.

05

Schema design

One table — intel_org_signal_feed — holds the activation copy. It is the only org-scoped table of the signal stack: organization_id and the score live here, never on the shared signal. Every row points back at one immutable intel_signals row, so many orgs' feed rows fan from one fact.

spotlight
shared store · RLS deny-allactivation layer · org-scoped1 signal→ per orgintel_signalssubject · signal_typeobserved_at · payloadimmutable · append-only · no org_idintel_org_signal_feedorganization_id = haloscore · tier · ranksignal_id → one shared factintel_org_signal_feedorganization_id = northscore · tier · ranksignal_id → one shared factintel_org_signal_feedorganization_id = meridianscore · tier · ranksignal_id → one shared factorganizationsHalo CollectiveorganizationsNorthstar LabsorganizationsMeridian Group

One intel_signals row fans to one intel_org_signal_feed row per org (1 ──< many); each feed row joins back to its organizations row (many >── 1). organization_id and the score live only inside the activation boundary — the shared store carries neither.

ColumnTypeRole
idPKuuidone row per (org, signal) survivor
organization_idFKuuid→ organizations — the per-org owner; the RLS subject. The store carries no org_id at all.
signal_idFKuuid→ intel_signals — many feed rows point back at one shared, immutable signal.
scorenumericweight × icp_fit × decay, captured at materialization — a snapshot, not a live recompute.
icp_fitnumericthis org's relevance 0..1 — the one factor that differs per org and explains the spread.
decaynumericthe recency multiplier applied when this row was scored, from the signal's observed_at.
tierENUMenum 'feed' | 'digest'which gate it cleared — feed_threshold 4 for the inbox, digest_threshold 7 for the digest.
originENUMenum 'sonar' | 'proactive'how this row entered the feed — a user's sonar pull or an automated push. Sonar wins when a signal arrives by both, so the launchpad label stays honest. The full discovery log lives in intel_signal_ingests.
rankintposition in this org's ranked feed, ordered by score.
surfaced_attimestamptzwhen it entered this org feed.
seen_attimestamptz · nullinbox read state — null until the user opens it.
(organization_id, signal_id)UNIQUEuniqueone feed row per signal per org — re-materialization upserts the score in place.

No signal_type, payload or subject_id is copied down — the feed row carries only relevance (score, icp_fit, tier, rank, origin) and joins through signal_id for the fact. How the signal was discovered (which run, which agent, when) lives in the separate intel_signal_ingests log, not here. The append-only store stays the single source of truth; the feed is a disposable, rebuildable projection.

06

Delivery surfaces

Three surfaces read the one feed table. The launchpad shows the hottest rows ranked by score — a mix of both origins, each tagged sonar or proactive so the user knows whether they pulled it or the system pushed it. The always-on signal inbox renders every feed-tier row; the periodic digest batches only the strongest (≥ digest_threshold 7). seen_at tracks read state per row, so what is new stays marked.

< 4 dropped — no feed row
≥ 4 inbox tier
≥ 7 digest tier
  • 1Acmefunding_roundproactive2ddigest8.6
  • 2Beaconexecutive_changesonar4ddigest7.4
  • 3Northwindhired_growth_roleproactive11dinbox5.7
  • 4Vertexscaled_meta_adssonar6dinbox4.6
  • 5Lumennew_tech_stackproactive20dinbox4.1

The hottest mix of both origins, ranked by score — the dashboard widget. sonar = the org pulled it; proactive = an agent pushed it.

07

Access + RLS

The two layers invert their access model. The shared store is RLS deny-all — no client touches it, only the ac-python-api service role writes and reads. The feed is RLS allow-per-org: each org reads exactly its own rows, scoped by organization_id, and the same service role is the only writer (the materializer). One fact, isolated per tenant at the feed boundary. Pick an actor to watch RLS resolve every store and feed row, with the feed predicate evaluating live.

An authenticated member of Halo. auth.org_id() resolves to org_halo, so the feed policy admits only Halo rows.

RowRLS verdict
storeintel_signals · sig_7b20
intel_signals has no policy → deny-all to every client roleDENY
feedfeed · sig_7b20 → Halo · 8.6
organization_id (org_halo) = auth.org_id() (org_halo) ✓ALLOW
feedfeed · sig_7b20 → Northstar · 5.4
organization_id (org_north) ≠ auth.org_id() (org_halo) ✗DENY

1 of 3 rows readable as Halo user.