← Index

TSE Voice Interview Pipeline

the solomon engine — serverless processing architecture

A two-part voice interview processed through a self-healing pipeline. No always-on servers. The system runs only when an interview completes, recovers from failures automatically, and pages a human only for genuine dead-ends.

Admin action
User action
System (automatic)
Failure / recovery
Supabase — database + edge functions
ElevenLabs — voice interview agents
Railway — processing worker (on-demand job)
Anthropic Claude — AI extraction + generation
Main Pipeline
flowchart TD
    subgraph ADMIN["ADMIN"]
        A1["Create invite
POST /create-invite
name + email"] A2["Email two links
to user"] A1 --> A2 end subgraph SB1["SUPABASE"] direction TB DB1[("vi_sessions
vi_invites
created")] end A1 -->|"creates session + invite"| DB1 A1 -->|"returns part_1_url + part_2_url"| A2 subgraph USER["USER"] U1["Clicks Part 1 link"] U2["Voice interview
Pain Points & Workflow
~6 min"] U3["Clicks Part 2 link"] U4["Voice interview
Work Style & Tools
~6 min"] U1 --> U2 --> U3 --> U4 end A2 -.->|"sends links"| U1 subgraph EL["ELEVENLABS"] EL1["Agent 1
var_session_id baked in"] EL2["Agent 2
var_session_id baked in"] end U2 -->|"talks to"| EL1 U4 -->|"talks to"| EL2 subgraph DISPATCH["SUPABASE EDGE FUNCTION"] D1["Dispatcher
/dispatcher"] D2{"Both halves
received?"} D1 --> D2 end EL1 -->|"post-call webhook
+ dynamic_variables"| D1 EL2 -->|"post-call webhook
+ dynamic_variables"| D1 subgraph SB2["SUPABASE DB"] DB2[("vi_transcripts
filed by session_id")] DB3[("vi_jobs
queued")] end D1 -->|"verify sig + file transcript"| DB2 D2 -->|"No — wait"| DB2 D2 -->|"Yes — enqueue"| DB3 subgraph WORKER["RAILWAY JOB"] W1["Claim job"] W2["Stage 1: Extract
Claude API
JSON extraction"] W3{"Validate
extraction"} W4["Stage 2: Generate
Claude API
markdown instructions"] W5{"Validate
output"} W6["Write deliverable"] W1 --> W2 --> W3 W3 -->|"pass"| W4 --> W5 W5 -->|"pass"| W6 end DB3 -->|"trigger via Railway API"| W1 subgraph SB3["SUPABASE DB"] DB4[("vi_deliverables
review_status: pending")] DB5[("vi_sessions
status: complete")] end W6 -->|"atomic write"| DB4 W6 -->|"mark complete"| DB5 subgraph REVIEW["ADMIN"] R1["Review deliverable"] R2{"Approve?"} R3["Deploy instructions"] R4["Request re-interview"] R1 --> R2 R2 -->|"yes"| R3 R2 -->|"no"| R4 end DB4 -.->|"admin reviews"| R1 subgraph FAIL["FAILURE + RECOVERY"] F1["Job fails"] F2["Increment retry_count"] F3{"retry_count
< 5?"} F4["Back to ready_to_process"] F5["Mark FAILED
+ alert admin"] end W3 -->|"fail"| F1 W5 -->|"fail"| F1 F1 --> F2 --> F3 F3 -->|"yes"| F4 F3 -->|"no"| F5 subgraph SWEEPER["SUPABASE CRON EDGE FUNCTION"] S1["Sweeper
every 5 min"] S2["Re-enqueue stuck jobs"] S3["Flag orphan sessions"] S1 --> S2 S1 --> S3 end F4 -.->|"sweeper picks up"| S2 S2 -->|"create new vi_job + trigger worker"| DB3 classDef adminNode fill:#f3e8ff,stroke:#7c3aed,color:#5b21b6,stroke-width:2px classDef userNode fill:#ecfeff,stroke:#0891b2,color:#155e75,stroke-width:2px classDef systemNode fill:#ecfdf5,stroke:#059669,color:#065f46,stroke-width:2px classDef dbNode fill:#f0fdf4,stroke:#10b981,color:#065f46,stroke-width:2px classDef failNode fill:#fef2f2,stroke:#dc2626,color:#991b1b,stroke-width:2px classDef elNode fill:#fffbeb,stroke:#f59e0b,color:#92400e,stroke-width:2px classDef railNode fill:#faf5ff,stroke:#9333ea,color:#581c87,stroke-width:2px classDef decisionNode fill:#fefce8,stroke:#d97706,color:#92400e,stroke-width:2px class A1,A2,R1,R3,R4 adminNode class U1,U2,U3,U4 userNode class D1,S1,S2,S3 systemNode class DB1,DB2,DB3,DB4,DB5 dbNode class F1,F2,F4,F5 failNode class EL1,EL2 elNode class W1,W2,W4,W6 railNode class D2,W3,W5,R2,F3 decisionNode style ADMIN fill:transparent,stroke:#7c3aed,stroke-width:2px,stroke-dasharray:5 5,color:#7c3aed style USER fill:transparent,stroke:#0891b2,stroke-width:2px,stroke-dasharray:5 5,color:#0891b2 style EL fill:transparent,stroke:#f59e0b,stroke-width:2px,stroke-dasharray:5 5,color:#f59e0b style DISPATCH fill:transparent,stroke:#10b981,stroke-width:2px,stroke-dasharray:5 5,color:#10b981 style WORKER fill:transparent,stroke:#9333ea,stroke-width:2px,stroke-dasharray:5 5,color:#9333ea style REVIEW fill:transparent,stroke:#7c3aed,stroke-width:2px,stroke-dasharray:5 5,color:#7c3aed style FAIL fill:transparent,stroke:#dc2626,stroke-width:2px,stroke-dasharray:5 5,color:#dc2626 style SWEEPER fill:transparent,stroke:#10b981,stroke-width:2px,stroke-dasharray:5 5,color:#10b981 style SB1 fill:transparent,stroke:#10b981,stroke-width:1px,stroke-dasharray:3 3,color:#10b981 style SB2 fill:transparent,stroke:#10b981,stroke-width:1px,stroke-dasharray:3 3,color:#10b981 style SB3 fill:transparent,stroke:#10b981,stroke-width:1px,stroke-dasharray:3 3,color:#10b981
Session State Machine
flowchart LR
    AH["awaiting_halves"]
    RTP["ready_to_process"]
    P["processing"]
    C["complete"]
    F["failed"]

    AH -->|"both halves received"| RTP
    RTP -->|"worker claims job"| P
    P -->|"deliverable written"| C
    P -->|"error + retries left"| RTP
    P -->|"retry cap exhausted"| F
    RTP -->|"retry cap exhausted"| F

    classDef waiting fill:#ecfeff,stroke:#0891b2,color:#155e75,stroke-width:2px
    classDef active fill:#fffbeb,stroke:#f59e0b,color:#92400e,stroke-width:2px
    classDef done fill:#ecfdf5,stroke:#059669,color:#065f46,stroke-width:2px
    classDef dead fill:#fef2f2,stroke:#dc2626,color:#991b1b,stroke-width:2px

    class AH waiting
    class RTP,P active
    class C done
    class F dead
    
Waiting
Active
Complete
Failed
Self-healing design. Two recovery layers catch different failures: In-call retry (3 attempts, 2s/8s/30s backoff) handles transient API errors silently. Sweeper (cron, every 5 min) catches jobs that crashed mid-processing or were never picked up. Retry cap of 5 prevents infinite loops on unprocessable transcripts. Admin is alerted only on permanent failed status.