jayesh20 commited on
Commit
f3655e3
·
verified ·
1 Parent(s): 0023e8b

Upload folder using huggingface_hub

Browse files
Files changed (4) hide show
  1. README.md +106 -190
  2. inference.py +109 -111
  3. server/Dockerfile +80 -80
  4. server/openenv_jayesh_environment.py +231 -320
README.md CHANGED
@@ -1,248 +1,164 @@
1
  ---
2
- title: OpenEnv Jayesh - Smart Personal Task Manager
3
- colorFrom: indigo
4
- colorTo: purple
5
  sdk: docker
6
  pinned: false
7
  app_port: 8000
8
  tags:
9
  - openenv
10
  - task-manager
11
- - ai-agent
12
- - planning
13
  base_path: /web
14
  ---
15
 
16
- # Smart Personal Task Manager - OpenEnv Jayesh
17
 
18
- > An AI agent environment for managing tasks with priorities, deadlines, and dependencies -- built for the **OpenEnv Hackathon Round 1**.
19
 
20
- - HF Space: https://huggingface.co/spaces/jayesh20/openenv_jayesh
21
- - Python 3.10+
22
 
23
  ---
24
 
25
- ## What is this?
26
 
27
- A **real-world Task Manager environment** where an AI agent must add, prioritize, and complete tasks while respecting deadlines and dependency constraints. The environment cycles through three meaningfully distinct difficulty levels -- each demanding progressively more sophisticated planning.
 
 
 
 
28
 
29
- This environment targets real-world utility: the kind of task scheduling problems that users, productivity apps, and organizational tools deal with every day.
30
 
31
  ---
32
 
33
- ## Difficulty Levels
34
-
35
- | Level | Goal | Key Constraints |
36
- |-------|------|-----------------|
37
- | **Easy** | Add 2-3 tasks, then `list` them | None -- basic task CRUD |
38
- | **Medium** | Add 4 tasks with priorities & deadlines; complete all High-priority before deadline | Deadline enforcement, priority management |
39
- | **Hard** | Add 5 tasks with priorities, deadlines, AND dependencies; complete in valid topological order | Dependency ordering + deadline enforcement + penalty accumulation |
40
-
41
- ---
42
-
43
- ## Action Space
44
 
45
  ```python
46
  TaskManagerAction(
47
- command = "add", # "add" | "complete" | "list"
48
- title = "Fix critical bug", # required for add / complete
49
- priority = "High", # "Low" | "Normal" | "High" (default: "Normal")
50
- deadline = "2026-04-15", # ISO-8601 date (optional; relevant in Medium & Hard)
51
- depends_on= ["Reproduce bug"] # list of prerequisite task titles (Hard only)
52
  )
53
  ```
54
 
55
- ### Commands
56
- | Command | Description |
57
- |---------|-------------|
58
- | `add` | Create a new task. Returns error if title already exists or dependency is unresolved. |
59
- | `complete` | Mark a task as done. Checks deadline & dependency constraints and applies penalties. |
60
- | `list` | Display all current tasks with status, priority, deadline, and dependency info. |
61
-
62
- ---
63
-
64
- ## Observation Space
65
 
66
  ```python
67
  TaskManagerObservation(
68
- success = True, # whether the last action succeeded
69
- message = "Task 'Fix bug' added", # status message or error description
70
- tasks = [...], # full task list snapshot
71
- violations = [...], # list of rule violations this episode
72
- reward = 0.45, # cumulative partial reward (0.0-1.0)
73
- done = False, # True when episode goal is achieved
74
- metadata = {
75
- "difficulty": "Hard",
76
- "step": 7,
77
- "tasks_added": 5,
78
- "tasks_completed": 3,
79
- "deadline_misses": 0,
80
- "dependency_violations": 0
81
- }
82
  )
83
  ```
84
 
85
- ### Task Object Fields
86
- | Field | Type | Description |
87
- |-------|------|-------------|
88
- | `title` | str | Task name |
89
- | `priority` | str | `"Low"` / `"Normal"` / `"High"` |
90
- | `deadline` | str | ISO-8601 date or `"none"` |
91
- | `depends_on` | list[str] | Prerequisite task titles |
92
- | `completed` | bool | Whether the task is done |
93
- | `deadline_missed` | bool | True if completed after deadline |
94
- | `dependency_violation` | bool | True if completed before all prerequisites |
95
-
96
  ---
97
 
98
- ## Reward Function
99
-
100
- ### Easy Mode
101
- | Event | Reward |
102
- |-------|--------|
103
- | Each task added (up to 3) | +0.15 |
104
- | Calling `list` | +0.20 |
105
- | **Goal: >=2 tasks added + list called** | **1.0** |
106
-
107
- ### Medium Mode
108
- | Event | Reward |
109
- |-------|--------|
110
- | Each task added (up to 4) | +0.15 |
111
- | Each task with explicit non-Normal priority | +0.10 |
112
- | Each High-priority task completed **on time** | +0.20 |
113
- | Deadline missed | **-0.25** |
114
- | **Goal: 4 tasks, >=2 High, all High completed on time** | **1.0** |
115
-
116
- ### Hard Mode
117
- | Event | Reward |
118
- |-------|--------|
119
- | Each task added (up to 5) | +0.15 |
120
- | Each task with non-Normal priority | +0.10 |
121
- | Each task completed without any violation | +0.25 |
122
- | Perfect run bonus (all done, zero violations) | **+0.10** |
123
- | Dependency violation | **-0.30** |
124
- | Deadline missed | **-0.25** |
125
- | **Goal: 5 tasks, >=2 High, all completed, zero violations** | **1.0** |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
  ---
128
 
129
- ## Quick Start
130
 
131
  ```bash
132
- # Install dependencies
133
  uv sync
134
-
135
- # Start the server
136
  uvicorn server.app:app --host 127.0.0.1 --port 8000
137
-
138
- # In another terminal, run the inference demo
139
  python inference.py
140
  ```
141
 
142
- ---
143
-
144
- ## Usage Examples
145
-
146
- ### Easy Mode
147
- ```python
148
- env = OpenenvJayeshEnvironment()
149
- obs = env.reset() # cycles to Easy
150
-
151
- env.step(TaskManagerAction(command="add", title="Buy groceries", priority="Normal"))
152
- # reward: 0.15
153
-
154
- env.step(TaskManagerAction(command="add", title="Call dentist", priority="Low"))
155
- # reward: 0.30
156
-
157
- obs = env.step(TaskManagerAction(command="list"))
158
- # reward: 1.0, done: True
159
- ```
160
-
161
- ### Medium Mode
162
- ```python
163
- obs = env.reset() # cycles to Medium
164
-
165
- env.step(TaskManagerAction(command="add", title="Fix critical bug", priority="High", deadline="2026-04-15"))
166
- env.step(TaskManagerAction(command="add", title="Deploy hotfix", priority="High", deadline="2026-04-16"))
167
- env.step(TaskManagerAction(command="add", title="Write release notes", priority="Normal", deadline="2026-04-22"))
168
- env.step(TaskManagerAction(command="add", title="Team prep", priority="Low"))
169
-
170
- env.step(TaskManagerAction(command="complete", title="Fix critical bug")) # +0.20 on-time
171
- obs = env.step(TaskManagerAction(command="complete", title="Deploy hotfix")) # +0.20 -> done=True, reward=1.0
172
- ```
173
-
174
- ### Hard Mode (with dependencies)
175
- ```python
176
- obs = env.reset() # cycles to Hard
177
-
178
- env.step(TaskManagerAction(command="add", title="Reproduce bug", priority="High", deadline="2026-04-15"))
179
- env.step(TaskManagerAction(command="add", title="Write tests", priority="Normal", deadline="2026-04-16"))
180
- env.step(TaskManagerAction(command="add", title="Write fix", priority="High", deadline="2026-04-18",
181
- depends_on=["Reproduce bug"]))
182
- env.step(TaskManagerAction(command="add", title="Code review", priority="Normal", deadline="2026-04-20",
183
- depends_on=["Write fix", "Write tests"]))
184
- env.step(TaskManagerAction(command="add", title="Deploy", priority="Low", deadline="2026-04-22",
185
- depends_on=["Code review"]))
186
-
187
- # Complete in valid topological order
188
- env.step(TaskManagerAction(command="complete", title="Reproduce bug")) # no deps
189
- env.step(TaskManagerAction(command="complete", title="Write tests")) # no deps
190
- env.step(TaskManagerAction(command="complete", title="Write fix")) # dep met
191
- env.step(TaskManagerAction(command="complete", title="Code review")) # deps met
192
- obs = env.step(TaskManagerAction(command="complete", title="Deploy")) # done=True, reward=1.0 + bonus
193
- ```
194
-
195
- ---
196
-
197
- ## Environment Design Rationale
198
-
199
- ### Why these three levels?
200
- - **Easy** establishes baseline task CRUD competency -- can the agent perform basic operations?
201
- - **Medium** adds time pressure and priority trade-offs -- a realistic proxy for real project management.
202
- - **Hard** requires multi-step planning with constraint satisfaction -- approximates real dependency scheduling (e.g., CI/CD pipelines, project Gantt charts).
203
-
204
- ### Why partial rewards?
205
- Smooth, dense reward signals (+0.15 per task, +0.10 per priority, etc.) enable reinforcement learning agents to make meaningful progress even without solving the full episode. This is superior to sparse reward environments where only terminal success counts.
206
-
207
- ### Why penalties?
208
- - Deadline misses (-0.25) discourage agents from completing tasks arbitrarily late.
209
- - Dependency violations (-0.30) teach agents that **order matters** -- a fundamental property of real-world task graphs.
210
-
211
- ---
212
-
213
- ## API Endpoints
214
 
