Skip to main content
The Modal app is agentos, served as one always-warm container. Configuration lives in the agentos-secrets Modal secret, and the database is a Neon Postgres project named agentos.
Modal lifecycle commands use the active CLI profile and environment with fixed app and secret names. The scripts do not persist that target. Confirm the active Modal workspace and environment before env-sync.sh, redeploy.sh, or down.sh; switching either can target a same-named app or secret while the teardown separately deletes the saved Neon project.

Manage

down.sh --yes skips only the wrapper script’s confirmation. It does not pass --yes to modal app stop, so Modal may still prompt while stopping the app.
modal_app.py pins min_containers=1 and max_containers=1. The always-warm container keeps the in-process scheduler and MCP streams alive. The cap controls cost. Schedule claims expire after 300 seconds without renewal, and the same container can reclaim a long-running job. Keep longer targets idempotent or use an external scheduler.

Production auth

Token-Based Authorization is on by default. Production startup requires JWT_VERIFICATION_KEY or a readable JWKS file at the container path in JWT_JWKS_FILE; otherwise the process exits. Token-Based Auth gives you three things:
  1. Protected application routes. AgentOS routes require a valid token. The operational and documentation routes /, /health, /info, /docs, /redoc, /openapi.json, and /docs/oauth2-redirect remain public.
  2. Per-request identity. Middleware validates the token and exposes its user_id, optional session_id, scopes, and claims to the request.
  3. Scope-based permissions. Token scopes control access to AgentOS routes and resources.
The templates do not enable per-user data isolation. To scope non-admin session, memory, trace, and run access to the JWT subject, pass authorization_config=AuthorizationConfig(user_isolation=True) to AgentOS. See User Isolation. To opt out (not recommended), set authorization=False in app/main.py and redeploy. Use this only inside a private VPC behind another auth layer. Without it, anyone who guesses your modal.run URL can access your AgentOS backend.

Customize

Ask your coding agent to run /create-new-agent, or do it by hand. Create agents/my_agent.py:
Register it in app/main.py:
Local containers hot-reload on save. For production, run ./scripts/modal/redeploy.sh.
app/settings.py defines default_model(), used by every agent. Change it in one place:
Add anthropic to pyproject.toml, set the provider key in your env, and regenerate pins:
Rebuild locally with docker compose up -d --build. For production:
env-sync.sh rewrites the secret with the new provider key and redeploys. The redeploy rebuilds the image, so the new dependency ships with it.
Agno ships 100+ toolkits. See Toolkits.
  1. Edit pyproject.toml.
  2. Regenerate pins: ./scripts/generate_requirements.sh (add upgrade to refresh every pin).
  3. Rebuild locally with docker compose up -d --build, or redeploy with ./scripts/modal/redeploy.sh.
Set both variables in your env file:
Sync with ./scripts/modal/env-sync.sh. The interface activates automatically and routes messages to Agent Builder; change the agent= argument in app/main.py to point at another agent. See Slack setup.
The deployment check runs daily by default (ENABLE_DEPLOY_CHECK=True); it is deterministic and free. Scheduled evals are off by default (ENABLE_SCHEDULED_EVALS=False) because they use model calls. Both workflows stay runnable on demand regardless.

Format, validate, and run evals

The format, validate, and eval scripts run on the host and need a venv. Set it up once:
./scripts/mcp_check.sh runs inside the container, so it needs no venv.

Environment variables

Troubleshooting

Install it with brew install neonctl or npm i -g neonctl, then run neonctl auth.
Neon projects are org-scoped, so neonctl projects create asks which organization to use and hangs non-interactive runs. Set NEON_ORG_ID in .env.production (find yours with neonctl orgs list) and re-run ./scripts/modal/up.sh; the script passes it as --org-id so the deploy runs unattended.
Expected. Mint the key at os.agno.com: connect your OS (Connect OSLive, enter your modal.run URL), then turn on Token-Based Authorization (JWT) under SettingsOS & Security and paste the full PEM. To add a PEM later, set JWT_VERIFICATION_KEY and run ./scripts/modal/env-sync.sh. To use JWKS, add the file to the Docker build context or configure a Modal mount, set JWT_JWKS_FILE to its container path, then deploy.
JWT auth is on whenever RUNTIME_ENV is not dev. Set JWT_VERIFICATION_KEY and sync. For JWT_JWKS_FILE, first make the file available inside the Modal image or through a mount, then set its container path and sync. To opt out inside a private VPC behind another auth layer, set authorization=False in app/main.py.
Secrets are read at container start, so rewriting the secret alone changes nothing. ./scripts/modal/env-sync.sh does both steps: it rewrites agentos-secrets and redeploys to roll the container.
AGENTOS_URL is still the localhost default. up.sh sets it to your modal.run URL automatically; for a custom domain or tunnel, set it by hand and run ./scripts/modal/env-sync.sh.
down.sh deletes the Neon project but leaves NEON_PROJECT_ID and the DB_* values in your env file, so up.sh thinks a database still exists. Delete those lines and re-run ./scripts/modal/up.sh to provision a fresh one.
The script only declares success once the app no longer shows as running in modal app list and the project is gone from neonctl projects list. Check both, then re-run it or finish by hand: modal app stop agentos and neonctl projects delete <project-id>.