Skip to content

feat(agent): read people from Context.dev instead of RapidAPI (CMP-86) - #158

Merged
ripgrim merged 4 commits into
mainfrom
rg/context-people-enrich
Aug 18, 2026
Merged

feat(agent): read people from Context.dev instead of RapidAPI (CMP-86)#158
ripgrim merged 4 commits into
mainfrom
rg/context-people-enrich

Conversation

@github-actions

@github-actions github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

One provider now answers what three did. Context's people.enrich returns the profile, the full work history, the photograph, education and skills in a single call, so get_linkedin_profile and get_contact_work_history stop making two lookups against an unofficial LinkedIn scraper.

linkdapi.ts is deleted — 290 lines. lookupCompany, getCompany and linkedinEnabled had no callers.

The score never leaves people.ts

docs/agent.md refuses a vendor score as proof. matchFrom drops a candidate below the floor and returns only the person, so a tool result cannot carry the number and the model cannot read it as confidence. A test asserts it is absent. The verdict is still derived here, from the name and employer we can see.

Calls key on the LinkedIn URL alone

No name, company or email is sent. Passing the company we already hold would let Context bias the candidate toward that employer, and we would then "verify" the employer against the clue we supplied. It also means no customer text leaves for this lookup.

An email match settles identity

A profile that lists the address we are identifying is profile.email-match — the strongest primary evidence there is. The verdict reports emailMatches and it settles identity on its own, without needing employer and name to agree. identity-matching.md maps it.

Both tools charge two units

They make the same 20-credit call. Charging one for work history was a hole. docs/agent.md now states that a unit is one metered call, not one credit: a brand lookup is 10 credits, an enrich is 20, and both charge 2 because the budget rations calls per contact.

Verified against the live endpoint

Not only fixtures. A real profile returned the name, three current roles, the source URL facts.ts requires, and no score. Two things only a live call showed:

  • Photos arrive on Context's own CDN, not LinkedIn. Better for mirror() — no hotlink blocking.
  • sourceUrl resolved to the LinkedIn URL, so the fact-source rule works on real data.

RAPIDAPI_KEY is gone from .env.example, README.md, docs/environment.md, both turbo.json files, capabilities.ts, telemetry and its allowlist. git grep -i rapidapi returns nothing.

Verification

check-types 13/13 · lint 0 · lint:slop 0 · apps/agent 326 pass / 0 fail (was 313, +13 new in people.spec.ts) · apps/api 348 pass. Pushed with the pre-push hook running, not --no-verify.

Known limits

  • emailMatches is untested against real data — the profile I tried lists no address.
  • One live call, one profile. Parsing is unproven on sparse or unusual records.
  • 20 credits per person is unmeasured against real budgets. spend(2) is a considered guess, not a measurement.

@vercel

vercel Bot commented Aug 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
crm-agent Ready Ready Preview Aug 18, 2026 3:33pm
crm-api Ready Ready Preview Aug 18, 2026 3:33pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
crm-app Skipped Skipped Aug 18, 2026 3:33pm

Request Review

@ripgrim ripgrim changed the title fix(agent): source LinkedIn people data from Context.dev feat(agent): read people from Context.dev instead of RapidAPI (CMP-86) Aug 17, 2026

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 24 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/agent/test/images.spec.ts">

<violation number="1" location="apps/agent/test/images.spec.ts:13">
P3: photoUrl now accepts any https URL, dropping the previous LinkedIn-CDN allowlist that rejected foreign hosts (e.g. `https://evil.example/x.jpg`). The avatar is fetched server-side by mirror() in storePortrait, so this broadens the set of hosts the runtime pulls from. Confine avatar URLs to trusted CDN hosts (licdn / Context's) as the old profilePhotoUrl did, or document why any https host is acceptable.</violation>
</file>

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread apps/agent/agent/tools/get_contact_work_history.ts Outdated
Comment thread apps/agent/agent/lib/portrait-sources.ts
Comment thread apps/agent/agent/tools/get_linkedin_profile.ts Outdated
Comment thread apps/agent/agent/tools/get_linkedin_profile.ts Outdated
Comment thread apps/agent/agent/lib/portrait-sources.ts
Comment thread apps/agent/agent/skills/identity-matching.md
Comment thread apps/agent/agent/skills/identity-matching.md
expect(
profilePhotoUrl({ profilePictureUrl: OVERVIEW.profilePictureURL }),
).toBe(OVERVIEW.profilePictureURL);
it("refuses a face served over anything but https", () => {

@cubic-dev-ai cubic-dev-ai Bot Aug 17, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: photoUrl now accepts any https URL, dropping the previous LinkedIn-CDN allowlist that rejected foreign hosts (e.g. https://evil.example/x.jpg). The avatar is fetched server-side by mirror() in storePortrait, so this broadens the set of hosts the runtime pulls from. Confine avatar URLs to trusted CDN hosts (licdn / Context's) as the old profilePhotoUrl did, or document why any https host is acceptable.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/agent/test/images.spec.ts, line 13:

<comment>photoUrl now accepts any https URL, dropping the previous LinkedIn-CDN allowlist that rejected foreign hosts (e.g. `https://evil.example/x.jpg`). The avatar is fetched server-side by mirror() in storePortrait, so this broadens the set of hosts the runtime pulls from. Confine avatar URLs to trusted CDN hosts (licdn / Context's) as the old profilePhotoUrl did, or document why any https host is acceptable.</comment>

<file context>
@@ -1,48 +1,29 @@
-		expect(
-			profilePhotoUrl({ profilePictureUrl: OVERVIEW.profilePictureURL }),
-		).toBe(OVERVIEW.profilePictureURL);
+	it("refuses a face served over anything but https", () => {
+		expect(photoUrl("http://media.licdn.com/x.jpg")).toBe(null);
+		expect(photoUrl("ftp://media.licdn.com/x.jpg")).toBe(null);
</file context>
Fix with cubic

Comment thread apps/agent/agent/lib/people.ts
Comment thread apps/agent/agent/lib/people.ts Outdated
One provider now answers what three did. Context returns the profile,
the whole work history, the photograph, education and skills in a single
call, so get_linkedin_profile and get_contact_work_history stop making
two lookups against an unofficial LinkedIn scraper.

linkdapi.ts is deleted. lookupCompany, getCompany and linkedinEnabled
had no callers.

Enrich reports a score. docs/agent.md refuses a vendor score as proof, so
the score never leaves people.ts: matchFrom drops a candidate below the
floor and returns only the person. A tool result cannot carry the number,
so the model cannot read it as confidence. The verdict is still derived
here, from the name and the employer we can see.

Calls key on the LinkedIn URL alone. Sending the company we already hold
would let Context bias the candidate toward that employer, and we would
then verify the employer against the clue we supplied. It also means no
customer text leaves for this lookup.

A profile that lists the address we are identifying is profile.email-match,
the strongest primary evidence there is. The verdict reports it and it
settles identity on its own.

Both tools charge two units. They make the same twenty credit call, so
charging one for work history was a hole. A unit is one metered call, not
one credit, and docs/agent.md now says so.

The photograph arrives on Context's own CDN rather than LinkedIn, which
mirror() copies exactly as before.

Verified against the live endpoint, not only fixtures: a real profile
returns the name, the roles, the source URL that facts.ts requires, and
no score.
@vercel
vercel Bot temporarily deployed to Preview – crm-app August 18, 2026 15:32 Inactive
@ripgrim
ripgrim merged commit 7b9288b into main Aug 18, 2026
8 checks passed
@ripgrim
ripgrim deleted the rg/context-people-enrich branch August 18, 2026 15:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant