jayesh20 commited on
Commit
7c07390
·
verified ·
1 Parent(s): fb103ba

Upload folder using huggingface_hub

Browse files
Dockerfile ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ ENV ENABLE_WEB_INTERFACE=true
81
+ CMD ["sh", "-c", "cd /app/env && uvicorn server.app:app --host 0.0.0.0 --port 8000"]
README.md CHANGED
Binary files a/README.md and b/README.md differ
 
__init__.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """Openenv Jayesh Environment."""
8
+
9
+ from .client import OpenenvJayeshEnv
10
+ from .models import OpenenvJayeshAction, OpenenvJayeshObservation
11
+
12
+ __all__ = [
13
+ "OpenenvJayeshAction",
14
+ "OpenenvJayeshObservation",
15
+ "OpenenvJayeshEnv",
16
+ ]
client.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+
4
+ """Openenv Jayesh Environment Client."""
5
+
6
+ from typing import Dict
7
+
8
+ from openenv.core import EnvClient
9
+ from openenv.core.client_types import StepResult
10
+ from openenv.core.env_server.types import State
11
+
12
+ try:
13
+ from .models import TaskManagerAction, TaskManagerObservation
14
+ except ImportError:
15
+ from models import TaskManagerAction, TaskManagerObservation
16
+
17
+
18
+ class OpenenvJayeshEnv(
19
+ EnvClient[TaskManagerAction, TaskManagerObservation, State]
20
+ ):
21
+ """Client for the Task Manager Environment."""
22
+
23
+ def _step_payload(self, action: TaskManagerAction) -> Dict:
24
+ """Convert TaskManagerAction to JSON payload."""
25
+ return {
26
+ "command": action.command,
27
+ "title": action.title,
28
+ "priority": action.priority,
29
+ "deadline": action.deadline
30
+ }
31
+
32
+ def _parse_result(self, payload: Dict) -> StepResult[TaskManagerObservation]:
33
+ """Parse server response."""
34
+ obs_data = payload.get("observation", {})
35
+ observation = TaskManagerObservation(
36
+ success=obs_data.get("success", True),
37
+ message=obs_data.get("message", ""),
38
+ tasks=obs_data.get("tasks", []),
39
+ done=payload.get("done", False),
40
+ reward=payload.get("reward", 0.0),
41
+ metadata=obs_data.get("metadata", {}),
42
+ )
43
+
44
+ return StepResult(
45
+ observation=observation,
46
+ reward=payload.get("reward", 0.0),
47
+ done=payload.get("done", False),
48
+ )
49
+
50
+ def _parse_state(self, payload: Dict) -> State:
51
+ return State(
52
+ episode_id=payload.get("episode_id"),
53
+ step_count=payload.get("step_count", 0),
54
+ )
inference.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import os
3
+ sys.path.append(os.path.dirname(os.path.abspath(__file__)))
4
+
5
+ from client import OpenenvJayeshEnv
6
+ from models import TaskManagerAction
7
+
8
+ def run_easy_task(client):
9
+ print("\n=== Scenario: Easy ===")
10
+ res = client.reset()
11
+ default_msg = res.observation.message if hasattr(res.observation, 'message') else "No message"
12
+ print(f"Goal: {default_msg}")
13
+
14
+ res = client.step(TaskManagerAction(command="add", title="Buy groceries", priority="Normal"))
15
+ print(f"Action 'add 1': {res.observation.message} | Score: {res.reward}")
16
+ res = client.step(TaskManagerAction(command="add", title="Do laundry", priority="Low"))
17
+ print(f"Action 'add 2': {res.observation.message} | Score: {res.reward}")
18
+ res = client.step(TaskManagerAction(command="list"))
19
+ print(f"Action 'list': {res.observation.message} | Score: {res.reward} | Done: {res.done}")
20
+
21
+ def run_medium_task(client):
22
+ print("\n=== Scenario: Medium ===")
23
+ res = client.reset()
24
+ default_msg = res.observation.message if hasattr(res.observation, 'message') else "No message"
25
+ print(f"Goal: {default_msg}")
26
+
27
+ res = client.step(TaskManagerAction(command="add", title="Write code", priority="Normal"))
28
+ print(f"Action 'add Normal': {res.observation.message} | Score: {res.reward}")
29
+ res = client.step(TaskManagerAction(command="add", title="Review PR", priority="High"))
30
+ print(f"Action 'add High': {res.observation.message} | Score: {res.reward}")
31
+ res = client.step(TaskManagerAction(command="add", title="Check emails", priority="Low"))
32
+ print(f"Action 'add Low': {res.observation.message} | Score: {res.reward}")
33
+
34
+ res = client.step(TaskManagerAction(command="complete", title="Review PR"))
35
+ print(f"Action 'complete High': {res.observation.message} | Score: {res.reward} | Done: {res.done}")
36
+
37
+ def run_hard_task(client):
38
+ print("\n=== Scenario: Hard ===")
39
+ res = client.reset()
40
+ default_msg = res.observation.message if hasattr(res.observation, 'message') else "No message"
41
+ print(f"Goal: {default_msg}")
42
+
43
+ res = client.step(TaskManagerAction(command="add", title="Fix prod bug", priority="High"))
44
+ print(f"Action 'add High 1': {res.observation.message} | Score: {res.reward}")
45
+ res = client.step(TaskManagerAction(command="add", title="Write incident report", priority="High"))
46
+ print(f"Action 'add High 2': {res.observation.message} | Score: {res.reward}")
47
+ res = client.step(TaskManagerAction(command="add", title="Refactor", priority="Normal"))
48
+ print(f"Action 'add Normal': {res.observation.message} | Score: {res.reward}")
49
+ res = client.step(TaskManagerAction(command="add", title="Update docs", priority="Low"))
50
+ print(f"Action 'add Low': {res.observation.message} | Score: {res.reward}")
51
+
52
+ res = client.step(TaskManagerAction(command="complete", title="Fix prod bug"))
53
+ print(f"Action 'complete High 1': {res.observation.message} | Score: {res.reward}")
54
+ res = client.step(TaskManagerAction(command="complete", title="Write incident report"))
55
+ print(f"Action 'complete High 2': {res.observation.message} | Score: {res.reward} | Done: {res.done}")
56
+
57
+
58
+ if __name__ == "__main__":
59
+ print("Connecting to OpenEnv Task Manager Server on http://127.0.0.1:8000...")
60
+ try:
61
+ with OpenenvJayeshEnv(base_url="http://127.0.0.1:8000").sync() as client:
62
+ run_easy_task(client)
63
+ run_medium_task(client)
64
+ run_hard_task(client)
65
+ except Exception as e:
66
+ print(f"Error connecting to server. Is it running? {e}")
inference_output.txt ADDED
Binary file (3.24 kB). View file
 
