Grey-penglin/pantry_pulse
Pantry Pulse Environment
A reinforcement learning environment that simulates 30-day household pantry management. The agent must intelligently purchase groceries, manage inventory, minimize waste from spoilage, and satisfy daily nutritional needs โ all within a fixed weekly budget.
๐ Read the blog post: Pantry Pulse: Long-Horizon Logistics & Nutrition Simulation
Training Glimpse
<details> <summary> Live Training Loop โ first 5 episodes</summary>
Starting Live Training Loop (100 Episodes)...
Episode 1/100 | Reward: -11.04
Episode 2/100 | Reward: 0.00
Episode 3/100 | Reward: -10.08
Episode 4/100 | Reward: -8.79
Episode 5/100 | Reward: 1.72</details>
Quick Start
The simplest way to use the Pantry Pulse environment is through the PantryPulseEnv class:
from pantry_pulse import PantryPulseAction, PantryPulseEnv
try:
# Create environment from Docker image
env = PantryPulseEnv.from_docker_image("pantry_pulse-env:latest")
# Reset to day 1
result = env.reset()
print(f"Day: {result.observation.day}")
print(f"Budget remaining: ${result.observation.budget_remaining:.2f}")
# Step through the simulation
for day in range(30):
action = PantryPulseAction(purchases={"rice": 2, "chicken": 1})
result = env.step(action)
print(f"Day {result.observation.day} โ Reward: {result.reward:.2f}")
if result.done:
break
finally:
# Always clean up
env.close()The PantryPulseEnv.from_docker_image() method handles:
- Starting the Docker container
- Waiting for the server to be ready
- Connecting to the environment
- Container cleanup when you call
close()
Building the Docker Image
Before using the environment, build the Docker image from the project root:
docker build -t pantry_pulse-env:latest -f server/Dockerfile .Deploying to Hugging Face Spaces
Deploy your environment to Hugging Face Spaces using the openenv push command:
# From the environment directory (where openenv.yaml is located)
openenv push
# Or specify options
openenv push --namespace my-org --privateThe openenv push command will:
- Validate that the directory is an OpenEnv environment (checks for
openenv.yaml) - Prepare a custom build for Hugging Face Docker Spaces (enables web interface)
- Upload to Hugging Face (prompting for login if not already authenticated)
Prerequisites
- Authenticate with Hugging Face โ the command will prompt for login if not already done.
Options
Examples
# Push to your personal namespace
openenv push
# Push to a specific repository
openenv push --repo-id my-org/my-env
# Push with a custom base image
openenv push --base-image ghcr.io/meta-pytorch/openenv-base:latest
# Push as a private space
openenv push --private
# Combine options
openenv push --repo-id my-org/my-env --base-image custom-base:latest --privateAfter deployment, your space will be available at: https://huggingface.co/spaces/<repo-id>
The deployed space includes:
Environment Details
Action
`PantryPulseAction` โ Represents the agent's purchasing decision for a given day.
Observation
`PantryPulseObservation` โ The environment state returned after each step.
Reward
The reward function balances nutrition, waste minimization, and budget efficiency:
Advanced Usage
Connecting to an Existing Server
If a Pantry Pulse server is already running, connect directly:
from pantry_pulse import PantryPulseEnv
env = PantryPulseEnv(base_url="<ENV_HTTP_URL_HERE>")
result = env.reset()
result = env.step(PantryPulseAction(purchases={"eggs": 6}))Note: When connecting to an existing server, env.close() will not stop the server.Using the Context Manager
The client supports context manager usage for automatic connection management:
from pantry_pulse import PantryPulseAction, PantryPulseEnv
with PantryPulseEnv(base_url="http://localhost:8000") as env:
result = env.reset()
for day in range(30):
action = PantryPulseAction(purchases={"bread": 1, "milk": 1})
result = env.step(action)
print(f"Day {result.observation.day} โ Reward: {result.reward:.2f}")
if result.done:
breakThe client uses WebSocket connections for:
- Lower latency โ no HTTP connection overhead per request
- Persistent session โ server maintains environment state across steps
- Efficient for episodes โ ideal for many sequential steps
Concurrent WebSocket Sessions
The server supports multiple concurrent WebSocket connections. Enable this by modifying server/app.py to use factory mode:
# In server/app.py โ use factory mode for concurrent sessions
app = create_app(
PantryPulseEnvironment, # Pass class, not instance
PantryPulseAction,
PantryPulseObservation,
max_concurrent_envs=4, # Allow 4 concurrent sessions
)Run multiple episodes concurrently:
from pantry_pulse import PantryPulseAction, PantryPulseEnv
from concurrent.futures import ThreadPoolExecutor
def run_episode(agent_id: int):
with PantryPulseEnv(base_url="http://localhost:8000") as env:
result = env.reset()
for day in range(30):
action = PantryPulseAction(purchases={"rice": 1, "chicken": 1})
result = env.step(action)
if result.done:
break
return agent_id, result.reward
# Run 4 episodes concurrently
with ThreadPoolExecutor(max_workers=4) as executor:
results = list(executor.map(run_episode, range(4)))Development & Testing
Direct Environment Testing
Test the environment logic directly without starting the HTTP server:
# From the project root
python3 server/pantry_pulse_environment.pyThis verifies that:
- Environment resets correctly to day 1
- Step executes purchase actions and updates inventory
- Expiration logic fires on the correct days
- Rewards are calculated correctly
Running Locally
Run the server locally for development:
uvicorn server.app:app --reloadProject Structure
pantry_pulse/
โโโ .dockerignore # Docker build exclusions
โโโ __init__.py # Module exports
โโโ README.md # This file
โโโ openenv.yaml # OpenEnv manifest
โโโ pyproject.toml # Project metadata and dependencies
โโโ uv.lock # Locked dependencies (generated)
โโโ client.py # PantryPulseEnv client
โโโ models.py # Action and Observation models
โโโ server/
โโโ __init__.py # Server module exports
โโโ pantry_pulse_environment.py # Core environment logic
โโโ app.py # FastAPI app (HTTP + WebSocket)
โโโ Dockerfile # Container image definition