Skip to content
Learn Agentic AI
Learn Agentic AI11 min read1 views

Building a Move-In/Move-Out Agent: Coordinating Transitions with AI

Build an AI agent that automates the move-in and move-out process, including checklist management, utility coordination, key tracking, and security deposit processing.

The Move-In/Move-Out Coordination Problem

A single unit turnover involves 15-20 discrete tasks: collecting keys, inspecting the unit, processing deposits, coordinating cleaning, transferring utilities, and communicating with both the departing and arriving tenant. Property managers juggle multiple turnovers simultaneously, and missed steps lead to delays, disputes, and lost revenue. An AI agent orchestrates this entire workflow.

Modeling the Transition Process

We define the turnover as a state machine with clear phases and dependencies.

flowchart TD
    START["Building a Move-In/Move-Out Agent: Coordinating T…"] --> A
    A["The Move-In/Move-Out Coordination Probl…"]
    A --> B
    B["Modeling the Transition Process"]
    B --> C
    C["Generating the Task Checklist"]
    C --> D
    D["Security Deposit Processing"]
    D --> E
    E["The Transition Agent"]
    E --> F
    F["FAQ"]
    F --> DONE["Key Takeaways"]
    style START fill:#4f46e5,stroke:#4338ca,color:#fff
    style DONE fill:#059669,stroke:#047857,color:#fff
from dataclasses import dataclass, field
from datetime import date, timedelta
from enum import Enum
from typing import Optional

class TransitionPhase(Enum):
    NOTICE_RECEIVED = "notice_received"
    PRE_MOVEOUT = "pre_moveout"
    MOVEOUT_DAY = "moveout_day"
    UNIT_TURNOVER = "unit_turnover"
    PRE_MOVEIN = "pre_movein"
    MOVEIN_DAY = "movein_day"
    COMPLETED = "completed"

@dataclass
class TransitionTask:
    task_id: str
    name: str
    phase: TransitionPhase
    assigned_to: str  # tenant, manager, vendor
    due_date: date
    completed: bool = False
    depends_on: list[str] = field(default_factory=list)
    notes: str = ""

@dataclass
class UnitTransition:
    transition_id: str
    unit: str
    departing_tenant: Optional[str]
    arriving_tenant: Optional[str]
    moveout_date: date
    movein_date: date
    current_phase: TransitionPhase
    tasks: list[TransitionTask] = field(default_factory=list)

Generating the Task Checklist

Each transition gets a customized checklist based on the situation.

def generate_transition_tasks(
    transition: UnitTransition,
) -> list[TransitionTask]:
    """Generate all tasks for a unit transition."""
    tasks = []
    mo = transition.moveout_date
    mi = transition.movein_date

    # Pre move-out tasks (assigned to departing tenant)
    if transition.departing_tenant:
        tasks.extend([
            TransitionTask(
                task_id="mo_01", name="Submit forwarding address",
                phase=TransitionPhase.PRE_MOVEOUT,
                assigned_to="tenant", due_date=mo - timedelta(days=14),
            ),
            TransitionTask(
                task_id="mo_02", name="Schedule utility disconnection",
                phase=TransitionPhase.PRE_MOVEOUT,
                assigned_to="tenant", due_date=mo - timedelta(days=7),
            ),
            TransitionTask(
                task_id="mo_03", name="Return all keys and access devices",
                phase=TransitionPhase.MOVEOUT_DAY,
                assigned_to="tenant", due_date=mo,
            ),
            TransitionTask(
                task_id="mo_04", name="Move-out inspection",
                phase=TransitionPhase.MOVEOUT_DAY,
                assigned_to="manager", due_date=mo,
                depends_on=["mo_03"],
            ),
            TransitionTask(
                task_id="mo_05", name="Process security deposit",
                phase=TransitionPhase.MOVEOUT_DAY,
                assigned_to="manager", due_date=mo + timedelta(days=21),
                depends_on=["mo_04"],
            ),
        ])

    # Unit turnover tasks
    tasks.extend([
        TransitionTask(
            task_id="to_01", name="Professional cleaning",
            phase=TransitionPhase.UNIT_TURNOVER,
            assigned_to="vendor", due_date=mo + timedelta(days=2),
            depends_on=["mo_04"] if transition.departing_tenant else [],
        ),
        TransitionTask(
            task_id="to_02", name="Maintenance repairs",
            phase=TransitionPhase.UNIT_TURNOVER,
            assigned_to="vendor", due_date=mo + timedelta(days=5),
            depends_on=["mo_04"] if transition.departing_tenant else [],
        ),
        TransitionTask(
            task_id="to_03", name="Paint touch-up",
            phase=TransitionPhase.UNIT_TURNOVER,
            assigned_to="vendor", due_date=mi - timedelta(days=5),
            depends_on=["to_01"],
        ),
    ])

    # Pre move-in tasks
    if transition.arriving_tenant:
        tasks.extend([
            TransitionTask(
                task_id="mi_01", name="Move-in inspection",
                phase=TransitionPhase.PRE_MOVEIN,
                assigned_to="manager", due_date=mi - timedelta(days=1),
                depends_on=["to_03"],
            ),
            TransitionTask(
                task_id="mi_02", name="Prepare key packets",
                phase=TransitionPhase.PRE_MOVEIN,
                assigned_to="manager", due_date=mi - timedelta(days=1),
            ),
            TransitionTask(
                task_id="mi_03", name="Key handoff and welcome",
                phase=TransitionPhase.MOVEIN_DAY,
                assigned_to="manager", due_date=mi,
                depends_on=["mi_01", "mi_02"],
            ),
        ])
    return tasks

Security Deposit Processing

