Skip to content

Implement Auto-Pause for Inactive Coaching Sessions #157

@mottych

Description

@mottych

Overview

Implement a background job that automatically pauses coaching sessions after a period of inactivity. This prevents abandoned sessions from blocking other users and ensures clean session lifecycle management.

Background

The coaching session infrastructure already supports:

  • inactivity_timeout_minutes configuration per topic (default: 30 minutes)
  • Session status transitions including activepaused
  • updated_at timestamp tracking on each session

What's missing is the scheduled job that checks for and auto-pauses inactive sessions.

Requirements

1. Scheduled Job Implementation

Create a Lambda function or background task that:

  1. Runs on a schedule (every 5 minutes recommended)
  2. Queries for all active sessions where:
    NOW() - updated_at > inactivity_timeout_minutes
    
  3. Transitions matching sessions to paused status
  4. Logs the auto-pause action for observability

2. Query Strategy

Option A: DynamoDB Scan with Filter (Simple but less efficient)

# Scan all active sessions, filter by updated_at
response = table.scan(
    FilterExpression="session_status = :active AND updated_at < :threshold",
    ExpressionAttributeValues={
        ":active": "active",
        ":threshold": (datetime.now(UTC) - timedelta(minutes=30)).isoformat()
    }
)

Option B: GSI on status + updated_at (More efficient, requires GSI)

  • Create GSI: status-updated_at-index
  • PK: session_status
  • SK: updated_at
  • Query active sessions with updated_at < threshold

3. Auto-Pause Logic

async def auto_pause_inactive_sessions() -> int:
    """Auto-pause sessions inactive beyond their timeout threshold.
    
    Returns:
        Number of sessions auto-paused
    """
    from coaching.src.core.coaching_topic_registry import COACHING_TOPIC_REGISTRY
    
    paused_count = 0
    
    # Get all active sessions
    active_sessions = await repository.get_active_sessions()
    
    for session in active_sessions:
        # Get topic-specific timeout (default 30 min)
        topic = COACHING_TOPIC_REGISTRY.get(session.topic_id)
        timeout_minutes = topic.inactivity_timeout_minutes if topic else 30
        
        # Check if session exceeds timeout
        threshold = datetime.now(UTC) - timedelta(minutes=timeout_minutes)
        if session.updated_at < threshold:
            session.pause()
            await repository.update(session)
            logger.info(
                "session.auto_paused",
                session_id=session.session_id,
                topic_id=session.topic_id,
                inactive_minutes=(datetime.now(UTC) - session.updated_at).total_seconds() / 60
            )
            paused_count += 1
    
    return paused_count

4. Lambda Handler (if using AWS Lambda)

# coaching/src/lambdas/auto_pause_sessions.py

import structlog
from coaching.src.services.coaching_session_service import CoachingSessionService

logger = structlog.get_logger()

def handler(event, context):
    """Lambda handler for auto-pausing inactive sessions."""
    service = CoachingSessionService(...)
    
    try:
        paused_count = service.auto_pause_inactive_sessions()
        logger.info("auto_pause.completed", paused_count=paused_count)
        return {
            "statusCode": 200,
            "body": {"paused_sessions": paused_count}
        }
    except Exception as e:
        logger.error("auto_pause.failed", error=str(e))
        raise

5. Infrastructure (Pulumi)

# Add to coaching/pulumi/__main__.py

auto_pause_lambda = aws.lambda_.Function(
    "auto-pause-sessions",
    runtime="python3.11",
    handler="coaching.src.lambdas.auto_pause_sessions.handler",
    role=lambda_role.arn,
    timeout=60,
    memory_size=256,
    environment={
        "variables": {
            "DYNAMODB_TABLE": sessions_table.name,
            "ENVIRONMENT": environment,
        }
    }
)

# CloudWatch Events Rule - Run every 5 minutes
schedule_rule = aws.cloudwatch.EventRule(
    "auto-pause-schedule",
    schedule_expression="rate(5 minutes)",
)

aws.cloudwatch.EventTarget(
    "auto-pause-target",
    rule=schedule_rule.name,
    arn=auto_pause_lambda.arn,
)

# Permission for CloudWatch to invoke Lambda
aws.lambda_.Permission(
    "auto-pause-cloudwatch-permission",
    action="lambda:InvokeFunction",
    function=auto_pause_lambda.name,
    principal="events.amazonaws.com",
    source_arn=schedule_rule.arn,
)

Files to Create/Modify

Create

  • coaching/src/lambdas/auto_pause_sessions.py - Lambda handler
  • coaching/tests/unit/lambdas/test_auto_pause_sessions.py - Unit tests

Modify

  • coaching/src/services/coaching_session_service.py - Add auto_pause_inactive_sessions() method
  • coaching/src/infrastructure/repositories/dynamodb_coaching_session_repository.py - Add get_active_sessions() method
  • coaching/pulumi/__main__.py - Add Lambda and CloudWatch infrastructure

Acceptance Criteria

  • auto_pause_inactive_sessions() method implemented in service
  • Repository method to query active sessions
  • Lambda handler created
  • Pulumi infrastructure for scheduled execution
  • Unit tests for auto-pause logic
  • Integration test verifying session state transition
  • Structured logging for observability
  • Sessions are paused based on topic-specific inactivity_timeout_minutes

Testing

async def test_auto_pause_inactive_sessions():
    """Test that inactive sessions are auto-paused."""
    # Create a session with old updated_at
    session = create_test_session(
        status="active",
        updated_at=datetime.now(UTC) - timedelta(minutes=45)  # 45 min ago
    )
    await repository.create(session)
    
    # Run auto-pause
    paused_count = await service.auto_pause_inactive_sessions()
    
    # Verify
    assert paused_count == 1
    updated = await repository.get(session.session_id)
    assert updated.status == "paused"

Related

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions