Resolve the owning profile version.
Job Search Agent
A multi-user job intelligence and notification system that makes expensive model work the last step—not the first—and keeps every decision explainable.
The problem
Job listings arrive from providers with different fields, weak location evidence, duplicate postings, and descriptions that are expensive to evaluate semantically. Multi-user schedules add ownership, retry, and privacy boundaries.
The design needed to personalize discovery without multiplying API or LLM calls, retain the evidence behind each recommendation, and avoid duplicate emails when scheduled work retries.
Graph workflow
Manual and scheduled runs enter the same compiled LangGraph. Each stage advances only when its artifacts are complete; otherwise the router records what it can and closes the run safely.
01 · Prepare
Build bounded role and location searches.
- Profile errorFinalize
- Query-planning errorPersist failed run
02 · Discover
Execute bounded provider runs.
Build canonical job artifacts.
Resolve stable job identity.
- All providers failedNormalize failure evidence
- Incomplete search, normalization, or identityFinalize
03 · Evaluate
Apply deterministic eligibility rules.
Run isolated semantic evaluation.
Apply threshold and result limit.
- Incomplete filter, score, or ranking artifactsFinalize
04 · Deliver
Commit history before effects.
Choose the notification path.
- Not persisted or no selected jobsFinalize without notification
Send an idempotent digest.
Close the run and status.
One correlation ID connects the database run, logs, notification intent, and privacy-projected LangSmith trace.
Production decisions
- Filter before scoring. Definite age, location, authorization, title, and salary mismatches are rejected before they can spend an LLM request.
- Keep graph state small. Full profiles, jobs, and evaluations live in run-scoped artifacts; the LangGraph state contains identifiers, routing status, timestamps, and warnings.
- Correlate without leaking content. The search-run ID is reused across PostgreSQL, logs, notifications, and traces. Trace exports allow IDs, versions, counts, and usage—not résumé text or job descriptions.
- Persist before sending. Notification intent is committed before Resend is called. A stable idempotency key and database claim prevent retry-driven duplicate delivery.
Engineering outcomes
Failure isolation
One provider or per-job scoring failure can produce a useful partial result without erasing completed work.
Bounded cost
Query, result, concurrency, token, call, and estimated-cost ceilings constrain each run.
Explainable history
Immutable observations and evaluation versions preserve what was seen and why a job ranked.
Shared execution path
Manual and scheduled runs use the same graph, persistence, tracing, and notification boundaries.