The deposit tool compares inspection reports and calculates deductions.

See AI Voice Agents Handle Real Calls

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

@dataclass
class DepositDeduction:
    item: str
    amount: float
    reason: str

def process_security_deposit(
    deposit_amount: float,
    inspection_damages: list[dict],
    normal_wear_items: list[str],
) -> dict:
    """Calculate security deposit return after deductions."""
    deductions = []
    for damage in inspection_damages:
        if damage["area"] not in normal_wear_items:
            deductions.append(DepositDeduction(
                item=damage["area"],
                amount=damage["repair_cost"],
                reason=damage["description"],
            ))

    total_deductions = sum(d.amount for d in deductions)
    refund = max(0, deposit_amount - total_deductions)

    return {
        "original_deposit": deposit_amount,
        "deductions": [
            {"item": d.item, "amount": d.amount, "reason": d.reason}
            for d in deductions
        ],
        "total_deductions": total_deductions,
        "refund_amount": refund,
    }

The Transition Agent

from agents import Agent, function_tool

@function_tool
async def get_transition_status(unit: str) -> str:
    """Get the current status of a unit transition."""
    return (
        f"Unit {unit} transition status: UNIT_TURNOVER phase\n"
        f"Completed: 5/12 tasks\n"
        f"Next due: Professional cleaning (tomorrow, assigned to CleanCo)\n"
        f"Blockers: None"
    )

@function_tool
async def complete_task(transition_id: str, task_id: str) -> str:
    """Mark a transition task as completed."""
    return f"Task {task_id} marked complete. Dependent tasks are now unblocked."

@function_tool
async def send_tenant_reminder(
    tenant_id: str,
    message_type: str,
) -> str:
    """Send a reminder to a tenant about upcoming transition tasks."""
    templates = {
        "key_return": "Reminder: Please return all keys to the office by your move-out date.",
        "utility_transfer": "Reminder: Schedule your utility disconnection at least 7 days before move-out.",
        "forwarding_address": "Please submit your forwarding address for deposit return.",
    }
    msg = templates.get(message_type, "Please contact the office for details.")
    return f"Reminder sent to tenant: {msg}"

@function_tool
async def calculate_deposit_return(
    unit: str,
    deposit_amount: float,
) -> str:
    """Calculate and generate the security deposit return statement."""
    result = process_security_deposit(
        deposit_amount=deposit_amount,
        inspection_damages=[
            {"area": "Kitchen faucet", "repair_cost": 150, "description": "Handle broken"},
        ],
        normal_wear_items=["carpet wear", "paint fading"],
    )
    return (
        f"Deposit: ${result['original_deposit']:,.2f}\n"
        f"Deductions: ${result['total_deductions']:,.2f}\n"
        f"Refund: ${result['refund_amount']:,.2f}"
    )

transition_agent = Agent(
    name="MoveInMoveOutAgent",
    instructions="""You are a unit transition coordinator.
    Track move-in/move-out tasks, send reminders, coordinate
    vendors, and process security deposits. Always ensure
    deposit returns comply with state timelines.""",
    tools=[
        get_transition_status, complete_task,
        send_tenant_reminder, calculate_deposit_return,
    ],
)

FAQ

How does the agent handle overlapping move-out and move-in dates?

The task dependency system prevents move-in tasks from starting before move-out tasks complete. If the timeline is too tight (e.g., same-day turnover), the agent flags it and recommends extending the gap or scheduling express cleaning services.

What happens if a vendor misses their scheduled task?

The agent monitors task completion deadlines. When a vendor task passes its due date without being marked complete, it sends an alert to the property manager and suggests rebooking with a backup vendor. Dependent tasks are automatically rescheduled.

How are security deposit disputes handled?

The agent generates an itemized deduction statement with photo evidence from inspections. If a tenant disputes a charge, the agent pulls the move-in and move-out inspection photos for that specific item, providing objective comparison. Final dispute resolution still involves human judgment.


#MoveInMoveOut #PropertyManagement #WorkflowAutomation #Python #AgenticAI #LearnAI #AIEngineering

Share
C

Written by

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.

Related Articles You May Like

Case Studies

AI Voice Agents for Real Estate & Property Management

See how property management companies use AI voice agents to handle tenant inquiries, maintenance requests, and leasing calls around the clock.

AI Interview Prep

7 AI Coding Interview Questions From Anthropic, Meta & OpenAI (2026 Edition)

Real AI coding interview questions from Anthropic, Meta, and OpenAI in 2026. Includes implementing attention from scratch, Anthropic's progressive coding screens, Meta's AI-assisted round, and vector search — with solution approaches.

Learn Agentic AI

Fine-Tuning LLMs for Agentic Tasks: When and How to Customize Foundation Models

When fine-tuning beats prompting for AI agents: dataset creation from agent traces, SFT and DPO training approaches, evaluation methodology, and cost-benefit analysis for agentic fine-tuning.

AI Interview Prep

7 Agentic AI & Multi-Agent System Interview Questions for 2026

Real agentic AI and multi-agent system interview questions from Anthropic, OpenAI, and Microsoft in 2026. Covers agent design patterns, memory systems, safety, orchestration frameworks, tool calling, and evaluation.

Learn Agentic AI

Building a Multi-Agent Data Pipeline: Ingestion, Transformation, and Analysis Agents

Build a three-agent data pipeline with ingestion, transformation, and analysis agents that process data from APIs, CSVs, and databases using Python.

Learn Agentic AI

Adaptive Thinking in Claude 4.6: How AI Agents Decide When and How Much to Reason

Technical exploration of adaptive thinking in Claude 4.6 — how the model dynamically adjusts reasoning depth, its impact on agent architectures, and practical implementation patterns.