| title | Email Triage OpenEnv | |||||||
|---|---|---|---|---|---|---|---|---|
| emoji | π¬ | |||||||
| colorFrom | blue | |||||||
| colorTo | green | |||||||
| sdk | docker | |||||||
| app_port | 7860 | |||||||
| pinned | false | |||||||
| license | mit | |||||||
| tags |
|
A real-world email inbox triage environment for training and evaluating AI agents.
Built on the OpenEnv spec by Meta & Hugging Face.
Email triage is one of the most common, high-value knowledge-worker tasks:
- Knowledge workers spend 28% of their workweek managing email (McKinsey)
- Enterprise AI assistants must handle priority classification, action routing, and professional reply drafting
- Unlike games or coding tasks, email triage tests real-world language understanding under ambiguity
This environment fills a gap in the OpenEnv ecosystem: no existing environment covers natural language inbox management with graded reward signals across all three dimensions.
| Property | Value |
|---|---|
| Task type | Classification + Action selection + NLG |
| Episodes | Per-task inbox of 5β12 emails |
| Reward type | Dense (per-step partial credit) |
| Tasks | 3 (easy β medium β hard) |
| Action space | Structured JSON (label/reply/archive/delete/escalate) |
| Observation space | Email content + inbox state + running score |
- 5 emails with clear, unambiguous priority signals
- Agent must assign:
urgent | high | medium | low - Max 10 steps
- Expected score range: 0.7β1.0 for capable models
- 8 emails with mixed signals and some ambiguity
- Agent must assign priority AND choose:
reply | archive | delete | escalate - Max 16 steps
- Expected score range: 0.45β0.75 for frontier models
- 12 emails across all categories
- Agent must label priority, choose action, AND draft relevant professional replies for emails that require a response
- Reply quality scored on: relevance, professionalism, urgency acknowledgment
- Max 30 steps
- Expected score range: 0.30β0.60 for frontier models
{
"email_id": "e001",
"action_type": "label",
"priority": "urgent",
"reply_body": null
}| Field | Type | Required | Values |
|---|---|---|---|
email_id |
string | β | ID of target email |
action_type |
string | β | label | reply | archive | delete | escalate | skip |
priority |
string | when action_type=label |
urgent | high | medium | low |
reply_body |
string | when action_type=reply |
Full reply text |
Action semantics:
labelβ assign a priority level to the emailreplyβ compose and send a reply (requiresreply_body)archiveβ file the email, no response neededdeleteβ remove from inbox (spam, newsletters, irrelevant)escalateβ flag as requiring immediate human attentionskipβ pass (penalized: β0.05 reward)
{
"current_email": {
"id": "e001",
"from": "cto@enterprise.com",
"subject": "URGENT: Production database is down",
"body": "...",
"timestamp": "2024-03-15T09:02:00Z",
"has_attachment": false,
"labels": []
},
"inbox_summary": {
"total": 5,
"processed": 1,
"pending": 4,
"current_index": 1
},
"last_action_result": "Action 'label' recorded for email 'e001'. Progress: 1/5.",
"last_action_error": null,
"task_name": "easy_triage",
"step_count": 1,
"max_steps": 10,
"score_so_far": 0.4,
"done": false,
"reward": 0.4
}Rewards are dense β the agent receives signal at every step, not just at episode end.
step_reward = (
priority_accuracy Γ task_priority_weight Γ 0.4
+ action_accuracy Γ task_action_weight Γ 0.4
+ reply_quality Γ task_reply_weight Γ 0.4
β 0.05 Γ (action == "skip")
β 0.10 Γ invalid_action
)
Priority accuracy: Full credit for exact match, partial credit for adjacent levels (e.g., predicting high when true is urgent gets 0.65 credit).
Action accuracy: Full credit for exact match, partial credit for semantically similar choices (e.g., reply when true is escalate gets 0.5 credit).
Reply quality (hard task): Heuristic scorer measuring:
- Message length and substance (0.2)
- Topic relevance β keyword overlap with subject/body (0.3)
- Professional tone β greeting + sign-off (0.2)
- Urgency acknowledgment for high-priority emails (0.3)
Task weights:
| Task | Priority | Action | Reply |
|---|---|---|---|
| easy | 1.0 | 0.0 | 0.0 |
| medium | 0.5 | 0.5 | 0.0 |
| hard | 0.35 | 0.35 | 0.30 |
Final episode score = weighted average across all emails (0.0β1.0).
- Python 3.10+
- Docker
pip install openenv-core
# Clone and install
git clone https://huggingface.co/spaces/your-org/email-triage-env
cd email-triage-env
pip install -e .
# Run server locally
PYTHONPATH=. uvicorn email_triage_env.server.app:app --host 0.0.0.0 --port 7860
# In another terminal β quick smoke test
curl -s -X POST http://localhost:7860/reset \
-H "Content-Type: application/json" \
-d '{}' | python3 -m json.tool# Build (from repo root)
docker build -f email_triage_env/server/Dockerfile -t email-triage-env .
# Run easy task
docker run -p 7860:7860 -e EMAIL_TRIAGE_TASK=easy_triage email-triage-env
# Run hard task
docker run -p 7860:7860 -e EMAIL_TRIAGE_TASK=hard_triage email-triage-env
# Test the server
curl -s http://localhost:7860/health
curl -s -X POST http://localhost:7860/reset -H "Content-Type: application/json" -d '{}'export HF_TOKEN=hf_your_token
export API_BASE_URL=https://router.huggingface.co/v1
export MODEL_NAME=Qwen/Qwen2.5-72B-Instruct
export IMAGE_NAME=email-triage-env # local Docker image
export EMAIL_TRIAGE_TASK=easy_triage,medium_triage,hard_triage
python inference.pyExpected output:
[START] task=easy_triage env=email-triage-env model=Qwen/Qwen2.5-72B-Instruct
[STEP] step=1 action=label(e001,priority=urgent) reward=0.40 done=false error=null
[STEP] step=2 action=label(e010,priority=low) reward=0.40 done=false error=null
...
[END] success=true steps=5 score=0.820 rewards=0.40,0.40,0.40,0.40,0.00
openenv validate# Install HF CLI
pip install huggingface_hub
# Login
huggingface-cli login
# Push
openenv push --repo-id your-org/email-triage-envTested with Qwen/Qwen2.5-72B-Instruct via HuggingFace router:
| Task | Score | Notes |
|---|---|---|
easy_triage |
~0.82 | Strong on clear signals, occasional priority confusion |
medium_triage |
~0.61 | Action selection harder; reply/escalate confusion |
hard_triage |
~0.47 | Reply quality limits score; urgency acknowledgment weak |
Frontier model ceiling (GPT-4o, Claude 3.5):
| Task | Score |
|---|---|
easy_triage |
~0.95 |
medium_triage |
~0.78 |
hard_triage |
~0.65 |
The hard task remains genuinely challenging: drafting relevant, professional replies that reference specific email context requires deep language understanding beyond surface-level classification.
The environment includes 15 realistic email scenarios across 9 categories:
| Category | Examples |
|---|---|
| Incident | Production DB down, security alert |
| Legal | Contract deadline, NDAs |
| Sales | Enterprise renewal, pricing request |
| Support | Bug reports, how-to questions |
| HR | Benefits enrollment, expense reports |
| Business | Partnership proposals, press requests |
| Billing | AWS invoices, subscription notices |
| Notifications | GitHub PR approvals, LinkedIn pings |
| Newsletter | Marketing, social media digests |
Each email has ground-truth labels for: true_priority, true_category, true_action, and requires_response.
email-triage-env/
βββ __init__.py # EmailTriageEnv, EmailAction, EmailObservation
βββ models.py # Pydantic model exports
βββ client.py # WebSocket EnvClient
βββ inference.py # β Baseline agent (root level, per OpenEnv spec)
βββ openenv.yaml # Environment manifest
βββ pyproject.toml # Package config
βββ README.md
βββ server/
βββ __init__.py
βββ email_data.py # 15 email scenarios + 3 task configs
βββ environment.py # EmailTriageEnvironment core logic
βββ app.py # FastAPI via openenv create_app()
βββ requirements.txt
βββ Dockerfile
| Variable | Default | Description |
|---|---|---|
EMAIL_TRIAGE_TASK |
easy_triage |
Active task name |
ENABLE_WEB_INTERFACE |
false |
Enable Gradio UI at /web |
PORT |
7860 |
Server port |
API_BASE_URL |
HF router | LLM endpoint for inference |
MODEL_NAME |
Qwen2.5-72B | Model for inference script |
HF_TOKEN |
β | HuggingFace API key |
IMAGE_NAME |
β | Docker image for client |
MIT β contributions welcome.