Agent Memory for HR & Recruitment

By Arc Labs Research9 min read

The Problem: Why Stateless Recruitment Fails

Traditional recruitment AI systems treat each candidate interaction as isolated. When recruiting across weeks or months:

  • Repeated interviews: Candidate is asked "Tell us about your background" in round 1, same question in round 2 by different agent, feels dismissive
  • Lost candidate context: Agent doesn't recall candidate mentioned "I'm an iOS expert with 8 years experience", evaluates them as junior web developer
  • Inconsistent scoring: Candidate scores 7/10 after round 1, agent re-evaluates after round 2 and scores 4/10 (no memory of prior assessment)
  • Dropped candidates: High-performing candidate from 2 weeks ago is forgotten, similar role opens, agent doesn't resurface them
  • Missed offer history: Agent doesn't know candidate already rejected a 150koffer,proposes150k offer, proposes 155k, candidate declines again
  • Role mismatch: Agent doesn't recall role requires "lead-time management experience", recommends candidates without it

Result: Slow hiring, candidate frustration, repeated feedback loops, missed opportunities, and poor hire quality.

Agent memory solves this by storing candidate profiles, interview feedback, role specs, and offer history—enabling faster, more consistent hiring decisions.

Memory Types for HR & Recruitment

A recruitment AI agent needs four types of memory:

TypeContentExampleRetrieval
Candidate FactsResume, skills, experience, location, salary expectations"Alice Chen: iOS engineer, 8 yrs, San Francisco, $180k target, visa sponsorship needed"Keyword (skill, location) + semantic
Interview FeedbackNotes from each round, scores, strengths, weaknesses, red flags"Round 1: Strong architecture discussion, weak on mobile performance optimization. Score: 7/10"Temporal (round-by-round) + semantic
Role RequirementsJob description, required skills, nice-to-haves, must-haves"React + Node.js required, GraphQL preferred, team lead experience required"Keyword (skill)
Offer HistoryOffers made, compensation, status (accepted/rejected/pending)"Offer 1: 160k(rejected20260501),Offer2:160k (rejected 2026-05-01), Offer 2: 180k (pending)"Temporal (most-recent-first)

Retrieval Pattern: "Active Candidates + Role Requirements + Interview Notes"

When screening a new candidate or updating their status, retrieve in order:

1. Candidate facts (keyword + semantic)
   → Recruiter knows background, skills, expectations, location/visa needs

2. Role requirements (keyword)
   → Agent checks skills match against role musts/nice-to-haves

3. Interview feedback (temporal)
   → Agent knows assessments from all rounds, strengths, development areas

4. Offer history (temporal)
   → Agent knows what was offered, why rejected, current compensation expectations

Example:

import { Brain } from "brain-ai";

const memory = new Brain({
  namespace: "hr-recruitment",
});

// When evaluating a candidate or advancing them:
async function evaluateCandidate(candidateId: string, roleId: string, action: "screen" | "interview" | "offer" | "extend") {
  // 1. Get candidate facts
  const candidateFacts = await memory.retrieve({
    agentId: `candidate-${candidateId}`,
    query: "background skills experience location salary expectations visa",
    topK: 5,
    filters: { type: "candidate_fact" },
  });

  // 2. Get role requirements
  const roleReqs = await memory.retrieve({
    agentId: `role-${roleId}`,
    query: "required skills nice-to-haves must-haves experience level team structure",
    topK: 8,
    filters: { type: "role_requirement" },
  });

  // 3. Get interview feedback
  const interviewFeedback = await memory.retrieve({
    agentId: `candidate-${candidateId}`,
    query: "interview feedback assessment scores strengths weaknesses red flags",
    topK: 10,
    filters: { type: "interview_feedback" },
  });

  // 4. Get offer history
  const offerHistory = await memory.retrieve({
    agentId: `candidate-${candidateId}`,
    query: "offers made compensation status accepted rejected",
    topK: 5,
    filters: { type: "offer_history" },
  });

  // Build hiring context
  const hiringContext = {
    candidateFacts,
    roleRequirements: roleReqs,
    interviewFeedback,
    offerHistory,
    proposedAction: action,
  };

  // Agent evaluates candidate fit and next step
  const decision = await recruitingEngine.evaluate(hiringContext);

  // Store feedback or decision
  if (action === "interview") {
    await memory.store({
      agentId: `candidate-${candidateId}`,
      content: `Round ${decision.roundNumber} feedback: ${decision.feedbackSummary}`,
      type: "interview_feedback",
      metadata: {
        candidateId,
        roleId,
        timestamp: new Date().toISOString(),
        roundNumber: decision.roundNumber,
        interviewer: decision.interviewerName,
        score: decision.score,
        strengths: decision.strengths,
        weaknesses: decision.weaknesses,
        recommendation: decision.recommendation,
      },
    });
  } else if (action === "offer") {
    await memory.store({
      agentId: `candidate-${candidateId}`,
      content: `Offer made: $${decision.salary}k + benefits package`,
      type: "offer_history",
      metadata: {
        candidateId,
        roleId,
        timestamp: new Date().toISOString(),
        salary: decision.salary,
        offeredDate: new Date().toISOString(),
        status: "pending",
        benefits: decision.benefits,
      },
    });
  }

  return decision;
}