215
  | URL | Description |
216
  |-----|-------------|
217
- | `GET /health` | Health check |
218
- | `POST /reset` | Start a new episode |
219
- | `POST /step` | Execute an action |
220
- | `GET /state` | Current episode metadata |
221
- | `GET /docs` | Interactive Swagger UI |
222
 
223
- ---
224
-
225
- ## Project Structure
226
 
227
  ```
228
  openenv_jayesh/
229
- +-- Dockerfile
230
- +-- openenv.yaml
231
- +-- pyproject.toml
232
- +-- models.py <- Action + Observation types
233
- +-- client.py <- HTTP client helper
234
- +-- inference.py <- End-to-end demo (all 3 levels)
235
- +-- server/
236
- +-- app.py <- FastAPI app entry point
237
- +-- openenv_jayesh_environment.py <- Core environment logic
 
238
  ```
239
 
240
- ---
241
-
242
  ## Deploy
243
 
244
  ```bash
245
  openenv push --repo-id jayesh20/openenv_jayesh
246
  ```
247
-
248
- Live space: https://huggingface.co/spaces/jayesh20/openenv_jayesh
 
1
  ---
2
+ title: OpenEnv Jayesh - Task Manager
3
+ colorFrom: blue
4
+ colorTo: green
5
  sdk: docker
6
  pinned: false
7
  app_port: 8000
8
  tags:
9
  - openenv
10
  - task-manager
 
 
11
  base_path: /web
12
  ---
13
 
14
+ # OpenEnv Jayesh - Task Manager Environment
15
 
16
+ An AI agent environment for managing tasks across three difficulty levels.
17
 
18
+ HF Space: https://huggingface.co/spaces/jayesh20/openenv_jayesh
 
19
 
20
  ---
21
 
22
+ ## Difficulty levels
23
 
24
+ | Level | Goal | Scoring (summary) |
25
+ |-------|------|---------------------|
26
+ | **Easy** | Add **3** tasks with **at least two different** priority levels among them, then call **`list`** so all current tasks are shown. | Partial credit builds with **~+0.11** per add and **~+0.12** for `list`. **Full reward (1.0)** and **`done=True`** only when there are **3 tasks**, **`list`** was called, and **at least two distinct** priorities appear. If you `list` with three tasks but **only one** priority level, you get a **lower** goal bonus (no full 1.0 / no episode success). |
27
+ | **Medium** | Add **4** tasks, each with a **priority** and **deadline**. **Complete every High-priority task on time** (on or before its deadline). At least **one** High task must exist. | **Gradual** partial credit: small **~+0.07** steps for add / non-Normal / deadline (capped buckets), plus credit for **High** completions on time. **−0.15** per **deadline miss**. **`done=True`** and **1.0** when four tasks are in place, all Highs are done **without** being late, and there are **no** deadline misses. |
28
+ | **Hard** | Add **5** tasks with **priorities**, **deadlines**, and **dependencies**. Complete **all** tasks in valid **topological** order and **respect every deadline** (no dependency violations, no missed deadlines). | **Adds** use slightly smaller per-field steps than Medium (for smoother score), plus **~+0.012** per **dependency edge** when you set `depends_on`. **Clean completes** rise in steps, with a small extra when **dependencies were satisfied**; **topological bonus** when all five finish cleanly. **−0.15** per deadline miss, **−0.18** per dependency violation. **`done=True`** and **1.0** on a perfect run. |
29
 
30
+ Penalties are constants in `server/openenv_jayesh_environment.py`. Reward stays in **`[0.0, 1.0]`**.
31
 
32
  ---
33
 
34
+ ## Action space
 
 
 
 
 
 
 
 
 
 
35
 
36
  ```python
37
  TaskManagerAction(
38
+ command="add", # "add" | "complete" | "list"
39
+ title="Fix bug",
40
+ priority="High",
41
+ deadline="2026-04-01",
42
+ depends_on=["Other task"],
43
  )
44
  ```
45
 
46
+ ## Observation space
 
 
 
 
 
 
 
 
 
47
 
48
  ```python
49
  TaskManagerObservation(
50
+ success=True,
51
+ message="...",
52
+ tasks=[...],
53
+ violations=[...],
54
+ reward=0.0,
55
+ done=False,
 
 
 
 
 
 
 
 
56
  )
57
  ```
58
 
 
 
 
 
 
 
 
 
 
 
 
59
  ---
60
 
61
+ ## Sample inference output
62
+
63
+ ```
64
+ ======================================================================
65
+ Task Manager OpenEnv - Inference Runner
66
+ ======================================================================
67
+
68
+ ======================================================================
69
+ EASY MODE (perfect: 3 tasks + list)
70
+ ======================================================================
71
+ Goal: EASY MODE
72
+
73
+ add 'Buy groceries' Low | score=0.110
74
+ add 'Call dentist' Normal | score=0.220
75
+ add 'Review PR' High | score=0.330
76
+ list (shows all tasks) | score=1.000 [DONE]
77
+
78
+ ======================================================================
79
+ MEDIUM MODE (one deadline miss on a High task)
80
+ ======================================================================
81
+ Goal: MEDIUM MODE
82
+
83
+ add 'Fix critical bug' High deadline=future | score=0.210
84
+ add 'Deploy hotfix' High deadline=PAST | score=0.420
85
+ add 'Write release notes' Normal | score=0.560
86
+ add 'Standup prep' Low | score=0.770
87
+ complete 'Fix critical bug' [on-time] | score=0.860
88
+ complete 'Deploy hotfix' [DEADLINE MISSED] | score=0.710
89
+ (!) DEADLINE MISSED: 'Deploy hotfix' was due 2020-01-01 (completed after deadline).
90
+ >> Score impact (Medium): -0.15 for this miss (total deadline-miss penalty: 0.15).
91
+
92
+ ======================================================================
93
+ HARD MODE (perfect topological order, all deadlines met)
94
+ ======================================================================
95
+ Goal: HARD MODE
96
+
97
+ add 'Reproduce bug' High | score=0.186
98
+ add 'Write tests' Normal | score=0.310
99
+ add 'Write fix' High (dep: Reproduce bug) | score=0.508
100
+ add 'Code review' (dep: Write fix, Write tests) | score=0.656
101
+ add 'Deploy' (dep: Code review) | score=0.804
102
+
103
+ complete 'Reproduce bug' | score=0.869
104
+ complete 'Write tests' | score=0.946
105
+ complete 'Write fix' | score=0.990
106
+ complete 'Code review' | score=0.990
107
+ complete 'Deploy' [goal] | score=1.000 [DONE]
108
+
109
+ -- Violation demo (fresh Hard episode) --
110
+ add 'Task A' High | score=0.186
111
+ add 'Task B' (dep: Task A) | score=0.322
112
+ WRONG: complete 'Task B' before 'Task A' | score=0.142
113
+ (!) DEP VIOLATION: 'Task B' completed before ['Task A'].
114
+ Score after dep violation: 0.188 (penalty applied)
115
+
116
+ ======================================================================
117
+ EASY final score : 1.000
118
+ MEDIUM final score : 0.710 (deadline miss penalty)
119
+ HARD final score : 1.000
120
+ AVERAGE : 0.903
121
+ ======================================================================
122
+ ```
123
 
124
  ---
125
 
126
+ ## Quick start
127
 
128
  ```bash
 
129
  uv sync
 
 
130
  uvicorn server.app:app --host 127.0.0.1 --port 8000
 
 
131
  python inference.py
132
  ```
133
 
134
+ ## API endpoints
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
 
136
  | URL | Description |
137
  |-----|-------------|
138
+ | http://127.0.0.1:8000/docs | Swagger UI |
139
+ | http://127.0.0.1:8000/health | Health check |
140
+ | POST /reset | Start new episode |
141
+ | POST /step | Execute action |
142
+ | GET /state | Current episode state |
143
 
144
+ ## Project structure
 
 
145
 
146
  ```
147
  openenv_jayesh/
148
+ ├── Dockerfile
149
+ ├── openenv.yaml
150
+ ├── pyproject.toml
151
+ ├── models.py
152
+ ├── client.py
153
+ ├── inference.py
154
+ └── server/
155
+ ├── app.py
156
+ ├── openenv_jayesh_environment.py
157
+ └── requirements.txt
158
  ```
159
 
 
 
160
  ## Deploy
161
 
162
  ```bash
163
  openenv push --repo-id jayesh20/openenv_jayesh
164
  ```
 
 
