-
Notifications
You must be signed in to change notification settings - Fork 440
feat: Add know-thy-person kit #298
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
c8b19e7
c64089f
a61728e
8ba61b1
a250b32
1cfd17e
aaf4757
a049453
9cb23d1
171d03f
cb33753
f073f7f
3a8d04a
5e1810b
7c4f866
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| # Deployed Lamatic flow ID for the know-thy-person flow | ||
| KNOW_THY_PERSON="KNOW_THY_PERSON Flow ID" | ||
| # Lamatic API access (Studio → Settings → API) | ||
| LAMATIC_API_URL="LAMATIC_API_URL" | ||
| LAMATIC_PROJECT_ID="LAMATIC_PROJECT_ID" | ||
| LAMATIC_API_KEY="LAMATIC_API_KEY" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| .lamatic/ | ||
| node_modules/ | ||
| .next/ | ||
| .env | ||
| .env.local | ||
| next-env.d.ts | ||
| package-lock.json | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| # Know Thy Person | ||
|
|
||
| Paste the **email** and **name** of whoever you're about to meet — optionally their | ||
| **company or personal website** — and get a one-page, **fully-sourced** dossier: who they | ||
| are, what they're into *outside work*, and warm talking points. Every claim links to a real | ||
| source; anything it can't verify is shown as "couldn't confirm" instead of invented. | ||
|
|
||
| > `person_context` accepts any link. A **company or personal site gets crawled** for real | ||
| > detail; a **LinkedIn/X profile isn't readable** (anti-bot walls) but still helps the agent | ||
| > find and confirm the right person among same-name results. | ||
|
|
||
| Built as a [Lamatic AgentKit](https://github.com/Lamatic/AgentKit) **kit** (one Lamatic | ||
| flow + a Next.js app). | ||
|
|
||
| ## Why it's different | ||
| Every existing research kit here is company-centric. This one researches the **individual | ||
| human** and leads with the **non-work / rapport** angle — and it's built to **never | ||
| fabricate** facts about a real person (the worst failure mode of person research). Only | ||
| sourced claims are ever rendered. | ||
|
|
||
| ## How it works | ||
| ``` | ||
| email + name + person_context? | ||
| │ (API Request) | ||
| ▼ | ||
| [1 Resolve] code (no LLM) → parses company/domain from the email; picks a research_url | ||
| (the company site, or the person_context link) | ||
| [2 Serper] → live public-presence web search (name + company + person_context) | ||
| [3 Firecrawl] → crawls the research_url for supplementary source content | ||
| [4 Synthesize] gemini-2.5-flash → dossier JSON from search results (primary) + crawl (supplementary), | ||
| each item carrying a source_url | ||
| │ (API Response) | ||
| ▼ | ||
| answer = dossier JSON | ||
| ``` | ||
|
|
||
| Sourcing is enforced end-to-end: the Synthesize node only emits claims with a `source_url`, | ||
| and the app's normalizer **drops any talking-point or outside-work item without a source** | ||
| before rendering — so the UI never shows an unsourced claim. | ||
|
|
||
| ### Output schema (`answer`) | ||
| ```json | ||
| { | ||
| "identity": { "name": "", "role": null, "company": null, "location": null, "sources": [] }, | ||
| "summary": "", | ||
| "outside_work": [{ "note": "", "source_url": "" }], | ||
| "talking_points": [{ "point": "", "why_it_works": "", "source_url": "" }], | ||
| "couldnt_confirm": [], | ||
| "sources": [], | ||
| "confidence": "high | medium | low" | ||
| } | ||
| ``` | ||
| > The app accepts either `snake_case` or `camelCase` field spellings from the flow and | ||
| > canonicalizes to the shape above. | ||
|
|
||
| ## Run locally | ||
| ```bash | ||
| cd kits/know-thy-person/apps | ||
| cp .env.example .env.local # fill in real values from Lamatic Studio | ||
| npm install --legacy-peer-deps | ||
| npm run dev # http://localhost:3000 | ||
| ``` | ||
| Run the tests with `npm run test` and a production build with `npm run build`. | ||
|
|
||
| > `--legacy-peer-deps` is needed because one transitive UI dependency (`vaul`) still | ||
| > declares a React 18 peer range while this app is on React 19. | ||
|
|
||
| ### Environment | ||
| | Var | Where | | ||
| |---|---| | ||
| | `KNOW_THY_PERSON` | deployed flow ID (Studio → Flow → Details) | | ||
| | `LAMATIC_API_URL` / `LAMATIC_PROJECT_ID` / `LAMATIC_API_KEY` | Studio → Settings → API Keys | | ||
|
|
||
| The **OpenRouter key** (for `gemini-2.5-flash`) and the **Serper** / **Firecrawl** keys are | ||
| configured in **Lamatic Studio**, not in this app. | ||
|
|
||
| ## Honest limitations | ||
| - **LinkedIn and X are not scraped directly** (anti-bot walls). We rely on live web search | ||
| + crawling of open pages, which often surfaces those facts indirectly. | ||
| - **Needs a crawlable anchor: a company email _or_ a website link.** A company email resolves | ||
| the domain to crawl; for a generic provider (gmail, outlook, …) supply the person's | ||
| **company or personal site** as `person_context` and that gets crawled instead. A generic | ||
| email with **no link** has nothing to crawl and is not supported. | ||
| - **Data-broker / people-search sites are blacklisted** as sources. | ||
| - Results are only as good as a person's public footprint. For **low-footprint people** the | ||
| agent correctly returns little and says so — that's the point, not a bug. | ||
| - Public information only. This is meeting prep, not surveillance. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| # Know Thy Person — Agent | ||
|
|
||
| ## Overview | ||
| A meeting-prep research agent. Given a person's email and name (and, optionally, a | ||
| link to their LinkedIn / X / company or personal site), it produces a fully-sourced | ||
| dossier to help you build genuine rapport before a scheduled meeting. | ||
|
|
||
| ## Purpose | ||
| Before a meeting you want to know the *person*, not just their title — enough to open | ||
| warmly and human. Doing this by hand doesn't scale, and the worst outcome is a *wrong* | ||
| fact stated with confidence. This agent leads with the non-work / rapport angle and is | ||
| built to **never fabricate**: every rendered claim carries a source URL, and anything it | ||
| can't verify is surfaced as "couldn't confirm" instead of invented. | ||
|
|
||
| ## Flow: `know-thy-person` | ||
| - **Trigger (API Request):** `email` (required), `name` (required), `person_context` | ||
| (optional — their company or personal website URL; a LinkedIn/X link isn't crawlable but | ||
| still helps pin the right person). | ||
| - **Processing:** | ||
| 1. **Resolve** (code, no LLM) — deterministically parses company/domain from the email | ||
| (no model call). Generic providers (gmail, outlook, …) resolve to no company, by design. | ||
| 2. **Serper** — live web search over the person's public presence. | ||
| 3. **Firecrawl** — crawls the most relevant public pages (personal/company sites, press, | ||
| talks, profiles) for source-attributable content. | ||
| 4. **Synthesize** (`gemini-2.5-flash`) — converts the sourced material into a strict | ||
| dossier JSON where every identity / outside-work / talking-point item carries a | ||
| `source_url`. | ||
| - **Response (API Response):** `answer` — the dossier object (see README for the schema). | ||
| - **When to use:** ahead of a 1:1, sales call, interview, or intro where rapport matters. | ||
| - **Output:** `{ result: { answer: <dossier> } }`. | ||
|
|
||
| ## Guardrails | ||
| - Cites or stays silent — never fabricates facts about a real person. | ||
| - Public information only. This is meeting prep, not surveillance. | ||
| - LinkedIn and X are **not** scraped directly (anti-bot walls); facts about them are used | ||
| only when they surface in public search/crawl results. | ||
| - Data-broker / people-search aggregator sites are blacklisted as sources. | ||
| - Low-footprint people correctly return sparse, honest results. | ||
|
|
||
| ## Integration Reference | ||
| - **Serper** — web search (configured in Lamatic Studio). | ||
| - **Firecrawl** — page crawling (configured in Lamatic Studio). | ||
| - **OpenRouter** — serves `gemini-2.5-flash` for the Synthesize node (the only LLM in the | ||
| flow; Resolve is deterministic code). Configured in Lamatic Studio, not in the app. | ||
|
|
||
| ## Environment Setup | ||
| | Var | Source / purpose | | ||
| |---|---| | ||
| | `KNOW_THY_PERSON` | Deployed flow ID (Studio → Flow → Details). | | ||
| | `LAMATIC_API_URL` / `LAMATIC_PROJECT_ID` / `LAMATIC_API_KEY` | Studio → Settings → API Keys. | | ||
|
|
||
| ## Quickstart | ||
| 1. `cd kits/know-thy-person/apps` | ||
| 2. `cp .env.example .env.local` and fill in the four values above. | ||
| 3. `npm install --legacy-peer-deps` | ||
| 4. `npm run dev` → open http://localhost:3000 | ||
|
|
||
| ## Inputs | ||
| - `email` (required), `name` (required), `person_context` (optional URL). | ||
|
|
||
| ## Output | ||
| - `answer`: a dossier object (see README for the schema). |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| KNOW_THY_PERSON="KNOW_THY_PERSON Flow ID" | ||
| LAMATIC_API_URL="LAMATIC_API_URL" | ||
| LAMATIC_PROJECT_ID="LAMATIC_PROJECT_ID" | ||
| LAMATIC_API_KEY="LAMATIC_API_KEY" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
|
||
| # dependencies | ||
| /node_modules | ||
|
|
||
| # next.js | ||
| /.next/ | ||
| /out/ | ||
|
|
||
| # production | ||
| /build | ||
|
|
||
| # debug | ||
| npm-debug.log* | ||
| yarn-debug.log* | ||
| yarn-error.log* | ||
| .pnpm-debug.log* | ||
|
|
||
| # env files | ||
| .env | ||
|
Comment on lines
+19
to
+20
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win Mission: keep runtime credentials out of Git.
🤖 Prompt for AI Agents |
||
|
|
||
| # vercel | ||
| .vercel | ||
|
|
||
| # typescript | ||
| *.tsbuildinfo | ||
| next-env.d.ts | ||
| # lockfiles (kept local; not committed) | ||
| pnpm-lock.yaml | ||
| yarn.lock | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| # Know Thy Person — app | ||
|
|
||
| The runnable Next.js app for the **Know Thy Person** kit. Enter an email + name (and, | ||
| optionally, a LinkedIn / X / company or personal site link) and get a fully-sourced | ||
| meeting-prep dossier. | ||
|
|
||
| See the kit README — [`../README.md`](../README.md) — for what it does, the flow, the output | ||
| schema, and honest limitations. | ||
|
|
||
| ## Run locally | ||
|
|
||
| ```bash | ||
| cp .env.example .env.local # fill in real values from Lamatic Studio | ||
| npm install --legacy-peer-deps | ||
| npm run dev # http://localhost:3000 | ||
| ``` | ||
|
|
||
| - `npm run test` — run the dossier-normalizer unit tests (Vitest). | ||
| - `npm run build` — production build. | ||
|
|
||
| ### Environment | ||
|
|
||
| | Var | Where | | ||
| | --- | --- | | ||
| | `KNOW_THY_PERSON` | deployed flow ID (Studio → Flow → Details) | | ||
| | `LAMATIC_API_URL` / `LAMATIC_PROJECT_ID` / `LAMATIC_API_KEY` | Studio → Settings → API Keys | | ||
|
|
||
| The OpenRouter, Serper, and Firecrawl keys are configured in **Lamatic Studio**, not here. | ||
|
|
||
| ## License | ||
|
|
||
| MIT License — see [LICENSE](../../../LICENSE). |
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,47 @@ | ||||||||||||||||
| "use server"; | ||||||||||||||||
|
|
||||||||||||||||
| import { getLamaticClient } from "@/lib/lamatic-client"; | ||||||||||||||||
| import { normalizeDossier, type Dossier } from "@/lib/dossier"; | ||||||||||||||||
|
|
||||||||||||||||
| export interface ResearchInput { | ||||||||||||||||
| email: string; | ||||||||||||||||
| name: string; | ||||||||||||||||
| personContext?: string; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| export async function researchPerson( | ||||||||||||||||
| input: ResearchInput | ||||||||||||||||
| ): Promise<{ success: boolean; data?: Dossier; error?: string }> { | ||||||||||||||||
| try { | ||||||||||||||||
| const workflowId = process.env.KNOW_THY_PERSON; | ||||||||||||||||
| if (!workflowId) throw new Error("Workflow ID not found in config."); | ||||||||||||||||
|
Comment on lines
+16
to
+17
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Mission: route workflow selection through the kit config. Import and use Proposed fix+import kitConfig from "../../lamatic.config";
+
- const workflowId = process.env.KNOW_THY_PERSON;
+ const step = kitConfig.steps.find((item) => item.id === "know-thy-person");
+ const workflowId = step ? process.env[step.envKey] : undefined;As per coding guidelines, Kit Next.js apps must import and use 📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSource: Coding guidelines |
||||||||||||||||
|
|
||||||||||||||||
| const inputs = { | ||||||||||||||||
| email: input.email, | ||||||||||||||||
| name: input.name, | ||||||||||||||||
| person_context: input.personContext ?? "", | ||||||||||||||||
| }; | ||||||||||||||||
|
|
||||||||||||||||
| const lamaticClient = getLamaticClient(); | ||||||||||||||||
| const resData = await lamaticClient.executeFlow(workflowId, inputs); | ||||||||||||||||
| // The flow maps the dossier fields flat onto `result` (identity, summary, | ||||||||||||||||
| // talkingPoints, ...). Some flows instead wrap it as `result.answer`; support both. | ||||||||||||||||
| const result = resData?.result as Record<string, unknown> | undefined; | ||||||||||||||||
| const dossier = result && "answer" in result && result.answer ? result.answer : result; | ||||||||||||||||
| if (!dossier || typeof dossier !== "object") { | ||||||||||||||||
| throw new Error("No dossier returned from the flow."); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| return { success: true, data: normalizeDossier(dossier) }; | ||||||||||||||||
| } catch (error) { | ||||||||||||||||
| let message = "Unknown error occurred"; | ||||||||||||||||
| if (error instanceof Error) { | ||||||||||||||||
| message = error.message; | ||||||||||||||||
| if (message.includes("fetch failed")) | ||||||||||||||||
| message = "Network error: could not reach the research service."; | ||||||||||||||||
| else if (message.toLowerCase().includes("api key")) | ||||||||||||||||
| message = "Authentication error: check your API configuration."; | ||||||||||||||||
| } | ||||||||||||||||
| return { success: false, error: message }; | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Mission directive: retain npm’s resolution lock.
The documented npm install path will resolve a potentially different dependency graph on each fresh install because
package-lock.jsoncannot be committed. Remove this ignore entry and commit the lockfile.🤖 Prompt for AI Agents