Spaces:
Sleeping
title: FlexTime AI
emoji: ๐
colorFrom: green
colorTo: blue
sdk: docker
app_port: 7860
tags:
- openenv
- scheduling
- reinforcement-learning
- workforce
- optimization
- real-world
pinned: false
license: mit
short_description: Real-world AI workforce scheduling OpenEnv environment.
๐ FlexTime โ AI Workforce Scheduling Environment
FlexTime is a fully-featured OpenEnv-compliant environment where AI agents learn to solve the real-world workforce scheduling problem: assigning employees to shifts while satisfying hard operational constraints (skill matching, availability, maximum hours) and optimizing soft objectives (fairness, employee preferences, demand coverage).
This is a problem that operations managers in retail, healthcare, logistics, and hospitality face every week โ affecting millions of workers worldwide. FlexTime models it faithfully, making it both a genuine benchmark and a practical tool for developing AI scheduling assistants.
๐บ Environment Overview
| Property | Value |
|---|---|
| Domain | Workforce Scheduling / Operations Research |
| Tasks | 3 (Easy โ Medium โ Hard) |
| Episode horizon | 20 / 60 / 120 steps |
| Reward | Dense, shaped (โ1.0 to +1.0) |
| Action space | Discrete: assign, remove, swap, noop |
| Observation | Structured JSON: employees, shifts, assignments, metrics |
| Constraints | 4 hard (H1โH4) + 4 soft (S1โS4) |
| Baseline agent | Greedy (rule-based) + LLM (OpenAI API) |
โก Quick Start
Docker
git clone https://huggingface.co/spaces/your-org/flextime
cd flextime
docker build -t flextime .
docker run -p 7860:7860 flextime
# Environment live at http://localhost:7860
Local (no Docker)
pip install -r requirements.txt
uvicorn app.main:app --host 0.0.0.0 --port 7860 --reload
Run Baseline
# Rule-based greedy baseline (no API key needed)
python -m scripts.baseline
# LLM-based baseline (requires OpenAI key)
export OPENAI_API_KEY=sk-...
python -m scripts.baseline --llm --model gpt-4o-mini
# Single task
python -m scripts.baseline --task task_hard
๐ Task Definitions
Task 1 โ Basic Shift Coverage (Easy)
Target score: 1.0
Assign employees to 5 open morning shifts for a single day. All employees are available; skills match every shift. The agent needs to fill all slots without creating overlaps.
Employees: 5 Shifts: 5 Max steps: 20
Constraints: H1 (no overlap), H2 (skill match)
Task 2 โ Weekly Schedule with Constraints (Medium)
Target score: 0.85
Build a complete weekly schedule for 8 employees across 30 shifts. Must respect:
- Skill requirements (cashier, supervisor, inventory)
- Availability windows per employee per day
- 40-hour maximum working week per employee
- Soft fairness distribution (ยฑ4h between min/max hours)
Employees: 8 Shifts: 30 Max steps: 60
Constraints: H1โH4 (hard) + S1โS2 (soft)
Task 3 โ Fair Optimization Under Pressure (Hard)
Target score: 0.75 on ALL sub-metrics simultaneously
The hardest task: 12 employees, 50 shifts, 3 pre-seeded conflicts to resolve.
The grader applies a joint threshold โ all of coverage, fairness, constraint satisfaction,
and demand satisfaction must exceed 0.75 simultaneously. Missing any single threshold
triggers a penalty: score = min(sub_scores) ร 0.9.
Employees: 12 Shifts: 50 Max steps: 120
Constraints: H1โH4 + S1โS4 (all 8 active)
Pre-seeded conflicts: 3
Skills: 5 (cashier, supervisor, inventory, customer_service, technician)
๐ API Reference
All endpoints are OpenEnv-compliant. Interactive docs at /docs.
Core Endpoints
POST /reset
Initialize or reset the environment for a given task.
// Request
{ "task_id": "task_medium", "seed": 42 }
// Response: Observation
{
"week_id": "week-a3f912",
"task_id": "task_medium",
"employees": [...],
"shifts": [...],
"assignments": [],
"unassigned_shifts": ["shf001", "shf002", ...],
"conflicts": [],
"metrics": {
"total_shifts": 30,
"assigned_shifts": 0,
"coverage_rate": 0.0,
"hard_violations": 0,
"fairness_delta": 0.0,
"fairness_score": 1.0,
"demand_satisfaction": 0.0,
"preference_satisfaction": 0.0
},
"done": false,
"step_count": 0,
"max_steps": 60
}
POST /step
Apply an action. Returns new observation, shaped reward, done flag, and info dict.
// Request โ Action
{
"action_type": "assign", // assign | remove | swap | noop
"employee_id": "emp001", // required for assign/remove/swap
"shift_id": "shf042", // required for assign/remove
"target_employee_id": "emp005" // required for swap only
}
// Response: StepResult
{
"observation": { ... }, // full Observation
"reward": {
"total": 0.15,
"components": {
"shift_covered": 0.225,
"demand_signal": 0.043,
"constraint_violated": 0.0,
"fairness": 0.02
},
"info": { "result": "action=assign, ฮcoverage=+0.033, hard_violations=0" }
},
"done": false,
"info": { "step": "1", "coverage": "0.033" }
}
GET /state
Returns current observation without changing state.
GET /tasks
Returns all tasks with descriptions, schemas, and expected difficulty.
GET /grader
Returns normalized score (0.0โ1.0) for the current episode.
{
"task_id": "task_medium",
"score": 0.842,
"breakdown": {
"coverage_score": 0.9333,
"fairness_score": 0.875,
"constraint_score": 0.9,
"demand_score": 0.891,
"preference_score": 0.723
},
"passed": true,
"summary": "Score 0.8420 (PASS) โ Coverage 93.3%, Fairness 87.5%, Hard violations: 1"
}
POST /baseline
Runs the baseline agent against all 3 tasks and returns reproducible scores.
// Query param: ?use_llm=true (requires OPENAI_API_KEY)
{
"model": "GreedyBaseline",
"results": [...],
"mean_score": 0.717,
"timestamp": "2026-03-30T12:00:00Z"
}
๐ฆ Observation Space
class Observation(BaseModel):
week_id: str # Unique episode identifier
task_id: str # Which task is active
employees: List[Employee] # Full roster with skills, availability, hours
shifts: List[Shift] # All shifts with day, period, skill, demand
assignments: List[Dict] # Active assignments [{employee_id, shift_id, hours}]
unassigned_shifts: List[str] # Shift IDs still needing coverage
conflicts: List[ConstraintViolation] # Active violations with type and severity
metrics: ScheduleMetrics # Computed KPIs
done: bool # Episode termination flag
step_count: int
max_steps: int
๐ฎ Action Space
class Action(BaseModel):
action_type: Literal["assign", "remove", "swap", "noop"]
employee_id: Optional[str] # emp001 ... emp012
shift_id: Optional[str] # shf001 ... shf050
target_employee_id: Optional[str] # For swap only
๐ Reward Function
Dense shaped reward at every step. Range: [โ1.0, +1.0]
| Component | Signal | Value |
|---|---|---|
shift_covered |
New shift filled | +0.15 ร ฮcoverage |
demand_signal |
Demand-weighted coverage | +0.05 ร ฮdemand |
constraint_violated |
New hard violation | โ0.20 per violation |
constraint_resolved |
Violation removed | +0.10 per resolution |
fairness |
Fairness score improved | +0.03 ร ฮfairness |
conflict_resolved |
Pre-seeded conflict fixed | +0.10 bonus |
invalid_action |
Non-existent IDs, etc. | โ0.05 |
noop |
No-operation | 0.0 |
๐ Constraint System
Hard Constraints (must not be violated)
| ID | Name | Description |
|---|---|---|
| H1 | No Overlapping Shifts | One shift per employee per (day, period) |
| H2 | SkillโRole Match | Employee skills โ shift required_skill |
| H3 | Max Hours | ฮฃ hours โค max_hours_per_week (typically 40h) |
| H4 | Availability | Employee must be available on shift day |
Soft Constraints (penalized in objective)
| ID | Name | Description |
|---|---|---|
| S1 | Fair Distribution | max(hours) โ min(hours) โค 4h |
| S2 | Preference Matching | Assign preferred shift period when possible |
| S3 | Min 11h Rest Gap | โฅ 11h between consecutive shifts |
| S4 | Consecutive Days | โค 5 consecutive working days |
๐ Baseline Scores
Reproducible scores with seed=42, GreedyBaseline agent:
| Task | Score | Steps | Pass? |
|---|---|---|---|
| task_easy | ~0.95 | ~5 | โ |
| task_medium | ~0.72 | ~28 | โ (target 0.85) |
| task_hard | ~0.48 | ~52 | โ (target 0.75) |
| Mean | ~0.72 |
The gap between greedy (0.72) and target (0.85) on medium/hard provides excellent
learning signal for RL agents.
๐งช Testing
pip install pytest pytest-asyncio httpx
# Run all tests
pytest tests/ -v
# Run specific test class
pytest tests/test_environment.py::TestSpecCompliance -v
# Run with coverage
pytest tests/ --cov=app --cov-report=html
๐ Project Structure
flextime/
โโโ app/
โ โโโ __init__.py
โ โโโ main.py # FastAPI application, all endpoints
โ โโโ engine.py # Core environment: state, step, reward, grader
โ โโโ models.py # Pydantic typed models (Observation, Action, Reward)
โ โโโ static/
โ โโโ index.html # Interactive demo UI
โโโ scripts/
โ โโโ __init__.py
โ โโโ baseline.py # Greedy + LLM baseline agents & CLI
โโโ tests/
โ โโโ test_environment.py # Full pytest test suite
โโโ openenv.yaml # OpenEnv spec metadata
โโโ requirements.txt
โโโ Dockerfile
โโโ README.md
๐ค HuggingFace Spaces Deployment
- Create a new HF Space: Docker template, tagged
openenv - Push this repo to the Space
- The Space will auto-build and serve at
https://huggingface.co/spaces/your-org/flextime
The HF Space will automatically:
- Build the Docker image
- Expose port 7860
- Respond to
reset()pings for OpenEnv validation
๐ง Why Workforce Scheduling?
Workforce scheduling is a genuine, high-value operations problem:
- Scale: Affects billions of shift workers globally (retail, healthcare, logistics)
- Complexity: Multi-constraint combinatorial optimization (NP-hard in general)
- Real cost: Poor scheduling โ $millions in overtime, turnover, and burnout
- AI gap: Existing tools are rule-based; LLM/RL agents could outperform dramatically
- Fairness stakes: Biased scheduling has real worker welfare consequences
FlexTime provides the first OpenEnv environment in this domain, enabling the community to benchmark and train agents on a problem with immediate real-world deployment value.
๐ License
MIT ยฉ FlexTime Team