Workflow Engine · push layer · forward-looking design

People Signals Monitoring

The push half of People Signals. Monitoring watches a cohort of people you already care about - past champions, past contacts, and target-account decision-makers - and fires a warm lead the moment one of them changes jobs, is promoted, or has a qualifying event.

Mode push · scheduledWatches people you knowFlagship follow your championStatus design doc
Watchlist cohortCollectors → person_signal_events·Match planeWarm-lead inbox
00

Why push

Pull discovery answers "who should I contact now?" on demand. But the highest-value people event - a known buyer changing jobs - happens on its own schedule, and the window is short. Monitoring is push: it watches the people an agency already has a relationship with and routes a warm lead the moment something changes, instead of waiting for the next search. B2B contact data decays ~2-3% a month; a watch loop catches the transition, not the stale state.

114%
Higher win rate
deal w/ prior champion
54%
Bigger deal size
prior champion present
3
Watch cohorts
champion · contact · target DM
2
Planes
collect · match
KEYFollow your champion

The flagship play: when a past buyer moves to a new company, they bring trust with them. Deals with a prior champion close at far higher win rates and larger sizes, so a job-change alert on a known contact is a warm-intro generator - the single most valuable people signal an agency can act on. Everything else in the watch loop is built around making that one alert reliable and fast.

01

System shape

Two planes, mirroring company signal monitoring. A data plane of collectors that re-check watched people and write append-only events; a match plane of per-agency watchlist rules that decide which events become warm leads, and where they go.

Data plane

Collectors → person_signal_events

Scheduled jobs re-crawl each watched person (LinkedIn URL diff, news, content feeds), pass candidate events through the substantiation gate, and append immutable rows. The graph stores facts; the collector watches them change.

Match plane

Per-agency watchlist rules

Each agency declares which cohorts and signal kinds it cares about. Every new event is matched against those rules to a 0-1 score, gated by a threshold, and the survivors fan out to the inbox.

The seam matters: collectors are global (re-crawl a person once, every watching agency benefits), but the match rules and the resulting warm leads are tenant-private. Who you watch and why never leaks across agencies.
02

System architecture

The same two planes as concrete, deployable containers - one colour per repo. The data path runs top to bottom; each edge is labeled with what crosses it and how. Solid edges are request-path / synchronous; dashed edges are scheduled or async. Hover any container for what it owns, or trace one event - a champion's job change - down the happy path.

ac-monitorac-backendmanaged (Anthropic)ac-python-apiac-frontendexternalsyncasync
External systems
Collection
Event log + rules (Postgres)
Match plane — scoring agent
Enrichment fan-out — complete the pair
Intelligence write-back (Postgres)
Event relay (the SSE seam)
Delivery store (Postgres)
Realtime fan-out
Surfaces
Six repos: ac-monitor re-crawls watched people, ac-backend owns the person tables and the intel_* write-back, the Claude managed agent scores on Anthropic infra, ac-python-api runs the enrichment fan-out + relay + delivery, ac-frontend renders the inbox. The middle tiers are the heart: on a survivor the agent completes the pair (enrich_person → resolve the new company → enrich_company), each resolved fact is written back to intel_* with provenance, and the fills stream to the open tab as agent.progress / agent.done frames - see §03. This is the mirror of company monitoring: a company signal enters knowing the company and finds the contact; a people signal enters knowing the person and resolves the company. Either way the warm lead is one (company, person) pair.
03

Stream contract

Two SSE legs meet at the ac-python-api relay. Upstream we subscribe to the managed agent's hosted session stream; downstream we emit our own SSE to the browser - the same frame contract company monitoring already speaks. The relay is the only place the two touch.

upstream · we subscribe
Claude managed agent (hosted session)SSE ↓ac-python-api · event relay
Anthropic session SSE
assistant turn · tool_use · tool_result · message_stop

The hosted loop runs on Anthropic infra. We never see the loop - only the events it streams. The relay answers tool_use by running the enrichment fan-out (enrich_person / enrich_company) and posting tool_result back into the same session, and mirrors the user-visible subset into Supabase for audit.

downstream · we emit
ac-python-api · SSE fan-outSSE ↓ac-frontend · EventSource
our SSE (contract unchanged)
inbox.insert · inbox.update · agent.progress · agent.done

One LISTEN consumer + the relay's in-process agent.progress feed merge into a pub/sub fanned to N browser tabs. The frontend keys every frame on inbox.id and upserts - inserts prepend a warm-lead card, updates patch it in place. Same frame contract company monitoring already speaks.

The relay answers tool_use by running the enrichment fan-out and posting tool_result back into the live session, mirrors the user-visible subset into Supabase, and writes the person_signal_inbox row. Everything downstream reads from that row - so the warm lead reaches the tab through the identical frame schema as a company signal.

Downstream frame schema what each frame does to the Pinia store

