Skip to content
Learn Agentic AI13 min read0 views

Building an Internal Mobility Agent: Job Posting, Skill Matching, and Transfer Assistance

Create an AI agent that powers internal job boards, matches employees to open positions based on skill profiles, supports transfer applications, and facilitates transition planning between teams.

Why Internal Mobility Matters

Employees who see no growth path within their organization leave. Research shows that internal mobility increases retention by 2x, yet most companies have opaque internal job markets where opportunities are shared through informal networks rather than equitable systems. An AI internal mobility agent democratizes access to opportunities by matching employee skills to open positions, identifying development gaps, and facilitating the transfer process.

Employee Profile and Job Posting Models

The mobility agent works at the intersection of employee skill profiles and internal job postings. Both data models must be rich enough to support meaningful matching.

from dataclasses import dataclass, field
from datetime import date
from typing import Optional
from agents import Agent, Runner, function_tool
import json

@dataclass
class EmployeeProfile:
    employee_id: str
    name: str
    current_role: str
    current_department: str
    tenure_years: float
    skills: list[str]
    skill_levels: dict[str, str]  # skill -> "beginner"|"intermediate"|"expert"
    interests: list[str]
    career_goals: list[str]
    willing_to_relocate: bool = False
    manager_approved_mobility: bool = True

@dataclass
class InternalPosting:
    posting_id: str
    title: str
    department: str
    hiring_manager: str
    location: str
    required_skills: list[str]
    preferred_skills: list[str]
    min_tenure_months: int  # minimum company tenure to apply
    description: str
    status: str = "open"

EMPLOYEE_DB: dict[str, EmployeeProfile] = {}
INTERNAL_POSTINGS: dict[str, InternalPosting] = {}

Skill Matching Engine

The matching engine goes beyond simple keyword overlap. It considers skill levels, career interests, and development potential — not just current qualifications.

@function_tool
def find_internal_opportunities(employee_id: str) -> str:
    """Find internal job postings matching an employee's skills and interests."""
    emp = EMPLOYEE_DB.get(employee_id)
    if not emp:
        return json.dumps({"error": "Employee not found"})

    matches = []
    for posting in INTERNAL_POSTINGS.values():
        if posting.status != "open":
            continue
        if posting.department == emp.current_department:
            continue  # exclude same-department lateral moves by default
        if emp.tenure_years * 12 < posting.min_tenure_months:
            continue

        # Skill match scoring
        emp_skills_lower = {s.lower() for s in emp.skills}
        required_lower = {s.lower() for s in posting.required_skills}
        preferred_lower = {s.lower() for s in posting.preferred_skills}

        required_match = emp_skills_lower & required_lower
        preferred_match = emp_skills_lower & preferred_lower
        skill_gaps = required_lower - emp_skills_lower

        if not required_lower:
            skill_score = 0.5
        else:
            skill_score = len(required_match) / len(required_lower)

        # Interest alignment bonus
        interest_overlap = set(i.lower() for i in emp.interests) & {
            posting.department.lower(), posting.title.lower()
        }
        interest_bonus = 0.1 if interest_overlap else 0.0

        total_score = min(skill_score + interest_bonus, 1.0)

        if total_score >= 0.4:
            matches.append({
                "posting_id": posting.posting_id,
                "title": posting.title,
                "department": posting.department,
                "match_score": round(total_score * 100),
                "matched_skills": list(required_match | preferred_match),
                "skill_gaps": list(skill_gaps),
                "development_needed": len(skill_gaps) > 0,
            })

    matches.sort(key=lambda x: x["match_score"], reverse=True)
    return json.dumps(matches[:10])

Gap Analysis and Development Planning

When an employee is interested in a role but lacks some skills, the agent generates a development plan to bridge the gap.

See AI Voice Agents Handle Real Calls

Book a free demo or calculate how much you can save with AI voice automation.

LEARNING_CATALOG = {
    "python": {"course": "Python Mastery", "duration_weeks": 8, "format": "online"},
    "data analysis": {"course": "Data Analytics Bootcamp", "duration_weeks": 6, "format": "hybrid"},
    "project management": {"course": "PMP Preparation", "duration_weeks": 12, "format": "online"},
    "machine learning": {"course": "ML Fundamentals", "duration_weeks": 10, "format": "online"},
    "leadership": {"course": "Leadership Essentials", "duration_weeks": 4, "format": "workshop"},
}