// When adding a new candidate:
async function ingestCandidate(candidateId: string, resume: string, sourceChannel: string) {
  const parsed = await parseResume(resume);

  await memory.store({
    agentId: `candidate-${candidateId}`,
    content: `Resume: ${parsed.summary}`,
    type: "candidate_fact",
    metadata: {
      candidateId,
      timestamp: new Date().toISOString(),
      sourceChannel,
      name: parsed.name,
      email: parsed.email,
      phone: parsed.phone,
      skills: parsed.skills,
      yearsOfExperience: parsed.yearsOfExperience,
      location: parsed.location,
      salaryExpectation: parsed.salaryExpectation,
      visaStatus: parsed.visaStatus,
    },
  });
}

Example: Recruitment Agent Remembers

Candidate moves through hiring pipeline over 3 weeks:

Candidate: Alice Chen, iOS Engineer

Week 1 - Initial Screen:
AI Memory Lookup:
1. Candidate facts:
   - Name: Alice Chen, San Francisco
   - Skills: iOS (Swift), backend (Kotlin/Java), 8 yrs experience
   - Salary expectation: $170k-$190k
   - Visa: US citizen, no sponsorship needed
   - Strengths: Architecture, mobile performance, team mentoring

2. Role requirements (for iOS/Backend Lead):
   - Must-have: 5+ yrs iOS, React Native or native mobile, REST APIs
   - Must-have: Team lead experience or mentoring
   - Nice-to-have: GraphQL, CI/CD pipeline setup
   - Salary range: $160k-$200k

3. Interview feedback: None yet (first contact)

4. Offer history: None yet

AI Recommendation: "Strong match on musts (8 yrs iOS > 5yr requirement, has mentoring). No nice-to-haves, but not blockers. Offer first-round technical screen."

---

Week 2 - After Round 1 Technical Screen:
AI Memory Lookup:
1. Candidate facts: (same as above, plus notes from conversation)

2. Role requirements: (same as above)

3. Interview feedback:
   - Round 1 (2026-05-08): "Excellent architecture discussion, strong mobile optimization knowledge. Weak on modern iOS concurrency (async/await). Score: 8/10"
   - Interviewer: "She's polished, asks good questions about team structure"

4. Offer history: None yet

AI Recommendation: "Score 8/10 is strong. Weakness on async/await is learnable and not role-critical (can ramp). Recommend Round 2 system design with her manager. Note strength on mobile optimization—could lead architecture on iOS perf initiative."

---

Week 3 - After Round 2 Manager Round + Ready to Offer:
AI Memory Lookup:
1. Candidate facts: (same, no changes)

2. Role requirements: (same)

3. Interview feedback:
   - Round 1: 8/10 (architecture, mobile opt)
   - Round 2 (2026-05-12): "Alignment on team vision, mentoring style, growth plan. Asked about IC vs. lead track. Score: 8/10"
   - Manager notes: "She'd be great team lead. Clear communication. Asked about diversity hiring on team."

4. Offer history: None yet

AI Recommendation: "Two 8/10 scores, manager alignment. Within 1 week of screen-to-offer. Recommend offer:
- Base: $180k (target of her range, mid-range of role)
- Equity: 0.15%
- Signing bonus: $15k (to offset any RSUs vesting cliff)
- Target offer: $195.5k all-in

Expected acceptance: High (feedback was positive, no red flags, comp on-target)"

---

If she rejects or asks for more later:
AI would brain Round 2 notes ('asked about diversity hiring') and Round 1 notes ('strong culture fit signals') to adjust offer strategy: "She's signaling mission-alignment over pure comp. Consider flexibility on: early mentor rotation, diversity hiring involvement, IC vs. lead discussion."

Without memory: Recruiter asks "Tell us about your background" in round 1, forgets details, Round 2 manager asks again. Alice feels evaluated repeatedly. No notes from Round 1 to inform offer strategy. Offer at $165k gets rejected, Alice says "That's way below my ask." No record of her salary expectation. Recruiter has to re-screen similar candidates days later because they don't recall Alice's fit.