inbox.insert{ id, person, kind, score, status:"new" }Prepend a new warm-lead card (no-op if id already present - replay-safe).
agent.progress{ id, phase:"enriching", note }Patch the card to a live "enriching…" state while the agent works.
agent.done{ id, company, rationale, draft }Fill in the new company + warm-intro draft on the existing card.
inbox.update{ id, status:"read"|"dismissed"|"actioned" }Patch status in place - a write from any surface or tab lands here.

Lifecycle invariants the rules the frontend leans on

upsert by idStore is a Map keyed on inbox.id - every frame upserts, never appends. Reconnect + replay is idempotent.
Last-Event-IDEach frame carries a monotonic id; on reconnect EventSource replays the gap from the inbox cursor (delivered_at, id).
id-only NOTIFYpg NOTIFY caps at 8KB - it carries inbox.id only; the SSE layer SELECTs the row (RLS-scoped) before framing.
heartbeatA :keepalive comment every < 55s holds the connection open past the Heroku router idle timeout.
authEventSource cannot set headers - token rides a cookie; org scope is enforced by RLS on the hydrate SELECT, not the client.
one consumerA single session-mode LISTEN connection fans to all tabs - never one LISTEN per EventSource (and never through txn-pooled pgbouncer).
04

Watch signals

The same shared signal vocabulary as discovery, seen through a monitoring lens - each kind has a detection source and a re-check cadence. Pick a kind to see how it is watched and why it matters.

job change

high severity

A watched champion moved companies. The flagship warm lead - follow them to the new account.

source LinkedIn diff · newscadence dailyenum JOB_CHANGE
05

Warm-lead matcher

When an event fires on a watched person, the match plane scores it against the agency's rule: cohort weight × signal weight × recency × ICP fit, gated by a tunable threshold. Set up a fired signal and watch it land in drop, inbox, or hot.

86%
hot

Hot warm lead - inbox + instant Slack / digest priority.

cohort × signal × recency × fit
watch cohort
fired signal
days since event · 3d
threshold · 0.60

Illustrative of the deterministic rule - exact weights and decay are tuned at build. The point: a champion's fresh job change clears almost any threshold; a cold-watch tenure nudge rarely does.

06

Schema additions

Three additions on top of the people intelligence layer. Events are global; watchlists and the inbox are tenant-scoped behind RLS.

person_signal_eventsglobal

Append-only. person_id · signal_type · observed_at · source_url · substantiated. One row per detected transition; collectors only insert.

person_watchlistsper-org

org_id · person_id · cohort (champion / contact / target_dm / cold) · added_at. The set each agency watches and why.

person_signal_inboxper-org

org_id · event_id · match_score · verdict · state (new / actioned / dismissed). The warm-lead feed the rep works.

07

Collector cadence

Re-check frequency follows signal volatility and cohort value, on celery beat - the volatile, high-value cohorts are watched hardest.

CollectorCadenceDiff strategy
Champion job / title diffdailyLinkedIn URL re-crawl vs last snapshot + news corroboration
Contact job / title diffevery 2-3 daysLinkedIn URL re-crawl, lower priority queue
Target-account DM scandailycompany leadership page + news for new buyer roles
Content / posting feed12hRSS + social poll, new items since last cursor
Event / speaking scanweeklyevent-site crawl for watched names
Tenure milestone sweepweeklyderived from role start dates, no fetch
08

Delivery fan-out

The inbox is the source of truth; everything else is a notification on top of it.

Inbox · truth

Signals inbox

Every survivor lands here as a warm lead with the person, the event, the match score, and a suggested outreach reason. The rep works the feed.

Push · timely

Slack & digest

Hot leads ping Slack instantly; everything else rolls into a daily digest so the inbox is not a firehose.

Integrate

Webhook / CRM

A webhook lets agencies route warm leads into their own outbound tooling; promotion writes the person + signal into CRM.

!GDPR applies to watching people

Monitoring stores and processes personal data on an ongoing basis. Each watched person needs source lineage for Article 14 disclosure and a takedown path, and the watch itself must respect lawful-basis limits. Provenance and deletion are build requirements, not follow-ups.

09

Open questions

Four questions to resolve before this leaves the design stage. Each carries a provisional answer.

Q1

How big can a watchlist get per agency before refresh cost bites?

Provisional: quota by plan, with the champion cohort always free to watch.

Q2

How far back should job-change backfill look on first import?

Provisional: 90 days, so a recent move is not missed at onboarding.

Q3

How is a job change detected reliably without a paid LinkedIn feed?

Provisional: LinkedIn URL re-crawl diff + news corroboration through the substantiation gate.

Q4

Do people signals decay out of the inbox, or persist until actioned?

Provisional: decay the score, persist the row - a warm lead stays visible but sinks.

This builds on People Signals discovery and the People Intelligence Database; it mirrors Company Signals Monitoring at the person grain.
AgencyCore · People Signals Monitoringpush layer · forward-looking design