renanserrano commited on
Commit
bd67f06
·
verified ·
1 Parent(s): 6711021

Upload folder using huggingface_hub

Browse files
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: Simulationlab Hr
3
- emoji: 🏃
4
- colorFrom: gray
5
- colorTo: yellow
6
  sdk: docker
7
- pinned: false
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 &amp; 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 &mdash; <em>"schedule a phone screen"</em>, <em>"approve a leave request"</em>,
65
+ <em>"onboard a new hire"</em> &mdash; 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 &mdash; 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 &rarr;</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> &larr; 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 &mdash; <a href="https://collinear.ai">Collinear AI</a> &middot; <a href="https://github.com/collinear-ai/simlab">GitHub</a> &middot; <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
+ )