Recruiting sourcing agent
This page explains how a recruiting sourcing agent searches public candidate profiles against approved role criteria and produces a sourcing list where every match reason carries a source. After reading it, you will understand why Web Agent is the core module here, why role rules live in the ATS or a policy store rather than Memory, and how bias risk in candidate evaluation is handled as an engineering concern.
Use case
Recruiting teams need to find public candidate profiles for a role and generate candidate summaries. Public material is scattered across personal sites, tech communities, and public résumé pages — searching manually is slow and inconsistent across batches, and handing a recruiting-system account to a script leaves candidate-data access and retention completely unbounded. The Agent only processes publicly visible information, and its output is a sourcing list for the recruiter to review.
Typical triggers:
- A new role opens, and a first sourcing list is needed within days.
- Role requirements change, and the existing candidate pool must be re-screened against the new criteria.
- A hard-to-fill role stays open, and newly appearing public candidate leads must be added periodically.
Engineering challenges
- The public-data collection boundary: candidate material sits on personal sites, communities, and portfolios; what is truly public versus sign-in-only must be enforced as a hard boundary — collecting candidate data past a login wall is a compliance incident, not a technical optimization.
- Criteria drift and bias entrenchment: if each script interprets the JD its own way, batches cannot be compared; and calibrating criteria from "who historically advanced to interviews" hardens past bias into a ranking signal. Criteria must come from approved, versioned role requirements — never from historical interview outcomes.
- Evidence and reviewability: every match reason must map to a concrete public source and capture time so the recruiter can verify it; an unsourced "feels like a fit" is neither auditable nor defensible.
Module composition
| Module | Role | Notes |
|---|---|---|
| GenAuth | Core | The runtime issues short-lived credentials through silent delegation (required for every product call): short-lived, revocable, audited; no interactive confirmation is needed by default in this scenario — upgrade to interactive delegation only when signing in to an ATS or recruiting system. |
| Web Agent | Core | Searches public candidate profiles via WebSearch, keeping a source URL and capture time per item; sign-in-only pages are skipped and recorded. |
| GUMem | Not used | Role requirements are approved, versioned recruiting rules, injected per version from the ATS or a policy store; past interview outcomes must not serve as a candidate ranking signal, so there is no "screening preference memory" worth persisting across sessions. |
When interactive consent is needed
Every product call must carry a GenAuth-issued delegation token — what is optional is not delegation itself but interactive confirmation. Public candidate searching does not need per-task user consent: a silently issued runtime credential already provides the constraints this scenario needs — it is short-lived, revocable at any time, and every search carries a grantId and auditId attributable to a specific role and task.
Situations that require upgrading to mode: 'interactive' delegation, with explicit recruiter confirmation in the Qoni Console:
- Signed-in access: the task needs candidate data behind the ATS, a recruiting system, or any other sign-in wall — not covered by this page's example.
- Write actions: messaging candidates or modifying the candidate pool — excluded by default here; outreach is always sent by a human after the recruiter approves the draft.
Note: the example on this page requests product-level delegation (products: ['webSearch']). Fine-grained boundaries such as searchable site lists are enforced by the GenAuth Agent Profile or your policy layer, not by the task prompt. See Delegate token and attenuation for the full semantics.
Workflow
The recruiter picks the role and confirms the sourcing scope.
Your app loads the approved role criteria (with a version number) from the ATS or policy store and obtains a silent runtime credential.
Web Agent searches public candidate profiles, extracting experience, skills, and public work per item, with a source URL and capture time.
Checkpoint: Only publicly visible information is collected; sign-in-only candidate pages are skipped and recorded, never bypassed.
The Agent generates candidate summaries and match reasons against the role criteria, each reason tied to a concrete source and citing the criteria version.
Checkpoint: Summaries must not contain sensitive attributes (age, family status, ethnicity, and so on) or signals unrelated to the role criteria; anything found is removed and recorded. Past interview outcomes play no part in ranking.
The recruiter reviews the list, marking advance or exclude; review results are recorded in the ATS and feed the next human revision review of the role criteria.
When outreach is wanted, the Agent drafts the message for the recruiter to approve, and a human sends it — candidates are never contacted automatically.
Example code
The example below wires this scenario into your backend with the official Qoni SDK (@qoniai/qoni): silent runtime credential → load approved role criteria from the ATS/policy store → one webSearch.run() for the public sweep and summaries → validate the output on the app side.
import { Qoni } from '@qoniai/qoni'
const qoni = new Qoni({
accessKey: process.env.QONI_ACCESS_KEY!,
secretKey: process.env.QONI_SECRET_KEY!,
})
export async function sourceCandidates(recruiterId: string, roleId: string) {
// 1. Silent runtime credential: public profiles only, no site sign-in
const { data: grant } = await qoni.delegateToken({
user: { id: recruiterId },
agent: 'recruiting-sourcing',
products: ['webSearch'],
})
// 2. Before the run: load approved role criteria from the ATS /
// policy store (this is versioned policy, not Memory)
const criteria = await loadApprovedRoleCriteria(roleId)
// e.g. { version: '2026-08', mustHave: [...], niceToHave: [...] }
// 3. One WebSearch run covers the public sweep and summaries
const search = await qoni.webSearch.run({
token: grant.token,
prompt: `
Source candidates for role ${roleId} from publicly visible
profiles, portfolios and talks only.
Match strictly against the approved role criteria below; tie every
match reason to a source URL and capture time, and cite criteria
version ${criteria.version}.
Skip and record sign-in-only pages; never bypass them.
Exclude sensitive attributes (age, family status, ethnicity, etc.)
and any signal unrelated to the role criteria. Do not use past
interview outcomes as a ranking signal. Do not contact anyone.
Approved role criteria (version ${criteria.version}):
${JSON.stringify(criteria)}
`,
maxResultsPerQuery: 8,
})
const result = await search.wait()
// 4. Validate the output contract on the app side: entries without a
// source or with a mismatched criteria version are dropped
const candidates = parseCandidates(result.output).filter(
(c) => c.sourceUrl && c.criteriaVersion === criteria.version,
)
// 5. Explicit sensitive-attribute screening; scoring and the list use screened only
const screened = dropSensitiveAttributes(candidates) // drops gender/age/ethnicity and proxy fields, recording the drop count
return {
sourcingList: screened,
criteriaVersion: criteria.version,
audit: { auditId: grant.auditId, permissionBoundary: grant.grantedScopes },
}
}The list structure is a contract set by the task prompt: entries carry a sourceUrl and criteriaVersion per person, parsed and validated by parseCandidates on the app side, with unsourced or unversioned entries dropped; dropSensitiveAttributes then explicitly removes sensitive and proxy fields, recording the drop count, and any downstream scoring uses only the filtered screened. The SDK itself returns the generic RunResult (runId, status, output, artifacts, and so on).
Data and memory boundaries
This scenario touches four kinds of data; none of them belongs in GUMem:
- Recruiting rules: role requirements — approved versions only, managed in the ATS or a policy store, with every match reason citing the criteria version. Past interview outcomes must not serve as a candidate ranking signal, nor be written back as "screening preferences".
- Business state: sourcing lists, candidate summaries, and review annotations — archived in the ATS, retained and used in line with local recruiting and personal-data regulations.
- Audit records: the delegation and behavior chain formed by
grantIdandauditId— maintained by GenAuth, attributing every search to a specific role and task. - User Memory: not used in this scenario. Candidate evaluation must not depend on personalized screening memory accumulated across sessions — that is exactly where bias hardens; criteria changes go only through the policy store's human revision review.
Failure handling
| Situation | Recommended handling |
|---|---|
| A candidate page shows a login wall or CAPTCHA | Skip the source and record it; escalate to the recruiter to decide on a manual look. |
| Public material cannot support a match judgment | State the insufficient evidence honestly; never guess a candidate's background. |
| An output entry lacks a source or criteria version | App-side validation drops the entry and the list notes how many were dropped. |
| A sensitive attribute or unrelated signal appears in a summary | Remove the field and record the event for bias-detection review. |
Production notes
Candidate ranking uses approved role criteria only; past interview outcomes must not serve as a ranking signal. Sensitive attributes (age, family status, ethnicity, and so on) never enter automated scoring, and proxy variables (school, region, name style — signals that can indirectly stand in for protected attributes) plus group-level screening skew must be checked periodically, with findings feeding the role-criteria revision review. Candidate data is used only for sourcing this role, retained and used in line with local recruiting and personal-data regulations. The Agent never messages candidates automatically — outreach is always sent by a human after the recruiter approves the draft.
Next steps
- Read the Quickstart to run the shortest path for Agent identity and delegation.
- Read WebSearch for how public profile search works.
- Continue with the Sales lead agent for an adjacent scenario.