inference.py CHANGED
@@ -1,7 +1,11 @@
1
  """
2
- inference.py - Demonstration of the Smart Personal Task Manager OpenEnv.
3
 
4
- Runs all three difficulty scenarios and prints step-by-step reward signals.
 
 
 
 
5
  """
6
 
7
  import sys
@@ -9,139 +13,133 @@ import os
9
 
10
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
11
 
12
- from client import OpenenvJayeshEnv
13
  from models import TaskManagerAction
14
 
15
- # ---------------------------------------------------------------------------
16
- # Helpers
17
- # ---------------------------------------------------------------------------
18
-
19
- def _step(client, action: TaskManagerAction, label: str):
20
- res = client.step(action)
21
- obs = res.observation
22
- done_tag = " [DONE]" if res.done else ""
23
- violation_tag = ""
24
- if hasattr(obs, "violations") and obs.violations:
25
- violation_tag = f"\n Violations: {obs.violations[-1]}"
26
- print(
27
- f" {label:<40} | Score: {res.reward:.3f}{done_tag}"
28
- f"\n -> {obs.message}{violation_tag}"
29
- )
30
- return res
31
-
32
 
33
- # ---------------------------------------------------------------------------
34
- # Easy scenario
35
- # ---------------------------------------------------------------------------
36
-
37
- def run_easy(client):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  print("\n" + "=" * 70)
39
- print("SCENARIO 1 - EASY MODE")
40
  print("=" * 70)
41
- res = client.reset()
42
- print(f"\nGoal:\n{res.observation.message}\n")
43
-
44
- today = "2026-04-15"
45
 
46
- _step(client, TaskManagerAction(command="add", title="Buy groceries", priority="Normal", deadline=today), "add 'Buy groceries' Normal")
47
- _step(client, TaskManagerAction(command="add", title="Call the dentist", priority="Low"), "add 'Call the dentist' Low")
48
- _step(client, TaskManagerAction(command="add", title="Review meeting notes", priority="Normal"), "add 'Review meeting notes'")
49
- _step(client, TaskManagerAction(command="list"), "list (triggers goal completion)")
 
50
 
51
- print()
52
 
53
-
54
- # ---------------------------------------------------------------------------
55
- # Medium scenario
56
- # ---------------------------------------------------------------------------
57
-
58
- def run_medium(client):
59
  print("\n" + "=" * 70)
60
- print("SCENARIO 2 - MEDIUM MODE")
61
  print("=" * 70)
62
- res = client.reset()
63
- print(f"\nGoal:\n{res.observation.message}\n")
64
-
65
- # Use future deadlines so completions are on-time
66
- today = "2026-04-15"
67
- tomorrow = "2026-04-16"
68
- next_week = "2026-04-22"
69
-
70
- _step(client, TaskManagerAction(command="add", title="Fix critical bug", priority="High", deadline=today), "add 'Fix critical bug' High")
71
- _step(client, TaskManagerAction(command="add", title="Deploy hotfix", priority="High", deadline=tomorrow), "add 'Deploy hotfix' High")
72
- _step(client, TaskManagerAction(command="add", title="Write release notes", priority="Normal", deadline=next_week), "add 'Write release notes' Normal")
73
- _step(client, TaskManagerAction(command="add", title="Team standup prep", priority="Low"), "add 'Team standup prep' Low")
74
-
75
- # Complete High-priority tasks on time
76
- _step(client, TaskManagerAction(command="complete", title="Fix critical bug"), "complete 'Fix critical bug' [High, on-time]")
77
- _step(client, TaskManagerAction(command="complete", title="Deploy hotfix"), "complete 'Deploy hotfix' [High, on-time -> GOAL]")
78
 
79
- print()
 
80
 
 
 
 
 
 
 
 
81
 
82
- # ---------------------------------------------------------------------------
83
- # Hard scenario
84
- # ---------------------------------------------------------------------------
85
 
86
- def run_hard(client):
87
  print("\n" + "=" * 70)
88
- print("SCENARIO 3 - HARD MODE (dependencies + deadlines)")
89
  print("=" * 70)
90
- res = client.reset()
91
- print(f"\nGoal:\n{res.observation.message}\n")
92
-
93
- t1 = "2026-04-15"
94
- t2 = "2026-04-16"
95
- t3 = "2026-04-18"
96
- t4 = "2026-04-20"
97
- t5 = "2026-04-22"
98
-
99
- # Build dependency graph:
100
- # Reproduce bug ---> Write fix ---> Code review ---> Deploy
101
- # Write tests ---> Code review
102
- _step(client, TaskManagerAction(command="add", title="Reproduce bug", priority="High", deadline=t1), "add 'Reproduce bug' High")
103
- _step(client, TaskManagerAction(command="add", title="Write tests", priority="Normal", deadline=t2), "add 'Write tests' Normal")
104
- _step(client, TaskManagerAction(command="add", title="Write fix", priority="High", deadline=t3, depends_on=["Reproduce bug"]), "add 'Write fix' High (depends: Reproduce bug)")
105
- _step(client, TaskManagerAction(command="add", title="Code review", priority="Normal", deadline=t4, depends_on=["Write fix", "Write tests"]), "add 'Code review' Normal (depends: Write fix, Write tests)")
106
- _step(client, TaskManagerAction(command="add", title="Deploy to production", priority="Low", deadline=t5, depends_on=["Code review"]), "add 'Deploy to prod' Low (depends: Code review)")
 
 
 
 
 
 
 
107
 
108
  print()
109
- print(" -- Completing in CORRECT topological order --")
110
- _step(client, TaskManagerAction(command="complete", title="Reproduce bug"), "complete 'Reproduce bug' [no deps, on-time]")
111
- _step(client, TaskManagerAction(command="complete", title="Write tests"), "complete 'Write tests' [no deps, on-time]")
112
- _step(client, TaskManagerAction(command="complete", title="Write fix"), "complete 'Write fix' [dep met, on-time]")
113
- _step(client, TaskManagerAction(command="complete", title="Code review"), "complete 'Code review' [deps met, on-time]")
114
- _step(client, TaskManagerAction(command="complete", title="Deploy to production"), "complete 'Deploy to prod' [all deps met -> GOAL]")
115
 
116
  print()
117
- print(" -- Violations demo (new reset, same Hard mode) --")
118
- res = client.reset() # same Hard cycle
119
- print(f" (Re-reset into: {res.observation.message.splitlines()[0]})")
120
-
121
- _step(client, TaskManagerAction(command="add", title="Task A", priority="High", deadline=t1), "add 'Task A' High")
122
- _step(client, TaskManagerAction(command="add", title="Task B", priority="Normal", deadline=t2, depends_on=["Task A"]), "add 'Task B' Normal (depends: Task A)")
123
- res = _step(client, TaskManagerAction(command="complete", title="Task B"), "WRONG: complete 'Task B' before 'Task A'")
124
- print(f" Score after violation: {res.reward:.3f}")
125
 
126
- print()
127
 
128
 
129
- # ---------------------------------------------------------------------------
130
- # Entry point
131
- # ---------------------------------------------------------------------------
132
-
133
  if __name__ == "__main__":
134
  print("=" * 70)
135
- print("Smart Personal Task Manager - OpenEnv Inference Demo")
136
- print("Connecting to http://127.0.0.1:8000 ...")
137
  print("=" * 70)
138
 
139
- try:
140
- with OpenenvJayeshEnv(base_url="http://127.0.0.1:8000").sync() as client:
141
- run_easy(client)
142
- run_medium(client)
143
- run_hard(client)
144
- print("\nAll scenarios completed successfully.")
145
- except Exception as e:
146
- print(f"\nError: Could not connect to server. Is it running?\n {e}")
147
- sys.exit(1)
 
 
 
 
1
  """
2
+ inference.py - Task Manager OpenEnv standalone test runner.
3
 
4
+ Runs three scenarios (no server). Easy and Hard show successful runs; Medium shows one deadline miss.
5
+ Hard includes a separate violation demo episode.
6
+
7
+ Usage:
8
+ python inference.py
9
  """
10
 
11
  import sys
 
13
 
14
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
15
 
16
+ from server.openenv_jayesh_environment import OpenenvJayeshEnvironment
17
  from models import TaskManagerAction
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
+ def step(env, action: TaskManagerAction, label: str):
21
+ obs = env.step(action)
22
+ done_tag = " [DONE]" if obs.done else ""
23
+ print(f" {label:<50} | score={obs.reward:.3f}{done_tag}")
24
+ msg = obs.message or ""
25
+ if obs.violations and (
26
+ "(!)" in msg
27
+ or "Score impact (Medium):" in msg
28
+ or "Score impact (Hard):" in msg
29
+ or "Dependency violation" in msg
30
+ ):
31
+ print(f" (!) {obs.violations[-1]}")
32
+ for marker in ("Score impact (Medium):", "Score impact (Hard):"):
33
+ if marker in msg:
34
+ sub = msg[msg.index(marker) :]
35
+ if "). Task" in sub:
36
+ line = sub.split("). Task", 1)[0] + ")."
37
+ else:
38
+ line = sub.strip()
39
+ print(f" >> {line}")
40
+ break
41
+ return obs
42
+
43
+
44
+ def run_easy():
45
  print("\n" + "=" * 70)