models.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+
4
+ """
5
+ Data models for the Openenv Jayesh Task Manager Environment.
6
+ """
7
+
8
+ from typing import Optional, List, Dict, Any
9
+ from openenv.core.env_server.types import Action, Observation
10
+ from pydantic import Field
11
+
12
+ class TaskManagerAction(Action):
13
+ """Action for the Task Manager."""
14
+ command: str = Field(..., description="The command to execute: 'add', 'complete', 'list'")
15
+ title: Optional[str] = Field(default=None, description="Task title (for 'add' or 'complete')")
16
+ priority: Optional[str] = Field(default="Normal", description="Task priority (for 'add')")
17
+ deadline: Optional[str] = Field(default=None, description="Task deadline (for 'add')")
18
+
19
+ class TaskManagerObservation(Observation):
20
+ """Observation from the Task Manager."""
21
+ success: bool = Field(default=True, description="Whether the action succeeded")
22
+ message: str = Field(default="", description="System message or error")
23
+ tasks: List[Dict[str, Any]] = Field(default_factory=list, description="List of tasks in the system")
openenv.yaml ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ spec_version: 1
2
+ name: task-manager-openenv
3
+ type: space
4
+ runtime: fastapi
5
+ app: server.app:app
6
+ port: 8000
7
+ description: "OpenEnv Hackathon Task Manager: AI agent environment tracking task states through Easy, Medium, and Hard dynamic constraints."
8
+ tags:
9
+ - task-manager
10
+ - productivity
11
+ - openenv
openenv_openenv_jayesh.egg-info/PKG-INFO ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.4
2
+ Name: openenv-openenv_jayesh
3
+ Version: 0.1.0
4
+ Summary: Openenv Jayesh environment for OpenEnv
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: openenv-core[core]>=0.2.1
7
+ Provides-Extra: dev
8
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
9
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
openenv_openenv_jayesh.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
+ openenv_openenv_jayesh.egg-info/PKG-INFO
7
+ openenv_openenv_jayesh.egg-info/SOURCES.txt
8
+ openenv_openenv_jayesh.egg-info/dependency_links.txt
9
+ openenv_openenv_jayesh.egg-info/entry_points.txt
10
+ openenv_openenv_jayesh.egg-info/requires.txt
11
+ openenv_openenv_jayesh.egg-info/top_level.txt
12
+ server/__init__.py
13
+ server/app.py
14
+ server/openenv_jayesh_environment.py
openenv_openenv_jayesh.egg-info/dependency_links.txt ADDED
@@ -0,0 +1 @@
 
 
1
+
openenv_openenv_jayesh.egg-info/entry_points.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ [console_scripts]
2
+ server = openenv_jayesh.server.app:main
openenv_openenv_jayesh.egg-info/requires.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ openenv-core[core]>=0.2.1
2
+
3
+ [dev]
4
+ pytest>=8.0.0
5
+ pytest-cov>=4.0.0
openenv_openenv_jayesh.egg-info/top_level.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ openenv_jayesh
out.py ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class Environment(ABC, Generic[ActT, ObsT, StateT]):
2
+ """Base class for all environment servers following Gym/Gymnasium API.
3
+
4
+ Args:
5
+ transform: Optional transform to apply to observations
6
+ rubric: Optional rubric for reward computation. When provided, the
7
+ rubric's output can be used to set the observation's reward in step().
8
+
9
+ Class Attributes:
10
+ SUPPORTS_CONCURRENT_SESSIONS: Whether this environment supports concurrent sessions.
11
+ When True, multiple WebSocket connections can each have their own
12
+ environment instance (up to max_concurrent_envs). When False (default),
13
+ the environment should only be used with a single session at a time.
14
+
15
+ Set this to True in your Environment subclass if:
16
+ - The environment uses proper session isolation (e.g., unique working dirs)
17
+ - No shared mutable state exists between instances
18
+ - External resources (databases, APIs) can handle concurrent access
19
+
20
+ Attributes:
21
+ rubric: Optional rubric for computing rewards. Environments can set this
22
+ in __init__ and use it in step() to compute observation rewards.
23
+ Training infrastructure can access it for introspection:
24
+ for name, r in env.rubric.named_rubrics():
25
+ print(f"{name}: {r.last_score}")
26
+
27
+ See RFC 004 for rubric design: rfcs/004-rubrics.md
28
+ """
29
+
30
+ # Class-level flag indicating whether this environment supports concurrent sessions
31
+ SUPPORTS_CONCURRENT_SESSIONS: bool = False
32
+
33
+ # Optional rubric for reward computation
34
+ rubric: Optional["Rubric"]
35
+
36
+ def __init__(
37
+ self,
38
+ transform: Optional[Transform[ObsT]] = None,
39
+ rubric: Optional["Rubric"] = None,
40
+ ):
41
+ self.transform = transform
42
+ self.rubric = rubric
43
+
44
+ @abstractmethod
45
+ def reset(
46
+ self,
47
+ seed: Optional[int] = None,
48
+ episode_id: Optional[str] = None,
49
+ **kwargs: Any,
50
+ ) -> ObsT:
51
+ """Reset the environment and return initial observation."""
52
+ pass
53
+
54
+ async def reset_async(
55
+ self,
56
+ seed: Optional[int] = None,
57
+ episode_id: Optional[str] = None,
58
+ **kwargs: Any,
59
+ ) -> ObsT:
60
+ """Async version of reset. Default implementation calls sync reset.
61
+
62
+ Override to provide true async implementation.
63
+ """
64
+ return self.reset(seed=seed, episode_id=episode_id, **kwargs)
65
+
66
+ @abstractmethod
67
+ def step(
68
+ self,
69
+ action: ActT,
70
+ timeout_s: Optional[float] = None,
71
+ **kwargs: Any,
72
+ ) -> ObsT:
73
+ """Take a step in the environment."""
74
+ pass
75
+
76
+ async def step_async(
77
+ self,
78
+ action: ActT,
79
+ timeout_s: Optional[float] = None,
80
+ **kwargs: Any,
81
+ ) -> ObsT:
82
+ """Async version of step. Default implementation calls sync step.
83
+
84
+ Override to provide true async implementation.
85
+ """
86
+ return self.step(action, timeout_s=timeout_s, **kwargs)
87
+
88
+ @property
89
+ @abstractmethod
90
+ def state(self) -> StateT:
91
+ """Get the current environment state."""
92
+ pass
93
+
94
+ def get_metadata(self) -> EnvironmentMetadata:
95
+ """
96
+ Get metadata about this environment.
97
+
98
+ Override this method to provide custom metadata for the environment.
99
+ Default implementation returns basic metadata derived from class name.
100
+
101
+ Returns:
102
+ EnvironmentMetadata with environment information
103
+ """
104
+ return EnvironmentMetadata(
105
+ name=self.__class__.__name__,
106
+ description=f"{self.__class__.__name__} environment",
107
+ version="1.0.0",
108
+ )
109
+
110
+ def _apply_transform(self, observation: ObsT) -> ObsT:
111
+ """Apply transform if one is provided."""
112
+ if self.transform is not None:
113
+ return self.transform(observation)
114
+ return observation
115
+
116
+ def _apply_rubric(self, action: ActT, observation: ObsT) -> float:
117
+ """Apply rubric if one is provided.
118
+
119
+ Args:
120
+ action: The action taken by the agent.
121
+ observation: The resulting observation.
122
+
123
+ Returns:
124
+ Reward value from the rubric, or 0.0 if no rubric is set.
125
+
126
+ Usage in step():
127
+ def step(self, action: MyAction, ...) -> MyObservation:
128
+ # ... execute action and create observation ...
129
+ observation.reward = self._apply_rubric(action, observation)
130
+ return observation
131
+ """
132
+ if self.rubric is not None:
133
+ return self.rubric(action, observation)
134
+ return 0.0
135
+
136
+ async def _apply_rubric_async(self, action: ActT, observation: ObsT) -> float:
137
+ """Apply rubric asynchronously if one is provided.
138
+
139
+ Args:
140
+ action: The action taken by the agent.
141
+ observation: The resulting observation.
142
+
143
+ Returns:
144
+ Reward value from the rubric, or 0.0 if no rubric is set.
145
+
146
+ Usage in step_async():
147
+ async def step_async(self, action: MyAction, ...) -> MyObservation:
148
+ # ... execute action and create observation ...
149
+ observation.reward = await self._apply_rubric_async(action, observation)
150
+ return observation
151
+ """
152
+ if self.rubric is not None:
153
+ result = self.rubric(action, observation)
154
+ # If rubric returns a coroutine, await it
155
+ if inspect.iscoroutine(result):
156
+ return await result
157
+ return result
158
+ return 0.0
159
+
160
+ def _reset_rubric(self) -> None:
161
+ """Reset the rubric state if one is provided.
162
+
163
+ Call this in reset() to clear any trajectory state in the rubric.
164
+
165
+ Usage in reset():
166
+ def reset(self, ...) -> MyObservation:
167
+ self._reset_rubric()
168
+ # ... create initial observation ...
169
+ return observation
170
+ """
171
+ if self.rubric is not None:
172
+ self.rubric.reset()
173
+
174
+ async def _reset_rubric_async(self) -> None:
175
+ """Reset the rubric state asynchronously if one is provided.
176
+
177
+ Call this in reset_async() to clear any trajectory state in the rubric.
178
+
179
+ Usage in reset_async():
180
+ async def reset_async(self, ...) -> MyObservation:
181
+ await self._reset_rubric_async()
182
+ # ... create initial observation ...
183
+ return observation
184
+ """
185
+ if self.rubric is not None:
186
+ # Check if rubric has async reset method
187
+ if hasattr(self.rubric, "reset_async"):
188
+ result = self.rubric.reset_async()
189
+ if inspect.iscoroutine(result):
190
+ await result
191
+ else:
192
+ self.rubric.reset()
193
+
194
+ def close(self) -> None:
195
+ """Clean up resources used by the environment.
196
+
197
+ Override this method to implement custom cleanup logic.
198
+ Called when the environment is being destroyed or reset.
199
+ """
200
+ pass
201
+
out_utf8.txt ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Connecting to OpenEnv Task Manager Server on http://127.0.0.1:8000...
2
+
3
+ === Scenario: Easy ===
4
+ Goal: Task Manager started in Easy mode. Goal: Add 2 tasks and list them.
5
+ Action 'add 1': Task 'Buy groceries' added successfully. (Priority: Normal) | Score: 0.4
6
+ Action 'add 2': Task 'Do laundry' added successfully. (Priority: Low) | Score: 0.8
7
+ Action 'list': Listed 2 current tasks. | Score: 1.0 | Done: True
8
+
9
+ === Scenario: Medium ===
10
+ Goal: Task Manager started in Medium mode. Goal: Add 3 tasks (mixed priorities) and complete all High priority ones.
11
+ Action 'add Normal': Task 'Write code' added successfully. (Priority: Normal) | Score: 0.2
12
+ Action 'add High': Task 'Review PR' added successfully. (Priority: High) | Score: 0.5
13
+ Action 'add Low': Task 'Check emails' added successfully. (Priority: Low) | Score: 0.7
14
+ Action 'complete High': Task 'Review PR' marked as completed. | Score: 1.0 | Done: True
15
+
16
+ === Scenario: Hard ===
17
+ Goal: Task Manager started in Hard mode. Goal: Add 4 tasks (at least 2 High) and complete at least 2 High priority tasks.
18
+ Action 'add High 1': Task 'Fix prod bug' added successfully. (Priority: High) | Score: 0.2
19
+ Action 'add High 2': Task 'Write incident report' added successfully. (Priority: High) | Score: 0.4
20
+ Action 'add Normal': Task 'Refactor' added successfully. (Priority: Normal) | Score: 0.5
21
+ Action 'add Low': Task 'Update docs' added successfully. (Priority: Low) | Score: 0.6
22
+ Action 'complete High 1': Task 'Fix prod bug' marked as completed. | Score: 0.8
23
+ Action 'complete High 2': Task 'Write incident report' marked as completed. | Score: 1.0 | Done: True
pyproject.toml ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ [build-system]
8
+ requires = ["setuptools>=45", "wheel"]
9
+ build-backend = "setuptools.build_meta"
10
+
11
+ [project]
12
+ name = "openenv-openenv_jayesh"
13
+ version = "0.1.0"
14
+ description = "Openenv Jayesh environment for OpenEnv"
15
+ requires-python = ">=3.10"
16
+ dependencies = [
17
+ # Core OpenEnv runtime (provides FastAPI server + HTTP client types)
18
+ # install from github
19
+ # "openenv-core[core] @ git+https://github.com/meta-pytorch/OpenEnv.git",
20
+ "openenv-core[core]>=0.2.1",
21
+ # Environment-specific dependencies
22
+ # Add all dependencies needed for your environment here
23
+ # Examples:
24
+ # "numpy>=1.19.0",
25
+ # "torch>=2.0.0",
26
+ # "gymnasium>=0.29.0",
27
+ # "openspiel>=1.0.0",
28
+ # "smolagents>=1.22.0,<2",
29
+ ]
30
+
31
+ [project.optional-dependencies]
32
+ dev = [
33
+ "pytest>=8.0.0",
34
+ "pytest-cov>=4.0.0",
35
+ ]
36
+
37
+ [project.scripts]
38
+ # Server entry point - enables running via: uv run --project . server
39
+ # or: python -m openenv_jayesh.server.app
40
+ server = "openenv_jayesh.server.app:main"
41
+
42
+ [tool.setuptools]
43
+ include-package-data = true
44
+ packages = ["openenv_jayesh", "openenv_jayesh.server"]
45
+ package-dir = { "openenv_jayesh" = ".", "openenv_jayesh.server" = "server" }
server/__init__.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """Openenv Jayesh environment server components."""
8
+
9
+ from .openenv_jayesh_environment import OpenenvJayeshEnvironment
10
+
11
+ __all__ = ["OpenenvJayeshEnvironment"]
server/app.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+
4
+ """
5
+ FastAPI application for the Openenv Jayesh Environment.
6
+ """
7
+
8
+ try:
9
+ from openenv.core.env_server.http_server import create_app
10
+ except Exception as e: # pragma: no cover
11
+ raise ImportError(
12
+ "openenv is required for the web interface. Install dependencies with '\n uv sync\n'"
13
+ ) from e
14
+
15
+ try:
16
+ from ..models import TaskManagerAction, TaskManagerObservation
17
+ from .openenv_jayesh_environment import OpenenvJayeshEnvironment
18
+ except (ModuleNotFoundError, ImportError):
19
+ from models import TaskManagerAction, TaskManagerObservation
20
+ from server.openenv_jayesh_environment import OpenenvJayeshEnvironment
21
+
22
+
23
+ app = create_app(
24
+ OpenenvJayeshEnvironment,
25
+ TaskManagerAction,
26
+ TaskManagerObservation,
27
+ env_name="openenv_jayesh",
28
+ max_concurrent_envs=1,
29
+ )
30
+
31
+ def main(host: str = "0.0.0.0", port: int = 8000):
32
+ import uvicorn
33
+
34
+ uvicorn.run(app, host=host, port=port)
35
+
36
+ if __name__ == "__main__":
37
+ import argparse
38
+
39
+ parser = argparse.ArgumentParser()
40
+ parser.add_argument("--port", type=int, default=8000)
41
+ args = parser.parse_args()
42
+ main(port=args.port)
server/openenv_jayesh_environment.py ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+
4
+ """
5
+ Openenv Jayesh Environment Implementation.
6
+ A Simple Task Manager Environment.
7
+ """
8
+
9
+ from typing import Optional, Any
10
+ from uuid import uuid4
11
+
12
+ from openenv.core.env_server.interfaces import Environment
13
+ from openenv.core.env_server.types import State
14
+
15
+ try:
16
+ from ..models import TaskManagerAction, TaskManagerObservation
17
+ except (ModuleNotFoundError, ImportError):
18
+ from models import TaskManagerAction, TaskManagerObservation
19
+
20
+
21
+ class OpenenvJayeshEnvironment(Environment):
22
+ """A Task Manager environment with 3 difficulty modes."""
23
+
24
+ SUPPORTS_CONCURRENT_SESSIONS: bool = True
25
+
26
+ def __init__(self):
27
+ super().__init__()
28
+ self._state = State(episode_id=str(uuid4()), step_count=0)
29
+ self._reset_count = 0
30
+ self._scenarios = ["Easy", "Medium", "Hard"]
31
+
32
+ # Internal state
33
+ self.tasks = []
34
+ self.difficulty = "Easy"
35
+ self.goal_completed = False
36
+ self.tasks_added = 0
37
+ self.high_priority_added = 0
38
+ self.high_priority_completed = 0
39
+ self.list_called = False
40
+
41
+ def reset(
42
+ self,
43
+ seed: Optional[int] = None,
44
+ episode_id: Optional[str] = None,
45
+ **kwargs: Any,
46
+ ) -> TaskManagerObservation:
47
+ """Reset the environment."""
48
+ self._state = State(episode_id=episode_id or str(uuid4()), step_count=0)
49
+
50
+ idx = self._reset_count % len(self._scenarios)
51
+ self.difficulty = self._scenarios[idx]
52
+ self._reset_count += 1
53
+
54
+ self.tasks = []
55
+ self.goal_completed = False
56
+ self.tasks_added = 0
57
+ self.high_priority_added = 0
58
+ self.high_priority_completed = 0
59
+ self.list_called = False
60
+
61
+ message = f"Task Manager started in {self.difficulty} mode. "
62
+ if self.difficulty == "Easy":
63
+ message += "Goal: Add 2 tasks and list them."
64
+ elif self.difficulty == "Medium":
65
+ message += "Goal: Add 3 tasks (mixed priorities) and complete all High priority ones."
66
+ elif self.difficulty == "Hard":
67
+ message += "Goal: Add 4 tasks (at least 2 High) and complete at least 2 High priority tasks."
68
+
69
+ return TaskManagerObservation(
70
+ success=True,
71
+ message=message,
72
+ tasks=self.tasks,
73
+ done=False,
74
+ reward=0.0
75
+ )
76
+
77
+ def step(
78
+ self,
79
+ action: TaskManagerAction,
80
+ timeout_s: Optional[float] = None,
81
+ **kwargs: Any,
82
+ ) -> TaskManagerObservation: # type: ignore[override]
83
+ """Execute a step."""
84
+ self._state.step_count += 1
85
+
86
+ cmd = action.command.lower()
87
+ success = True
88
+ message = ""
89
+
90
+ if cmd == "add":
91
+ if not action.title:
92
+ success = False
93
+ message = "Title is required to add a task."
94
+ else:
95
+ prio = action.priority or "Normal"
96
+ new_task = {
97
+ "title": action.title,
98
+ "priority": prio,
99
+ "deadline": action.deadline or "N/A",
100
+ "completed": False
101
+ }
102
+ self.tasks.append(new_task)
103
+ self.tasks_added += 1
104
+ if prio == "High":
105
+ self.high_priority_added += 1
106
+ message = f"Task '{action.title}' added successfully. (Priority: {prio})"
107
+ elif cmd == "complete":
108
+ if not action.title:
109
+ success = False
110
+ message = "Title is required to complete a task."
111
+ else:
112
+ found = False
113
+ for t in self.tasks:
114
+ if t["title"] == action.title:
115
+ if not t["completed"]:
116
+ t["completed"] = True
117
+ if t["priority"] == "High":
118
+ self.high_priority_completed += 1
119
+ found = True
120
+ message = f"Task '{action.title}' marked as completed."
121
+ break
122
+ if not found:
123
+ success = False
124
+ message = f"Task '{action.title}' not found."
125
+ elif cmd == "list":
126
+ self.list_called = True
127
+ message = f"Listed {len(self.tasks)} current tasks."
128
+ else:
129
+ success = False
130
+ message = f"Unknown command: '{cmd}'"
131
+
132
+ reward = self._calculate_reward()
133
+ done = self.goal_completed
134
+
135
+ return TaskManagerObservation(
136
+ success=success,
137
+ message=message,
138
+ tasks=self.tasks,
139
+ done=done,
140
+ reward=reward,
141
+ metadata={"difficulty": self.difficulty, "step": self._state.step_count}
142
+ )
143
+
144
+ def _calculate_reward(self) -> float:
145
+ reward = 0.0
146
+ self.goal_completed = False
147
+
148
+ if self.difficulty == "Easy":
149
+ # Add 2 tasks (0.4 each) + list them (0.2)
150
+ reward += min(0.8, self.tasks_added * 0.4)
151
+ if self.list_called:
152
+ reward += 0.2
153
+
154
+ if self.tasks_added >= 2 and self.list_called:
155
+ reward = 1.0
156
+ self.goal_completed = True
157
+
158
+ elif self.difficulty == "Medium":
159
+ # Add 3 tasks (0.2 each) + Add High (0.1) + Complete High (0.3 ratio)
160
+ reward += min(0.6, self.tasks_added * 0.2)
161
+ if self.high_priority_added >= 1:
162
+ reward += 0.1
163
+ completion_ratio = self.high_priority_completed / self.high_priority_added
164
+ reward += min(0.3, completion_ratio * 0.3)
165
+
166
+ if self.tasks_added >= 3 and self.high_priority_added >= 1:
167
+ if self.high_priority_completed >= self.high_priority_added:
168
+ reward = 1.0
169
+ self.goal_completed = True
170
+
171
+ elif self.difficulty == "Hard":
172
+ # Add 4 tasks (0.1 each) + Add 2 High (0.1 each) + Complete 2 High (0.2 each)
173
+ reward += min(0.4, self.tasks_added * 0.1)
174
+ reward += min(0.2, self.high_priority_added * 0.1)
175
+ reward += min(0.4, self.high_priority_completed * 0.2)
176
+
177
+ if self.tasks_added >= 4 and self.high_priority_added >= 2 and self.high_priority_completed >= 2:
178
+ reward = 1.0
179
+ self.goal_completed = True
180
+
181
+ return round(min(1.0, reward), 2)
182
+
183
+ @property
184
+ def state(self) -> State:
185
+ return self._state
server/requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ openenv-core[core]>=0.2.1
2
+ fastapi>=0.115.0
3
+ uvicorn>=0.24.0
uv.lock ADDED
The diff for this file is too large to render. See raw diff