Spaces:
Paused
Paused
Sync Space with HCM-21-Private main (72f5f30): reward recalibration, seeded RNG streams, MCP integration, poster artifacts
670ccf0 verified | """MCP tool server for HCM:21 — lets an MCP client (e.g. Nous Hermes-Agent) | |
| drive the OpenEnv HR environment natively, with zero bespoke glue. | |
| Exposes the environment as five tools: | |
| hcm21_reset(seed, size, scenario) -> start an episode | |
| hcm21_step(action_type, ...) -> take one action (phase-gated) | |
| hcm21_available_actions() -> valid action_types right now | |
| hcm21_state() -> full state snapshot | |
| hcm21_score() -> final 0-1 score (once the episode ends) | |
| The env runs in-process (no HTTP), so a Hermes skill can scan -> plan -> produce | |
| -> control across 6 quarters by calling these tools in a loop, then read the | |
| final score to learn from the trajectory. | |
| Run (stdio transport, the default Hermes/MCP expects): | |
| pip install -e ".[server]" fastmcp | |
| python -m integrations.hcm21_mcp_server | |
| Register with Hermes by adding an MCP server entry pointing at that command | |
| (see RUNBOOK.md, step 5). | |
| """ | |
| from __future__ import annotations | |
| from typing import Any, Dict, List, Optional | |
| from fastmcp import FastMCP | |
| from hr_env.models import HRAction | |
| from hr_env.server.environment import HRProductivityEnvironment | |
| from hr_env.server.scoring import compute_final_score | |
| mcp = FastMCP("hcm21") | |
| # Single in-process episode per server connection. | |
| _env = HRProductivityEnvironment() | |
| _started = False | |
| _final_score: Optional[float] = None | |
| def _obs_to_dict(obs: Any) -> Dict[str, Any]: | |
| """Compact, agent-friendly view of an observation.""" | |
| return { | |
| "message": getattr(obs, "message", ""), | |
| "current_quarter": getattr(obs, "current_quarter", None), | |
| "current_phase": getattr(obs, "current_phase", None), | |
| "step_in_quarter": getattr(obs, "step_in_quarter", None), | |
| "available_actions": getattr(obs, "available_actions", []), | |
| "warnings": getattr(obs, "warnings", []), | |
| "reward": getattr(obs, "reward", None), | |
| "done": getattr(obs, "done", False), | |
| "data": getattr(obs, "data", None), | |
| } | |
| def hcm21_reset(seed: int = 42, size: int = 300, scenario: Optional[str] = None) -> Dict[str, Any]: | |
| """Start a new 6-quarter episode and return the baseline observation. | |
| scenario: one of high_eng_turnover | budget_cuts | rapid_growth | | |
| balanced_optimization, or omit for the default company. | |
| """ | |
| global _started, _final_score | |
| kwargs: Dict[str, Any] = {"size": size} | |
| if scenario: | |
| kwargs["scenario"] = scenario | |
| obs = _env.reset(seed=seed, **kwargs) | |
| _started = True | |
| _final_score = None | |
| return _obs_to_dict(obs) | |
| def hcm21_step( | |
| action_type: str, | |
| department: Optional[str] = None, | |
| employee_ids: Optional[List[str]] = None, | |
| metric_name: Optional[str] = None, | |
| amount: Optional[float] = None, | |
| count: Optional[int] = None, | |
| parameters: Optional[Dict[str, Any]] = None, | |
| rationale: Optional[str] = None, | |
| ) -> Dict[str, Any]: | |
| """Take one action. Actions are phase-gated — call hcm21_available_actions | |
| first if unsure. When the episode ends, `done` is true and the final score is | |
| available via hcm21_score(). | |
| """ | |
| global _final_score | |
| if not _started: | |
| return {"error": "Call hcm21_reset first."} | |
| action = HRAction( | |
| action_type=action_type, | |
| department=department, | |
| employee_ids=employee_ids, | |
| metric_name=metric_name, | |
| amount=amount, | |
| count=count, | |
| parameters=parameters, | |
| rationale=rationale, | |
| ) | |
| obs = _env.step(action) | |
| result = _obs_to_dict(obs) | |
| if result.get("done"): | |
| _final_score = compute_final_score(_env.state.metric_history) | |
| result["final_score"] = _final_score | |
| return result | |
| def hcm21_available_actions() -> Dict[str, Any]: | |
| """Return the action_types currently valid in the active phase.""" | |
| if not _started: | |
| return {"error": "Call hcm21_reset first."} | |
| st = _env.state | |
| return { | |
| "current_phase": st.current_phase, | |
| "current_quarter": st.current_quarter, | |
| "available_actions": _env._phase_manager.get_available_actions(), | |
| } | |
| def hcm21_state() -> Dict[str, Any]: | |
| """Return the full environment state (company snapshot, metric history, events).""" | |
| if not _started: | |
| return {"error": "Call hcm21_reset first."} | |
| st = _env.state | |
| return { | |
| "current_quarter": st.current_quarter, | |
| "current_phase": st.current_phase, | |
| "total_steps": st.total_steps, | |
| "company_snapshot": st.company_snapshot, | |
| "metric_history": st.metric_history, | |
| "budget_remaining": st.budget_remaining, | |
| "recent_events": st.recent_events, | |
| } | |
| def hcm21_score() -> Dict[str, Any]: | |
| """Return the final 0-1 episode score (only available once the episode is done).""" | |
| if _final_score is None: | |
| return {"error": "Episode not finished. Final score is computed after Q6."} | |
| return {"final_score": _final_score} | |
| if __name__ == "__main__": | |
| mcp.run() | |