46
+ print("EASY MODE (perfect: 3 tasks + list)")
47
  print("=" * 70)
48
+ env = OpenenvJayeshEnvironment()
49
+ obs = env.reset()
50
+ print(f"Goal: {obs.message.splitlines()[0]}\n")
 
51
 
52
+ step(env, TaskManagerAction(command="add", title="Buy groceries", priority="Low"), "add 'Buy groceries' Low")
53
+ step(env, TaskManagerAction(command="add", title="Call dentist", priority="Normal"), "add 'Call dentist' Normal")
54
+ step(env, TaskManagerAction(command="add", title="Review PR", priority="High"), "add 'Review PR' High")
55
+ obs = step(env, TaskManagerAction(command="list"), "list (shows all tasks)")
56
+ return obs.reward
57
 
 
58
 
59
+ def run_medium():
 
 
 
 
 
60
  print("\n" + "=" * 70)
61
+ print("MEDIUM MODE (one deadline miss on a High task)")
62
  print("=" * 70)
63
+ env = OpenenvJayeshEnvironment()
64
+ env._reset_count = 1
65
+ obs = env.reset()
66
+ print(f"Goal: {obs.message.splitlines()[0]}\n")
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
+ future = "2099-12-31"
69
+ past = "2020-01-01"
70
 
71
+ step(env, TaskManagerAction(command="add", title="Fix critical bug", priority="High", deadline=future), "add 'Fix critical bug' High deadline=future")
72
+ step(env, TaskManagerAction(command="add", title="Deploy hotfix", priority="High", deadline=past), "add 'Deploy hotfix' High deadline=PAST")
73
+ step(env, TaskManagerAction(command="add", title="Write release notes", priority="Normal", deadline=future), "add 'Write release notes' Normal")
74
+ step(env, TaskManagerAction(command="add", title="Standup prep", priority="Low", deadline=future), "add 'Standup prep' Low")
75
+ step(env, TaskManagerAction(command="complete", title="Fix critical bug"), "complete 'Fix critical bug' [on-time]")
76
+ obs = step(env, TaskManagerAction(command="complete", title="Deploy hotfix"), "complete 'Deploy hotfix' [DEADLINE MISSED]")
77
+ return obs.reward
78
 
 
 
 
79
 
80
+ def run_hard():
81
  print("\n" + "=" * 70)
82
+ print("HARD MODE (perfect topological order, all deadlines met)")
83
  print("=" * 70)
84
+ env = OpenenvJayeshEnvironment()
85
+ env._reset_count = 2
86
+ obs = env.reset()
87
+ print(f"Goal: {obs.message.splitlines()[0]}\n")
88
+
89
+ future = "2099-12-31"
90
+
91
+ step(env, TaskManagerAction(command="add", title="Reproduce bug", priority="High", deadline=future), "add 'Reproduce bug' High")
92
+ step(env, TaskManagerAction(command="add", title="Write tests", priority="Normal", deadline=future), "add 'Write tests' Normal")
93
+ step(
94
+ env,
95
+ TaskManagerAction(command="add", title="Write fix", priority="High", deadline=future, depends_on=["Reproduce bug"]),
96
+ "add 'Write fix' High (dep: Reproduce bug)",
97
+ )
98
+ step(
99
+ env,
100
+ TaskManagerAction(command="add", title="Code review", priority="Normal", deadline=future, depends_on=["Write fix", "Write tests"]),
101
+ "add 'Code review' (dep: Write fix, Write tests)",
102
+ )
103
+ step(
104
+ env,
105
+ TaskManagerAction(command="add", title="Deploy to production", priority="Low", deadline=future, depends_on=["Code review"]),
106
+ "add 'Deploy' (dep: Code review)",
107
+ )
108
 
109
  print()
110
+ step(env, TaskManagerAction(command="complete", title="Reproduce bug"), "complete 'Reproduce bug'")
111
+ step(env, TaskManagerAction(command="complete", title="Write tests"), "complete 'Write tests'")
112
+ step(env, TaskManagerAction(command="complete", title="Write fix"), "complete 'Write fix'")
113
+ step(env, TaskManagerAction(command="complete", title="Code review"), "complete 'Code review'")
114
+ obs = step(env, TaskManagerAction(command="complete", title="Deploy to production"), "complete 'Deploy' [goal]")
 
115
 
116
  print()
117
+ print(" -- Violation demo (fresh Hard episode) --")
118
+ env2 = OpenenvJayeshEnvironment()
119
+ env2._reset_count = 2
120
+ env2.reset()
121
+ step(env2, TaskManagerAction(command="add", title="Task A", priority="High", deadline=future), "add 'Task A' High")
122
+ step(env2, TaskManagerAction(command="add", title="Task B", priority="Normal", deadline=future, depends_on=["Task A"]), "add 'Task B' (dep: Task A)")
123
+ obs2 = step(env2, TaskManagerAction(command="complete", title="Task B"), "WRONG: complete 'Task B' before 'Task A'")
124
+ print(f" Score after dep violation: {obs2.reward:.3f} (penalty applied)")
125
 
126
+ return obs.reward
127
 
128
 
 
 
 
 
129
  if __name__ == "__main__":
130
  print("=" * 70)
131
+ print(" Task Manager OpenEnv - Inference Runner")
 
132
  print("=" * 70)
133
 
