Proprietary data · CRM · Signals

The signals tables

signals is the CRM's sales-intelligence layer - first-class events like funding rounds, hires and acquisitions, attached to companies and people through two join tables. This page traces it top-down: where signals are served from, how one is born and attached, then the full schema across all three tables, their relationships and rules.

Tables signals · company_signals · person_signalsScope orgTypes 13 canonicalLinks companies · people
Signals feedsignals APIsignals
00

System design

Start from the top. The signals feed never touches storage directly - it talks to the signals subdomain, the only thing that reads or writes signals and its two join tables. Producers emit signals during their runs; the UI reads them back as chips. Click a node to trace what it connects to.

Signals feedVue · signals storesignals subdomainFastAPI · attach / detachsignalsPostgres · first-class eventcompany_signalsjoin + scoreperson_signalsjoin + scoreSonar / Envoy / backfillproducers
01

Data flow - birth of a signal

How a signal lands and is surfaced, as a closed loop. A producer emits an event, it normalizes to a canonical type, a row is written under RLS, a join row attaches it to a company or person, the UI reads it back as a chip - and the lifecycle closes back to the start. Click a stage to follow it.

raw eventwith provenancecanonicaltyped payloadrow writtenid mintedjoin rowscored linkchips shownread backdetachedloop closes1Emitproducer discoversSonar · Envoy2Normalizemap to signal_type13 canonical types3Insertorg-scoped writeRLS · trigger4Attachlink to entityunique join key5Surfacechips + feedbatch · score order6Lifecycledetach + soft deletesoft delete · detach
02

Schema

Three tables: the signal itself plus two join tables that attach it to an entity. Pick a table; colour marks each column's kind, and clicking a column expands its category and any table it references. Everything is org-scoped; the signal carries its own provenance and soft-delete columns.

The intelligence event itself - one org-scoped row per signal, with provenance baked in.

12 columns
  • primary key

    Primary key.

  • foreign key

    Tenant scope. Enforced by RLS on every read.

    → organizations
  • foreign key

    Nullable - the run that discovered it. Provenance.

    → workflow_runs
  • text

    Producer: sonar · envoy · manual · backfill.

  • text

    One of 13 canonical types: HIRING, FUNDING, IPO, ACQUISITION, DEBT, PARTNERSHIP, REBRANDING, LEADERSHIP_CHANGE, EXPANSION, NEW_LOCATION, TECH_PIVOT, PRODUCT_LAUNCH, AWARD.

  • text

    Human-readable summary of the event.

  • timestamp

    When the event happened.

  • text

    Link back to the source.

  • text

    Quoted excerpt from the source.

  • timestamp

    When the producer found it.

  • timestamp

    Audit timestamps.

  • timestamp

    Soft delete - queries filter NULL.

keyprimary keyforeign keytexttimestamp
03

Relationships

Signals are cross-entity intelligence - they attach to companies and people through two scored join tables, and point back at the run that discovered them. Hover or click a table to trace its link and see its key columns.

crm_* · org-scopedtenant

signals attach to companies and people through two scored join tables, with provenance back to the workflow run.

vsIntelligence, not interaction history

Signals are cross-entity sales intelligence - what is happening to a company or person, observed by an agent. That is distinct from activities and communications, which record the interaction history between your team and the entity. One signal can attach to many companies and people; the unique join keys stop the same signal attaching twice.

04

First-class entity, RLS & lifecycle

The database is storage only - no business logic. What it does enforce: tenant isolation, the canonical type set, idempotent attachment, and the soft-delete that keeps the feed clean.

First-class entity

ENG-661 promoted signals from a sonar JSONB blob into a relational signals table, so signals are queryable, filterable and de-dupable instead of buried in an opaque column.

Canonical types

13 fixed signal_type values (HIRING, FUNDING, IPO, ACQUISITION, DEBT, PARTNERSHIP, REBRANDING, LEADERSHIP_CHANGE, EXPANSION, NEW_LOCATION, TECH_PIVOT, PRODUCT_LAUNCH, AWARD) keep the feed consistent across every producer.

Scored attachment

attachment_score on the join lets the UI rank which signals matter most for a given company or person.

Idempotent attach

unique (company_id, signal_id) and unique (person_id, signal_id) prevent the same signal attaching to the same entity twice.

Soft delete

deleted_at marks a signal removed; list queries filter deleted_at IS NULL. A signal can also detach from an entity without being deleted.

Tenant RLS

is_member_of_organization on all three tables gates every read. Superadmins bypass. The scoped client applies the same filter at the app layer.

RLSStorage only

The signals tables hold no rules of their own - tables, indexes, RLS and triggers only. Signals are the CRM's intelligence layer: written by agents, read by humans. Provenance travels with every row - source_agent records the producer and workflow_run_id records the run that found it - so where a signal came from is never lost.

05

Where this is going

The three tables above are what ships today. The decided v1 design splits this single org-scoped signals table into shared layers, so one objective fact can fan out to many orgs at different scores instead of being copied per org. This page stays the source of truth until that lands; the new model is specced on the intelligence-database pages.

signals → intel_signals

The fact loses organization_id and becomes a shared, append-only, polymorphic store - company and person behind one subject_type. Relevance and score move out of the row.

new · intel_signal_ingests

A provenance log - one row per discovery - replaces the single workflow_run_id column, so a re-find by another run or the scraper is attributed without doubling the fact, and the sonar app reads its own results by ingest_path.

company_signals / person_signals → intel_org_signal_feed

The join tables and their never-populated attachment_score give way to a per-org, RLS-per-org feed carrying the computed score, tier, origin and rank. A user pull writes ungated; an automated push is threshold-gated.

origin · pull vs push

Every feed row records an origin (sonar or proactive); the launchpad shows a ranked mix of both. A competitor-ICP check can suppress a pushed lead for rival agencies sharing an ICP (sketched, not yet built).

Specced on the intelligence-database pages

The full design lives in Signals Intelligence Database (the shared store plus the provenance log) and Org Signal Feed (the per-org activation layer).

AgencyCore · CRM · signalsac-frontend · ac-python-api · ac-backend