exec-ops-env / app.py
saaheerpurav's picture
first commit
1bb18a0
Raw
History Blame Contribute Delete
5.47 kB
import os
from fastapi import FastAPI, Query, HTTPException
from fastapi.staticfiles import StaticFiles
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from env import ExecOpsEnv, Action, grade
app = FastAPI(title="AI Executive Operations Manager", version="1.0.0")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# Global environment instance (single session)
env = ExecOpsEnv("easy")
@app.api_route("/reset", methods=["GET", "POST"])
def reset(task: str = Query("easy")):
"""Reset the environment to a fresh state for the given task."""
global env
valid_tasks = ["easy", "medium", "hard"]
if task not in valid_tasks:
raise HTTPException(status_code=400, detail=f"Invalid task. Choose from: {valid_tasks}")
env = ExecOpsEnv(task)
return env.reset()
@app.post("/step")
def step(action: Action):
"""Execute one action step in the environment."""
global env
try:
result = env.step(action)
return result
except (ValueError, RuntimeError) as e:
raise HTTPException(status_code=400, detail=str(e))
@app.get("/state")
def get_state():
"""Return the full current environment state."""
global env
return env.get_state()
@app.get("/grade")
def get_grade():
"""Return the current grade score (0.0-1.0)."""
global env
return {"score": grade(env._state), "task_id": env.task_id}
@app.get("/health")
def health():
"""OpenEnv runtime health check."""
return {"status": "healthy"}
@app.get("/metadata")
def metadata():
"""OpenEnv runtime metadata."""
return {
"name": "ai-executive-ops-manager",
"description": (
"Simulates a startup CEO's operational day. An AI agent manages a realistic "
"executive inbox under time pressure, making triage decisions - reply, schedule, "
"delegate, or ignore - to handle conflicting priorities and cascading crises."
),
"version": "1.0.0",
"tasks": ["easy", "medium", "hard"],
}
@app.get("/schema")
def schema():
"""OpenEnv runtime schema for action, observation, and state."""
return {
"action": {
"type": "object",
"properties": {
"type": {"type": "string", "enum": ["reply", "schedule", "delegate", "ignore"]},
"email_id": {"type": "string", "description": "ID of the email to act on (e.g. 'e1')"},
},
"required": ["type", "email_id"],
},
"observation": {
"type": "object",
"properties": {
"time": {"type": "string"},
"step": {"type": "integer"},
"max_steps": {"type": "integer"},
"steps_remaining": {"type": "integer"},
"inbox": {"type": "array", "items": {"type": "object"}},
"calendar": {"type": "array", "items": {"type": "object"}},
"goals": {"type": "array", "items": {"type": "object"}},
"pending_goals": {"type": "array", "items": {"type": "object"}},
"total_reward": {"type": "number"},
"done": {"type": "boolean"},
},
},
"state": {
"type": "object",
"properties": {
"current_step": {"type": "integer"},
"max_steps": {"type": "integer"},
"current_time": {"type": "string"},
"inbox": {"type": "array"},
"calendar": {"type": "array"},
"goals": {"type": "array"},
"history": {"type": "array"},
"total_reward": {"type": "number"},
"done": {"type": "boolean"},
"task_id": {"type": "string"},
},
},
}
@app.post("/mcp")
def mcp(request: dict = None):
"""Minimal MCP/JSON-RPC endpoint for OpenEnv runtime compliance."""
return {"jsonrpc": "2.0", "result": {"tools": []}, "id": None}
@app.get("/tasks")
def list_tasks():
"""Return available task descriptions."""
return {
"tasks": [
{
"id": "easy",
"name": "Monday Morning Catchup",
"difficulty": "Easy",
"description": "A manageable Monday inbox. Priorities are clear.",
"max_steps": 6,
},
{
"id": "medium",
"name": "Investor Demo Day Prep",
"difficulty": "Medium",
"description": "Conflicting priorities on demo day. Scheduling required.",
"max_steps": 8,
},
{
"id": "hard",
"name": "Series B Crisis Day",
"difficulty": "Hard",
"description": "Cascading crises. Impossible to handle everything.",
"max_steps": 8,
},
]
}
# Serve React frontend - MUST come after all API routes
FRONTEND_DIR = os.path.join(os.path.dirname(__file__), "frontend", "build")
if os.path.exists(FRONTEND_DIR):
app.mount("/", StaticFiles(directory=FRONTEND_DIR, html=True), name="frontend")
else:
# Fallback: return health check at root if frontend not built yet
@app.get("/")
def root():
return {"status": "ok", "message": "AI Executive Operations Manager API", "frontend": "not built"}