openenv_jayesh / server /openenv_jayesh_environment.py
jayesh20's picture
Upload folder using huggingface_hub
f3655e3 verified
Raw
History Blame
14.2 kB
"""
Smart Personal Task Manager - OpenEnv Environment
Easy: three tasks + list; full 1.0 when at least 2 distinct priorities; else partial credit.
Medium: small +0.07 signals; deadline miss −0.15.
Hard: +0.018 per dep edge on add; rising clean completes; dep-satisfied micro-bonus; topo bonus;
deadline miss −0.15, dependency violation −0.18.
Reward clamp [0.0, 1.0].
"""
from __future__ import annotations
from datetime import date
from typing import Any, Dict, List, Optional
from uuid import uuid4
from openenv.core.env_server.interfaces import Environment
from openenv.core.env_server.types import State
try:
from ..models import TaskManagerAction, TaskManagerObservation
except (ModuleNotFoundError, ImportError):
from models import TaskManagerAction, TaskManagerObservation
VALID_PRIORITIES = {"Low", "Normal", "High"}
# Fair penalties (requested ranges)
MEDIUM_DEADLINE_MISS_PENALTY = 0.15 # -0.12 … -0.18
HARD_DEADLINE_MISS_PENALTY = 0.15 # -0.12 … -0.18
HARD_DEP_VIOLATION_PENALTY = 0.18 # -0.15 … -0.20
def _parse_date(s: Optional[str]) -> Optional[date]:
if not s:
return None
try:
return date.fromisoformat(s)
except ValueError:
return None
def _today() -> date:
return date.today()
class OpenenvJayeshEnvironment(Environment):
"""Task Manager with Easy / Medium / Hard difficulty levels."""
SUPPORTS_CONCURRENT_SESSIONS: bool = True
def __init__(self) -> None:
super().__init__()
self._reset_count = 0
self._scenarios = ["Easy", "Medium", "Hard"]
self._state = State(episode_id=str(uuid4()), step_count=0)
self._init_state()
def _init_state(self) -> None:
self.tasks: List[Dict[str, Any]] = []
self.difficulty = "Easy"
self.goal_completed = False
self.list_called = False
self.violations: List[str] = []
self.tasks_added = 0
self.high_added = 0
self.high_on_time = 0
self.clean_completions = 0
self.deadline_misses = 0
self.dep_violations = 0
self._r_add = 0.0
self._r_priority = 0.0
self._r_deadline_set = 0.0
self._r_complete = 0.0
self._r_hard_dep_ok = 0.0
self._r_hard_edge = 0.0
def reset(
self,
seed: Optional[int] = None,
episode_id: Optional[str] = None,
**kwargs: Any,
) -> TaskManagerObservation:
self._state = State(episode_id=episode_id or str(uuid4()), step_count=0)
self._init_state()
self.difficulty = self._scenarios[self._reset_count % 3]
self._reset_count += 1
msgs = {
"Easy": (
"EASY MODE\n"
"Goal: Add 3 tasks with at least 2 different priorities, then call 'list'.\n"
"Full score (1.0) when 3 tasks + list + 2+ priority levels; "
"otherwise partial credit if you list without enough variety."
),
"Medium": (
"MEDIUM MODE\n"
"Goal: Add 4 tasks with priorities and deadlines. Every High task on time.\n"
"Rewards: small steps (~+0.07 per signal) for smooth partial credit.\n"
f"Penalty: -{MEDIUM_DEADLINE_MISS_PENALTY:.2f} per deadline miss."
),
"Hard": (
"HARD MODE\n"
"Goal: 5 tasks with deps + deadlines; valid order; no misses.\n"
"Rewards: tiny edge credit when linking deps on add; rising clean completes; "
"small bonus when deps satisfied at complete.\n"
f"Penalties: -{HARD_DEP_VIOLATION_PENALTY:.2f} dependency, -{HARD_DEADLINE_MISS_PENALTY:.2f} deadline miss."
),
}
return TaskManagerObservation(
success=True,
message=msgs[self.difficulty],
tasks=[],
violations=[],
done=False,
reward=0.0,
)
def step(
self,
action: TaskManagerAction,
timeout_s: Optional[float] = None,
**kwargs: Any,
) -> TaskManagerObservation:
self._state.step_count += 1
cmd = (action.command or "").strip().lower()
if cmd == "add":
success, msg = self._add(action)
elif cmd == "complete":
success, msg = self._complete(action)
elif cmd == "list":
self.list_called = True
success, msg = True, self._fmt_list()
else:
success, msg = False, f"Unknown command '{cmd}'. Use: add | complete | list."
reward = self._score()
return TaskManagerObservation(
success=success,
message=msg,
tasks=list(self.tasks),
violations=list(self.violations),
done=self.goal_completed,
reward=reward,
metadata={
"difficulty": self.difficulty,
"step": self._state.step_count,
"tasks_added": self.tasks_added,
"clean_completions": self.clean_completions,
"dep_violations": self.dep_violations,
"deadline_misses": self.deadline_misses,
},
)
def _add(self, action: TaskManagerAction):
title = (action.title or "").strip()
if not title:
return False, "Error: 'title' is required for add."
if any(t["title"] == title for t in self.tasks):
return False, f"Error: task '{title}' already exists."
priority = (action.priority or "Normal").strip()
if priority not in VALID_PRIORITIES:
priority = "Normal"
deadline_str = (action.deadline or "").strip() or None
depends_on: List[str] = []
for dep in (action.depends_on or []):
dep = dep.strip()
if not dep:
continue
if not any(t["title"] == dep for t in self.tasks):
return False, f"Error: dependency '{dep}' not found. Add it first."
depends_on.append(dep)
task: Dict[str, Any] = {
"title": title,
"priority": priority,
"deadline": deadline_str,
"depends_on": depends_on,
"completed": False,
"deadline_missed": False,
"dep_violation": False,
}
self.tasks.append(task)
self.tasks_added += 1
if priority == "High":
self.high_added += 1
if self.difficulty == "Medium":
# Smaller per-signal steps (~0.07) so scores rise gradually (avoid large single-step jumps)
self._r_add += 0.07
if priority != "Normal":
self._r_priority += 0.07
if deadline_str:
self._r_deadline_set += 0.07
elif self.difficulty == "Hard":
# Slightly smaller per-signal steps than Medium to smooth the last adds (e.g. Deploy)
inc = 0.062
self._r_add += inc
if priority != "Normal":
self._r_priority += inc
if deadline_str:
self._r_deadline_set += inc
if depends_on:
self._r_hard_edge += 0.012 * float(len(depends_on))
parts = [f"Task '{title}' added (priority={priority}"]
if deadline_str:
parts[0] += f", deadline={deadline_str}"
if depends_on:
parts[0] += f", depends_on={depends_on}"
parts[0] += ")."
return True, " ".join(parts)
def _complete(self, action: TaskManagerAction):
title = (action.title or "").strip()
if not title:
return False, "Error: 'title' is required for complete."
task = next((t for t in self.tasks if t["title"] == title), None)
if task is None:
return False, f"Error: task '{title}' not found."
if task["completed"]:
return False, f"Task '{title}' is already completed."
msgs = []
dep_viol = False
dl_miss = False
unmet = [
d for d in task["depends_on"]
if not any(t["title"] == d and t["completed"] for t in self.tasks)
]
if unmet:
dep_viol = True
task["dep_violation"] = True
self.dep_violations += 1
v = f"DEP VIOLATION: '{title}' completed before {unmet}."
self.violations.append(v)
msgs.append("(!) Dependency violation — penalty applied.")
dl = _parse_date(task["deadline"])
if dl and _today() > dl:
dl_miss = True
task["deadline_missed"] = True
self.deadline_misses += 1
v = f"DEADLINE MISSED: '{title}' was due {task['deadline']} (completed after deadline)."
self.violations.append(v)
msgs.append("(!) Deadline missed — penalty applied.")
if self.difficulty == "Medium":
msgs.append(
f"Score impact (Medium): -{MEDIUM_DEADLINE_MISS_PENALTY:.2f} for this miss "
f"(total deadline-miss penalty: "
f"{self.deadline_misses * MEDIUM_DEADLINE_MISS_PENALTY:.2f})."
)
elif self.difficulty == "Hard":
msgs.append(
f"Score impact (Hard): -{HARD_DEADLINE_MISS_PENALTY:.2f} for this miss "
f"(total: {self.deadline_misses * HARD_DEADLINE_MISS_PENALTY:.2f})."
)
task["completed"] = True
if not dep_viol and not dl_miss:
order_idx = self.clean_completions
self.clean_completions += 1
if task["priority"] == "High":
self.high_on_time += 1
msgs.append(f"Task '{title}' completed cleanly.")
if self.difficulty == "Medium":
if task["priority"] == "High":
self._r_complete += 0.09
elif self.difficulty == "Hard":
# Rising reward each clean step; tiny extra when prerequisites were satisfied
step_r = 0.065 + 0.012 * float(order_idx)
self._r_complete += min(0.125, step_r)
if task["depends_on"]:
self._r_hard_dep_ok += 0.025
else:
msgs.append(f"Task '{title}' completed with violations.")
return True, " ".join(msgs)
def _score(self) -> float:
if self.difficulty == "Easy":
return self._score_easy()
if self.difficulty == "Medium":
return self._score_medium()
return self._score_hard()
def _easy_has_two_priorities(self) -> bool:
if len(self.tasks) < 3:
return False
return len({t["priority"] for t in self.tasks}) >= 2
def _score_easy(self) -> float:
# Partial: ~+0.11/add, ~+0.12/list. Full 1.0 + done only with 2+ distinct priorities + list.
r = min(3, self.tasks_added) * 0.11
if self.list_called:
r += 0.12
if self.tasks_added >= 3 and self.list_called:
if self._easy_has_two_priorities():
r += 0.55
self.goal_completed = True
else:
r += 0.40
return round(min(1.0, r), 3)
def _score_medium(self) -> float:
# Softer caps match smaller increments (4×0.07 ≈ 0.28 per bucket)
r = min(0.28, self._r_add)
r += min(0.28, self._r_priority)
r += min(0.28, self._r_deadline_set)
r += min(0.22, self._r_complete)
r -= self.deadline_misses * MEDIUM_DEADLINE_MISS_PENALTY
high_tasks = [t for t in self.tasks if t["priority"] == "High"]
all_high_on_time = (
len(high_tasks) >= 1
and all(t["completed"] and not t["deadline_missed"] for t in high_tasks)
)
if self.tasks_added >= 4 and all_high_on_time and self.deadline_misses == 0:
self.goal_completed = True
return 1.0
return round(min(0.94, max(0.0, r)), 3)
def _score_hard(self) -> float:
# Caps aligned with 0.062 increments (~0.31 over 5 adds) to avoid one huge step on task 5
r = min(0.34, self._r_add)
r += min(0.26, self._r_priority)
r += min(0.26, self._r_deadline_set)
r += min(0.42, self._r_complete)
r += min(0.14, self._r_hard_dep_ok)
r += min(0.08, self._r_hard_edge)
topo_bonus = 0.0
if self.clean_completions == 5 and self.dep_violations == 0:
topo_bonus = 0.055
r += topo_bonus
r -= self.dep_violations * HARD_DEP_VIOLATION_PENALTY
r -= self.deadline_misses * HARD_DEADLINE_MISS_PENALTY
all_done = self.tasks_added >= 5 and all(t["completed"] for t in self.tasks)
if all_done and self.dep_violations == 0 and self.deadline_misses == 0:
self.goal_completed = True
return 1.0
return round(min(0.99, max(0.0, r)), 3)
def _fmt_list(self) -> str:
if not self.tasks:
return "No tasks yet."
lines = [f"Tasks ({len(self.tasks)}):"]
for i, t in enumerate(self.tasks, 1):
status = "DONE" if t["completed"] else "PENDING"
flags = []
if t.get("deadline_missed"):
flags.append("LATE")
if t.get("dep_violation"):
flags.append("DEP-ERR")
flag_str = f" [{','.join(flags)}]" if flags else ""
dl = f" due={t['deadline']}" if t["deadline"] else ""
dep = f" deps={t['depends_on']}" if t["depends_on"] else ""
lines.append(f" {i}. [{status}]{flag_str} {t['title']} ({t['priority']}{dl}{dep})")
return "\n".join(lines)
@property
def state(self) -> State:
return self._state