Imported from team-telnyx/telnyx-toolkit (
tools/missions/SKILL.md). Install upstream withnpx skills add team-telnyx/telnyx-toolkit --skill missions. Copyright stays with the author.
Telnyx AI Missions
Track multi-step agent activities using the Telnyx AI Missions API. Create voice/SMS assistants, schedule calls, and retrieve conversation insights.
π GUARDRAILS β Actions Requiring Explicit User Permission
The following actions are NEVER allowed without explicit user approval. Do not proceed with any of these β even if the mission plan implies them β until the user has reviewed and confirmed.
These guardrails apply to all contexts: interactive sessions, cron-triggered runs, sub-agent executions, and any automated workflow that uses this skill.
Prohibited Without Permission
-
Remove a connection from a phone number β Never unassign or change the connection profile on a phone number without user review. This can break live call routing.
-
Create, edit, or delete an AI assistant β Assistants are shared resources. Creating new ones, modifying instructions/tools/voice on existing ones, or deleting them requires explicit approval. (Reusing an existing assistant as-is is fine.)
-
Create or edit TeXML apps or other connections β Missions should never need to create or modify TeXML applications, SIP connections, FQDN connections, or any other connection type. If a mission plan seems to require this, stop and ask the user β the approach is wrong.
-
Schedule a cron job β Never create, modify, or enable a cron job (OpenClaw cron, system cron, or any scheduled automation) without user review. This includes cron jobs for polling, retries, or follow-up actions.
Enforcement
- Before executing any of the above: pause, describe what you intend to do and why, and wait for explicit approval.
- If running via cron or automation: the cron-triggered agent must also follow these guardrails. Automation does not grant implicit permission. If a guardrailed action is needed, notify the user and wait β do not proceed unattended.
- If in doubt: ask. It is always better to pause and confirm than to take an irreversible action.
Setup
The Python script telnyx_api.py handles all API calls:
# Set your API key
export TELNYX_API_KEY="your_key_here"
# Run commands using the script
python3 {baseDir}/scripts/telnyx_api.py <command> [args...]
# Or create an alias for convenience
alias missions="python3 {baseDir}/scripts/telnyx_api.py"
Note: All command examples in this document use python telnyx_api.py for brevity. Replace with the full path python3 {baseDir}/scripts/telnyx_api.py or use the alias above.
This skill enables you to track your work using the Telnyx AI Missions API, including making phone calls and sending SMS messages through AI assistants.
β οΈ CRITICAL: SAVE STATE FREQUENTLY β οΈ
You MUST save your progress after EVERY significant action. If the session crashes or restarts, unsaved work is LOST.
Two-Layer Persistence: Memory + Events
Always save to BOTH:
- Local Memory (
.missions_state.json) - Fast, survives restarts - Events API (cloud) - Permanent audit trail, survives local file loss
When to Save (After EVERY action!)
| Action | Save Memory | Log Event |
|---|---|---|
| Web search returns results | β append-memory | β log-event (tool_call) |
| Found a contractor/lead | β append-memory | β log-event (custom) |
| Created assistant | β save-memory | β log-event (custom) |
| Assigned phone number | β save-memory | β log-event (custom) |
| Scheduled a call/SMS | β append-memory | β log-event (custom) |
| Call completed | β save-memory | β log-event (custom) |
| Got quote/insight | β save-memory | β log-event (custom) |
| Made a decision | β save-memory | β log-event (message) |
| Step started | β save-memory | β update-step (in_progress) + log-event (step_started) |
| Step completed | β save-memory | β update-step (completed) + log-event (step_completed) |
| Step failed | β save-memory | β update-step (failed) + log-event (error) |
| Error occurred | β save-memory | β log-event (error) |
Memory Commands (Local Backup)
# Save a single value
python telnyx_api.py save-memory "<slug>" "key" '{"data": "value"}'
# Append to a list (great for collecting multiple items)
python telnyx_api.py append-memory "<slug>" "contractors" '{"name": "ABC Co", "phone": "+1234567890"}'
# Retrieve memory
python telnyx_api.py get-memory "<slug>" # Get all memory
python telnyx_api.py get-memory "<slug>" "key" # Get specific key
Event Commands (Cloud Backup)
# Log an event (step_id is REQUIRED - links event to a plan step)
python telnyx_api.py log-event <mission_id> <run_id> <type> "<summary>" <step_id> '[payload_json]'
# Event types: tool_call, custom, message, error, step_started, step_completed
# step_id: Use the step_id from your plan (e.g., "research", "setup", "calls")
# Use "-" if event doesn't belong to a specific step
Example: Complete Save Pattern
After finding a contractor via web search, do BOTH:
# 1. Save to local memory (fast recovery)
python telnyx_api.py append-memory "find-window-washers" "contractors_found" '{"name": "ABC Cleaning", "phone": "+13125551234", "source": "google search"}'
# 2. Log to events API with step_id (permanent cloud record linked to plan step)
python telnyx_api.py log-event "$MISSION_ID" "$RUN_ID" custom "Found contractor: ABC Cleaning +13125551234" "research" '{"contractor": "ABC Cleaning", "phone": "+13125551234", "source": "google search"}'
After scheduling a call:
# 1. Local memory
python telnyx_api.py append-memory "find-window-washers" "calls_scheduled" '{"event_id": "evt_123", "contractor": "ABC Cleaning", "time": "2024-12-01T15:00:00Z"}'
# 2. Cloud event with step_id
python telnyx_api.py log-event "$MISSION_ID" "$RUN_ID" custom "Scheduled call to ABC Cleaning for 3:00 PM" "calls" '{"scheduled_event_id": "evt_123", "contractor": "ABC Cleaning", "scheduled_for": "2024-12-01T15:00:00Z"}'
After getting a quote from a call:
# 1. Local memory
python telnyx_api.py save-memory "find-window-washers" "quotes" '{"ABC Cleaning": {"amount": 350, "available": "next week"}}'
# 2. Cloud event with step_id
python telnyx_api.py log-event "$MISSION_ID" "$RUN_ID" custom "Call completed: ABC Cleaning quoted $350" "calls" '{"contractor": "ABC Cleaning", "quote": 350, "availability": "next week", "conversation_id": "conv_xyz"}'
Best Practices
- Save IMMEDIATELY - Don't wait, don't batch
- Save to BOTH - Memory (local) AND Events (cloud)
- Be verbose - More data saved = easier recovery
- Include context - Timestamps, sources, IDs
- Save partial results - Something is better than nothing
- Save before risky operations - Before long API calls or waits
When to Use This Skill
This skill has two modes: full missions (tracked, multi-step) and simple calls (one-off, no mission overhead). Pick the right one.
Use a Full Mission When:
- The task involves multiple calls or SMS (batch outreach, surveys, sweeps)
- You need a complete audit trail with events, plans, and state tracking
- The task is multi-step and takes significant effort across phases
- Retries and failure tracking matter
- You need to compare results across multiple calls
Examples:
- "Find me window washing contractors in Chicago, call them and negotiate rates"
- "Contact all leads in this list and schedule demos"
- "Call 10 weather stations and find the hottest one"
Do NOT Use a Mission When:
- The task is a single outbound call β just create an assistant (or reuse one) and schedule the call directly
- It's a one-off SMS β schedule it and done
- The task doesn't need tracking, plans, or state recovery
- You'd be creating a mission with one step and one call β that's overengineering
For simple calls, just:
# Reuse or create an assistant
python telnyx_api.py list-assistants --name=<relevant>
# Schedule the call
python telnyx_api.py schedule-call <assistant_id> <to> <from> <datetime> <mission_id> <run_id> <step_id>
# Poll for completion
python telnyx_api.py get-event <assistant_id> <event_id>
# Get insights
python telnyx_api.py get-insights <conversation_id>
No mission, no run, no plan. Keep it simple.
Required Setup
The Python script telnyx_api.py handles all API calls. Check that TELNYX_API_KEY environment variable is set:
python telnyx_api.py check-key
State Persistence
The script automatically manages state in .missions_state.json. This survives restarts and supports multiple concurrent missions.
State Commands
# List all active missions
python telnyx_api.py list-state
# Get state for a specific mission
python telnyx_api.py get-state "find-window-washing-contractors"
# Remove a mission from state
python telnyx_api.py remove-state "find-window-washing-contractors"
Core Workflow
Phase 1: Initialize Tracking
Step 1.1: Create a Mission
python telnyx_api.py create-mission "Brief descriptive name" "Full description of the task"
Save the returned mission_id - you'll need it for all subsequent calls.
Step 1.2: Start a Run
python telnyx_api.py create-run <mission_id> '{"original_request": "The exact user request", "context": "Any relevant context"}'
Save the returned run_id.
Step 1.3: Create a Plan
Before executing, outline your plan:
python telnyx_api.py create-plan <mission_id> <run_id> '[
{"step_id": "step_1", "description": "Research contractors online", "sequence": 1},
{"step_id": "step_2", "description": "Create voice agent for calls", "sequence": 2},
{"step_id": "step_3", "description": "Schedule calls to each contractor", "sequence": 3},
{"step_id": "step_4", "description": "Monitor call completions", "sequence": 4},
{"step_id": "step_5", "description": "Analyze results and select best options", "sequence": 5}
]'
Step 1.4: Set Run to Running
python telnyx_api.py update-run <mission_id> <run_id> running
High-Level Alternative: Initialize Everything at Once
Use the init command to create mission, run, plan, and set status in one step:
python telnyx_api.py init "Find window washing contractors" "Find contractors in Chicago, call them, negotiate rates" "User wants window washing quotes" '[
{"step_id": "research", "description": "Find contractors online", "sequence": 1},
{"step_id": "setup", "description": "Create voice agent", "sequence": 2},
{"step_id": "calls", "description": "Schedule and make calls", "sequence": 3},
{"step_id": "analyze", "description": "Analyze results", "sequence": 4}
]'
This also automatically resumes if a mission with the same name already exists.
Phase 2: Voice/SMS Agent Setup
When your task requires making calls or sending SMS, create an AI assistant first.
Step 2.1: Create a Voice/SMS Assistant
For phone calls:
python telnyx_api.py create-assistant "Contractor Outreach Agent" "You are calling on behalf of [COMPANY]. Your goal is to [SPECIFIC GOAL]. Be professional and concise. Collect: [WHAT TO COLLECT]. If they cannot talk now, ask for a good callback time." "Hi, this is an AI assistant calling on behalf of [COMPANY]. Is this [BUSINESS NAME]? I am calling to inquire about your services. Do you have a moment?" '["telephony"]'
For SMS:
python telnyx_api.py create-assistant "SMS Outreach Agent" "You send SMS messages to collect information. Keep messages brief and professional." "Hi! I am reaching out on behalf of [COMPANY] regarding [PURPOSE]. Could you please reply with [REQUESTED INFO]?" '["messaging"]'
Save the returned assistant_id.
Step 2.2: Find and Assign a Phone Number
2.2.1: List Available Phone Numbers
python telnyx_api.py list-phones --available
Or get the first available one directly:
python telnyx_api.py get-available-phone
If no phone numbers are available, STOP and inform the user:
"No available phone numbers found. You need to purchase phone numbers from Telnyx at https://portal.telnyx.com before I can make calls."
2.2.2: Get Assistant's Connection ID
# For voice calls
python telnyx_api.py get-connection-id <assistant_id> telephony
# For SMS
python telnyx_api.py get-connection-id <assistant_id> messaging
2.2.3: Assign Phone Number to Assistant
# For voice calls
python telnyx_api.py assign-phone <phone_number_id> <connection_id> voice
# For SMS
python telnyx_api.py assign-phone <phone_number_id> <connection_id> sms
High-Level Alternative: Setup Agent in One Step
Use the setup-agent command to create assistant and assign phone number:
python telnyx_api.py setup-agent "find-window-washing-contractors" "Contractor Caller" "You are calling to get quotes for commercial window washing. Ask about: rates per floor, availability, insurance. Be professional." "Hi, I am calling to inquire about your commercial window washing services. Do you have a moment to discuss rates?"
This automatically:
- Creates the assistant with telephony features
- Links the agent to the mission run (if mission_id and run_id are in state)
- Finds an available phone number
- Assigns it to the assistant
- Saves all IDs to the state file
Step 2.3: Link Agent to Mission Run
IMPORTANT: After creating an assistant, you MUST link it to the mission run. This allows the system to track which agents are working on which missions.
If using setup-agent command: Linking is done automatically when mission_id and run_id are in the state.
If setting up manually:
python telnyx_api.py link-agent <mission_id> <run_id> <assistant_id>
You can also list and unlink agents:
# List all agents linked to a run
python telnyx_api.py list-linked-agents <mission_id> <run_id>
# Unlink an agent from a run
python telnyx_api.py unlink-agent <mission_id> <run_id> <assistant_id>
Step 2.4: Log the Setup
python telnyx_api.py log-event <mission_id> <run_id> custom "Created voice assistant and assigned phone number" "setup" '{"assistant_id": "<assistant_id>", "phone_number": "+15551234567", "type": "telephony"}'
Phase 3: Research & Data Gathering
Search for the information you need (contractors, leads, etc.):
- Use web search tools if available
- Use any specialized tools provided for the task
- Log each search as an event with step_id
python telnyx_api.py log-event <mission_id> <run_id> tool_call "Searching for window washing contractors in Chicago" "research" '{"tool": "WebSearch", "query": "commercial window washing contractors Chicago"}'
Phase 4: Scheduling Calls/SMS
Business Hours Consideration
CRITICAL: Before scheduling calls, consider business hours:
- Typical business hours: 9 AM - 5 PM local time
- If current time is outside business hours, schedule for next business day
scheduled_at_fixed_datetimemust be in the future (at least 1 minute from now)
Step 4.1: Schedule a Phone Call
python telnyx_api.py schedule-call <assistant_id> "+15551234567" "+15559876543" "2024-12-01T14:30:00Z" <mission_id> <run_id> <step_id>
Save the returned scheduled_event_id.
Step 4.2: Schedule an SMS
python telnyx_api.py schedule-sms <assistant_id> "+15551234567" "+15559876543" "2024-12-01T14:30:00Z" "Hi! I am reaching out on behalf of [COMPANY] to inquire about your window cleaning rates for commercial buildings. Could you share your pricing?" <mission_id> <run_id> <step_id>
Step 4.3: Log Each Scheduled Event
python telnyx_api.py log-event <mission_id> <run_id> custom "Scheduled call to ABC Window Cleaning for 2:30 PM" "calls" '{"scheduled_event_id": "<event_id>", "contractor": "ABC Window Cleaning", "phone": "+15551234567", "scheduled_for": "2024-12-01T14:30:00Z"}'
Phase 5: Monitoring Call Completion
After a call is scheduled, you need to poll for completion.
Step 5.1: Check Scheduled Event Status
python telnyx_api.py get-event <assistant_id> <scheduled_event_id>
Event Status Values
The event-level status tracks the overall lifecycle:
| Status | Meaning | Action |
|---|---|---|
pending |
Waiting for scheduled time | Wait and check again later |
in_progress |
Call/SMS in progress | Check again in a few minutes |
completed |
Finished successfully | Get conversation_id, fetch insights |
failed |
Failed after retries | Consider rescheduling |
Call Status Values (Phone Calls Only)
The call_status field provides the telephony-level outcome. This is the most important field for deciding what to do next.
| call_status | Meaning | Action |
|---|---|---|
ringing |
Phone is ringing, not yet answered | Still in progress β wait and poll again in 1-2 minutes |
in-progress |
Call is active, conversation ongoing | Still in progress β poll again in 2-3 minutes |
completed |
Call connected and finished normally | Success β get conversation_id, fetch insights |
no-answer |
Phone rang but nobody picked up | Retryable β reschedule for a different time |
busy |
Line is busy | Retryable β reschedule in 10-15 minutes, line may free up quickly |
canceled |
Call was canceled | Check if you canceled it; if not, may need to reschedule |
failed |
Call failed (network/system error) | Retryable β reschedule after a short backoff (5-10 minutes) |
Step 5.2: Polling Strategy
When to start polling: A few minutes after scheduled_at_fixed_datetime
Polling intervals based on call_status:
ringingβ poll again in 1-2 minutes (call may connect any moment)in-progressβ poll again in 2-3 minutes (conversation is happening)pending(event status) β poll every 5 minutes until scheduled time passesno-answer/busy/failedβ stop polling, handle retry immediatelycompletedβ done, proceed to fetch insights
Timeout: If still ringing or in-progress after 30 minutes, treat as failed.
Step 5.3: Handle Retryable Call Statuses
When call_status is no-answer, busy, or failed, the call can be retried:
- Update the call tracker with the failure reason
- Check retry count β by default, retry up to 3 times (unless user specifies otherwise)
- Schedule a retry at a different time based on the failure type:
busyβ retry in 10-15 minutes (line may free up)no-answerβ retry in 30 minutes to 2 hours (try a different time of day)failedβ retry in 5-10 minutes (transient error)
- Log the failure and retry as events
# Update tracker
python telnyx_api.py save-memory "<slug>" "call_tracker" '{"+15551234567": {"status": "no_answer", "attempts": 1, "call_status": "no-answer", "next_retry": "2024-12-02T10:00:00Z"}}'
# Log the failure
python telnyx_api.py log-event "$MISSION_ID" "$RUN_ID" custom "Call not answered (busy), scheduling retry #2" "calls" '{"phone": "+15551234567", "call_status": "busy", "attempt": 1}'
# Schedule retry
python telnyx_api.py schedule-call <assistant_id> "+15551234567" "+15559876543" "2024-12-02T10:00:00Z" <mission_id> <run_id> <step_id>
Phase 6: Getting Conversation Insights
Once a call completes and you have a conversation_id, retrieve the conversation insights.
IMPORTANT: Always use insights to get the call summary. Do NOT fetch raw conversation messages - insights provide a structured summary of the conversation outcome.
Step 6.1: Get Conversation Insights
python telnyx_api.py get-insights <conversation_id>
Step 6.2: Poll Until Insight is Complete
The insight may not be immediately ready after the call ends. You must poll until the insight status is "completed".
Polling strategy:
- Check immediately after getting the
conversation_id - If status is NOT "completed", wait 10 seconds and retry
- Continue polling until status is "completed" or 20 minutes have passed
- Only use the insight data when status is "completed"
Example polling flow:
# First attempt
python telnyx_api.py get-insights "conv_xyz"
# Output: Insight status: in_progress
# Wait 10 seconds, try again
python telnyx_api.py get-insights "conv_xyz"
# Output: Insight status: in_progress
# Wait 10 seconds, try again
python telnyx_api.py get-insights "conv_xyz"
# Output: Insight: Customer quoted $350 for a 10-story building...
Step 6.3: Rename the Conversation
By default, all conversations are named "Voice Assistant Conversation" which is useless for reviewing in the portal. Rename each conversation with a meaningful label after it completes:
python telnyx_api.py rename-conversation "<conv_id>" "Call 1: ABC Window Cleaning - $350/visit"
Best practice: Include the call sequence number, target name, and key outcome in the name. This makes conversations instantly identifiable when browsing the mission in the Telnyx portal.
Step 6.4: Log the Insight
python telnyx_api.py log-event <mission_id> <run_id> custom "Call completed with ABC Window Cleaning - quoted $350" "calls" '{"conversation_id": "<conv_id>", "contractor": "ABC Window Cleaning", "outcome": "success", "quote": "$350", "availability": "next week", "notes": "Willing to negotiate for recurring contracts"}'
Phase 7: Complete the Mission
β οΈ MANDATORY Completion Checklist
Every mission MUST complete ALL of these before it can be considered done. Skipping any of these leaves the mission in a broken state in the Telnyx portal.
| # | Action | Command | Why |
|---|---|---|---|
| 1 | Update ALL plan step statuses | update-step <mid> <rid> <step_id> completed |
Steps show in portal β "pending" means it looks unfinished |
| 2 | Log events for every completed call | log-event ... custom "Call completed" <step_id> '{"conversation_id": "..."}' |
Creates audit trail linking conversations to the mission |
| 3 | Set run result_summary | Included in complete or update-run |
Human-readable summary visible in portal |
| 4 | Set run result_payload | Included in complete or update-run |
Structured data for programmatic consumption |
| 5 | Mark run as succeeded/failed | complete or update-run <mid> <rid> succeeded |
Closes the run |
The complete command handles #3, #4, and #5 in one call β but you still need to do #1 and #2 yourself.
# 1. Update every step status (do this THROUGHOUT the mission, not just at the end)
python telnyx_api.py update-step <mission_id> <run_id> "setup" "completed"
python telnyx_api.py update-step <mission_id> <run_id> "calls" "completed"
python telnyx_api.py update-step <mission_id> <run_id> "analyze" "completed"
# 2. Complete the run with summary and payload
python telnyx_api.py complete "<slug>" <mission_id> <run_id> \
"Contacted 5 contractors, received 4 quotes. Best: ABC Cleaning ($350)" \
'{"contractors_contacted": 5, "quotes_received": 4, "recommended": [{"name": "ABC Cleaning", "quote": 350}]}'
Common mistake: Logging step_completed events but NOT calling update-step. These are separate β events are the audit log, step status is the progress tracker. You need BOTH.
Step 7.1: Analyze Results
After all calls complete:
- Compare quotes and outcomes
- Select best options based on criteria
- Prepare summary for user
Step 7.2: Complete the Run
Use the complete command to set result_summary, result_payload, mark the run as succeeded, and clean up state in one step:
python telnyx_api.py complete "find-window-washing-contractors" <mission_id> <run_id> "Contacted 5 contractors, received 4 quotes. Best options: ABC Cleaning ($350) and XYZ Windows ($380)." '{"contractors_contacted": 5, "quotes_received": 4, "recommended": [{"name": "ABC Cleaning", "quote": 350}, {"name": "XYZ Windows", "quote": 380}]}'
Or use update-run directly with all fields:
python telnyx_api.py update-run <mission_id> <run_id> succeeded "Human-readable summary here" '{"structured": "payload"}'
The complete command also removes the mission from the state file.
Event Logging Reference
Log EVERY action as an event for complete audit trail. Events are stored in the cloud and provide permanent backup even if local files are lost.
CRITICAL: Update Step Status (Not Just Events!)
You MUST update the plan step status via update-step when starting or completing each step. Logging events alone does NOT change the step status β the client tracks progress by looking at step statuses, not events.
# When STARTING a step:
python telnyx_api.py update-step "$MISSION_ID" "$RUN_ID" "research" "in_progress"
python telnyx_api.py log-event "$MISSION_ID" "$RUN_ID" step_started "Starting: Research contractors" "research"
# When COMPLETING a step:
python telnyx_api.py update-step "$MISSION_ID" "$RUN_ID" "research" "completed"
python telnyx_api.py log-event "$MISSION_ID" "$RUN_ID" step_completed "Completed: Research contractors" "research"
# When a step FAILS:
python telnyx_api.py update-step "$MISSION_ID" "$RUN_ID" "calls" "failed"
python telnyx_api.py log-event "$MISSION_ID" "$RUN_ID" error "Failed: Could not reach any contractors" "calls"
# To SKIP a step:
python telnyx_api.py update-step "$MISSION_ID" "$RUN_ID" "setup" "skipped"
Always call update-step BEFORE log-event β this ensures the step status is correct even if the event logging fails.
IMPORTANT: step_id is Required
step_id is a required parameter - it links events to your plan steps, enabling tracking of which activities belong to which phase.
# With step_id (links to plan step)
python telnyx_api.py log-event "$MISSION_ID" "$RUN_ID" custom "Found contractor" "research" '{"name": "ABC"}'
# Use "-" if event doesn't belong to a specific step
python telnyx_api.py log-event "$MISSION_ID" "$RUN_ID" custom "General note" "-" '{"note": "value"}'
The step_id should match one of the step_id values from your plan (e.g., "research", "setup", "calls", "analyze").
| Action | Step Status Update | Event Type | step_id | Example Summary |
|---|---|---|---|---|
| Starting a plan step | update-step ... in_progress |
step_started |
step_id | "Starting: Research contractors" |
| Completing a step | update-step ... completed |
step_completed |
step_id | "Completed: Research contractors" |
| Step failed | update-step ... failed |
error |
step_id | "Failed: Could not reach contractors" |
| Web search | β | tool_call |
"research" | "Searching for window cleaning contractors" |
| Creating assistant | β | custom |
"setup" | "Created voice assistant: ast_123" |
| Scheduling call | β | custom |
"calls" | "Scheduled call to ABC for 2:30 PM" |
| Call completed | β | custom |
"calls" | "Call completed with ABC - got quote $350" |
| Call failed | β | error |
"calls" | "Call to XYZ not answered after 3 attempts" |
| Decision made | β | message |
"analyze" | "Selected ABC and XYZ as top choices" |
Quick Reference: All Commands
# Check setup
python telnyx_api.py check-key
# Missions
python telnyx_api.py create-mission <name> <instructions>
python telnyx_api.py get-mission <mission_id>
python telnyx_api.py list-missions
# Runs
python telnyx_api.py create-run <mission_id> <input_json>
python telnyx_api.py get-run <mission_id> <run_id>
python telnyx_api.py update-run <mission_id> <run_id> <status>
python telnyx_api.py list-runs <mission_id>
# Plan
python telnyx_api.py create-plan <mission_id> <run_id> <steps_json>
python telnyx_api.py get-plan <mission_id> <run_id>
python telnyx_api.py update-step <mission_id> <run_id> <step_id> <status>
# status: pending, in_progress, completed, skipped, failed
# Events (step_id is REQUIRED - use "-" if no specific step)
python telnyx_api.py log-event <mission_id> <run_id> <type> <summary> <step_id> [payload_json]
python telnyx_api.py list-events <mission_id> <run_id>
# Assistants
python telnyx_api.py list-assistants [--name=<filter>] [--page=<n>] [--size=<n>]
python telnyx_api.py create-assistant <name> <instructions> <greeting> [options_json]
python telnyx_api.py get-assistant <assistant_id>
python telnyx_api.py update-assistant <assistant_id> <updates_json>
python telnyx_api.py get-connection-id <assistant_id> [telephony|messaging]
# Phone Numbers
python telnyx_api.py list-phones [--available]
python telnyx_api.py get-available-phone
python telnyx_api.py assign-phone <phone_id> <connection_id> [voice|sms]
# Scheduled Events
python telnyx_api.py schedule-call <assistant_id> <to_phone> <from_phone> <datetime> <mission_id> <run_id> [step_id] [dynamic_variables_json]
python telnyx_api.py schedule-sms <assistant_id> <to_phone> <from_phone> <datetime> <text> [mission_id] [mission_run_id] [step_id] [dynamic_variables_json]
python telnyx_api.py get-event <assistant_id> <event_id>
python telnyx_api.py cancel-scheduled-event <assistant_id> <event_id>
python telnyx_api.py list-events-assistant <assistant_id>
# Insights (conversation results - POLL until status is "completed"!)
python telnyx_api.py get-insights <conversation_id>
python telnyx_api.py rename-conversation <conversation_id> <name>
# Insight Templates (CRUD)
python telnyx_api.py create-insight <name> <instructions> [options_json] # options: json_schema, webhook
python telnyx_api.py get-insight <insight_id>
python telnyx_api.py list-insights
python telnyx_api.py update-insight <insight_id> <updates_json>
# Insight Groups
python telnyx_api.py create-insight-group <name> [options_json] # options: description, webhook
python telnyx_api.py get-insight-group <group_id>
python telnyx_api.py list-insight-groups
python telnyx_api.py update-insight-group <group_id> <updates_json>
python telnyx_api.py assign-insight <group_id> <insight_id>
python telnyx_api.py unassign-insight <group_id> <insight_id>
# Mission Run Agents (linking agents to runs)
python telnyx_api.py link-agent <mission_id> <run_id> <telnyx_agent_id>
python telnyx_api.py list-linked-agents <mission_id> <run_id>
python telnyx_api.py unlink-agent <mission_id> <run_id> <telnyx_agent_id>
# State Management
python telnyx_api.py list-state
python telnyx_api.py get-state <slug>
python telnyx_api.py remove-state <slug>
# Memory (SAVE OFTEN!)
python telnyx_api.py save-memory <slug> <key> <value_json>
python telnyx_api.py get-memory <slug> [key]
python telnyx_api.py append-memory <slug> <key> <item_json>
# High-Level Workflows
python telnyx_api.py init <name> <instructions> <request> [steps_json]
python telnyx_api.py setup-agent <slug> <name> <instructions> <greeting>
python telnyx_api.py complete <slug> <mission_id> <run_id> <summary> [payload_json]
Complete Example: Window Washing Contractors
# 1. Initialize the mission (creates mission, run, plan, sets to running)
python telnyx_api.py init "Find window washing contractors" \
"Find contractors in Chicago, call them, negotiate rates, select best two" \
"Find me window washing contractors in Chicago" \
'[{"step_id": "research", "description": "Find contractors online", "sequence": 1}, {"step_id": "setup", "description": "Create voice agent", "sequence": 2}, {"step_id": "calls", "description": "Schedule and make calls", "sequence": 3}, {"step_id": "analyze", "description": "Analyze results", "sequence": 4}]'
# Output: Created mission: mis_abc123
# Created run: run_def456
# 2. Get the mission slug and IDs from state
python telnyx_api.py get-state "find-window-washing-contractors"
# 3. Mark research step as in_progress and start working
python telnyx_api.py update-step "mis_abc123" "run_def456" "research" "in_progress"
python telnyx_api.py log-event "mis_abc123" "run_def456" step_started "Starting: Find contractors online" "research"
# 4. Setup voice agent (creates assistant, links to run, assigns phone number)
python telnyx_api.py update-step "mis_abc123" "run_def456" "setup" "in_progress"
python telnyx_api.py setup-agent "find-window-washing-contractors" \
"Contractor Caller" \
"You are calling to get quotes for commercial window washing. Ask about: rates per floor, availability, insurance. Be professional." \
"Hi, I am calling to inquire about your commercial window washing services. Do you have a moment to discuss rates?"
# Output: Created assistant: ast_xyz789
# Linked agent ast_xyz789 to run run_def456
# Found available: +15559876543
# Assigned phone number 123456
python telnyx_api.py update-step "mis_abc123" "run_def456" "setup" "completed"
python telnyx_api.py log-event "mis_abc123" "run_def456" step_completed "Completed: Voice agent setup" "setup"
# 5. Get agent phone from state
AGENT_PHONE=$(python telnyx_api.py get-state "find-window-washing-contractors" | python -c "import sys,json; print(json.load(sys.stdin).get('agent_phone',''))")
ASSISTANT_ID=$(python telnyx_api.py get-state "find-window-washing-contractors" | python -c "import sys,json; print(json.load(sys.stdin).get('assistant_id',''))")
# 6. After research, SAVE to memory AND log events with step_id (CRITICAL!)
python telnyx_api.py append-memory "find-window-washing-contractors" "contractors_found" '{"name": "ABC Cleaning", "phone": "+13125551234", "source": "web search"}'
python telnyx_api.py log-event "mis_abc123" "run_def456" custom "Found contractor: ABC Cleaning" "research" '{"name": "ABC Cleaning", "phone": "+13125551234"}'
python telnyx_api.py append-memory "find-window-washing-contractors" "contractors_found" '{"name": "XYZ Windows", "phone": "+13125555678", "source": "web search"}'
python telnyx_api.py log-event "mis_abc123" "run_def456" custom "Found contractor: XYZ Windows" "research" '{"name": "XYZ Windows", "phone": "+13125555678"}'
# 7. Complete research step, start calls step
python telnyx_api.py update-step "mis_abc123" "run_def456" "research" "completed"
python telnyx_api.py log-event "mis_abc123" "run_def456" step_completed "Completed: Found 2 contractors" "research"
python telnyx_api.py update-step "mis_abc123" "run_def456" "calls" "in_progress"
python telnyx_api.py log-event "mis_abc123" "run_def456" step_started "Starting: Schedule calls" "calls"
# 8. Schedule calls
python telnyx_api.py schedule-call "$ASSISTANT_ID" "+13125551234" "$AGENT_PHONE" "2024-12-01T15:00:00Z" "$MISSION_ID" "$RUN_ID" "$STEP_ID"
# Output: Scheduled call: evt_abc123
# 9. SAVE scheduled event to memory AND log event with step_id (CRITICAL!)
python telnyx_api.py append-memory "find-window-washing-contractors" "calls_scheduled" '{"event_id": "evt_abc123", "contractor": "ABC Cleaning", "scheduled_for": "2024-12-01T15:00:00Z"}'
python telnyx_api.py log-event "mis_abc123" "run_def456" custom "Scheduled call to ABC Cleaning for 3:00 PM" "calls" '{"scheduled_event_id": "evt_abc123", "contractor": "ABC Cleaning"}'
# 10. Poll for completion (after scheduled time)
python telnyx_api.py get-event "$ASSISTANT_ID" "evt_abc123"
# Output: Status: completed, conversation_id: conv_xyz
# 11. Get insights - POLL UNTIL STATUS IS "completed"
python telnyx_api.py get-insights "conv_xyz"
# Output: Insight status: in_progress
# (wait 10 seconds and retry)
python telnyx_api.py get-insights "conv_xyz"
# Output: Insight status: in_progress
# (wait 10 seconds and retry)
python telnyx_api.py get-insights "conv_xyz"
# Output: Insight: Customer quoted $350 for a 10-story building. Available next week.
# (status is now "completed" - proceed with the insight data)
# 12. SAVE call results to memory AND log event with step_id (CRITICAL!)
python telnyx_api.py save-memory "find-window-washing-contractors" "call_results" '{"ABC Cleaning": {"status": "completed", "conversation_id": "conv_xyz", "quote": 350, "availability": "next week"}}'
python telnyx_api.py log-event "mis_abc123" "run_def456" custom "Call completed: ABC Cleaning quoted $350, available next week" "calls" '{"contractor": "ABC Cleaning", "quote": 350, "conversation_id": "conv_xyz"}'
# 13. Complete calls step, start analyze step
python telnyx_api.py update-step "mis_abc123" "run_def456" "calls" "completed"
python telnyx_api.py log-event "mis_abc123" "run_def456" step_completed "Completed: All calls done" "calls"
python telnyx_api.py update-step "mis_abc123" "run_def456" "analyze" "in_progress"
python telnyx_api.py log-event "mis_abc123" "run_def456" step_started "Starting: Analyze results" "analyze"
# 14. Complete the mission (mark analyze step done first)
python telnyx_api.py update-step "mis_abc123" "run_def456" "analyze" "completed"
python telnyx_api.py log-event "mis_abc123" "run_def456" step_completed "Completed: Analysis done" "analyze"
python telnyx_api.py complete "find-window-washing-contractors" "mis_abc123" "run_def456" \
"Found 2 best contractors: ABC ($350) and XYZ ($380)" \
'{"recommended": ["ABC Cleaning", "XYZ Windows"]}'
# Output: Updated run run_def456: succeeded
# Mission 'find-window-washing-contractors' completed successfully
β οΈ BEFORE CREATING ANYTHING: Review Existing Resources
Always check what already exists before creating new assistants, insights, or insight groups. Reuse is better than duplication.
Pre-flight Checklist
Run these commands at the start of every mission to inventory what's available:
# 1. Search for existing assistants by name β maybe one already fits your use case
python telnyx_api.py list-assistants --name=Weather
python telnyx_api.py list-assistants # or list all (paginated)
python telnyx_api.py list-assistants --page=2 # next page
# 2. List existing insight templates β reuse structured insights across missions
python telnyx_api.py list-insights
# 3. List existing insight groups β you may only need to add an insight to an existing group
python telnyx_api.py list-insight-groups
# 4. List available phone numbers β check what's already assigned vs free
python telnyx_api.py list-phones --available
All list commands are paginated. If you have many resources, page through with --page=N. The assistant name filter does substring matching β use it to quickly find relevant assistants instead of scrolling through pages.
Decision Flow
β οΈ CRITICAL: Reuse Without Modification
The rule is: reuse existing resources IF you can use them as-is. Do NOT modify existing assistants, insights, or insight groups that may be in use by other missions or users. Editing a shared resource (e.g., changing an assistant's instructions or an insight's schema) can silently break unrelated workflows that depend on the current configuration.
Safe to reuse without modification:
- An existing assistant whose instructions, tools, voice, and settings already match your needs exactly
- An existing insight template whose schema/instructions already extract what you need
- The default "Summary" insight (always reuse this β never recreate it)
- An existing insight group that already contains the insights you need
When to create new instead of reusing:
- You need different instructions, tools, voice, or model β create a new assistant
- You need a different extraction schema β create a new insight template
- You need a different combination of insights β create a new insight group
- The existing resource is "close but needs tweaks" β create new, don't modify the existing one
For dynamic context between calls (e.g., Class 3 Sequential Negotiation, where you inject "best quote so far" into each call), use dynamic variables passed via the scheduled events API rather than modifying the assistant. Define variable placeholders in the assistant's instructions (e.g., {{best_quote}}) and pass the values at schedule time. This keeps the assistant immutable while varying context per call.
-
Assistants: Search for existing assistants. If one matches your needs exactly (instructions, tools, voice, model), reuse it. If it's close but not quite right, create a new one β don't modify the existing one:
python telnyx_api.py list-assistants --name=Weather # search by name python telnyx_api.py get-assistant <id> # inspect full config before deciding # If it fits β reuse as-is # If it doesn't fit β create a new assistant instead -
Insights: A structured insight like "extract high temperature and snow chance" is reusable across many missions. Check
list-insightsbefore creating a new one. If a good insight exists with the right schema, assign it to your group. If you need a different schema, create a new insight template β don't modify the existing one. -
Insight Groups: Create a new group per mission (they're cheap), but populate it with existing insight templates when they match. Only create new insight templates when your data extraction needs are genuinely new.
-
Phone Numbers: Numbers already assigned to a connection can't be reused for a different assistant. Only grab unassigned numbers.
Mission Classes
Not all missions are the same. Before planning, identify which class your mission falls into β it determines whether calls run in parallel or serial, how state flows between calls, and whether human gates are needed.
Decision Tree
Does call N depend on results of call N-1?
YES β Is it negotiation (leveraging previous results)?
YES β Class 3: Sequential Negotiation
NO β Does it have distinct rounds with human approval?
YES β Class 4: Multi-Round / Follow-up
NO β Class 5: Information Gathering β Action
NO β Do you need structured scoring/ranking?
YES β Class 2: Parallel Screening with Rubric
NO β Class 1: Parallel Sweep
Class 1: Parallel Sweep
Fan out calls in parallel batches. Every call asks the same question. No call depends on another's results. Collect all answers, then compare.
When to Use
- Same question to many targets (weather, hours, availability, prices)
- Order doesn't matter β all calls are independent
- You want raw data collection, not scoring or ranking
Key Patterns
- All calls use the same assistant with identical instructions
- Schedule all calls in one batch (respect throttling limits β stagger by 1-2 min)
- Use structured insights with a JSON schema to extract comparable data
- Analysis happens after ALL calls complete
Example 1: Weather IVR Sweep
Goal: Call 10 weather stations, extract today's high temperature, compare.
# Plan steps
[
{"step_id": "setup", "description": "Create IVR assistant with DTMF + structured insight", "sequence": 1},
{"step_id": "calls", "description": "Schedule calls to all 10 stations", "sequence": 2},
{"step_id": "poll", "description": "Poll for completion and collect insights", "sequence": 3},
{"step_id": "analyze", "description": "Compare temperatures, find hottest/coldest", "sequence": 4}
]
# Insight schema
{"type": "object", "properties": {
"location": {"type": "string"},
"high_temp_f": {"type": "number"},
"snow_mentioned": {"type": "boolean"},
"forecast_summary": {"type": "string"}
}, "required": ["location", "high_temp_f"]}
# Flow:
# 1. Create assistant with send_dtmf tool + insight group
# 2. Schedule all 10 calls in one batch (staggered 1 min apart)
# 3. Cron job polls every 2 min, collects structured insights
# 4. When all done: compare high_temp_f across all results, report hottest city
Example 2: Store Hours Check
Goal: Call 10 retail stores to confirm holiday hours.
# Same pattern: single assistant, all calls in parallel
# Assistant instructions: "You are calling to confirm store hours for [HOLIDAY].
# Ask: What time do you open and close on [DATE]? Are you open at all?"
# Insight schema:
{"type": "object", "properties": {
"store_name": {"type": "string"},
"is_open": {"type": "boolean"},
"open_time": {"type": "string"},
"close_time": {"type": "string"},
"notes": {"type": "string"}
}, "required": ["store_name", "is_open"]}
# Schedule all 10 calls β poll β compare β report table of hours
Class 2: Parallel Screening with Rubric
Fan out calls in parallel, but each call follows a structured rubric. Results are scored automatically via structured insights, then ranked post-hoc.
When to Use
- You need to rank or shortlist from many candidates
- Each call evaluates against the same criteria (scorecard)
- Scoring is objective enough to define as a schema
- You want automated ranking, not just raw data
Key Patterns
- Define the rubric as a structured insight schema upfront β include numeric scores, enums, booleans
- The insight template does the scoring automatically from the conversation
- After all calls complete, sort/filter results by score fields
- The assistant instructions should guide the conversation to cover all rubric dimensions
Example 1: Restaurant Reservation Scout
Goal: Call 10 restaurants, score on availability/price/ambiance, rank top 3.
# Insight schema (the rubric)
{"type": "object", "properties": {
"restaurant_name": {"type": "string"},
"has_availability": {"type": "boolean", "description": "Table available for requested date/time/party size"},
"availability_score": {"type": "integer", "description": "1-5, 5 = exact time available, 1 = nothing close"},
"price_range": {"type": "string", "enum": ["$", "$$", "$$$", "$$$$"]},
"estimated_per_person": {"type": "number"},
"ambiance_notes": {"type": "string"},
"ambiance_score": {"type": "integer", "description": "1-5 based on description of atmosphere"},
"wait_time_minutes": {"type": "number"},
"overall_score": {"type": "integer", "description": "1-10 overall recommendation"}
}, "required": ["restaurant_name", "has_availability", "availability_score", "price_range", "overall_score"]}
# Assistant instructions:
# "You are calling to check availability for dinner Friday at 7pm, party of 4.
# Ask about: availability, approximate price per person, dress code/atmosphere,
# expected wait time. Be conversational and polite."
# Flow:
# 1. Create insight template with rubric schema
# 2. Create assistant with insight group wired up
# 3. Schedule all 10 calls in parallel
# 4. Collect structured insights β sort by overall_score desc β report top 3
Example 2: Interview Screening
Goal: Phone-screen 10 candidates, score on communication/experience/culture-fit, shortlist top 3.
# Insight schema (the rubric)
{"type": "object", "properties": {
"candidate_name": {"type": "string"},
"communication_score": {"type": "integer", "description": "1-5, clarity and professionalism"},
"experience_years": {"type": "number"},
"relevant_experience_score": {"type": "integer", "description": "1-5, relevance to the role"},
"culture_fit_score": {"type": "integer", "description": "1-5, enthusiasm and alignment"},
"salary_expectation": {"type": "number"},
"available_start_date": {"type": "string"},
"red_flags": {"type": "string", "description": "Any concerns noted"},
"overall_score": {"type": "integer", "description": "1-10 overall recommendation"}
}, "required": ["candidate_name", "communication_score", "relevant_experience_score", "culture_fit_score", "overall_score"]}
# Assistant instructions cover specific screening questions for the role
# All 10 calls run in parallel β rank by overall_score β shortlist top 3
Class 3: Sequential Negotiation
Calls MUST run serially. Each call's strategy depends on previous results. You're leveraging information from earlier calls to get better outcomes in later ones.
β οΈ NEVER parallelize these. The entire value comes from sequential information advantage.
When to Use
- You're negotiating β quotes, prices, terms
- "Best quote so far is $X, can you beat it?"
- Each call needs context from all previous calls
- Call ordering is a strategic decision
Key Patterns
- Dynamic variables: Use
dynamic_variablesin the scheduled event to inject context per call β no need to modify the assistant between calls - State carries forward: Track "best offer so far" in memory, pass it as a dynamic variable to the next call
- Call ordering strategy: Start with whoever is least likely to give the best deal (weakest hand first) so you build leverage. Save the strongest candidate for last. Alternative: start with whoever will give a reliable baseline.
- One call at a time: Schedule (with dynamic vars) β poll β get insight β update state β schedule next
- Assistant stays immutable β define
{{best_quote}}and{{best_company}}placeholders in instructions once, then pass different values per call via the scheduled events API
Example 1: Roofer Quotes
Goal: Call 5 roofers sequentially, negotiate each against the best previous quote.
# Plan steps
[
{"step_id": "setup", "description": "Create assistant + find roofers", "sequence": 1},
{"step_id": "call-1", "description": "Call roofer 1 (baseline)", "sequence": 2},
{"step_id": "call-2", "description": "Call roofer 2 with context", "sequence": 3},
{"step_id": "call-3", "description": "Call roofer 3 with context", "sequence": 4},
{"step_id": "call-4", "description": "Call roofer 4 with context", "sequence": 5},
{"step_id": "call-5", "description": "Call roofer 5 with context", "sequence": 6},
{"step_id": "analyze", "description": "Select best deal", "sequence": 7}
]
# Flow:
# 1. Create assistant with dynamic variable placeholders in instructions:
# "Ask for a quote for roof repair on a 2000 sq ft home. Get price, timeline, warranty.
# {{#best_quote}}
# CONTEXT: You have received a quote of {{best_quote}} from {{best_company}}.
# Mention this if the price seems high. Ask if they can match or beat it.
# {{/best_quote}}"
# Set dynamic_variables: {"best_quote": null, "best_company": null}
# 2. Call roofer 1 (no leverage yet β best_quote is null, so that section is skipped)
# python telnyx_api.py schedule-call <id> "+1555..." "+1555..." "<time>" $MISSION_ID $RUN_ID $STEP_ID
# β get insight β save quote ($500)
# python telnyx_api.py save-memory "<slug>" "best_quote" '{"amount": 500, "company": "Roofer 1"}'
# 3. Call roofer 2 β pass dynamic variables via scheduled event:
# python telnyx_api.py schedule-call <id> "+1555..." "+1555..." "<time>" $MISSION_ID $RUN_ID $STEP_ID \
# '{"best_quote": "$500", "best_company": "Roofer 1"}'
# β get insight β if better ($420), update best_quote
# 4. Call roofer 3 β pass updated context:
# python telnyx_api.py schedule-call <id> "+1555..." "+1555..." "<time>" $MISSION_ID $RUN_ID $STEP_ID \
# '{"best_quote": "$420", "best_company": "Roofer 2"}'
# 5. Repeat: schedule with new dynamic vars β poll β insight β update state β next
# 6. After all 5: report best deal with full comparison
Example 2: Car Insurance Quotes
Goal: Call 4 insurance providers, use each quote to leverage the next.
# Ordering strategy: Start with the provider you care least about (get a baseline),
# end with your preferred provider (maximum leverage).
# Assistant instructions use dynamic variable placeholders:
# "You are calling about auto insurance for a 2022 Toyota Camry.
# {{#best_quote}}
# LEVERAGE: The best quote so far is {{best_quote}}/month
# from {{best_company}}. Mention this and ask them to beat it.
# {{/best_quote}}"
# Sequential pattern β pass dynamic variables per call:
# Call 1 (baseline): no leverage vars β "What's your rate for [coverage details]?"
# Call 2: {"best_quote": "$180", "best_company": "Geico"}
# Call 3: {"best_quote": "$155", "best_company": "Progressive"}
# Call 4 (preferred): {"best_quote": "$145", "best_company": "StateFarm"}
Class 4: Multi-Round / Follow-up
The mission has distinct phases. Round 1 is broad outreach. Results are analyzed, a human approves the shortlist, then Round 2 does deep-dive calls with a different focus.
When to Use
- Two or more distinct phases of calling
- Human judgment needed between rounds (approval gate)
- Round 2 targets a subset of Round 1
- Round 2 may use a completely different assistant/instructions
Key Patterns
- Plan has explicit phases:
round-1-calls,round-1-analysis,human-approval,round-2-calls - Human approval gate: DM the human via Telegram/Slack with Round 1 results + recommendation. Pause until they respond.
- Round 2 assistant may have completely different instructions than Round 1
- Track which candidates advanced to which round in memory
- The cron job for Round 1 should trigger the human DM, then stop. A new cron handles Round 2 after approval.
Example 1: Vendor Selection
Goal: Screen 10 vendors on basic criteria, shortlist top 3 with human approval, deep-dive on technical specs.
# Plan steps
[
{"step_id": "setup", "description": "Create screening assistant + insight rubric", "sequence": 1},
{"step_id": "round-1-calls", "description": "Screen all 10 vendors", "sequence": 2},
{"step_id": "round-1-analysis", "description": "Rank and shortlist top 3", "sequence": 3},
{"step_id": "human-approval", "description": "DM human with top 3, wait for approval", "sequence": 4},
{"step_id": "round-2-setup", "description": "Create deep-dive assistant with technical questions", "sequence": 5},
{"step_id": "round-2-calls", "description": "Deep-dive calls with approved vendors", "sequence": 6},
{"step_id": "final-analysis", "description": "Final recommendation", "sequence": 7}
]
# Round 1 assistant: "Ask about pricing, lead time, minimum order, and general capabilities."
# Round 1 insight rubric: price_score, lead_time_days, meets_minimum, capability_match
# After Round 1 completes:
# β Rank by scores β DM human:
# "Round 1 complete. Top 3 vendors:
# 1. VendorA β score 8.5, $12/unit, 2-week lead
# 2. VendorB β score 7.8, $14/unit, 1-week lead
# 3. VendorC β score 7.2, $11/unit, 3-week lead
# Approve these for Round 2? Reply YES or adjust."
# Wait for human response (pause cron or check for reply)
# Round 2 assistant (different!): "Ask detailed technical questions:
# API integration support? SLA guarantees? Disaster recovery?
# Reference customers we can contact?"
# Round 2 insight rubric: api_support, sla_score, dr_plan, references_provided
Example 2: Candidate Recruiting
Goal: Phone-screen 15 candidates (Round 1), shortlist 5 (human approval), detailed interviews (Round 2).
# Round 1: Quick 5-minute screen β "Tell me about your background, why this role,
# salary expectations, availability."
# Insight rubric: communication_score, experience_match, salary_in_range, enthusiasm
# β Rank β DM human with top 5 + scores
# β Human approves (or swaps in someone from position 6-7)
# Round 2: 15-minute deep dive β completely different assistant:
# "Ask about: specific project experience with [TECH], how they handle conflict,
# a time they failed and what they learned, questions they have for us."
# Different insight rubric: technical_depth, problem_solving, culture_fit, curiosity
# Track in memory:
python telnyx_api.py save-memory "<slug>" "rounds" '{
"round_1": {"candidates": [...], "advanced": ["+1555...", "+1555..."]},
"round_2": {"candidates": [...], "results": [...]}
}'
Class 5: Information Gathering β Action
Call to collect information, but the mission doesn't just report β it takes action based on results. Once you find what you need, stop searching and act.
When to Use
- You need to find something (availability, a slot, a match) and then do something (book it, confirm it, reserve it)
- Early termination: stop calling once the goal is met
- The last call transitions from "asking" to "acting"
Key Patterns
- Early termination: When a call succeeds (e.g., restaurant has availability), cancel remaining scheduled calls. Use
list-events-assistantto find pending events, thencancel-scheduled-eventto cancel each one. - Two-phase assistant instructions: Phase 1 = "Are you available?" (screening). Phase 2 = "I'd like to book" (action). Use
update-assistantto switch modes. - Fallback expansion: If the first batch yields no results, expand to more candidates
- The action step is the goal β the mission succeeds when the action is taken, not when all calls complete
Example 1: Restaurant Booking
Goal: Call restaurants until one has availability Friday 7pm for 4, then book it.
# Plan steps
[
{"step_id": "setup", "description": "Create screening assistant", "sequence": 1},
{"step_id": "screen", "description": "Call restaurants to check availability", "sequence": 2},
{"step_id": "book", "description": "Book at first available restaurant", "sequence": 3}
]
# Flow:
# 1. Create assistant: "Call and ask if they have a table for 4 this Friday at 7pm.
# If yes, say you'd like to book it under the name [NAME]. Confirm the reservation.
# If no, ask about Saturday instead, then politely end the call."
# 2. Schedule calls to 5 restaurants (parallel β screening doesn't depend on each other)
# 3. Poll results as they come in:
# - Restaurant A: no availability β continue
# - Restaurant B: has availability, BOOKED! β SUCCESS
# - Cancel/ignore remaining calls (Restaurants C, D, E)
# - If none of the 5 work: expand to 5 more restaurants (fallback)
# Insight schema:
{"type": "object", "properties": {
"restaurant_name": {"type": "string"},
"has_availability": {"type": "boolean"},
"reservation_confirmed": {"type": "boolean"},
"reservation_time": {"type": "string"},
"reservation_name": {"type": "string"},
"confirmation_number": {"type": "string"},
"alternative_offered": {"type": "string"}
}, "required": ["restaurant_name", "has_availability", "reservation_confirmed"]}
# Early termination: when reservation_confirmed = true, mission succeeds
Example 2: Appointment Scheduling
Goal: Call dentist offices until one has a slot this week, confirm the appointment.
# Same pattern as restaurant booking:
# 1. Screen in parallel: "Do you have any openings this week for a cleaning?"
# 2. First office with availability: "I'd like to book that slot for [NAME], DOB [DOB]."
# 3. Stop remaining calls once booked.
# Key difference: may need to provide insurance info, patient details.
# Assistant instructions include all necessary details upfront.
# Fallback: if no office has availability this week, expand search to next week
# or expand to more offices.
Cross-Cutting Patterns
These patterns apply across multiple mission classes.
1. Dynamic Context Between Calls
Use dynamic variables passed via the scheduled events API to inject context from previous call results into each call. This keeps the assistant immutable β define {{variable}} placeholders in instructions once, then pass different values per scheduled event. Essential for Class 3 (Sequential Negotiation) and Class 5 (action phase).
# Assistant instructions use placeholders:
# "You are calling to get a quote for roof repair on a 2000 sq ft home.
# {{#best_quote}}
# IMPORTANT CONTEXT: Another contractor has quoted {{best_quote}}.
# If this contractor quotes higher, mention you have a better offer and ask if
# they can match or beat it. Be professional but firm.
# {{/best_quote}}"
# After getting a quote of $350 from call 1, pass it as a dynamic variable on call 2:
python telnyx
*Truncated - read the full file at https://github.com/team-telnyx/telnyx-toolkit/blob/0c752ffbcc141c0311175c79c169b9e31a19d4db/tools/missions/SKILL.md.