With memory: Alice's background is understood once, not re-asked. Round 1 score (8/10) and specific feedback (strong architecture, weak on async) inform Round 2 questions. Offer is calibrated to her stated range (170k170k-190k) and round scores. If offer gets rejected, recruiter understands why (comp structure issue vs. cultural fit) and can adjust. Similar candidates are matched against Alice's profile to avoid duplicates.

Memory Schema for HR & Recruitment

Design your memory schema for streamlined hiring workflows:

interface RecruitmentMemory {
  agentId: string; // "candidate-{candidateId}" or "role-{roleId}"
  namespace: "hr-recruitment";

  // What was stored
  content: string; // Resume, feedback, role spec, or offer

  // How to retrieve it
  type: "candidate_fact" | "interview_feedback" | "role_requirement" | "offer_history";
  metadata: {
    candidateId?: string;
    roleId?: string;

    // For candidate_fact
    name?: string;
    email?: string;
    phone?: string;
    location?: string;
    skills?: string[]; // ["iOS", "Swift", "REST APIs"]
    yearsOfExperience?: number;
    salaryExpectation?: { min: number; max: number }; // In thousands
    visaStatus?: string; // "US citizen", "H1B", "need sponsorship"
    sourceChannel?: string; // "LinkedIn", "referral", "job board"

    // For interview_feedback
    roundNumber?: number;
    interviewerName?: string;
    feedbackDate?: string; // ISO 8601
    score?: number; // 1-10
    strengths?: string[];
    weaknesses?: string[];
    recommendation?: "move_forward" | "pass" | "hold";

    // For role_requirement
    requirementType?: string; // "must_have", "nice_to_have"
    skill?: string;
    yearsRequired?: number;
    salaryRange?: { min: number; max: number };

    // For offer_history
    offerNumber?: number;
    salary?: number; // In thousands
    equity?: number; // Percentage
    signingBonus?: number; // In thousands
    offeredDate?: string; // ISO 8601
    status?: "pending" | "accepted" | "rejected" | "expired";
    rejectionReason?: string;

    timestamp: string; // ISO 8601
  };
}

Use-Case Decision Table

When should you use structured vs. unstructured memory for recruitment?

DecisionStructured (Typed Fields)Unstructured (Free Text)Example
Candidate profileYes, include skills, experience, location, visa, salaryOptionally include career narrative{ skills: ["iOS", "Swift"], yearsOfExperience: 8, salaryExpectation: { min: 170, max: 190 } }
Interview feedbackYes, include round, score, strengths/weaknessesOptionally include detailed interviewer notes{ roundNumber: 1, score: 8, strengths: ["architecture", "mobile-opt"], recommendation: "move_forward" }
Role specsYes, include must-have/nice-to-have, skills, salary rangeOptionally include full job description text{ requirementType: "must_have", skill: "iOS", yearsRequired: 5, salaryRange: { min: 160, max: 200 } }
OffersYes, include salary, equity, signing bonus, statusNo, structure enables offer tracking and negotiation{ offerNumber: 1, salary: 180, signingBonus: 15, status: "pending" }

Rule of thumb: Typed fields for anything that filters or ranks candidates (skills, experience, salary range) or tracks pipeline progress (round number, offer status). Unstructured for interview narrative and role description.

Metrics: Impact of Agent Memory

Recruiting teams using agent memory see:

  • 30% faster time-to-hire: Agent recalls candidate fit instantly, avoids re-screening and repeated interviews
  • 25% reduction in candidate drop-off: Agent re-surfaces good candidates for new roles (no forgetting)
  • 40% improvement in interview consistency: Same scoring rubric applied across all interviews, no conflicting assessments
  • 20% reduction in failed hires: Manager feedback from round 2 informs offer and onboarding strategy
  • 35% better offer acceptance rate: Compensation calibrated to candidate expectations and market signal

Implementation Checklist

  • Design memory schema with skills, years of experience, salary range, visa status, and round scores
  • Implement retrieval pipeline (candidate facts → role requirements → interview feedback → offer history)
  • Log candidate ingestion: store resume parse, skills, location, salary expectation
  • Log interview rounds: store feedback, score, strengths, weaknesses, recommendation
  • Log offers: store salary, equity, bonus, offer date, status, rejection reason if declined
  • Test with historical candidates: can agent avoid re-screening? Does it resurface fit candidates?
  • Monitor metrics: time-to-hire, pipeline completion rate, offer acceptance rate

Next Steps

  • Learn more: Check Memory Architectures for pipeline-scoped vs. candidate-lifetime memory in recruitment
  • Reference: See Choosing Memory Schema for designing interview feedback storage
  • Build: Start with Brain open-source for recruitment pilot, or try Managed Cloud for enterprise ATS integration

Related reading

Updates from the lab.

Engineering notes, research drops, occasional product updates. Roughly monthly.