Spaces:
Runtime error
Runtime error
Upload folder using huggingface_hub
Browse files- Dockerfile +18 -0
- README.md +153 -6
- __init__.py +5 -0
- client.py +42 -0
- docker-compose.yaml +173 -0
- landing.py +152 -0
- models.py +52 -0
- openenv.yaml +11 -0
- pyproject.toml +28 -0
- server/Dockerfile +36 -0
- server/__init__.py +0 -0
- server/app.py +31 -0
- server/environment.py +136 -0
- server/requirements.txt +4 -0
- simlab_hr.egg-info/PKG-INFO +143 -0
- simlab_hr.egg-info/SOURCES.txt +14 -0
- simlab_hr.egg-info/dependency_links.txt +1 -0
- simlab_hr.egg-info/requires.txt +3 -0
- simlab_hr.egg-info/top_level.txt +1 -0
- tasks.py +168 -0
Dockerfile
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.13-slim
|
| 2 |
+
|
| 3 |
+
RUN useradd -m -u 1000 user
|
| 4 |
+
USER user
|
| 5 |
+
ENV PATH="/home/user/.local/bin:$PATH"
|
| 6 |
+
|
| 7 |
+
WORKDIR /app
|
| 8 |
+
|
| 9 |
+
COPY --chown=user . /app
|
| 10 |
+
RUN pip install --no-cache-dir --upgrade -e .
|
| 11 |
+
|
| 12 |
+
ENV PYTHONPATH="/app:$PYTHONPATH"
|
| 13 |
+
ENV HRMS_TOOL_SERVER_URL="http://localhost:8030"
|
| 14 |
+
ENV EMAIL_TOOL_SERVER_URL="http://localhost:8040"
|
| 15 |
+
ENV CALENDAR_TOOL_SERVER_URL="http://localhost:8050"
|
| 16 |
+
ENV ROCKETCHAT_TOOL_SERVER_URL="http://localhost:8060"
|
| 17 |
+
|
| 18 |
+
CMD ["uvicorn", "simlab_hr.server.app:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
CHANGED
|
@@ -1,10 +1,157 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: docker
|
| 7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: SimLab HR — AI Recruiting & People Management Agent Environment
|
| 3 |
+
emoji: 👔
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: green
|
| 6 |
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
+
short_description: "AI HR agent environment — HRMS, email, calendar, chat"
|
| 9 |
+
tags:
|
| 10 |
+
- openenv
|
| 11 |
+
- hr
|
| 12 |
+
- human-resources
|
| 13 |
+
- recruiting
|
| 14 |
+
- hrms
|
| 15 |
+
- agent-evaluation
|
| 16 |
+
- simlab
|
| 17 |
+
- reinforcement-learning
|
| 18 |
+
- rl-environment
|
| 19 |
+
- ai-agent
|
| 20 |
+
- tool-use
|
| 21 |
+
- enterprise
|
| 22 |
+
- multi-tool
|
| 23 |
+
- gymnasium
|
| 24 |
+
- collinear
|
| 25 |
+
pinned: true
|
| 26 |
+
license: apache-2.0
|
| 27 |
---
|
| 28 |
|
| 29 |
+
# SimLab HR — AI Recruiting & People Management Agent Environment
|
| 30 |
+
|
| 31 |
+
A fully-functional HR simulation for training, evaluating, and benchmarking AI recruiting and people management agents. Built on [OpenEnv](https://github.com/meta-pytorch/OpenEnv) and powered by [SimLab](https://github.com/collinear-ai/simlab).
|
| 32 |
+
|
| 33 |
+
Your agent gets a task ("schedule a phone screen", "approve a leave request", "onboard a new hire") and a real workplace with an HRMS, email, calendar, and team chat. Can it get the job done?
|
| 34 |
+
|
| 35 |
+
## 4 Tool Servers, 1 Environment
|
| 36 |
+
|
| 37 |
+
| Server | Port | What it does |
|
| 38 |
+
|---|---|---|
|
| 39 |
+
| **HRMS** (Frappe) | 8030 | Employee records, leave management, attendance, payroll |
|
| 40 |
+
| **Email** (MailHog) | 8040 | Send and read emails, inbox management |
|
| 41 |
+
| **Calendar** (Baikal/Chronos) | 8050 | Schedule meetings, check availability, manage events |
|
| 42 |
+
| **RocketChat** | 8060 | Team messaging, channels, direct messages |
|
| 43 |
+
|
| 44 |
+
Agents must reason across all four systems to complete real HR workflows — just like a human would.
|
| 45 |
+
|
| 46 |
+
## Quickstart
|
| 47 |
+
|
| 48 |
+
```python
|
| 49 |
+
from simlab_hr import HRAction
|
| 50 |
+
from simlab_hr.client import HREnv
|
| 51 |
+
|
| 52 |
+
client = HREnv(base_url="http://localhost:8000")
|
| 53 |
+
|
| 54 |
+
with client:
|
| 55 |
+
obs = client.reset()
|
| 56 |
+
print(obs.observation.task_instruction)
|
| 57 |
+
print(obs.observation.tools_available) # {'hrms': [...], 'email': [...], ...}
|
| 58 |
+
|
| 59 |
+
# Check leave balance in HRMS
|
| 60 |
+
result = client.step(HRAction(
|
| 61 |
+
tool_server="hrms",
|
| 62 |
+
tool_name="get_leave_balance",
|
| 63 |
+
parameters={"employee_id": "EMP-0042"}
|
| 64 |
+
))
|
| 65 |
+
|
| 66 |
+
# Send an email notification
|
| 67 |
+
result = client.step(HRAction(
|
| 68 |
+
tool_server="email",
|
| 69 |
+
tool_name="send_email",
|
| 70 |
+
parameters={"to": "manager@company.com", "subject": "Leave approved", "body": "..."}
|
| 71 |
+
))
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
## What's Inside
|
| 75 |
+
|
| 76 |
+
**8 sample tasks** covering real HR workflows:
|
| 77 |
+
|
| 78 |
+
| Difficulty | Example |
|
| 79 |
+
|---|---|
|
| 80 |
+
| Easy | Approve a leave request, update an employee's designation |
|
| 81 |
+
| Medium | Schedule a phone screen + send confirmation, run an attendance report |
|
| 82 |
+
| Hard | Multi-person panel interview scheduling, full new-hire onboarding flow |
|
| 83 |
+
|
| 84 |
+
Every task requires the agent to coordinate across multiple tool servers — this is what makes it hard.
|
| 85 |
+
|
| 86 |
+
## Run Locally
|
| 87 |
+
|
| 88 |
+
```bash
|
| 89 |
+
git clone https://github.com/collinear-ai/simlab.git
|
| 90 |
+
cd simlab/envs/simlab_hr
|
| 91 |
+
|
| 92 |
+
# Start all services (HRMS, Email, Calendar, RocketChat, OpenEnv wrapper)
|
| 93 |
+
docker compose up
|
| 94 |
+
|
| 95 |
+
# First run pulls ~10 images and takes a few minutes for HRMS to initialize
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
Or run from Hugging Face:
|
| 99 |
+
|
| 100 |
+
```python
|
| 101 |
+
from simlab_hr.client import HREnv
|
| 102 |
+
|
| 103 |
+
client = HREnv.from_hub("collinear/simlab-hr")
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
## Unlock 14+ Tasks from the API
|
| 107 |
+
|
| 108 |
+
This environment ships with 8 sample tasks. Want more?
|
| 109 |
+
|
| 110 |
+
Set your Collinear API key to unlock the full task set with real HR scenarios:
|
| 111 |
+
|
| 112 |
+
```bash
|
| 113 |
+
export COLLINEAR_API_KEY="your-key-here"
|
| 114 |
+
```
|
| 115 |
+
|
| 116 |
+
Get a free API key at **[platform.collinear.ai](https://platform.collinear.ai)** (Developer Resources → API Keys).
|
| 117 |
+
|
| 118 |
+
With the API key, every `reset()` pulls a fresh task from Collinear's Scenario Manager — recruiting workflows, people management scenarios, compliance tasks, and more.
|
| 119 |
+
|
| 120 |
+
## Use with TRL / GRPOTrainer
|
| 121 |
+
|
| 122 |
+
Compatible with Hugging Face TRL for RL fine-tuning:
|
| 123 |
+
|
| 124 |
+
```python
|
| 125 |
+
from simlab_hr import HRAction
|
| 126 |
+
from simlab_hr.client import HREnv
|
| 127 |
+
|
| 128 |
+
env = HREnv.from_hub("collinear/simlab-hr")
|
| 129 |
+
with env:
|
| 130 |
+
obs = env.reset()
|
| 131 |
+
# ... your training loop
|
| 132 |
+
```
|
| 133 |
+
|
| 134 |
+
## More Environments
|
| 135 |
+
|
| 136 |
+
SimLab includes **5 enterprise simulation scenarios** with **14 tool servers**:
|
| 137 |
+
|
| 138 |
+
| Scenario | Tools |
|
| 139 |
+
|---|---|
|
| 140 |
+
| **Human Resources** | HRMS, email, calendar, team chat ← *you are here* |
|
| 141 |
+
| **Customer Service** | Helpdesk ticketing, team chat, email |
|
| 142 |
+
| **Finance** | SEC filings, market data, Google Workspace |
|
| 143 |
+
| **Coding** | Sandboxed IDE, browser automation, team chat |
|
| 144 |
+
| **CRM** | Contacts, deals, pipelines, activities |
|
| 145 |
+
|
| 146 |
+
Install the full toolkit:
|
| 147 |
+
|
| 148 |
+
```bash
|
| 149 |
+
pip install simulationlab
|
| 150 |
+
simlab templates list
|
| 151 |
+
```
|
| 152 |
+
|
| 153 |
+
Learn more: [github.com/collinear-ai/simlab](https://github.com/collinear-ai/simlab) | [docs.collinear.ai](https://docs.collinear.ai)
|
| 154 |
+
|
| 155 |
+
## License
|
| 156 |
+
|
| 157 |
+
Apache 2.0 — [Collinear AI](https://collinear.ai)
|
__init__.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""SimLab HR — OpenEnv environment for training AI recruiting and people management agents."""
|
| 2 |
+
|
| 3 |
+
from simlab_hr.models import HRAction, HRObservation
|
| 4 |
+
|
| 5 |
+
__all__ = ["HRAction", "HRObservation"]
|
client.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Typed client for the SimLab HR OpenEnv environment."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from openenv.core.client_types import StepResult
|
| 6 |
+
from openenv.core.env_client import EnvClient
|
| 7 |
+
from openenv.core.env_server.types import State
|
| 8 |
+
|
| 9 |
+
from simlab_hr.models import HRAction, HRObservation
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class HREnv(EnvClient[HRAction, HRObservation, State]):
|
| 13 |
+
"""WebSocket client for the SimLab HR environment."""
|
| 14 |
+
|
| 15 |
+
def _step_payload(self, action: HRAction) -> dict:
|
| 16 |
+
return {
|
| 17 |
+
"tool_server": action.tool_server,
|
| 18 |
+
"tool_name": action.tool_name,
|
| 19 |
+
"parameters": action.parameters,
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
def _parse_result(self, payload: dict) -> StepResult[HRObservation]:
|
| 23 |
+
obs_data = payload.get("observation", {})
|
| 24 |
+
obs = HRObservation(
|
| 25 |
+
result=obs_data.get("result", ""),
|
| 26 |
+
is_error=obs_data.get("is_error", False),
|
| 27 |
+
tools_available=obs_data.get("tools_available", {}),
|
| 28 |
+
task_instruction=obs_data.get("task_instruction", ""),
|
| 29 |
+
done=payload.get("done", False),
|
| 30 |
+
reward=payload.get("reward"),
|
| 31 |
+
)
|
| 32 |
+
return StepResult(
|
| 33 |
+
observation=obs,
|
| 34 |
+
reward=payload.get("reward"),
|
| 35 |
+
done=payload.get("done", False),
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
def _parse_state(self, payload: dict) -> State:
|
| 39 |
+
return State(
|
| 40 |
+
episode_id=payload.get("episode_id"),
|
| 41 |
+
step_count=payload.get("step_count", 0),
|
| 42 |
+
)
|
docker-compose.yaml
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
services:
|
| 2 |
+
# --- HRMS (Frappe) ---
|
| 3 |
+
frappe-hrms-mariadb:
|
| 4 |
+
image: ghcr.io/collinear-ai/collinear/frappe-hrms-mariadb:latest
|
| 5 |
+
environment:
|
| 6 |
+
MYSQL_ROOT_PASSWORD: admin
|
| 7 |
+
MARIADB_ROOT_PASSWORD: admin
|
| 8 |
+
|
| 9 |
+
frappe-hrms-redis:
|
| 10 |
+
image: ghcr.io/collinear-ai/collinear/frappe-hrms-redis:latest
|
| 11 |
+
|
| 12 |
+
frappe-hrms:
|
| 13 |
+
image: ghcr.io/collinear-ai/collinear/frappe-hrms:latest
|
| 14 |
+
environment:
|
| 15 |
+
FRAPPE_SITE_NAME: hrms.localhost
|
| 16 |
+
FRAPPE_ADMIN_PASSWORD: admin
|
| 17 |
+
FRAPPE_DB_HOST: frappe-hrms-mariadb
|
| 18 |
+
FRAPPE_DB_PORT: "3306"
|
| 19 |
+
FRAPPE_DB_ROOT_PASSWORD: admin
|
| 20 |
+
FRAPPE_REDIS_HOST: frappe-hrms-redis
|
| 21 |
+
FRAPPE_REDIS_PORT: "6379"
|
| 22 |
+
FRAPPE_SOCKETIO_PORT: "9000"
|
| 23 |
+
FRAPPE_DEVELOPER_MODE: "1"
|
| 24 |
+
FRAPPE_ENABLE_SCHEDULER: "1"
|
| 25 |
+
FRAPPE_INSTALL_APPS: hrms,collinear_hrms_seed
|
| 26 |
+
FRAPPE_DEPENDENCY_TIMEOUT: "180"
|
| 27 |
+
depends_on:
|
| 28 |
+
frappe-hrms-mariadb:
|
| 29 |
+
condition: service_healthy
|
| 30 |
+
frappe-hrms-redis:
|
| 31 |
+
condition: service_healthy
|
| 32 |
+
|
| 33 |
+
frappe-hrms-env:
|
| 34 |
+
image: ghcr.io/collinear-ai/collinear/frappe-hrms-env:latest
|
| 35 |
+
ports:
|
| 36 |
+
- "8030:8030"
|
| 37 |
+
environment:
|
| 38 |
+
FRAPPE_BASE_URL: http://frappe-hrms:8000
|
| 39 |
+
FRAPPE_SITE_NAME: hrms.localhost
|
| 40 |
+
FRAPPE_ADMIN_USERNAME: Administrator
|
| 41 |
+
FRAPPE_ADMIN_PASSWORD: admin
|
| 42 |
+
depends_on:
|
| 43 |
+
frappe-hrms:
|
| 44 |
+
condition: service_healthy
|
| 45 |
+
healthcheck:
|
| 46 |
+
test: ["CMD-SHELL", "python3 -c \"import urllib.request; urllib.request.urlopen('http://localhost:8030/health')\""]
|
| 47 |
+
interval: 15s
|
| 48 |
+
timeout: 5s
|
| 49 |
+
retries: 120
|
| 50 |
+
start_period: 60s
|
| 51 |
+
|
| 52 |
+
# --- Email (MailHog) ---
|
| 53 |
+
mailhog:
|
| 54 |
+
image: mailhog/mailhog:v1.0.1
|
| 55 |
+
healthcheck:
|
| 56 |
+
test: ["CMD-SHELL", "wget --spider -q http://localhost:8025/api/v2/messages || exit 1"]
|
| 57 |
+
interval: 10s
|
| 58 |
+
timeout: 5s
|
| 59 |
+
retries: 6
|
| 60 |
+
start_period: 10s
|
| 61 |
+
|
| 62 |
+
email-env:
|
| 63 |
+
image: ghcr.io/collinear-ai/collinear/email-env:latest
|
| 64 |
+
ports:
|
| 65 |
+
- "8040:8040"
|
| 66 |
+
environment:
|
| 67 |
+
MAILHOG_BASE_URL: http://mailhog:8025
|
| 68 |
+
MAILHOG_SMTP_HOST: mailhog
|
| 69 |
+
MAILHOG_SMTP_PORT: "1025"
|
| 70 |
+
depends_on:
|
| 71 |
+
mailhog:
|
| 72 |
+
condition: service_healthy
|
| 73 |
+
healthcheck:
|
| 74 |
+
test: ["CMD-SHELL", "python3 -c \"import urllib.request; urllib.request.urlopen('http://localhost:8040/health')\""]
|
| 75 |
+
interval: 10s
|
| 76 |
+
timeout: 5s
|
| 77 |
+
retries: 6
|
| 78 |
+
start_period: 10s
|
| 79 |
+
|
| 80 |
+
# --- Calendar (Baikal + Chronos) ---
|
| 81 |
+
baikal:
|
| 82 |
+
image: ckulka/baikal:0.10.1-nginx
|
| 83 |
+
volumes:
|
| 84 |
+
- baikal-config:/var/www/baikal/config
|
| 85 |
+
- baikal-specific:/var/www/baikal/Specific
|
| 86 |
+
healthcheck:
|
| 87 |
+
test: ["CMD-SHELL", "curl -sf http://localhost:80/ > /dev/null || exit 1"]
|
| 88 |
+
interval: 10s
|
| 89 |
+
timeout: 5s
|
| 90 |
+
retries: 10
|
| 91 |
+
start_period: 10s
|
| 92 |
+
|
| 93 |
+
chronos-mcp:
|
| 94 |
+
image: ghcr.io/collinear-ai/collinear/chronos-mcp:latest
|
| 95 |
+
environment:
|
| 96 |
+
CALDAV_BASE_URL: http://baikal:80/dav.php
|
| 97 |
+
CALDAV_USERNAME: chronos
|
| 98 |
+
CALDAV_PASSWORD: admin
|
| 99 |
+
depends_on:
|
| 100 |
+
baikal:
|
| 101 |
+
condition: service_healthy
|
| 102 |
+
|
| 103 |
+
chronos-server:
|
| 104 |
+
image: ghcr.io/collinear-ai/collinear/chronos-server:latest
|
| 105 |
+
ports:
|
| 106 |
+
- "8050:8050"
|
| 107 |
+
environment:
|
| 108 |
+
CHRONOS_MCP_URL: http://chronos-mcp:8040/mcp
|
| 109 |
+
CALDAV_BASE_URL: http://baikal:80/dav.php
|
| 110 |
+
CALDAV_USERNAME: chronos
|
| 111 |
+
CALDAV_PASSWORD: admin
|
| 112 |
+
depends_on:
|
| 113 |
+
chronos-mcp:
|
| 114 |
+
condition: service_healthy
|
| 115 |
+
healthcheck:
|
| 116 |
+
test: ["CMD-SHELL", "python3 -c \"import urllib.request; urllib.request.urlopen('http://localhost:8050/health')\""]
|
| 117 |
+
interval: 10s
|
| 118 |
+
timeout: 5s
|
| 119 |
+
retries: 18
|
| 120 |
+
start_period: 30s
|
| 121 |
+
|
| 122 |
+
# --- RocketChat ---
|
| 123 |
+
rocketchat-mongodb:
|
| 124 |
+
image: ghcr.io/collinear-ai/collinear/rocketchat-mongodb:6-preseeded
|
| 125 |
+
|
| 126 |
+
rocketchat:
|
| 127 |
+
image: ghcr.io/collinear-ai/collinear/rocketchat:7.6.0
|
| 128 |
+
depends_on:
|
| 129 |
+
rocketchat-mongodb:
|
| 130 |
+
condition: service_healthy
|
| 131 |
+
|
| 132 |
+
rocketchat-env:
|
| 133 |
+
image: ghcr.io/collinear-ai/collinear/rocketchat-env:latest
|
| 134 |
+
ports:
|
| 135 |
+
- "8060:8060"
|
| 136 |
+
environment:
|
| 137 |
+
ROCKETCHAT_BASE_URL: http://rocketchat:3000
|
| 138 |
+
depends_on:
|
| 139 |
+
rocketchat:
|
| 140 |
+
condition: service_healthy
|
| 141 |
+
healthcheck:
|
| 142 |
+
test: ["CMD-SHELL", "python3 -c \"import urllib.request; urllib.request.urlopen('http://localhost:8060/health')\""]
|
| 143 |
+
interval: 10s
|
| 144 |
+
timeout: 5s
|
| 145 |
+
retries: 12
|
| 146 |
+
start_period: 30s
|
| 147 |
+
|
| 148 |
+
# --- OpenEnv wrapper ---
|
| 149 |
+
openenv-server:
|
| 150 |
+
build:
|
| 151 |
+
context: .
|
| 152 |
+
dockerfile: server/Dockerfile
|
| 153 |
+
ports:
|
| 154 |
+
- "8000:8000"
|
| 155 |
+
environment:
|
| 156 |
+
HRMS_TOOL_SERVER_URL: http://frappe-hrms-env:8030
|
| 157 |
+
EMAIL_TOOL_SERVER_URL: http://email-env:8040
|
| 158 |
+
CALENDAR_TOOL_SERVER_URL: http://chronos-server:8050
|
| 159 |
+
ROCKETCHAT_TOOL_SERVER_URL: http://rocketchat-env:8060
|
| 160 |
+
COLLINEAR_API_KEY: ${COLLINEAR_API_KEY:-}
|
| 161 |
+
depends_on:
|
| 162 |
+
frappe-hrms-env:
|
| 163 |
+
condition: service_healthy
|
| 164 |
+
email-env:
|
| 165 |
+
condition: service_healthy
|
| 166 |
+
chronos-server:
|
| 167 |
+
condition: service_healthy
|
| 168 |
+
rocketchat-env:
|
| 169 |
+
condition: service_healthy
|
| 170 |
+
|
| 171 |
+
volumes:
|
| 172 |
+
baikal-config: {}
|
| 173 |
+
baikal-specific: {}
|
landing.py
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Root landing page HTML for the SimLab HR Space on Hugging Face."""
|
| 2 |
+
|
| 3 |
+
LANDING_HTML = """<!DOCTYPE html>
|
| 4 |
+
<html lang="en">
|
| 5 |
+
<head>
|
| 6 |
+
<meta charset="utf-8">
|
| 7 |
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
| 8 |
+
<title>SimLab HR — AI Recruiting & People Management Agent Environment</title>
|
| 9 |
+
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
| 10 |
+
<style>
|
| 11 |
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
| 12 |
+
body { font-family: 'DM Sans', sans-serif; background: #0a0e17; color: #e2e8f0; min-height: 100vh; }
|
| 13 |
+
a { color: #60a5fa; text-decoration: none; }
|
| 14 |
+
a:hover { text-decoration: underline; }
|
| 15 |
+
code { font-family: 'JetBrains Mono', monospace; background: #1e293b; padding: 2px 6px; border-radius: 4px; font-size: 0.85em; }
|
| 16 |
+
pre { font-family: 'JetBrains Mono', monospace; background: #1e293b; padding: 16px 20px; border-radius: 8px; overflow-x: auto; font-size: 0.85em; line-height: 1.6; }
|
| 17 |
+
.container { max-width: 900px; margin: 0 auto; padding: 40px 24px 60px; }
|
| 18 |
+
.badge { display: inline-block; padding: 4px 10px; border-radius: 20px; font-size: 0.75em; font-weight: 500; margin-right: 6px; }
|
| 19 |
+
.badge-green { background: #065f46; color: #6ee7b7; }
|
| 20 |
+
.badge-blue { background: #1e3a5f; color: #93c5fd; }
|
| 21 |
+
.badge-purple { background: #3b1f6e; color: #c4b5fd; }
|
| 22 |
+
|
| 23 |
+
h1 { font-size: 2em; font-weight: 700; margin: 16px 0 8px; line-height: 1.2; }
|
| 24 |
+
.subtitle { font-size: 1.1em; color: #94a3b8; margin-bottom: 32px; }
|
| 25 |
+
h2 { font-size: 1.3em; font-weight: 700; margin: 36px 0 16px; color: #f1f5f9; }
|
| 26 |
+
p { line-height: 1.7; color: #cbd5e1; margin-bottom: 12px; }
|
| 27 |
+
|
| 28 |
+
.servers { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; margin: 20px 0 28px; }
|
| 29 |
+
.server-card { background: #1e293b; border: 1px solid #334155; border-radius: 10px; padding: 16px; }
|
| 30 |
+
.server-card h3 { font-size: 1em; font-weight: 700; margin-bottom: 4px; color: #f8fafc; }
|
| 31 |
+
.server-card p { font-size: 0.85em; color: #94a3b8; margin: 0; line-height: 1.4; }
|
| 32 |
+
.server-card .port { font-family: 'JetBrains Mono', monospace; font-size: 0.75em; color: #64748b; }
|
| 33 |
+
|
| 34 |
+
.tasks-table { width: 100%; border-collapse: collapse; margin: 12px 0 24px; }
|
| 35 |
+
.tasks-table th { text-align: left; padding: 8px 12px; font-size: 0.85em; color: #64748b; border-bottom: 1px solid #334155; }
|
| 36 |
+
.tasks-table td { padding: 8px 12px; font-size: 0.9em; border-bottom: 1px solid #1e293b; }
|
| 37 |
+
|
| 38 |
+
.cta { background: linear-gradient(135deg, #1e3a5f, #1e293b); border: 1px solid #334155; border-radius: 12px; padding: 24px; margin: 28px 0; }
|
| 39 |
+
.cta h3 { font-size: 1.1em; margin-bottom: 8px; color: #f8fafc; }
|
| 40 |
+
.cta p { margin-bottom: 12px; }
|
| 41 |
+
.cta a.btn { display: inline-block; padding: 10px 20px; background: #2563eb; color: #fff; border-radius: 8px; font-weight: 500; }
|
| 42 |
+
.cta a.btn:hover { background: #1d4ed8; text-decoration: none; }
|
| 43 |
+
|
| 44 |
+
.links { display: flex; gap: 12px; flex-wrap: wrap; margin: 20px 0; }
|
| 45 |
+
.links a { padding: 8px 16px; border: 1px solid #334155; border-radius: 8px; font-size: 0.9em; color: #93c5fd; }
|
| 46 |
+
.links a:hover { background: #1e293b; text-decoration: none; }
|
| 47 |
+
|
| 48 |
+
.footer { margin-top: 48px; padding-top: 24px; border-top: 1px solid #1e293b; color: #475569; font-size: 0.85em; }
|
| 49 |
+
</style>
|
| 50 |
+
</head>
|
| 51 |
+
<body>
|
| 52 |
+
<div class="container">
|
| 53 |
+
<div>
|
| 54 |
+
<span class="badge badge-green">OpenEnv</span>
|
| 55 |
+
<span class="badge badge-blue">4 Tool Servers</span>
|
| 56 |
+
<span class="badge badge-purple">14+ Tasks</span>
|
| 57 |
+
</div>
|
| 58 |
+
|
| 59 |
+
<h1>SimLab HR</h1>
|
| 60 |
+
<p class="subtitle">AI Recruiting & People Management Agent Environment</p>
|
| 61 |
+
|
| 62 |
+
<p>
|
| 63 |
+
A fully-functional HR simulation for training, evaluating, and benchmarking AI agents.
|
| 64 |
+
Your agent gets a task — <em>"schedule a phone screen"</em>, <em>"approve a leave request"</em>,
|
| 65 |
+
<em>"onboard a new hire"</em> — and a real workplace with an HRMS, email, calendar, and team chat.
|
| 66 |
+
</p>
|
| 67 |
+
|
| 68 |
+
<div class="links">
|
| 69 |
+
<a href="/api/docs">API Docs (Swagger)</a>
|
| 70 |
+
<a href="https://github.com/collinear-ai/simlab" target="_blank">GitHub (SimLab)</a>
|
| 71 |
+
<a href="https://platform.collinear.ai" target="_blank">Get API Key</a>
|
| 72 |
+
</div>
|
| 73 |
+
|
| 74 |
+
<h2>4 Tool Servers, 1 Environment</h2>
|
| 75 |
+
<div class="servers">
|
| 76 |
+
<div class="server-card">
|
| 77 |
+
<h3>HRMS</h3>
|
| 78 |
+
<p>Employee records, leave management, attendance, payroll</p>
|
| 79 |
+
<span class="port">:8030</span>
|
| 80 |
+
</div>
|
| 81 |
+
<div class="server-card">
|
| 82 |
+
<h3>Email</h3>
|
| 83 |
+
<p>Send and read emails, inbox management</p>
|
| 84 |
+
<span class="port">:8040</span>
|
| 85 |
+
</div>
|
| 86 |
+
<div class="server-card">
|
| 87 |
+
<h3>Calendar</h3>
|
| 88 |
+
<p>Schedule meetings, check availability, manage events</p>
|
| 89 |
+
<span class="port">:8050</span>
|
| 90 |
+
</div>
|
| 91 |
+
<div class="server-card">
|
| 92 |
+
<h3>RocketChat</h3>
|
| 93 |
+
<p>Team messaging, channels, direct messages</p>
|
| 94 |
+
<span class="port">:8060</span>
|
| 95 |
+
</div>
|
| 96 |
+
</div>
|
| 97 |
+
|
| 98 |
+
<h2>Quickstart</h2>
|
| 99 |
+
<pre>from simlab_hr import HRAction
|
| 100 |
+
from simlab_hr.client import HREnv
|
| 101 |
+
|
| 102 |
+
client = HREnv(base_url="http://localhost:8000")
|
| 103 |
+
|
| 104 |
+
with client:
|
| 105 |
+
obs = client.reset()
|
| 106 |
+
print(obs.observation.task_instruction)
|
| 107 |
+
|
| 108 |
+
result = client.step(HRAction(
|
| 109 |
+
tool_server="hrms",
|
| 110 |
+
tool_name="get_leave_balance",
|
| 111 |
+
parameters={"employee_id": "EMP-0042"}
|
| 112 |
+
))</pre>
|
| 113 |
+
|
| 114 |
+
<h2>Sample Tasks</h2>
|
| 115 |
+
<table class="tasks-table">
|
| 116 |
+
<tr><th>Difficulty</th><th>Example</th></tr>
|
| 117 |
+
<tr><td><span class="badge badge-green">Easy</span></td><td>Approve a leave request, update an employee's designation</td></tr>
|
| 118 |
+
<tr><td><span class="badge badge-blue">Medium</span></td><td>Schedule a phone screen + send confirmation, run attendance report</td></tr>
|
| 119 |
+
<tr><td><span class="badge badge-purple">Hard</span></td><td>Multi-person panel interview scheduling, full onboarding flow</td></tr>
|
| 120 |
+
</table>
|
| 121 |
+
<p>8 tasks included out of the box. Every task requires coordinating across multiple tool servers.</p>
|
| 122 |
+
|
| 123 |
+
<div class="cta">
|
| 124 |
+
<h3>Unlock 14+ Tasks from the API</h3>
|
| 125 |
+
<p>Set <code>COLLINEAR_API_KEY</code> to access the full task set with real HR scenarios — recruiting workflows, people management, compliance tasks, and more.</p>
|
| 126 |
+
<a class="btn" href="https://platform.collinear.ai" target="_blank">Get a Free API Key →</a>
|
| 127 |
+
</div>
|
| 128 |
+
|
| 129 |
+
<h2>Run Locally</h2>
|
| 130 |
+
<pre>git clone https://github.com/collinear-ai/simlab.git
|
| 131 |
+
cd simlab/envs/simlab_hr
|
| 132 |
+
docker compose up</pre>
|
| 133 |
+
|
| 134 |
+
<h2>More Environments</h2>
|
| 135 |
+
<p>SimLab includes <strong>5 enterprise simulation scenarios</strong> with <strong>14 tool servers</strong>:</p>
|
| 136 |
+
<table class="tasks-table">
|
| 137 |
+
<tr><th>Scenario</th><th>Tools</th></tr>
|
| 138 |
+
<tr><td><strong>Human Resources</strong> ← you are here</td><td>HRMS, email, calendar, team chat</td></tr>
|
| 139 |
+
<tr><td>Customer Service</td><td>Helpdesk ticketing, team chat, email</td></tr>
|
| 140 |
+
<tr><td>Finance</td><td>SEC filings, market data, Google Workspace</td></tr>
|
| 141 |
+
<tr><td>Coding</td><td>Sandboxed IDE, browser automation, team chat</td></tr>
|
| 142 |
+
<tr><td>CRM</td><td>Contacts, deals, pipelines, activities</td></tr>
|
| 143 |
+
</table>
|
| 144 |
+
<pre>pip install simulationlab
|
| 145 |
+
simlab templates list</pre>
|
| 146 |
+
|
| 147 |
+
<div class="footer">
|
| 148 |
+
<p>Apache 2.0 — <a href="https://collinear.ai">Collinear AI</a> · <a href="https://github.com/collinear-ai/simlab">GitHub</a> · <a href="https://docs.collinear.ai">Docs</a></p>
|
| 149 |
+
</div>
|
| 150 |
+
</div>
|
| 151 |
+
</body>
|
| 152 |
+
</html>"""
|
models.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Action and Observation models for the SimLab HR environment."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from typing import Literal
|
| 6 |
+
|
| 7 |
+
from pydantic import Field
|
| 8 |
+
|
| 9 |
+
from openenv.core.env_server.types import Action, Observation
|
| 10 |
+
|
| 11 |
+
TOOL_SERVERS = ("hrms", "email", "calendar", "rocketchat")
|
| 12 |
+
ToolServerName = Literal["hrms", "email", "calendar", "rocketchat"]
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class HRAction(Action):
|
| 16 |
+
"""An action in the HR environment — invoke a tool on one of the tool servers."""
|
| 17 |
+
|
| 18 |
+
tool_server: ToolServerName = Field(
|
| 19 |
+
...,
|
| 20 |
+
description="Which tool server to target: 'hrms' (employee records, leave, payroll), "
|
| 21 |
+
"'email' (send/read email), 'calendar' (schedule events), or 'rocketchat' (team chat).",
|
| 22 |
+
)
|
| 23 |
+
tool_name: str = Field(
|
| 24 |
+
...,
|
| 25 |
+
description="Name of the tool to invoke on the selected server. "
|
| 26 |
+
"Use the tools_available field from the observation to see what's available per server.",
|
| 27 |
+
)
|
| 28 |
+
parameters: dict = Field(
|
| 29 |
+
default_factory=dict,
|
| 30 |
+
description="Tool-specific parameters as a JSON object.",
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class HRObservation(Observation):
|
| 35 |
+
"""Observation returned by the HR environment after each action."""
|
| 36 |
+
|
| 37 |
+
result: str = Field(
|
| 38 |
+
...,
|
| 39 |
+
description="Result of the tool invocation, or initial environment status on reset.",
|
| 40 |
+
)
|
| 41 |
+
is_error: bool = Field(
|
| 42 |
+
default=False,
|
| 43 |
+
description="Whether the tool invocation resulted in an error.",
|
| 44 |
+
)
|
| 45 |
+
tools_available: dict[str, list[str]] = Field(
|
| 46 |
+
default_factory=dict,
|
| 47 |
+
description="Available tools grouped by server: {'hrms': [...], 'email': [...], ...}.",
|
| 48 |
+
)
|
| 49 |
+
task_instruction: str = Field(
|
| 50 |
+
default="",
|
| 51 |
+
description="The task the agent should complete in this episode.",
|
| 52 |
+
)
|
openenv.yaml
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: simlab-hr
|
| 2 |
+
display_name: "SimLab HR"
|
| 3 |
+
description: "HR simulation environment for training AI recruiting and people management agents — HRMS, email, calendar, and team chat."
|
| 4 |
+
tags:
|
| 5 |
+
- hr
|
| 6 |
+
- human-resources
|
| 7 |
+
- recruiting
|
| 8 |
+
- enterprise
|
| 9 |
+
- agent-evaluation
|
| 10 |
+
- simlab
|
| 11 |
+
- collinear
|
pyproject.toml
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "simlab-hr"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "HR simulation environment for training AI recruiting and people management agents — powered by SimLab"
|
| 5 |
+
readme = "README.md"
|
| 6 |
+
license = { text = "Apache-2.0" }
|
| 7 |
+
authors = [{ name = "Collinear AI" }]
|
| 8 |
+
requires-python = ">=3.11"
|
| 9 |
+
dependencies = [
|
| 10 |
+
"openenv-core>=0.2.3",
|
| 11 |
+
"pydantic>=2.0",
|
| 12 |
+
"requests>=2.28",
|
| 13 |
+
]
|
| 14 |
+
|
| 15 |
+
[project.urls]
|
| 16 |
+
Homepage = "https://github.com/collinear-ai/simlab"
|
| 17 |
+
Documentation = "https://docs.collinear.ai"
|
| 18 |
+
|
| 19 |
+
[build-system]
|
| 20 |
+
requires = ["setuptools>=68.0"]
|
| 21 |
+
build-backend = "setuptools.build_meta"
|
| 22 |
+
|
| 23 |
+
[tool.setuptools]
|
| 24 |
+
packages = ["simlab_hr", "simlab_hr.server"]
|
| 25 |
+
|
| 26 |
+
[tool.setuptools.package-dir]
|
| 27 |
+
"simlab_hr" = "."
|
| 28 |
+
"simlab_hr.server" = "server"
|
server/Dockerfile
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
ARG BASE_IMAGE=openenv-base:latest
|
| 2 |
+
FROM ${BASE_IMAGE} AS builder
|
| 3 |
+
|
| 4 |
+
WORKDIR /app
|
| 5 |
+
|
| 6 |
+
COPY . /app/env
|
| 7 |
+
|
| 8 |
+
WORKDIR /app/env
|
| 9 |
+
RUN --mount=type=cache,target=/root/.cache/uv \
|
| 10 |
+
if [ -f uv.lock ]; then \
|
| 11 |
+
uv sync --frozen --no-install-project --no-editable; \
|
| 12 |
+
else \
|
| 13 |
+
uv sync --no-install-project --no-editable; \
|
| 14 |
+
fi
|
| 15 |
+
|
| 16 |
+
RUN --mount=type=cache,target=/root/.cache/uv \
|
| 17 |
+
if [ -f uv.lock ]; then \
|
| 18 |
+
uv sync --frozen --no-editable; \
|
| 19 |
+
else \
|
| 20 |
+
uv sync --no-editable; \
|
| 21 |
+
fi
|
| 22 |
+
|
| 23 |
+
FROM ${BASE_IMAGE}
|
| 24 |
+
|
| 25 |
+
WORKDIR /app
|
| 26 |
+
|
| 27 |
+
COPY --from=builder /app/env/.venv /app/.venv
|
| 28 |
+
COPY --from=builder /app/env /app/env
|
| 29 |
+
|
| 30 |
+
ENV PATH="/app/.venv/bin:$PATH"
|
| 31 |
+
ENV PYTHONPATH="/app/env:$PYTHONPATH"
|
| 32 |
+
|
| 33 |
+
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
| 34 |
+
CMD curl -f http://localhost:8000/health || exit 1
|
| 35 |
+
|
| 36 |
+
CMD ["sh", "-c", "cd /app/env && uvicorn server.app:app --host 0.0.0.0 --port 8000"]
|
server/__init__.py
ADDED
|
File without changes
|
server/app.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""FastAPI application for the SimLab HR OpenEnv environment."""
|
| 2 |
+
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
|
| 5 |
+
from fastapi import FastAPI
|
| 6 |
+
from fastapi.responses import HTMLResponse
|
| 7 |
+
from openenv.core.env_server import create_app
|
| 8 |
+
|
| 9 |
+
from simlab_hr.models import HRAction, HRObservation
|
| 10 |
+
from simlab_hr.server.environment import HREnvironment
|
| 11 |
+
|
| 12 |
+
_openenv_app = create_app(HREnvironment, HRAction, HRObservation, env_name="simlab-hr")
|
| 13 |
+
|
| 14 |
+
app = FastAPI(title="SimLab HR")
|
| 15 |
+
|
| 16 |
+
_landing_html = None
|
| 17 |
+
_landing_path = Path(__file__).resolve().parent.parent / "landing.py"
|
| 18 |
+
if _landing_path.exists():
|
| 19 |
+
_ns = {}
|
| 20 |
+
exec(compile(_landing_path.read_text(), _landing_path, "exec"), _ns)
|
| 21 |
+
_landing_html = _ns.get("LANDING_HTML")
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
@app.get("/", response_class=HTMLResponse, include_in_schema=False)
|
| 25 |
+
async def landing_page():
|
| 26 |
+
if _landing_html:
|
| 27 |
+
return _landing_html
|
| 28 |
+
return '<h1>SimLab HR</h1><p><a href="/api/docs">API Docs</a></p>'
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
app.mount("/api", _openenv_app)
|
server/environment.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""HR environment — wraps 4 tool servers with OpenEnv's reset/step/state contract."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import json
|
| 6 |
+
import logging
|
| 7 |
+
import os
|
| 8 |
+
from uuid import uuid4
|
| 9 |
+
|
| 10 |
+
import requests
|
| 11 |
+
from openenv.core.env_server.interfaces import Environment
|
| 12 |
+
from openenv.core.env_server.types import State
|
| 13 |
+
|
| 14 |
+
from simlab_hr.models import HRAction, HRObservation
|
| 15 |
+
from simlab_hr.tasks import BUNDLED_TASKS, get_task
|
| 16 |
+
|
| 17 |
+
logger = logging.getLogger(__name__)
|
| 18 |
+
|
| 19 |
+
MAX_STEPS_PER_EPISODE = 30
|
| 20 |
+
|
| 21 |
+
TOOL_SERVER_ENV_MAP = {
|
| 22 |
+
"hrms": "HRMS_TOOL_SERVER_URL",
|
| 23 |
+
"email": "EMAIL_TOOL_SERVER_URL",
|
| 24 |
+
"calendar": "CALENDAR_TOOL_SERVER_URL",
|
| 25 |
+
"rocketchat": "ROCKETCHAT_TOOL_SERVER_URL",
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
TOOL_SERVER_DEFAULTS = {
|
| 29 |
+
"hrms": "http://localhost:8030",
|
| 30 |
+
"email": "http://localhost:8040",
|
| 31 |
+
"calendar": "http://localhost:8050",
|
| 32 |
+
"rocketchat": "http://localhost:8060",
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class HREnvironment(Environment):
|
| 37 |
+
"""OpenEnv environment backed by SimLab's HR tool servers."""
|
| 38 |
+
|
| 39 |
+
def __init__(self) -> None:
|
| 40 |
+
self._server_urls: dict[str, str] = {}
|
| 41 |
+
for name, env_var in TOOL_SERVER_ENV_MAP.items():
|
| 42 |
+
self._server_urls[name] = os.environ.get(env_var, TOOL_SERVER_DEFAULTS[name])
|
| 43 |
+
|
| 44 |
+
self._state = State(episode_id=str(uuid4()), step_count=0)
|
| 45 |
+
self._current_task = BUNDLED_TASKS[0]
|
| 46 |
+
self._tools: dict[str, list[str]] = {}
|
| 47 |
+
self._episode_count = 0
|
| 48 |
+
|
| 49 |
+
def reset(self) -> HRObservation:
|
| 50 |
+
self._current_task = get_task(self._episode_count)
|
| 51 |
+
self._episode_count += 1
|
| 52 |
+
self._state = State(episode_id=str(uuid4()), step_count=0)
|
| 53 |
+
self._tools = self._discover_all_tools()
|
| 54 |
+
|
| 55 |
+
return HRObservation(
|
| 56 |
+
result=(
|
| 57 |
+
"HR environment ready. You have access to 4 tool servers: "
|
| 58 |
+
"hrms (employee records, leave, payroll), email (inbox), "
|
| 59 |
+
"calendar (scheduling), and rocketchat (team messaging). "
|
| 60 |
+
"Use the tools to complete the task."
|
| 61 |
+
),
|
| 62 |
+
is_error=False,
|
| 63 |
+
tools_available=self._tools,
|
| 64 |
+
task_instruction=self._current_task.instruction,
|
| 65 |
+
done=False,
|
| 66 |
+
reward=0.0,
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
def step(self, action: HRAction) -> HRObservation:
|
| 70 |
+
self._state.step_count += 1
|
| 71 |
+
|
| 72 |
+
server_url = self._server_urls.get(action.tool_server)
|
| 73 |
+
if server_url is None:
|
| 74 |
+
return HRObservation(
|
| 75 |
+
result=f"Unknown tool server: '{action.tool_server}'. Use one of: hrms, email, calendar, rocketchat.",
|
| 76 |
+
is_error=True,
|
| 77 |
+
tools_available=self._tools,
|
| 78 |
+
task_instruction=self._current_task.instruction,
|
| 79 |
+
done=False,
|
| 80 |
+
reward=0.0,
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
payload = {"action": {"tool_name": action.tool_name, "parameters": action.parameters}}
|
| 84 |
+
try:
|
| 85 |
+
resp = requests.post(
|
| 86 |
+
f"{server_url}/step",
|
| 87 |
+
json=payload,
|
| 88 |
+
headers={"Content-Type": "application/json"},
|
| 89 |
+
timeout=30,
|
| 90 |
+
)
|
| 91 |
+
result = resp.text
|
| 92 |
+
is_error = resp.status_code != 200
|
| 93 |
+
|
| 94 |
+
try:
|
| 95 |
+
parsed = resp.json()
|
| 96 |
+
result = json.dumps(parsed, indent=2) if isinstance(parsed, (dict, list)) else str(parsed)
|
| 97 |
+
except (json.JSONDecodeError, ValueError):
|
| 98 |
+
pass
|
| 99 |
+
|
| 100 |
+
except requests.RequestException as exc:
|
| 101 |
+
result = f"Tool invocation failed on {action.tool_server}: {exc}"
|
| 102 |
+
is_error = True
|
| 103 |
+
|
| 104 |
+
done = self._state.step_count >= MAX_STEPS_PER_EPISODE
|
| 105 |
+
|
| 106 |
+
return HRObservation(
|
| 107 |
+
result=result,
|
| 108 |
+
is_error=is_error,
|
| 109 |
+
tools_available=self._tools,
|
| 110 |
+
task_instruction=self._current_task.instruction,
|
| 111 |
+
done=done,
|
| 112 |
+
reward=0.0,
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
@property
|
| 116 |
+
def state(self) -> State:
|
| 117 |
+
return self._state
|
| 118 |
+
|
| 119 |
+
def _discover_all_tools(self) -> dict[str, list[str]]:
|
| 120 |
+
"""Fetch available tools from each tool server."""
|
| 121 |
+
all_tools: dict[str, list[str]] = {}
|
| 122 |
+
for name, url in self._server_urls.items():
|
| 123 |
+
all_tools[name] = self._discover_tools(name, url)
|
| 124 |
+
return all_tools
|
| 125 |
+
|
| 126 |
+
def _discover_tools(self, server_name: str, server_url: str) -> list[str]:
|
| 127 |
+
"""Fetch tool names from a single server's GET /tools endpoint."""
|
| 128 |
+
try:
|
| 129 |
+
resp = requests.get(f"{server_url}/tools", timeout=15)
|
| 130 |
+
resp.raise_for_status()
|
| 131 |
+
data = resp.json()
|
| 132 |
+
tools = data.get("tools", []) if isinstance(data, dict) else []
|
| 133 |
+
return [t["name"] for t in tools if isinstance(t, dict) and "name" in t]
|
| 134 |
+
except Exception as exc:
|
| 135 |
+
logger.warning("Could not discover tools from %s: %s", server_name, exc)
|
| 136 |
+
return []
|
server/requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
openenv-core>=0.2.3
|
| 2 |
+
pydantic>=2.0
|
| 3 |
+
requests>=2.28
|
| 4 |
+
uvicorn>=0.30
|
simlab_hr.egg-info/PKG-INFO
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.4
|
| 2 |
+
Name: simlab-hr
|
| 3 |
+
Version: 0.1.0
|
| 4 |
+
Summary: HR simulation environment for training AI recruiting and people management agents — powered by SimLab
|
| 5 |
+
Author: Collinear AI
|
| 6 |
+
License: Apache-2.0
|
| 7 |
+
Project-URL: Homepage, https://github.com/collinear-ai/simlab
|
| 8 |
+
Project-URL: Documentation, https://docs.collinear.ai
|
| 9 |
+
Requires-Python: >=3.11
|
| 10 |
+
Description-Content-Type: text/markdown
|
| 11 |
+
Requires-Dist: openenv-core>=0.2.3
|
| 12 |
+
Requires-Dist: pydantic>=2.0
|
| 13 |
+
Requires-Dist: requests>=2.28
|
| 14 |
+
|
| 15 |
+
# SimLab HR — AI Recruiting & People Management Agent Environment
|
| 16 |
+
|
| 17 |
+
A fully-functional HR simulation for training, evaluating, and benchmarking AI recruiting and people management agents. Built on [OpenEnv](https://github.com/meta-pytorch/OpenEnv) and powered by [SimLab](https://github.com/collinear-ai/simlab).
|
| 18 |
+
|
| 19 |
+
Your agent gets a task ("schedule a phone screen", "approve a leave request", "onboard a new hire") and a real workplace with an HRMS, email, calendar, and team chat. Can it get the job done?
|
| 20 |
+
|
| 21 |
+
## 4 Tool Servers, 1 Environment
|
| 22 |
+
|
| 23 |
+
| Server | Port | What it does |
|
| 24 |
+
|---|---|---|
|
| 25 |
+
| **HRMS** (Frappe) | 8030 | Employee records, leave management, attendance, payroll |
|
| 26 |
+
| **Email** (MailHog) | 8040 | Send and read emails, inbox management |
|
| 27 |
+
| **Calendar** (Baikal/Chronos) | 8050 | Schedule meetings, check availability, manage events |
|
| 28 |
+
| **RocketChat** | 8060 | Team messaging, channels, direct messages |
|
| 29 |
+
|
| 30 |
+
Agents must reason across all four systems to complete real HR workflows — just like a human would.
|
| 31 |
+
|
| 32 |
+
## Quickstart
|
| 33 |
+
|
| 34 |
+
```python
|
| 35 |
+
from simlab_hr import HRAction
|
| 36 |
+
from simlab_hr.client import HREnv
|
| 37 |
+
|
| 38 |
+
client = HREnv(base_url="http://localhost:8000")
|
| 39 |
+
|
| 40 |
+
with client:
|
| 41 |
+
obs = client.reset()
|
| 42 |
+
print(obs.observation.task_instruction)
|
| 43 |
+
print(obs.observation.tools_available) # {'hrms': [...], 'email': [...], ...}
|
| 44 |
+
|
| 45 |
+
# Check leave balance in HRMS
|
| 46 |
+
result = client.step(HRAction(
|
| 47 |
+
tool_server="hrms",
|
| 48 |
+
tool_name="get_leave_balance",
|
| 49 |
+
parameters={"employee_id": "EMP-0042"}
|
| 50 |
+
))
|
| 51 |
+
|
| 52 |
+
# Send an email notification
|
| 53 |
+
result = client.step(HRAction(
|
| 54 |
+
tool_server="email",
|
| 55 |
+
tool_name="send_email",
|
| 56 |
+
parameters={"to": "manager@company.com", "subject": "Leave approved", "body": "..."}
|
| 57 |
+
))
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
## What's Inside
|
| 61 |
+
|
| 62 |
+
**8 sample tasks** covering real HR workflows:
|
| 63 |
+
|
| 64 |
+
| Difficulty | Example |
|
| 65 |
+
|---|---|
|
| 66 |
+
| Easy | Approve a leave request, update an employee's designation |
|
| 67 |
+
| Medium | Schedule a phone screen + send confirmation, run an attendance report |
|
| 68 |
+
| Hard | Multi-person panel interview scheduling, full new-hire onboarding flow |
|
| 69 |
+
|
| 70 |
+
Every task requires the agent to coordinate across multiple tool servers — this is what makes it hard.
|
| 71 |
+
|
| 72 |
+
## Run Locally
|
| 73 |
+
|
| 74 |
+
```bash
|
| 75 |
+
git clone https://github.com/collinear-ai/simlab.git
|
| 76 |
+
cd simlab/envs/simlab_hr
|
| 77 |
+
|
| 78 |
+
# Start all services (HRMS, Email, Calendar, RocketChat, OpenEnv wrapper)
|
| 79 |
+
docker compose up
|
| 80 |
+
|
| 81 |
+
# First run pulls ~10 images and takes a few minutes for HRMS to initialize
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
Or run from Hugging Face:
|
| 85 |
+
|
| 86 |
+
```python
|
| 87 |
+
from simlab_hr.client import HREnv
|
| 88 |
+
|
| 89 |
+
client = HREnv.from_hub("collinear/simlab-hr")
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
## Unlock 14+ Tasks from the API
|
| 93 |
+
|
| 94 |
+
This environment ships with 8 sample tasks. Want more?
|
| 95 |
+
|
| 96 |
+
Set your Collinear API key to unlock the full task set with real HR scenarios:
|
| 97 |
+
|
| 98 |
+
```bash
|
| 99 |
+
export COLLINEAR_API_KEY="your-key-here"
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
+
Get a free API key at **[platform.collinear.ai](https://platform.collinear.ai)** (Developer Resources → API Keys).
|
| 103 |
+
|
| 104 |
+
With the API key, every `reset()` pulls a fresh task from Collinear's Scenario Manager — recruiting workflows, people management scenarios, compliance tasks, and more.
|
| 105 |
+
|
| 106 |
+
## Use with TRL / GRPOTrainer
|
| 107 |
+
|
| 108 |
+
Compatible with Hugging Face TRL for RL fine-tuning:
|
| 109 |
+
|
| 110 |
+
```python
|
| 111 |
+
from simlab_hr import HRAction
|
| 112 |
+
from simlab_hr.client import HREnv
|
| 113 |
+
|
| 114 |
+
env = HREnv.from_hub("collinear/simlab-hr")
|
| 115 |
+
with env:
|
| 116 |
+
obs = env.reset()
|
| 117 |
+
# ... your training loop
|
| 118 |
+
```
|
| 119 |
+
|
| 120 |
+
## More Environments
|
| 121 |
+
|
| 122 |
+
SimLab includes **5 enterprise simulation scenarios** with **14 tool servers**:
|
| 123 |
+
|
| 124 |
+
| Scenario | Tools |
|
| 125 |
+
|---|---|
|
| 126 |
+
| **Human Resources** | HRMS, email, calendar, team chat ← *you are here* |
|
| 127 |
+
| **Customer Service** | Helpdesk ticketing, team chat, email |
|
| 128 |
+
| **Finance** | SEC filings, market data, Google Workspace |
|
| 129 |
+
| **Coding** | Sandboxed IDE, browser automation, team chat |
|
| 130 |
+
| **CRM** | Contacts, deals, pipelines, activities |
|
| 131 |
+
|
| 132 |
+
Install the full toolkit:
|
| 133 |
+
|
| 134 |
+
```bash
|
| 135 |
+
pip install simulationlab
|
| 136 |
+
simlab templates list
|
| 137 |
+
```
|
| 138 |
+
|
| 139 |
+
Learn more: [github.com/collinear-ai/simlab](https://github.com/collinear-ai/simlab) | [docs.collinear.ai](https://docs.collinear.ai)
|
| 140 |
+
|
| 141 |
+
## License
|
| 142 |
+
|
| 143 |
+
Apache 2.0 — [Collinear AI](https://collinear.ai)
|
simlab_hr.egg-info/SOURCES.txt
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
README.md
|
| 2 |
+
pyproject.toml
|
| 3 |
+
./__init__.py
|
| 4 |
+
./client.py
|
| 5 |
+
./models.py
|
| 6 |
+
./tasks.py
|
| 7 |
+
server/__init__.py
|
| 8 |
+
server/app.py
|
| 9 |
+
server/environment.py
|
| 10 |
+
simlab_hr.egg-info/PKG-INFO
|
| 11 |
+
simlab_hr.egg-info/SOURCES.txt
|
| 12 |
+
simlab_hr.egg-info/dependency_links.txt
|
| 13 |
+
simlab_hr.egg-info/requires.txt
|
| 14 |
+
simlab_hr.egg-info/top_level.txt
|
simlab_hr.egg-info/dependency_links.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
simlab_hr.egg-info/requires.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
openenv-core>=0.2.3
|
| 2 |
+
pydantic>=2.0
|
| 3 |
+
requests>=2.28
|
simlab_hr.egg-info/top_level.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
simlab_hr
|
tasks.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Task source for the HR environment — bundled samples + optional API upgrade."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import logging
|
| 6 |
+
import os
|
| 7 |
+
import random
|
| 8 |
+
from dataclasses import dataclass
|
| 9 |
+
|
| 10 |
+
logger = logging.getLogger(__name__)
|
| 11 |
+
|
| 12 |
+
COLLINEAR_PLATFORM_URL = "https://platform.collinear.ai"
|
| 13 |
+
SCENARIO_MANAGER_API_URL = "https://rl-gym-api.collinear.ai"
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
@dataclass
|
| 17 |
+
class Task:
|
| 18 |
+
"""A single HR task for the agent to complete."""
|
| 19 |
+
|
| 20 |
+
id: str
|
| 21 |
+
instruction: str
|
| 22 |
+
difficulty: str
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
BUNDLED_TASKS: list[Task] = [
|
| 26 |
+
Task(
|
| 27 |
+
id="hr-001",
|
| 28 |
+
instruction=(
|
| 29 |
+
"A new candidate, Priya Mehta, has applied for the Senior Software Engineer role. "
|
| 30 |
+
"Create her employee record in the HRMS, schedule a phone screening for next "
|
| 31 |
+
"Tuesday at 2 PM on the calendar, and send her a confirmation email at "
|
| 32 |
+
"priya.mehta@gmail.com with the interview details."
|
| 33 |
+
),
|
| 34 |
+
difficulty="medium",
|
| 35 |
+
),
|
| 36 |
+
Task(
|
| 37 |
+
id="hr-002",
|
| 38 |
+
instruction=(
|
| 39 |
+
"Employee James Wilson (EMP-0042) has requested 5 days of annual leave starting "
|
| 40 |
+
"next Monday. Check his remaining leave balance in the HRMS, approve the request "
|
| 41 |
+
"if he has sufficient days, and notify his manager Sarah Chen via Rocket.Chat."
|
| 42 |
+
),
|
| 43 |
+
difficulty="easy",
|
| 44 |
+
),
|
| 45 |
+
Task(
|
| 46 |
+
id="hr-003",
|
| 47 |
+
instruction=(
|
| 48 |
+
"Run a monthly attendance report: pull the attendance records for all employees "
|
| 49 |
+
"from the HRMS for the current month, identify anyone with more than 2 absences, "
|
| 50 |
+
"and send a summary email to hr-team@company.com with the findings."
|
| 51 |
+
),
|
| 52 |
+
difficulty="medium",
|
| 53 |
+
),
|
| 54 |
+
Task(
|
| 55 |
+
id="hr-004",
|
| 56 |
+
instruction=(
|
| 57 |
+
"The recruiting team needs to schedule a panel interview for candidate Alex Rivera "
|
| 58 |
+
"for the Product Manager position. Check the availability of three interviewers "
|
| 59 |
+
"(Sarah Chen, Mike Johnson, Lisa Park) on the calendar for this week, find a "
|
| 60 |
+
"1-hour slot that works for all three, book the meeting, and send calendar "
|
| 61 |
+
"invites via email to all participants including the candidate at alex.rivera@email.com."
|
| 62 |
+
),
|
| 63 |
+
difficulty="hard",
|
| 64 |
+
),
|
| 65 |
+
Task(
|
| 66 |
+
id="hr-005",
|
| 67 |
+
instruction=(
|
| 68 |
+
"Employee Maria Santos has been promoted from Junior Developer to Senior Developer. "
|
| 69 |
+
"Update her designation and salary grade in the HRMS, send her a congratulatory "
|
| 70 |
+
"email, and post an announcement in the #general channel on Rocket.Chat."
|
| 71 |
+
),
|
| 72 |
+
difficulty="easy",
|
| 73 |
+
),
|
| 74 |
+
Task(
|
| 75 |
+
id="hr-006",
|
| 76 |
+
instruction=(
|
| 77 |
+
"A new employee, David Kim, is starting next Monday. Complete the onboarding "
|
| 78 |
+
"checklist: create his employee record in the HRMS with department 'Engineering', "
|
| 79 |
+
"send him a welcome email at david.kim@company.com with first-day instructions, "
|
| 80 |
+
"schedule a 30-minute orientation meeting on his start date, and add him to the "
|
| 81 |
+
"#engineering channel on Rocket.Chat."
|
| 82 |
+
),
|
| 83 |
+
difficulty="hard",
|
| 84 |
+
),
|
| 85 |
+
Task(
|
| 86 |
+
id="hr-007",
|
| 87 |
+
instruction=(
|
| 88 |
+
"Check how many open leave requests are pending approval in the HRMS. "
|
| 89 |
+
"List them all and send a reminder email to the respective approving managers "
|
| 90 |
+
"asking them to review the pending requests."
|
| 91 |
+
),
|
| 92 |
+
difficulty="medium",
|
| 93 |
+
),
|
| 94 |
+
Task(
|
| 95 |
+
id="hr-008",
|
| 96 |
+
instruction=(
|
| 97 |
+
"The quarterly performance review cycle is starting. Look up all employees "
|
| 98 |
+
"in the Engineering department from the HRMS, schedule individual 45-minute "
|
| 99 |
+
"review meetings with their manager for next week on the calendar, and "
|
| 100 |
+
"send each employee an email notification about their scheduled review time."
|
| 101 |
+
),
|
| 102 |
+
difficulty="hard",
|
| 103 |
+
),
|
| 104 |
+
]
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def get_task(task_index: int | None = None) -> Task:
|
| 108 |
+
"""Return a task — from the API if COLLINEAR_API_KEY is set, else from bundled set."""
|
| 109 |
+
api_key = os.environ.get("COLLINEAR_API_KEY", "").strip()
|
| 110 |
+
|
| 111 |
+
if api_key:
|
| 112 |
+
try:
|
| 113 |
+
return _fetch_api_task(api_key, task_index)
|
| 114 |
+
except Exception:
|
| 115 |
+
logger.warning(
|
| 116 |
+
"Failed to fetch task from Scenario Manager API, falling back to bundled tasks.",
|
| 117 |
+
exc_info=True,
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
if task_index is not None:
|
| 121 |
+
return BUNDLED_TASKS[task_index % len(BUNDLED_TASKS)]
|
| 122 |
+
return random.choice(BUNDLED_TASKS)
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def _fetch_api_task(api_key: str, task_index: int | None) -> Task:
|
| 126 |
+
"""Fetch a task from the Collinear Scenario Manager API."""
|
| 127 |
+
import requests
|
| 128 |
+
|
| 129 |
+
base_url = os.environ.get("SIMLAB_SCENARIO_MANAGER_API_URL", SCENARIO_MANAGER_API_URL).rstrip(
|
| 130 |
+
"/"
|
| 131 |
+
)
|
| 132 |
+
headers = {"Accept": "application/json", "API-Key": api_key}
|
| 133 |
+
|
| 134 |
+
resp = requests.get(f"{base_url}/v1/scenarios", headers=headers, timeout=30)
|
| 135 |
+
resp.raise_for_status()
|
| 136 |
+
scenarios = resp.json()
|
| 137 |
+
|
| 138 |
+
hr_scenario = None
|
| 139 |
+
for s in scenarios:
|
| 140 |
+
name = (s.get("name") or "").lower().replace(" ", "-").replace("_", "-")
|
| 141 |
+
if "hr" in name or "human-resource" in name or "recruiting" in name:
|
| 142 |
+
hr_scenario = s
|
| 143 |
+
break
|
| 144 |
+
|
| 145 |
+
if hr_scenario is None:
|
| 146 |
+
raise ValueError("No HR scenario found in Scenario Manager")
|
| 147 |
+
|
| 148 |
+
scenario_id = hr_scenario["scenario_id"]
|
| 149 |
+
resp = requests.get(
|
| 150 |
+
f"{base_url}/v1/scenarios/{scenario_id}/tasks", headers=headers, timeout=30
|
| 151 |
+
)
|
| 152 |
+
resp.raise_for_status()
|
| 153 |
+
data = resp.json()
|
| 154 |
+
tasks = data.get("tasks", [])
|
| 155 |
+
|
| 156 |
+
if not tasks:
|
| 157 |
+
raise ValueError(f"No tasks found for scenario {scenario_id}")
|
| 158 |
+
|
| 159 |
+
if task_index is not None:
|
| 160 |
+
api_task = tasks[task_index % len(tasks)]
|
| 161 |
+
else:
|
| 162 |
+
api_task = random.choice(tasks)
|
| 163 |
+
|
| 164 |
+
return Task(
|
| 165 |
+
id=api_task.get("task_id", "api-unknown"),
|
| 166 |
+
instruction=api_task.get("description", ""),
|
| 167 |
+
difficulty=api_task.get("difficulty", "unknown"),
|
| 168 |
+
)
|