@function_tool
def generate_development_plan(employee_id: str, posting_id: str) -> str:
    """Create a development plan to bridge skill gaps for a target role."""
    emp = EMPLOYEE_DB.get(employee_id)
    posting = INTERNAL_POSTINGS.get(posting_id)

    if not emp or not posting:
        return json.dumps({"error": "Employee or posting not found"})

    emp_skills_lower = {s.lower() for s in emp.skills}
    required_lower = {s.lower() for s in posting.required_skills}
    gaps = required_lower - emp_skills_lower

    if not gaps:
        return json.dumps({
            "message": "No skill gaps detected. You are ready to apply.",
            "recommendation": "Submit your application directly.",
        })

    plan_items = []
    total_weeks = 0
    for gap in gaps:
        course = LEARNING_CATALOG.get(gap)
        if course:
            plan_items.append({
                "skill": gap,
                "course": course["course"],
                "duration": f"{course['duration_weeks']} weeks",
                "format": course["format"],
            })
            total_weeks += course["duration_weeks"]
        else:
            plan_items.append({
                "skill": gap,
                "suggestion": "Seek mentorship or job shadowing opportunity",
                "duration": "Ongoing",
            })

    return json.dumps({
        "target_role": posting.title,
        "gaps_identified": len(gaps),
        "development_plan": plan_items,
        "estimated_timeline": f"{total_weeks} weeks to address all gaps",
        "next_step": "Discuss this plan with your manager for approval and time allocation.",
    })

Transfer Application Tool

@function_tool
def submit_transfer_application(
    employee_id: str,
    posting_id: str,
    motivation: str,
) -> str:
    """Submit an internal transfer application."""
    emp = EMPLOYEE_DB.get(employee_id)
    posting = INTERNAL_POSTINGS.get(posting_id)

    if not emp or not posting:
        return json.dumps({"error": "Employee or posting not found"})

    if not emp.manager_approved_mobility:
        return json.dumps({
            "status": "blocked",
            "reason": "Manager approval for internal mobility is required. "
                      "Please discuss with your manager first.",
        })

    return json.dumps({
        "status": "submitted",
        "application_id": f"INT-{employee_id[:4]}-{posting_id[:4]}",
        "current_role": emp.current_role,
        "target_role": posting.title,
        "hiring_manager_notified": posting.hiring_manager,
        "next_steps": "The hiring manager will review your application "
                      "and reach out to schedule a conversation.",
    })

mobility_agent = Agent(
    name="MobilityBot",
    instructions="""You are MobilityBot, an internal career mobility assistant.
Help employees discover internal opportunities that match their skills and goals.
When skill gaps exist, create actionable development plans rather than discouraging.
Maintain confidentiality — do not reveal who else has applied for a role.
Encourage employees to discuss mobility plans with their managers openly.""",
    tools=[find_internal_opportunities, generate_development_plan, submit_transfer_application],
)

FAQ

Should the agent notify the employee's current manager when they explore internal moves?

This is a design decision that depends on company culture. Some organizations require manager approval before applying, while others allow confidential exploration. A common middle ground is allowing browsing and gap analysis without notification, but requiring manager acknowledgment before a formal application is submitted.

How do you prevent skill inflation in employee profiles?

Pair self-reported skills with evidence: certifications, project contributions (from version control or project management tools), and peer endorsements. The agent can cross-reference claimed skills with actual project history to flag discrepancies.

What about lateral moves within the same department?

The default configuration excludes same-department postings to focus on cross-functional mobility. However, the filter is configurable. Some roles — like moving from individual contributor to team lead within engineering — are valid lateral moves that the agent should surface when the employee's career goals include leadership.


#InternalMobility #SkillMatching #CareerDevelopment #TalentRetention #AgenticAI #LearnAI #AIEngineering

Share this article
C

CallSphere Team

Expert insights on AI voice agents and customer communication automation.

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.