Bluejay → Coval Migration
Migrate all configuration from Bluejay to Coval for $ARGUMENTS.
API References
- references/bluejay-api.md — Bluejay endpoints, auth, pagination
- references/coval-api.md — Coval endpoints, auth, request schemas
- references/entity-mapping.md — Field-by-field mapping tables
- references/metric-type-mapping.md — Bluejay response_type → Coval metric_type
Step 0: Pre-flight
Collect API Keys
Parse $ARGUMENTS for two positional args: [bluejay-api-key] [coval-api-key].
If not provided, ask the user for each key individually.
Validate Bluejay Key
curl -s -o /dev/null -w "%{http_code}" \
-H "X-API-Key: <BLUEJAY_KEY>" \
"https://api.getbluejay.ai/v1/all-agents"
Expect 200. On 401/403, tell user key is invalid.
Validate Coval Key
curl -s -o /dev/null -w "%{http_code}" \
-H "X-API-Key: <COVAL_KEY>" \
"https://api.coval.dev/v1/agents?page_size=1"
Expect 200. On 401, tell user key is invalid.
Fetch Coval Voice Catalog
curl -s -H "X-API-Key: <COVAL_KEY>" \
"https://api.coval.dev/v1/personas/voices"
Store the voice list for persona mapping in Step 4. Response shape:
{ "voices": [{ "voice_name": "aria", "supported_languages": ["en-US", ...] }] }
Step 1: Inventory
Fetch all Bluejay entities. Use the endpoints in references/bluejay-api.md.
Fetch these in parallel where possible:
| Entity | Endpoint |
|---|---|
| Agents | GET /v1/all-agents |
| Simulations | GET /v1/get-all-simulations |
| Custom Metrics | GET /v1/custom-metrics?page=1&page_size=100 (paginate) |
| Communities | GET /v1/communities |
| Schedules | Derive from simulations (no list-all endpoint — check each simulation) |
| Customer Personas | Fetch per agent: GET /v1/customer-personas-by-agent/{agent_id} |
| Folders | GET /v1/all-folders |
For each agent, also fetch:
- Latest prompt:
GET /v1/agents/{agent_id}/prompts/versions?page_size=1(latest version) - Latest knowledge base:
GET /v1/agents/{agent_id}/knowledge-bases/versions?page_size=1 - Digital humans: Fetch per simulation:
GET /v1/digital-humans-by-simulation/{simulation_id}
Present summary to user:
Found:
- X agents
- Y digital humans (across Z simulations)
- N custom metrics
- M communities
- P customer personas
- Q schedules
- R folders
Proceed with migration?
Wait for user confirmation before continuing.
Step 2: Check Existing Coval State
Fetch existing Coval entities to enable idempotency:
# Agents
curl -s -H "X-API-Key: <COVAL_KEY>" "https://api.coval.dev/v1/agents?page_size=100"
# Personas
curl -s -H "X-API-Key: <COVAL_KEY>" "https://api.coval.dev/v1/personas?page_size=100"
# Test Sets
curl -s -H "X-API-Key: <COVAL_KEY>" "https://api.coval.dev/v1/test-sets?page_size=100"
# Metrics
curl -s -H "X-API-Key: <COVAL_KEY>" "https://api.coval.dev/v1/metrics?page_size=100"
# Run Templates
curl -s -H "X-API-Key: <COVAL_KEY>" "https://api.coval.dev/v1/run-templates?page_size=100"
# Scheduled Runs
curl -s -H "X-API-Key: <COVAL_KEY>" "https://api.coval.dev/v1/scheduled-runs?page_size=100"
Paginate if next_page_token is present.
Build a lookup map of display_name → ID for each entity type. When checking for duplicates:
- If entity exists with
bluejay_migration: truein metadata → skip (already migrated) - If name collision with non-migrated entity → ask user whether to skip or rename
Phase 1: Independent Entities (Steps 3–5)
These have no cross-references. Execute in order but each step is independent.
Step 3: Migrate Metrics
Map Bluejay Custom Metrics → Coval Metrics.
For each Bluejay metric, use the mapping in references/metric-type-mapping.md.
For each metric:
- Determine
metric_typefrom Bluejayresponse_type - Build
promptfrom Bluejayscoring_guidance+description - For
qualitative→ ask user for category list - For
quantitative→ use Bluejaymin_value/max_valueif present, else ask - For
enum→ useenum_optionsascategories - If
eval_route == "AUDIO"→ useMETRIC_AUDIO_LLM_*variants - For
json→ useMETRIC_LLM_BINARY, warn user this is an approximation
curl -s -X POST "https://api.coval.dev/v1/metrics" \
-H "X-API-Key: <COVAL_KEY>" \
-H "Content-Type: application/json" \
-d '{
"metric_name": "<bluejay_metric_name>",
"description": "<bluejay_description>",
"metric_type": "<mapped_type>",
"prompt": "<from_scoring_guidance>",
"metadata": {
"bluejay_migration": true,
"bluejay_original_id": "<bluejay_metric_id>"
}
}'
Add type-specific fields (categories, min_value, max_value) per the mapping table.
Record: bluejay_metric_id → coval_metric_id
Step 4: Migrate Personas
Map Bluejay Digital Humans (voice/behavior) AND Customer Personas → Coval Personas.
Digital Human → Persona
For each unique digital human:
Voice mapping — Present the Bluejay voice config to the user:
Bluejay Digital Human: "<name>" Gender: <gender> Accent: <accent> Language: <language> Available Coval voices: aria, asteria, athena, luna, ... Which Coval voice should map to this configuration?Cache voice mappings — if same gender+accent+language combo appears again, reuse the mapping without asking.
Map
language→language_code(e.g.,en→en-US,es→es-ES)Map
background_noise→background_sound(see entity mapping reference)Build
persona_promptfromrole_description+traits+ behavioral fields
curl -s -X POST "https://api.coval.dev/v1/personas" \
-H "X-API-Key: <COVAL_KEY>" \
-H "Content-Type: application/json" \
-d '{
"name": "<digital_human_name>",
"voice_name": "<mapped_coval_voice>",
"language_code": "<mapped_language_code>",
"persona_prompt": "<built_from_role_description_and_traits>",
"background_sound": "<mapped_background_noise>",
"conversation_initiation": "<from_speaks_first_config>",
"metadata": {
"bluejay_migration": true,
"bluejay_original_id": "<digital_human_id>"
}
}'
Record: bluejay_digital_human_id → coval_persona_id
Customer Persona → Persona
For each Bluejay Customer Persona:
curl -s -X POST "https://api.coval.dev/v1/personas" \
-H "X-API-Key: <COVAL_KEY>" \
-H "Content-Type: application/json" \
-d '{
"name": "<customer_persona_name>",
"voice_name": "aria",
"language_code": "en-US",
"persona_prompt": "Name: <name>\nDescription: <description>\nGoal: <goal>",
"metadata": {
"bluejay_migration": true,
"bluejay_original_id": "<customer_persona_id>",
"bluejay_entity_type": "customer_persona"
}
}'
Ask user which voice to use for customer personas (default to aria).
Step 5: Migrate Agents
Map Bluejay Agents → Coval Agents.
For each Bluejay agent:
- Determine
model_type: Bluejaymode=VOICE→MODEL_TYPE_VOICE,mode=TEXT→MODEL_TYPE_TEXT - Build
promptfrom: latest Prompt versionprompt_text+ agentgoals(append as numbered list) - If agent has a
system_prompt, prepend it to the prompt - Store
knowledge_basecontent in agent metadata - If agent has a
folder, prefixdisplay_namewith folder name:"[FolderName] AgentName"
curl -s -X POST "https://api.coval.dev/v1/agents" \
-H "X-API-Key: <COVAL_KEY>" \
-H "Content-Type: application/json" \
-d '{
"display_name": "<folder_prefix><agent_name>",
"model_type": "<MODEL_TYPE_VOICE|MODEL_TYPE_TEXT>",
"prompt": "<system_prompt + latest_prompt + goals>",
"metadata": {
"bluejay_migration": true,
"bluejay_original_id": "<bluejay_agent_id>",
"knowledge_base": "<kb_text_content>",
"bluejay_connection_type": "<connection_type>",
"bluejay_phone_number": "<phone_number>"
}
}'
Record: bluejay_agent_id → coval_agent_id
Phase 2: Dependent on Phase 1 (Steps 6–7)
Step 6: Migrate Test Sets
Map Bluejay Simulations + Communities → Coval Test Sets.
Simulation → Test Set
For each Bluejay simulation:
curl -s -X POST "https://api.coval.dev/v1/test-sets" \
-H "X-API-Key: <COVAL_KEY>" \
-H "Content-Type: application/json" \
-d '{
"display_name": "<simulation_name>",
"description": "Migrated from Bluejay simulation <simulation_id>",
"test_set_type": "SCENARIO",
"test_set_metadata": {
"bluejay_migration": true,
"bluejay_original_id": "<simulation_id>",
"bluejay_entity_type": "simulation"
}
}'
Record: bluejay_simulation_id → coval_test_set_id
Community → Test Set
For each Bluejay community:
curl -s -X POST "https://api.coval.dev/v1/test-sets" \
-H "X-API-Key: <COVAL_KEY>" \
-H "Content-Type: application/json" \
-d '{
"display_name": "<community_title>",
"description": "<community_description>",
"test_set_type": "SCENARIO",
"test_set_metadata": {
"bluejay_migration": true,
"bluejay_original_id": "<community_id>",
"bluejay_entity_type": "community"
}
}'
Record: bluejay_community_id → coval_test_set_id
Step 7: Migrate Test Cases
Map Bluejay Digital Human intents → Coval Test Cases.
For each digital human associated with a simulation:
- Use
intent→input_str - Use
success_criteria→expected_behaviors(split into array if multi-line) - Look up
coval_test_set_idfrom the simulation mapping (Step 6) - If
scripted_responsespresent → setinput_type: "SCRIPT"withsimulation_metadata_input.script_turns
curl -s -X POST "https://api.coval.dev/v1/test-cases" \
-H "X-API-Key: <COVAL_KEY>" \
-H "Content-Type: application/json" \
-d '{
"test_set_id": "<coval_test_set_id>",
"input_str": "<digital_human_intent>",
"expected_behaviors": ["<success_criteria_line_1>", "<success_criteria_line_2>"],
"description": "<digital_human_name>: <digital_human_tag>",
"input_type": "SCENARIO",
"metadata": {
"bluejay_migration": true,
"bluejay_original_id": "<digital_human_id>"
}
}'
For scripted test cases:
curl -s -X POST "https://api.coval.dev/v1/test-cases" \
-H "X-API-Key: <COVAL_KEY>" \
-H "Content-Type: application/json" \
-d '{
"test_set_id": "<coval_test_set_id>",
"input_str": "<digital_human_intent>",
"expected_behaviors": ["<success_criteria>"],
"input_type": "SCRIPT",
"simulation_metadata_input": {
"script_turns": ["<turn_1>", "<turn_2>", "..."]
},
"metadata": {
"bluejay_migration": true,
"bluejay_original_id": "<digital_human_id>"
}
}'
Record: bluejay_digital_human_id → coval_test_case_id
Phase 3: Depends on Phase 1 + 2 (Step 8)
Step 8: Migrate Run Templates
Map Bluejay Simulations → Coval Run Templates.
For each Bluejay simulation, create a Run Template that ties together the migrated resources:
- Look up
coval_agent_idfrombluejay_agent_id(the simulation's agent) - Pick the first persona mapped from a digital human in this simulation, or ask the user
- Look up
coval_test_set_idfrom the simulation mapping - Collect
coval_metric_idsfrom the agent's associated metrics
curl -s -X POST "https://api.coval.dev/v1/run-templates" \
-H "X-API-Key: <COVAL_KEY>" \
-H "Content-Type: application/json" \
-d '{
"display_name": "<simulation_name> Template",
"description": "Migrated from Bluejay simulation <simulation_id>",
"agent_id": "<coval_agent_id>",
"persona_id": "<coval_persona_id>",
"test_set_id": "<coval_test_set_id>",
"metric_ids": ["<coval_metric_id_1>", "<coval_metric_id_2>"],
"iteration_count": 1,
"concurrency": 5,
"metadata": {
"bluejay_migration": true,
"bluejay_original_id": "<simulation_id>"
}
}'
Record: bluejay_simulation_id → coval_run_template_id
Phase 4: Depends on Phase 3 (Step 9)
Step 9: Migrate Scheduled Runs
Map Bluejay Schedules → Coval Scheduled Runs.
All scheduled runs are created DISABLED for safety. The customer must manually enable them after review.
For each Bluejay schedule:
- Look up
coval_run_template_idfrom the simulation mapping - Convert schedule format:
- Bluejay
cronexpression → Covalcron(...)format - Bluejay
every_n_minutes→rate(<n> minutes) - Bluejay
every_n_hours→rate(<n> hours) - Bluejay
dailyat HH:MM →cron(<MM> <HH> ? * * *) - Bluejay
weekly→cron(<MM> <HH> ? * <DAY> *) - Bluejay
monthly→cron(<MM> <HH> <DOM> * ? *)
- Bluejay
curl -s -X POST "https://api.coval.dev/v1/scheduled-runs" \
-H "X-API-Key: <COVAL_KEY>" \
-H "Content-Type: application/json" \
-d '{
"display_name": "<simulation_name> Schedule",
"run_template_id": "<coval_run_template_id>",
"schedule_expression": "<converted_schedule>",
"enabled": false,
"metadata": {
"bluejay_migration": true,
"bluejay_original_id": "<schedule_id>"
}
}'
Record: bluejay_schedule_id → coval_scheduled_run_id
Step 10: Migration Report
Print a final summary:
## Migration Complete
### Created Entities
| Type | Count | Details |
|------|-------|---------|
| Metrics | N | bluejay_id → coval_id for each |
| Personas | N | ... |
| Agents | N | ... |
| Test Sets | N | ... |
| Test Cases | N | ... |
| Run Templates | N | ... |
| Scheduled Runs | N | (all disabled) |
### Skipped (Already Existed)
- List any entities skipped due to idempotency
### Warnings
- List any ambiguous mappings or approximations made
- Note any `json` metrics mapped to METRIC_LLM_BINARY
- Note any voice mappings that may need review
### Next Steps
1. Review migrated entities in the Coval dashboard: https://app.coval.dev
2. Enable scheduled runs after verifying configuration
3. Run a test evaluation from a migrated Run Template to verify
4. Review persona voice assignments and adjust if needed
Error Handling
- HTTP 429 (Rate Limited): Wait 5 seconds and retry. Max 3 retries per request.
- HTTP 400 (Bad Request): Show the error details to the user. Ask whether to fix the input and retry, or skip this entity.
- HTTP 401/403 (Auth Error): Stop and ask user to verify their API key.
- HTTP 500 (Server Error): Show error, skip entity, continue with others.
- Network Error: Retry once after 3 seconds. If still failing, stop and report what was created so far.
After any failure, always show what has been successfully created so far so the user can resume.
Idempotency Rules
- Before creating any entity, check the name lookup from Step 2
- If entity exists with
bluejay_migration: truein metadata → skip silently - If name collision with non-migrated entity → ask user: "A Coval named '' already exists but wasn't from a Bluejay migration. Skip, rename, or overwrite?"
- All created entities get metadata:
{ "bluejay_migration": true, "bluejay_original_id": "<id>" } - Re-running the migration should produce zero new entities if nothing changed