Guides deploying PolicyEngine custom backend APIs to the shared Modal 'policyengine' workspace: authentication via CLI, environment selection (main/staging/testing), setup commands, verification, and troubleshooting.
From essentialnpx claudepluginhub policyengine/policyengine-claude --plugin data-scienceThis skill uses the workspace's default tool permissions.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Migrates code, prompts, and API calls from Claude Sonnet 4.0/4.5 or Opus 4.1 to Opus 4.5, updating model strings on Anthropic, AWS, GCP, Azure platforms.
Details PluginEval's skill quality evaluation: 3 layers (static, LLM judge), 10 dimensions, rubrics, formulas, anti-patterns, badges. Use to interpret scores, improve triggering, calibrate thresholds.
How to deploy PolicyEngine backend APIs (custom Modal backends for dashboards and interactive tools) to Modal under the PolicyEngine organizational workspace.
This skill applies only when a dashboard or tool uses the custom-backend data pattern. If the project uses api-v2-alpha (stub data or direct API calls), no Modal deployment is needed.
PolicyEngine uses a shared Modal workspace called policyengine. All backend deployments MUST target this workspace — never a personal workspace.
The policyengine workspace has three environments:
| Environment | Web suffix | URL pattern | Purpose |
|---|---|---|---|
main | (empty) | policyengine--<app>-<func>.modal.run | Production |
staging | staging | policyengine-staging--<app>-<func>.modal.run | Pre-production testing |
testing | testing | policyengine-testing--<app>-<func>.modal.run | Development/CI |
Default to main for production deployments.
pip install modalpolicyengine workspace stored in a local profile# Create a token for the policyengine workspace (opens browser)
modal token new --profile policyengine
# Activate the profile
modal profile activate policyengine
# Verify — must show "Workspace: policyengine"
modal token info
HUMAN GATE: Before any deployment, verify the active workspace:
modal token info
modal profile list
Expected output from modal profile list:
┏━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ ┃ Profile ┃ Workspace ┃
┡━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ • │ policyengine │ policyengine │
└───┴──────────────┴──────────────┘
The • indicates the active profile. If policyengine is not active or not present:
Authentication required. Your Modal CLI is not configured for the
policyengineworkspace.Run:
modal token new --profile policyengine modal profile activate policyengineIf you don't have access to the PolicyEngine workspace, ask a workspace owner to invite you at https://modal.com/settings/policyengine
Do NOT proceed with deployment until modal token info shows Workspace: policyengine.
Modal CLI respects MODAL_TOKEN_ID and MODAL_TOKEN_SECRET environment variables, which override the profile. If these are set (e.g., from a CI environment), the CLI will deploy to whatever workspace those tokens belong to — potentially a personal workspace.
Always unset before deploying:
unset MODAL_TOKEN_ID MODAL_TOKEN_SECRET
The Modal app name MUST match the dashboard/tool repo name in kebab-case:
app = modal.App("my-dashboard-name")
This keeps the app name consistent with the GitHub repo and Vercel project.
Build a container image with the required Python packages:
image = (
modal.Image.debian_slim(python_version="3.12")
.pip_install(
"policyengine-us>=1.155.0", # Pin minimum version
"fastapi[standard]",
"numpy",
"pandas",
)
.env({"NUMEXPR_MAX_THREADS": "4"})
.add_local_dir("api", "/root/api") # Local source code
.add_local_file("config.yaml", "/root/config.yaml") # Config files
)
Guidelines:
debian_slim with Python 3.12 or 3.13policyengine-us / policyengine-uk.add_local_dir() / .add_local_file() for project source code.env() for non-secret environment variablesFor tools with one calculation endpoint:
@app.function(image=image, timeout=300, memory=2048)
@modal.web_endpoint(method="POST")
def calculate(data: dict) -> dict:
from policyengine_us import Simulation
# Build simulation from data, return results
return {"result": value}
URL: https://policyengine--<app-name>-calculate.modal.run
For dashboards with multiple API routes:
@app.function(image=image, timeout=300, memory=2048)
@modal.concurrent(max_inputs=100)
@modal.asgi_app()
def fastapi_app():
from api.main import app as api
return api
URL: https://policyengine--<app-name>-fastapi-app.modal.run
Every Modal backend SHOULD include a health check:
@app.function(image=image)
@modal.web_endpoint(method="GET")
def health():
return {"status": "ok"}
| Parameter | Default | Recommended for PE | Purpose |
|---|---|---|---|
timeout | 60s | 300 | PolicyEngine simulations can take minutes |
memory | 128MB | 2048 | PE models are memory-intensive |
@modal.concurrent(max_inputs=N) | 1 | 100 | Handle concurrent requests without cold starts |
Store sensitive values as Modal Secrets (not in code or .env files):
# Create a secret
modal secret create my-secret API_KEY=abc123
# List secrets
modal secret list
Reference in code:
@app.function(
image=image,
secrets=[modal.Secret.from_name("my-secret")],
)
def my_function():
import os
api_key = os.environ["API_KEY"] # Injected by Modal
Existing secrets in the policyengine workspace:
policyengine-logfire — logging/observabilitygcp-credentials — Google Cloud accesshuggingface-token — HuggingFace model accessanthropic-api-key — Anthropic API access# 1. Ensure correct workspace
unset MODAL_TOKEN_ID MODAL_TOKEN_SECRET
modal token info # Verify "Workspace: policyengine"
# 2. Deploy to production
modal deploy modal_app.py --env main
# 3. Deploy to staging (for testing)
modal deploy modal_app.py --env staging
Flags:
--env main / --env staging / --env testing — target environment--name TEXT — override the deployment name (rarely needed)--tag TEXT — tag the deployment with a version string--stream-logs — stream container logs during deployment# List deployed apps
modal app list --env main
# Health check
curl -s -w "\n%{http_code}" https://policyengine--DASHBOARD_NAME-health.modal.run
# Test the endpoint
curl -X POST https://policyengine--DASHBOARD_NAME-calculate.modal.run \
-H "Content-Type: application/json" \
-d '{"test": true}'
After Modal deployment, set the API URL as an environment variable in the Vercel project:
vercel env add NEXT_PUBLIC_API_URL production
# Enter: https://policyengine--DASHBOARD_NAME-calculate.modal.run
vercel --prod --force --yes --scope policy-engine
The --force flag is required to rebuild with the new environment variable.
Redeploying an existing app is the same command — Modal handles zero-downtime transitions:
modal deploy modal_app.py --env main
WARNING: This is destructive and irreversible. A stopped app cannot be restarted; you must redeploy.
modal app stop <app-name>
# View logs for a deployed app
modal app logs <app-name>
# List all apps and their status
modal app list --env main
App states:
deployed — running and accepting requestsephemeral — temporary (from modal serve)stopped — permanently stopped| Issue | Cause | Fix |
|---|---|---|
modal token info shows wrong workspace | Wrong profile active | modal profile activate policyengine |
| Deploy goes to personal workspace | MODAL_TOKEN_ID env var set | unset MODAL_TOKEN_ID MODAL_TOKEN_SECRET |
| 404 on endpoint URL | App stopped or never deployed | modal deploy modal_app.py --env main |
| Cold start latency (5-15s) | No warm containers | Add @modal.concurrent(max_inputs=100) |
MemoryError during simulation | Container memory too low | Increase memory= (try 4096) |
| Timeout error | Simulation exceeds limit | Increase timeout= (try 600) |
| Python dependency conflict | Version mismatch | Pin exact versions in .pip_install() |
| Missing local files in container | Forgot .add_local_dir() | Add source dirs to image definition |
| Modal app silently disappeared | Unknown — can happen | curl the URL; if 404, redeploy |
policyengine workspaceMODAL_TOKEN_ID / MODAL_TOKEN_SECRET env vars set when deploying via profilemodal run for production deployments — use modal deploymodal serve for production — it creates ephemeral apps that stop when you close your terminalmodal token info