Skip to main content
Secrets are stored in YAML files locally and synced to AWS Secrets Manager for production.

Quick Setup

# Copy example files
cp infra/secrets/prd_api_secrets.example.yml infra/secrets/prd_api_secrets.yml
cp infra/secrets/prd_db_secrets.example.yml infra/secrets/prd_db_secrets.yml
The infra/secrets/ directory is excluded from version control (see .gitignore). Treat these files with the same security as passwords.

API Keys

Required

ServiceGet Key AtUsed For
OpenAIplatform.openai.com/api-keysAll agents

Optional

ServiceGet Key AtUsed For
Exadashboard.exa.aiPal research feature

Set your keys

Edit infra/secrets/prd_api_secrets.yml:
infra/secrets/prd_api_secrets.yml
OPENAI_API_KEY: "sk-your-key-here"
EXA_API_KEY: "exa-your-key-here"  # Optional

Verify keys work

Before deploying, test that your keys are valid:
# Test OpenAI - should return a list of models
curl -s https://api.openai.com/v1/models \
  -H "Authorization: Bearer $OPENAI_API_KEY" | head -20

# Test Exa (optional) - should return search results
curl -s https://api.exa.ai/search \
  -H "x-api-key: $EXA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "test", "numResults": 1}'

Database Credentials

Edit infra/secrets/prd_db_secrets.yml:
infra/secrets/prd_db_secrets.yml
DB_USER: "ai"
DB_PASS: "YourSecurePassword123"
Generate a secure password:
openssl rand -base64 24
Avoid special characters in DB_PASS: @, #, %, &, ", 'These require URL encoding and cause silent connection failures. Stick to alphanumeric characters and !.Safe example: MySecurePass123!

How credentials flow

prd_db_secrets.yml → AWS Secrets Manager → ECS Task → App environment
FileVariableBecomes in App
prd_db_secrets.ymlDB_USERDB_USER
prd_db_secrets.ymlDB_PASSDB_PASS

How Secrets Work

Local Development

Apps read secrets directly from YAML files:
dev_resources.py
dev_fastapi = FastApi(
    ...
    secrets_file=infra_settings.infra_root.joinpath("infra/secrets/dev_api_secrets.yml"),
)

Production

Secrets are stored in AWS Secrets Manager and injected into ECS tasks:
prd_resources.py
# API secrets (OpenAI, Exa)
prd_secret = SecretsManager(
    name=f"{infra_settings.prd_key}-secrets",
    secret_files=[infra_settings.infra_root.joinpath("infra/secrets/prd_api_secrets.yml")],
)

# Database secrets
prd_db_secret = SecretsManager(
    name=f"{infra_settings.prd_key}-db-secrets",
    secret_files=[infra_settings.infra_root.joinpath("infra/secrets/prd_db_secrets.yml")],
)
Why two secrets? API keys and database credentials are stored separately for security isolation. You can rotate one without affecting the other.

Verify Secrets in AWS

After deploying (ag infra up prd:aws), verify secrets were created:
aws secretsmanager list-secrets \
  --query "SecretList[?contains(Name, 'agentos-aws-template-prd')].[Name]" \
  --output table
If you changed infra_name in settings.py, replace agentos-aws-template with your value.
Expected output:
-------------------------------------------
|              ListSecrets               |
+----------------------------------------+
|  agentos-aws-template-prd-secrets      |
|  agentos-aws-template-prd-db-secrets   |
+----------------------------------------+
To view secret values (be careful - this exposes credentials):
aws secretsmanager get-secret-value \
  --secret-id agentos-aws-template-prd-secrets \
  --query SecretString --output text

Troubleshooting

The secret doesn’t exist yet. Run ag infra up prd:aws to create it from your YAML files.
Check for special characters in DB_PASS. Remove any @, #, %, & characters and redeploy.
  1. Verify the key works locally (use the curl commands above)
  2. Check the secret was updated: aws secretsmanager get-secret-value --secret-id {name}
  3. Redeploy the task to pick up new secrets: ag infra patch prd:aws:::service