134
+ easy_score = run_easy()
135
+ medium_score = run_medium()
136
+ hard_score = run_hard()
137
+
138
+ avg = (easy_score + medium_score + hard_score) / 3
139
+
140
+ print("\n" + "=" * 70)
141
+ print(f" EASY final score : {easy_score:.3f}")
142
+ print(f" MEDIUM final score : {medium_score:.3f} (deadline miss penalty)")
143
+ print(f" HARD final score : {hard_score:.3f}")
144
+ print(f" AVERAGE : {avg:.3f}")
145
+ print("=" * 70)
server/Dockerfile CHANGED
@@ -1,80 +1,80 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- # All rights reserved.
3
- #
4
- # This source code is licensed under the BSD-style license found in the
5
- # LICENSE file in the root directory of this source tree.
6
-
7
- # Multi-stage build using openenv-base
8
- # This Dockerfile is flexible and works for both:
9
- # - In-repo environments (with local OpenEnv sources)
10
- # - Standalone environments (with openenv from PyPI/Git)
11
- # The build script (openenv build) handles context detection and sets appropriate build args.
12
-
13
- ARG BASE_IMAGE=ghcr.io/meta-pytorch/openenv-base:latest
14
- FROM ${BASE_IMAGE} AS builder
15
-
16
- WORKDIR /app
17
-
18
- # Ensure git is available (required for installing dependencies from VCS)
19
- RUN apt-get update && \
20
- apt-get install -y --no-install-recommends git && \
21
- rm -rf /var/lib/apt/lists/*
22
-
23
- # Build argument to control whether we're building standalone or in-repo
24
- ARG BUILD_MODE=in-repo
25
- ARG ENV_NAME=openenv_jayesh
26
-
27
- # Copy environment code (always at root of build context)
28
- COPY . /app/env
29
-
30
- # For in-repo builds, openenv is already vendored in the build context
31
- # For standalone builds, openenv will be installed via pyproject.toml
32
- WORKDIR /app/env
33
-
34
- # Ensure uv is available (for local builds where base image lacks it)
35
- RUN if ! command -v uv >/dev/null 2>&1; then \
36
- curl -LsSf https://astral.sh/uv/install.sh | sh && \
37
- mv /root/.local/bin/uv /usr/local/bin/uv && \
38
- mv /root/.local/bin/uvx /usr/local/bin/uvx; \
39
- fi
40
-
41
- # Install dependencies using uv sync
42
- # If uv.lock exists, use it; otherwise resolve on the fly
43
- RUN --mount=type=cache,target=/root/.cache/uv \
44
- if [ -f uv.lock ]; then \
45
- uv sync --frozen --no-install-project --no-editable; \
46
- else \
47
- uv sync --no-install-project --no-editable; \
48
- fi
49
-
50
- RUN --mount=type=cache,target=/root/.cache/uv \
51
- if [ -f uv.lock ]; then \
52
- uv sync --frozen --no-editable; \
53
- else \
54
- uv sync --no-editable; \
55
- fi
56
-
57
- # Final runtime stage
58
- FROM ${BASE_IMAGE}
59
-
60
- WORKDIR /app
61
-
62
- # Copy the virtual environment from builder
63
- COPY --from=builder /app/env/.venv /app/.venv
64
-
65
- # Copy the environment code
66
- COPY --from=builder /app/env /app/env
67
-
68
- # Set PATH to use the virtual environment
69
- ENV PATH="/app/.venv/bin:$PATH"
70
-
71
- # Set PYTHONPATH so imports work correctly
72
- ENV PYTHONPATH="/app/env:$PYTHONPATH"
73
-
74
- # Health check
75
- HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
76
- CMD curl -f http://localhost:8000/health || exit 1
77
-
78
- # Run the FastAPI server
79
- # The module path is constructed to work with the /app/env structure
80
- CMD ["sh", "-c", "cd /app/env && uvicorn server.app:app --host 0.0.0.0 --port 8000"]
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ # Multi-stage build using openenv-base
8
+ # This Dockerfile is flexible and works for both:
9
+ # - In-repo environments (with local OpenEnv sources)
10
+ # - Standalone environments (with openenv from PyPI/Git)
11
+ # The build script (openenv build) handles context detection and sets appropriate build args.
12
+
13
+ ARG BASE_IMAGE=ghcr.io/meta-pytorch/openenv-base:latest
14
+ FROM ${BASE_IMAGE} AS builder
15
+
16
+ WORKDIR /app
17
+
18
+ # Ensure git is available (required for installing dependencies from VCS)
19
+ RUN apt-get update && \
20
+ apt-get install -y --no-install-recommends git && \
21
+ rm -rf /var/lib/apt/lists/*
22
+
23
+ # Build argument to control whether we're building standalone or in-repo
24
+ ARG BUILD_MODE=in-repo
25
+ ARG ENV_NAME=openenv_jayesh
26
+
27
+ # Copy environment code (always at root of build context)
28
+ COPY . /app/env
29
+
30
+ # For in-repo builds, openenv is already vendored in the build context
31
+ # For standalone builds, openenv will be installed via pyproject.toml
32
+ WORKDIR /app/env
33
+
34
+ # Ensure uv is available (for local builds where base image lacks it)
35
+ RUN if ! command -v uv >/dev/null 2>&1; then \
36
+ curl -LsSf https://astral.sh/uv/install.sh | sh && \
37
+ mv /root/.local/bin/uv /usr/local/bin/uv && \
38
+ mv /root/.local/bin/uvx /usr/local/bin/uvx; \
39
+ fi
40
+
41
+ # Install dependencies using uv sync
42
+ # If uv.lock exists, use it; otherwise resolve on the fly
43
+ RUN --mount=type=cache,target=/root/.cache/uv \
44
+ if [ -f uv.lock ]; then \
45
+ uv sync --frozen --no-install-project --no-editable; \
46
+ else \
47
+ uv sync --no-install-project --no-editable; \
48
+ fi
49
+
50
+ RUN --mount=type=cache,target=/root/.cache/uv \
51
+ if [ -f uv.lock ]; then \
52
+ uv sync --frozen --no-editable; \
53
+ else \
54
+ uv sync --no-editable; \
55
+ fi
56
+
57
+ # Final runtime stage
58
+ FROM ${BASE_IMAGE}
59
+
60
+ WORKDIR /app
61
+
62
+ # Copy the virtual environment from builder
63
+ COPY --from=builder /app/env/.venv /app/.venv
64
+
65
+ # Copy the environment code
66
+ COPY --from=builder /app/env /app/env
67
+
68
+ # Set PATH to use the virtual environment
69
+ ENV PATH="/app/.venv/bin:$PATH"
70
+
71
+ # Set PYTHONPATH so imports work correctly
72
+ ENV PYTHONPATH="/app/env:$PYTHONPATH"
73
+
74
+ # Health check
75
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
76
+ CMD curl -f http://localhost:8000/health || exit 1
77
+
78
+ # Run the FastAPI server
79
+ # The module path is constructed to work with the /app/env structure
80
+ CMD ["sh", "-c", "cd /app/env && uvicorn server.app:app --host 0.0.0.0 --port 8000"]
server/openenv_jayesh_environment.py CHANGED
@@ -1,31 +1,12 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- # All rights reserved.
3
-
4
  """
5
- Smart Personal Task Manager - OpenEnv Environment Implementation.
6
-
7
- Three difficulty tiers:
8
-
9
- Easy (reset 0, 3, 6 ...)
10
- Goal: Add 2-3 tasks of any priority, then call 'list'.
11
- Reward: +0.15 per task added (up to 3), +0.20 for listing. Full 1.0 on completion.
12
-
13
- Medium (reset 1, 4, 7 ...)
14
- Goal: Add 4 tasks with mixed priorities AND deadlines.
15
- Complete ALL High-priority tasks before their deadlines.
16
- Reward: +0.15 per task added (up to 4), +0.10 per correct priority label,
17
- +0.20 per High-priority task completed on time.
18
- -0.25 penalty per deadline miss.
19
- Full 1.0 on completion.
20
-
21
- Hard (reset 2, 5, 8 ...)
22
- Goal: Add 5 tasks with priorities, deadlines, AND dependencies.
23
- Complete tasks in valid dependency order; respect all deadlines.
24
- Reward: +0.15 per task added (up to 5), +0.10 per correct priority,
25
- +0.25 per task completed without violation.
26
- Bonus +0.10 for achieving perfect (optimal) topological ordering.
27
- -0.30 per dependency violation, -0.25 per deadline miss.
28
- Full 1.0 on perfect completion.
29
  """
30
 
31
  from __future__ import annotations
@@ -42,21 +23,19 @@ try:
42
  except (ModuleNotFoundError, ImportError):
43
  from models import TaskManagerAction, TaskManagerObservation
44
 
45
-
46
- # ---------------------------------------------------------------------------
47
- # Helpers
48
- # ---------------------------------------------------------------------------
49
-
50
  VALID_PRIORITIES = {"Low", "Normal", "High"}
51
- PRIORITY_RANK = {"Low": 0, "Normal": 1, "High": 2}
 
 
 
 
52
 
53
 
54
- def _parse_date(dt_str: Optional[str]) -> Optional[date]:
55
- """Parse an ISO-8601 date string; return None on failure."""
56
- if not dt_str:
57
  return None
58
  try:
59
- return date.fromisoformat(dt_str)
60
  except ValueError:
61
  return None
62
 
@@ -65,60 +44,38 @@ def _today() -> date:
65
  return date.today()
66
 
67
 
68
- # ---------------------------------------------------------------------------
69
- # Environment
70
- # ---------------------------------------------------------------------------
71
-
72
  class OpenenvJayeshEnvironment(Environment):
73
- """
74
- Smart Personal Task Manager with three distinct difficulty levels.
75
-
76
- Easy -> add tasks + list
77
- Medium -> deadlines + priority management
78
- Hard -> deadlines + priority + dependency ordering
79
- """
80
 
81
  SUPPORTS_CONCURRENT_SESSIONS: bool = True
82
 
83
- # ------------------------------------------------------------------
84
- # Lifecycle
85
- # ------------------------------------------------------------------
86
-
87
  def __init__(self) -> None:
88
  super().__init__()
89
- self._state = State(episode_id=str(uuid4()), step_count=0)
90
  self._reset_count = 0
91
  self._scenarios = ["Easy", "Medium", "Hard"]
92
- self._init_episode_state()
93
-
94
- def _init_episode_state(self) -> None:
95
- """Zero all mutable per-episode state."""
96
- self.tasks: List[Dict[str, Any]] = [] # ordered list of task dicts
97
- self.difficulty: str = "Easy"
98
- self.goal_completed: bool = False
99
-
100
- # Counters
101
- self.tasks_added: int = 0
102
- self.tasks_completed: int = 0
103
- self.high_tasks_added: int = 0
104
- self.high_tasks_completed_on_time: int = 0
105
- self.deadline_misses: int = 0
106
- self.dependency_violations: int = 0
107
- self.completion_order: List[str] = [] # titles in completion order
108
-
109
- # Episode-level flag
110
- self.list_called: bool = False
111
-
112
- # Violations log (for observation)
113
  self.violations: List[str] = []
114
 
115
- # Target counts per difficulty
116
- self.target_tasks: int = 2
117
- self.target_high: int = 0
 
 
 
118
 
119
- # ------------------------------------------------------------------
120
- # reset
121
- # ------------------------------------------------------------------
 
 
 
122
 
123
  def reset(
124
  self,
@@ -127,61 +84,41 @@ class OpenenvJayeshEnvironment(Environment):
127
  **kwargs: Any,
128
  ) -> TaskManagerObservation:
129
  self._state = State(episode_id=episode_id or str(uuid4()), step_count=0)
130
- self._init_episode_state()
131
 
132
- idx = self._reset_count % len(self._scenarios)
133
- self.difficulty = self._scenarios[idx]
134
  self._reset_count += 1
135
 
136
- if self.difficulty == "Easy":
137
- self.target_tasks = 2
138
- self.target_high = 0
139
- msg = (
140
- "=== EASY MODE ===\n"
141
- "Goal: Add 2-3 tasks of any priority, then call 'list' to review them.\n"
142
- "Rewards: +0.15 per task added (max 3), +0.20 for calling list.\n"
143
- "Complete the goal to receive full reward (1.0)."
144
- )
145
- elif self.difficulty == "Medium":
146
- self.target_tasks = 4
147
- self.target_high = 2
148
- msg = (
149
- "=== MEDIUM MODE ===\n"
150
- "Goal: Add 4 tasks with priorities AND deadlines.\n"
151
- " Complete ALL High-priority tasks before their deadlines.\n"
152
- "Tips: Include at least 2 High-priority tasks.\n"
153
- " Use deadline='YYYY-MM-DD' (today or future date for on-time credit).\n"
154
- "Rewards: +0.15/task added, +0.10/correct priority, +0.20/High completed on time.\n"
155
- "Penalty: -0.25 per deadline miss."
156
- )
157
- else: # Hard
158
- self.target_tasks = 5
159
- self.target_high = 2
160
- msg = (
161
- "=== HARD MODE ===\n"
162
- "Goal: Add 5 tasks with priorities, deadlines, AND dependencies.\n"
163
- " Complete tasks in valid dependency order (dependencies first).\n"
164
- " Respect all deadlines.\n"
165
- "Tips: Use depends_on=['Task Title'] when adding a dependent task.\n"
166
- " Complete prerequisite tasks before their dependents.\n"
167
- "Rewards: +0.15/task, +0.10/priority, +0.25/completion without violation.\n"
168
- " +0.10 bonus for perfect topological ordering.\n"
169
- "Penalty: -0.30/dependency violation, -0.25/deadline miss."
170
- )
171
-
172
  return TaskManagerObservation(
173
  success=True,
174
- message=msg,
175
  tasks=[],
176
  violations=[],
177
  done=False,
178
  reward=0.0,
179
  )
180
 
181
- # ------------------------------------------------------------------
182
- # step
183
- # ------------------------------------------------------------------
184
-
185
  def step(
186
  self,
187
  action: TaskManagerAction,
@@ -190,25 +127,22 @@ class OpenenvJayeshEnvironment(Environment):
190
  ) -> TaskManagerObservation:
191
  self._state.step_count += 1
192
  cmd = (action.command or "").strip().lower()
193
- success = True
194
- message = ""
195
 
196
  if cmd == "add":
197
- success, message = self._handle_add(action)
198
  elif cmd == "complete":
199
- success, message = self._handle_complete(action)
200
  elif cmd == "list":
201
  self.list_called = True
202
- message = self._format_task_list()
203
  else:
204
- success = False
205
- message = f"Unknown command '{cmd}'. Valid commands: 'add', 'complete', 'list'."
206
 
207
- reward = self._calculate_reward()
208
 
209
  return TaskManagerObservation(
210
  success=success,
211
- message=message,
212
  tasks=list(self.tasks),
213
  violations=list(self.violations),
214
  done=self.goal_completed,
@@ -217,247 +151,224 @@ class OpenenvJayeshEnvironment(Environment):
217
  "difficulty": self.difficulty,
218
  "step": self._state.step_count,
219
  "tasks_added": self.tasks_added,
220
- "tasks_completed": self.tasks_completed,
 
221
  "deadline_misses": self.deadline_misses,
222
- "dependency_violations": self.dependency_violations,
223
  },
224
  )
225
 
226
- # ------------------------------------------------------------------
227
- # Command handlers
228
- # ------------------------------------------------------------------
229
-
230
- def _handle_add(self, action: TaskManagerAction):
231
- if not action.title or not action.title.strip():
232
- return False, "Error: 'title' is required for the 'add' command."
233
-
234
- title = action.title.strip()
235
-
236
- # Duplicate check
237
  if any(t["title"] == title for t in self.tasks):
238
- return False, f"Error: A task titled '{title}' already exists."
239
 
240
  priority = (action.priority or "Normal").strip()
241
  if priority not in VALID_PRIORITIES:
242
  priority = "Normal"
243
 
244
- deadline_str = action.deadline
245
- deadline_obj = _parse_date(deadline_str)
246
 
247
  depends_on: List[str] = []
248
- if action.depends_on:
249
- for dep in action.depends_on:
250
- dep = dep.strip()
251
- if dep and any(t["title"] == dep for t in self.tasks):
252
- depends_on.append(dep)
253
- elif dep:
254
- return (
255
- False,
256
- f"Error: Dependency '{dep}' does not exist yet. "
257
- "Add prerequisite tasks first.",
258
- )
259
 
260
  task: Dict[str, Any] = {
261
  "title": title,
262
  "priority": priority,
263
- "deadline": deadline_str or "none",
264
  "depends_on": depends_on,
265
  "completed": False,
266
  "deadline_missed": False,
267
- "dependency_violation": False,
268
- "added_step": self._state.step_count,
269
  }
270
  self.tasks.append(task)
271
  self.tasks_added += 1
272
  if priority == "High":
273
- self.high_tasks_added += 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
 
275
- msg = (
276
- f"Task added: '{title}' | Priority: {priority}"
277
- + (f" | Deadline: {deadline_str}" if deadline_str else "")
278
- + (f" | Depends on: {depends_on}" if depends_on else "")
279
- )
280
- return True, msg
281
-
282
- def _handle_complete(self, action: TaskManagerAction):
283
- if not action.title or not action.title.strip():
284
- return False, "Error: 'title' is required for the 'complete' command."
285
-
286
- title = action.title.strip()
287
  task = next((t for t in self.tasks if t["title"] == title), None)
288
-
289
  if task is None:
290
- return False, f"Error: Task '{title}' not found."
291
  if task["completed"]:
292
  return False, f"Task '{title}' is already completed."
293
 
294
- # ---- Dependency check ----
295
- dep_violation = False
 
 
296
  unmet = [
297
- dep for dep in task["depends_on"]
298
- if not any(t["title"] == dep and t["completed"] for t in self.tasks)
299
  ]
300
  if unmet:
301
- dep_violation = True
302
- task["dependency_violation"] = True
303
- self.dependency_violations += 1
304
- violation_msg = (
305
- f"DEPENDENCY VIOLATION: Completed '{title}' before prerequisites: {unmet}. "
306
- "Penalty applied."
307
- )
308
- self.violations.append(violation_msg)
309
-
310
- # ---- Deadline check ----
311
- deadline_missed = False
312
  dl = _parse_date(task["deadline"])
313
- if dl is not None and _today() > dl:
314
- deadline_missed = True
315
  task["deadline_missed"] = True
316
  self.deadline_misses += 1
317
- violation_msg = (
318
- f"DEADLINE MISSED: '{title}' was due {task['deadline']} "
319
- f"but completed on {_today().isoformat()}. Penalty applied."
320
- )
321
- self.violations.append(violation_msg)
 
 
 
 
 
 
 
 
 
322
 
323
- # ---- Mark completed ----
324
  task["completed"] = True
325
- self.tasks_completed += 1
326
- self.completion_order.append(title)
327
-
328
- if task["priority"] == "High" and not deadline_missed:
329
- self.high_tasks_completed_on_time += 1
330
-
331
- msg_parts = [f"Task '{title}' marked complete."]
332
- if dep_violation:
333
- msg_parts.append("(!) Dependency violation penalty applied.")
334
- if deadline_missed:
335
- msg_parts.append("(!) Deadline miss penalty applied.")
336
- if not dep_violation and not deadline_missed:
337
- msg_parts.append("Clean completion -- no penalties.")
338
-
339
- return True, " ".join(msg_parts)
340
-
341
- # ------------------------------------------------------------------
342
- # Reward calculation
343
- # ------------------------------------------------------------------
344
-
345
- def _calculate_reward(self) -> float:
346
- self.goal_completed = False
347
- reward = 0.0
348
 
349
- if self.difficulty == "Easy":
350
- reward = self._reward_easy()
351
- elif self.difficulty == "Medium":
352
- reward = self._reward_medium()
 
 
 
 
 
 
 
 
 
 
 
353
  else:
354
- reward = self._reward_hard()
355
 
356
- return round(min(1.0, max(0.0, reward)), 3)
357
 
358
- def _reward_easy(self) -> float:
359
- r = 0.0
360
- # +0.15 per task added, up to 3 tasks
361
- r += min(3, self.tasks_added) * 0.15
362
- # +0.20 for calling list
 
 
 
 
 
 
 
 
 
 
363
  if self.list_called:
364
- r += 0.20
365
- # Goal: >=2 tasks added + list called
366
- if self.tasks_added >= 2 and self.list_called:
367
- r = 1.0
368
- self.goal_completed = True
369
- return r
370
-
371
- def _reward_medium(self) -> float:
372
- r = 0.0
373
- # +0.15 per task added, up to 4
374
- r += min(4, self.tasks_added) * 0.15
375
- # +0.10 per task that has an explicit priority label (not default "Normal" by omission)
376
- explicit_priority_tasks = sum(
377
- 1 for t in self.tasks
378
- if t["priority"] != "Normal" or t.get("priority_explicit", False)
379
- )
380
- r += min(4, explicit_priority_tasks) * 0.10
381
- # +0.20 per High-priority task completed on time
382
- r += self.high_tasks_completed_on_time * 0.20
383
- # Penalties
384
- r -= self.deadline_misses * 0.25
385
- # Goal: >=4 tasks, >=2 High added, ALL High completed on time, no deadline misses
386
  high_tasks = [t for t in self.tasks if t["priority"] == "High"]
387
- all_high_done = all(t["completed"] and not t["deadline_missed"] for t in high_tasks)
388
- if (
389
- self.tasks_added >= 4
390
- and self.high_tasks_added >= 2
391
- and all_high_done
392
- and self.deadline_misses == 0
393
- ):
394
- r = 1.0
395
- self.goal_completed = True
396
- return r
397
-
398
- def _reward_hard(self) -> float:
399
- r = 0.0
400
- # +0.15 per task added, up to 5
401
- r += min(5, self.tasks_added) * 0.15
402
- # +0.10 per task with non-Normal or explicit priority
403
- explicit_priority_tasks = sum(
404
- 1 for t in self.tasks if t["priority"] != "Normal"
405
  )
406
- r += min(5, explicit_priority_tasks) * 0.10
407
- # +0.25 per task completed without any violation
408
- clean_completions = sum(
409
- 1 for t in self.tasks
410
- if t["completed"] and not t["deadline_missed"] and not t["dependency_violation"]
411
- )
412
- r += clean_completions * 0.25
413
- # Penalty
414
- r -= self.dependency_violations * 0.30
415
- r -= self.deadline_misses * 0.25
416
- # Bonus: optimal ordering (no violations at all + all done)
417
- all_done = all(t["completed"] for t in self.tasks)
418
- if all_done and self.dependency_violations == 0 and self.deadline_misses == 0:
419
- r += 0.10 # perfect-run bonus
420
- # Goal: >=5 tasks, >=2 High, all completed, zero violations
421
- high_tasks = [t for t in self.tasks if t["priority"] == "High"]
422
- if (
423
- self.tasks_added >= 5
424
- and self.high_tasks_added >= 2
425
- and len(self.tasks) == self.tasks_completed
426
- and self.dependency_violations == 0
427
- and self.deadline_misses == 0
428
- ):
429
- r = 1.0
430
  self.goal_completed = True
431
- return r
432
-
433
- # ------------------------------------------------------------------
434
- # Helpers
435
- # ------------------------------------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
436
 
437
- def _format_task_list(self) -> str:
438
  if not self.tasks:
439
- return "No tasks in the system."
440
- lines = [f"Current tasks ({len(self.tasks)} total):"]
441
  for i, t in enumerate(self.tasks, 1):
442
  status = "DONE" if t["completed"] else "PENDING"
443
  flags = []
444
  if t.get("deadline_missed"):
445
  flags.append("LATE")
446
- if t.get("dependency_violation"):
447
- flags.append("DEP-VIOLATION")
448
- flag_str = f" [{', '.join(flags)}]" if flags else ""
449
- dep_str = f" | Deps: {t['depends_on']}" if t["depends_on"] else ""
450
- dl_str = f" | Due: {t['deadline']}" if t["deadline"] != "none" else ""
451
- lines.append(
452
- f" {i}. [{status}]{flag_str} {t['title']} "
453
- f"(Priority: {t['priority']}{dl_str}{dep_str})"
454
- )
455
  return "\n".join(lines)
456
 
457
- # ------------------------------------------------------------------
458
- # State property
459
- # ------------------------------------------------------------------
460
-
461
  @property
462
  def state(self) -> State:
463
  return self._state
 
 
 
 
1
  """
2
+ Smart Personal Task Manager - OpenEnv Environment
3
+
4
+ Easy: three tasks + list; full 1.0 when at least 2 distinct priorities; else partial credit.
5
+ Medium: small +0.07 signals; deadline miss −0.15.
6
+ Hard: +0.018 per dep edge on add; rising clean completes; dep-satisfied micro-bonus; topo bonus;
7
+ deadline miss −0.15, dependency violation −0.18.
8
+
9
+ Reward clamp [0.0, 1.0].
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  """
11
 
12
  from __future__ import annotations
 
23
  except (ModuleNotFoundError, ImportError):
24
  from models import TaskManagerAction, TaskManagerObservation
25
 
 
 
 
 
 
26
  VALID_PRIORITIES = {"Low", "Normal", "High"}
27
+
28
+ # Fair penalties (requested ranges)
29
+ MEDIUM_DEADLINE_MISS_PENALTY = 0.15 # -0.12 … -0.18
30
+ HARD_DEADLINE_MISS_PENALTY = 0.15 # -0.12 … -0.18
31
+ HARD_DEP_VIOLATION_PENALTY = 0.18 # -0.15 … -0.20
32
 
33
 
34
+ def _parse_date(s: Optional[str]) -> Optional[date]:
35
+ if not s:
 
36
  return None
37
  try:
38
+ return date.fromisoformat(s)
39
  except ValueError:
40
  return None
41
 
 
44
  return date.today()
45
 
46
 
 
 
 
 
47
  class OpenenvJayeshEnvironment(Environment):
48
+ """Task Manager with Easy / Medium / Hard difficulty levels."""
 
 
 
 
 
 
49
 
50
  SUPPORTS_CONCURRENT_SESSIONS: bool = True
51
 
 
 
 
 
52
  def __init__(self) -> None:
53
  super().__init__()
 
54
  self._reset_count = 0
55
  self._scenarios = ["Easy", "Medium", "Hard"]
56
+ self._state = State(episode_id=str(uuid4()), step_count=0)
57
+ self._init_state()
58
+
59
+ def _init_state(self) -> None:
60
+ self.tasks: List[Dict[str, Any]] = []
61
+ self.difficulty = "Easy"
62
+ self.goal_completed = False
63
+ self.list_called = False
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  self.violations: List[str] = []
65
 
66
+ self.tasks_added = 0
67
+ self.high_added = 0
68
+ self.high_on_time = 0
69
+ self.clean_completions = 0
70
+ self.deadline_misses = 0
71
+ self.dep_violations = 0
72
 
73
+ self._r_add = 0.0
74
+ self._r_priority = 0.0
75
+ self._r_deadline_set = 0.0
76
+ self._r_complete = 0.0
77
+ self._r_hard_dep_ok = 0.0
78
+ self._r_hard_edge = 0.0
79
 
80
  def reset(
81
  self,
 
84
  **kwargs: Any,
85
  ) -> TaskManagerObservation:
86
  self._state = State(episode_id=episode_id or str(uuid4()), step_count=0)
87
+ self._init_state()
88
 
89
+ self.difficulty = self._scenarios[self._reset_count % 3]
 
90
  self._reset_count += 1
91
 
92
+ msgs = {
93
+ "Easy": (
94
+ "EASY MODE\n"
95
+ "Goal: Add 3 tasks with at least 2 different priorities, then call 'list'.\n"
96
+ "Full score (1.0) when 3 tasks + list + 2+ priority levels; "
97
+ "otherwise partial credit if you list without enough variety."
98
+ ),
99
+ "Medium": (
100
+ "MEDIUM MODE\n"
101
+ "Goal: Add 4 tasks with priorities and deadlines. Every High task on time.\n"
102
+ "Rewards: small steps (~+0.07 per signal) for smooth partial credit.\n"
103
+ f"Penalty: -{MEDIUM_DEADLINE_MISS_PENALTY:.2f} per deadline miss."
104
+ ),
105
+ "Hard": (
106
+ "HARD MODE\n"
107
+ "Goal: 5 tasks with deps + deadlines; valid order; no misses.\n"
108
+ "Rewards: tiny edge credit when linking deps on add; rising clean completes; "
109
+ "small bonus when deps satisfied at complete.\n"
110
+ f"Penalties: -{HARD_DEP_VIOLATION_PENALTY:.2f} dependency, -{HARD_DEADLINE_MISS_PENALTY:.2f} deadline miss."
111
+ ),
112
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  return TaskManagerObservation(
114
  success=True,
115
+ message=msgs[self.difficulty],
116
  tasks=[],
117
  violations=[],
118
  done=False,
119
  reward=0.0,
120
  )
121
 
 
 
 
 
122
  def step(
123
  self,
124
  action: TaskManagerAction,
 
127
  ) -> TaskManagerObservation:
128
  self._state.step_count += 1
129
  cmd = (action.command or "").strip().lower()
 
 
130
 
131
  if cmd == "add":
132
+ success, msg = self._add(action)
133
  elif cmd == "complete":
134
+ success, msg = self._complete(action)
135
  elif cmd == "list":
136
  self.list_called = True
137
+ success, msg = True, self._fmt_list()
138
  else:
139
+ success, msg = False, f"Unknown command '{cmd}'. Use: add | complete | list."
 
140
 
141
+ reward = self._score()
142
 
143
  return TaskManagerObservation(
144
  success=success,
145
+ message=msg,
146
  tasks=list(self.tasks),
147
  violations=list(self.violations),
148
  done=self.goal_completed,
 
151
  "difficulty": self.difficulty,
152
  "step": self._state.step_count,
153
  "tasks_added": self.tasks_added,
154
+ "clean_completions": self.clean_completions,
155
+ "dep_violations": self.dep_violations,
156
  "deadline_misses": self.deadline_misses,
 
157
  },
158
  )
159
 
160
+ def _add(self, action: TaskManagerAction):
161
+ title = (action.title or "").strip()
162
+ if not title:
163
+ return False, "Error: 'title' is required for add."
 
 
 
 
 
 
 
164
  if any(t["title"] == title for t in self.tasks):
165
+ return False, f"Error: task '{title}' already exists."
166
 
167
  priority = (action.priority or "Normal").strip()
168
  if priority not in VALID_PRIORITIES:
169
  priority = "Normal"
170
 
171
+ deadline_str = (action.deadline or "").strip() or None
 
172
 
173
  depends_on: List[str] = []
174
+ for dep in (action.depends_on or []):
175
+ dep = dep.strip()
176
+ if not dep:
177
+ continue
178
+ if not any(t["title"] == dep for t in self.tasks):
179
+ return False, f"Error: dependency '{dep}' not found. Add it first."
180
+ depends_on.append(dep)
 
 
 
 
181
 
182
  task: Dict[str, Any] = {
183
  "title": title,
184
  "priority": priority,
185
+ "deadline": deadline_str,
186
  "depends_on": depends_on,
187
  "completed": False,
188
  "deadline_missed": False,
189
+ "dep_violation": False,
 
190
  }
191
  self.tasks.append(task)
192
  self.tasks_added += 1
193
  if priority == "High":
194
+ self.high_added += 1
195
+
196
+ if self.difficulty == "Medium":
197
+ # Smaller per-signal steps (~0.07) so scores rise gradually (avoid large single-step jumps)
198
+ self._r_add += 0.07
199
+ if priority != "Normal":
200
+ self._r_priority += 0.07
201
+ if deadline_str:
202
+ self._r_deadline_set += 0.07
203
+ elif self.difficulty == "Hard":
204
+ # Slightly smaller per-signal steps than Medium to smooth the last adds (e.g. Deploy)
205
+ inc = 0.062
206
+ self._r_add += inc
207
+ if priority != "Normal":
208
+ self._r_priority += inc
209
+ if deadline_str:
210
+ self._r_deadline_set += inc
211
+ if depends_on:
212
+ self._r_hard_edge += 0.012 * float(len(depends_on))
213
+
214
+ parts = [f"Task '{title}' added (priority={priority}"]
215
+ if deadline_str:
216
+ parts[0] += f", deadline={deadline_str}"
217
+ if depends_on:
218
+ parts[0] += f", depends_on={depends_on}"
219
+ parts[0] += ")."
220
+ return True, " ".join(parts)
221
+
222
+ def _complete(self, action: TaskManagerAction):
223
+ title = (action.title or "").strip()
224
+ if not title:
225
+ return False, "Error: 'title' is required for complete."
226
 
 
 
 
 
 
 
 
 
 
 
 
 
227
  task = next((t for t in self.tasks if t["title"] == title), None)
 
228
  if task is None:
229
+ return False, f"Error: task '{title}' not found."
230
  if task["completed"]:
231
  return False, f"Task '{title}' is already completed."
232
 
233
+ msgs = []
234
+ dep_viol = False
235
+ dl_miss = False
236
+
237
  unmet = [
238
+ d for d in task["depends_on"]
239
+ if not any(t["title"] == d and t["completed"] for t in self.tasks)
240
  ]
241
  if unmet:
242
+ dep_viol = True
243
+ task["dep_violation"] = True
244
+ self.dep_violations += 1
245
+ v = f"DEP VIOLATION: '{title}' completed before {unmet}."
246
+ self.violations.append(v)
247
+ msgs.append("(!) Dependency violation — penalty applied.")
248
+
 
 
 
 
249
  dl = _parse_date(task["deadline"])
250
+ if dl and _today() > dl:
251
+ dl_miss = True
252
  task["deadline_missed"] = True
253
  self.deadline_misses += 1
254
+ v = f"DEADLINE MISSED: '{title}' was due {task['deadline']} (completed after deadline)."
255
+ self.violations.append(v)
256
+ msgs.append("(!) Deadline missed penalty applied.")
257
+ if self.difficulty == "Medium":
258
+ msgs.append(
259
+ f"Score impact (Medium): -{MEDIUM_DEADLINE_MISS_PENALTY:.2f} for this miss "
260
+ f"(total deadline-miss penalty: "
261
+ f"{self.deadline_misses * MEDIUM_DEADLINE_MISS_PENALTY:.2f})."
262
+ )
263
+ elif self.difficulty == "Hard":
264
+ msgs.append(
265
+ f"Score impact (Hard): -{HARD_DEADLINE_MISS_PENALTY:.2f} for this miss "
266
+ f"(total: {self.deadline_misses * HARD_DEADLINE_MISS_PENALTY:.2f})."
267
+ )
268
 
 
269
  task["completed"] = True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
 
271
+ if not dep_viol and not dl_miss:
272
+ order_idx = self.clean_completions
273
+ self.clean_completions += 1
274
+ if task["priority"] == "High":
275
+ self.high_on_time += 1
276
+ msgs.append(f"Task '{title}' completed cleanly.")
277
+ if self.difficulty == "Medium":
278
+ if task["priority"] == "High":
279
+ self._r_complete += 0.09
280
+ elif self.difficulty == "Hard":
281
+ # Rising reward each clean step; tiny extra when prerequisites were satisfied
282
+ step_r = 0.065 + 0.012 * float(order_idx)
283
+ self._r_complete += min(0.125, step_r)
284
+ if task["depends_on"]:
285
+ self._r_hard_dep_ok += 0.025
286
  else:
287
+ msgs.append(f"Task '{title}' completed with violations.")
288
 
289
+ return True, " ".join(msgs)
290
 
291
+ def _score(self) -> float:
292
+ if self.difficulty == "Easy":
293
+ return self._score_easy()
294
+ if self.difficulty == "Medium":
295
+ return self._score_medium()
296
+ return self._score_hard()
297
+
298
+ def _easy_has_two_priorities(self) -> bool:
299
+ if len(self.tasks) < 3:
300
+ return False
301
+ return len({t["priority"] for t in self.tasks}) >= 2
302
+
303
+ def _score_easy(self) -> float:
304
+ # Partial: ~+0.11/add, ~+0.12/list. Full 1.0 + done only with 2+ distinct priorities + list.
305
+ r = min(3, self.tasks_added) * 0.11
306
  if self.list_called:
307
+ r += 0.12
308
+ if self.tasks_added >= 3 and self.list_called:
309
+ if self._easy_has_two_priorities():
310
+ r += 0.55
311
+ self.goal_completed = True
312
+ else:
313
+ r += 0.40
314
+ return round(min(1.0, r), 3)
315
+
316
+ def _score_medium(self) -> float:
317
+ # Softer caps match smaller increments (4×0.07 0.28 per bucket)
318
+ r = min(0.28, self._r_add)
319
+ r += min(0.28, self._r_priority)
320
+ r += min(0.28, self._r_deadline_set)
321
+ r += min(0.22, self._r_complete)
322
+ r -= self.deadline_misses * MEDIUM_DEADLINE_MISS_PENALTY
323
+
 
 
 
 
 
324
  high_tasks = [t for t in self.tasks if t["priority"] == "High"]
325
+ all_high_on_time = (
326
+ len(high_tasks) >= 1
327
+ and all(t["completed"] and not t["deadline_missed"] for t in high_tasks)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  )
329
+ if self.tasks_added >= 4 and all_high_on_time and self.deadline_misses == 0:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
330
  self.goal_completed = True
331
+ return 1.0
332
+ return round(min(0.94, max(0.0, r)), 3)
333
+
334
+ def _score_hard(self) -> float:
335
+ # Caps aligned with 0.062 increments (~0.31 over 5 adds) to avoid one huge step on task 5
336
+ r = min(0.34, self._r_add)
337
+ r += min(0.26, self._r_priority)
338
+ r += min(0.26, self._r_deadline_set)
339
+ r += min(0.42, self._r_complete)
340
+ r += min(0.14, self._r_hard_dep_ok)
341
+ r += min(0.08, self._r_hard_edge)
342
+ topo_bonus = 0.0
343
+ if self.clean_completions == 5 and self.dep_violations == 0:
344
+ topo_bonus = 0.055
345
+ r += topo_bonus
346
+ r -= self.dep_violations * HARD_DEP_VIOLATION_PENALTY
347
+ r -= self.deadline_misses * HARD_DEADLINE_MISS_PENALTY
348
+
349
+ all_done = self.tasks_added >= 5 and all(t["completed"] for t in self.tasks)
350
+ if all_done and self.dep_violations == 0 and self.deadline_misses == 0:
351
+ self.goal_completed = True
352
+ return 1.0
353
+ return round(min(0.99, max(0.0, r)), 3)
354
 
355
+ def _fmt_list(self) -> str:
356
  if not self.tasks:
357
+ return "No tasks yet."
358
+ lines = [f"Tasks ({len(self.tasks)}):"]
359
  for i, t in enumerate(self.tasks, 1):
360
  status = "DONE" if t["completed"] else "PENDING"
361
  flags = []
362
  if t.get("deadline_missed"):
363
  flags.append("LATE")
364
+ if t.get("dep_violation"):
365
+ flags.append("DEP-ERR")
366
+ flag_str = f" [{','.join(flags)}]" if flags else ""
367
+ dl = f" due={t['deadline']}" if t["deadline"] else ""
368
+ dep = f" deps={t['depends_on']}" if t["depends_on"] else ""
369
+ lines.append(f" {i}. [{status}]{flag_str} {t['title']} ({t['priority']}{dl}{dep})")
 
 
 
370
  return "\n".join(lines)
371
 
 
 
 
 
372
  @property
373
  def state(self) -> State:
374
  return self._state