Model 4: Remote Managed Runner
When to Use : Multi-tenant production environments, cloud deployments, or when you need centralized recipe management with authentication, rate limiting, and observability.
How It Works
The Remote Managed Runner is a production-grade deployment with authentication, rate limiting, metrics, and horizontal scaling.
Pros & Cons
Multi-tenant - Serve multiple clients with isolation
Centralized management - Single source of truth for recipes
Production-ready - Auth, rate limiting, metrics built-in
Scalable - Horizontal scaling with load balancer
Secure - TLS, API keys, JWT authentication
Observable - Prometheus metrics, distributed tracing
Network latency - Remote calls add latency
Operational complexity - Requires infrastructure management
Cost - Cloud resources, monitoring, etc.
Single point of failure - Unless properly distributed
Step-by-Step Tutorial
Deploy the Runner
docker run -d \
--name praisonai-runner \
-p 8765:8765 \
-e OPENAI_API_KEY= $OPENAI_API_KEY \
-e PRAISONAI_API_KEY=your-secure-key \
-e PRAISONAI_AUTH=api-key \
praisonai/runner:latest
Configure Authentication
# serve.yaml
host : 0.0.0.0
port : 8765
auth : api-key # or "jwt" for JWT authentication
api_key : ${PRAISONAI_API_KEY}
# JWT configuration (if using jwt auth)
jwt_secret : ${PRAISONAI_JWT_SECRET}
jwt_algorithm : HS256
Set Up Load Balancer
# nginx.conf
upstream praisonai {
least_conn ;
server runner1:8765;
server runner2:8765;
server runner3:8765;
}
server {
listen 443 ssl ;
server_name api.example.com ;
ssl_certificate /etc/ssl/certs/cert.pem ;
ssl_certificate_key /etc/ssl/private/key.pem ;
location / {
proxy_pass http://praisonai ;
proxy_http_version 1.1 ;
proxy_set_header Upgrade $ http_upgrade ;
proxy_set_header Connection "upgrade" ;
proxy_set_header Host $ host ;
proxy_set_header X-Real-IP $ remote_addr ;
proxy_read_timeout 300s ;
}
}
Connect from Client
import os
from praisonai . endpoints import EndpointsClient
client = EndpointsClient (
base_url = " https://api.example.com " ,
api_key = os . environ [ " PRAISONAI_API_KEY " ]
)
# Check health
health = client . health ()
print ( f "Server status: { health [ ' status ' ] } " )
# Run recipe
result = client . invoke (
" my-recipe " ,
input ={ " query " : " Hello " },
stream = False
)
print ( result [ " output " ])
Production-Ready Example
import os
import logging
from typing import Any , Dict , Optional
import requests
from requests . adapters import HTTPAdapter
from urllib3 . util . retry import Retry
logging . basicConfig ( level = logging . INFO )
logger = logging . getLogger ( __name__ )
class RemoteRecipeClient :
""" Production client for remote PraisonAI runner. """
def __init__ (
self ,
base_url : str ,
api_key : str ,
timeout : int = 60 ,
retries : int = 3
):
self . base_url = base_url . rstrip ( " / " )
self . timeout = timeout
# Configure session with retries
self . session = requests . Session ()
self . session . headers . update ({
" Content-Type " : " application/json " ,
" X-API-Key " : api_key ,
})
retry_strategy = Retry (
total = retries ,
backoff_factor = 0.5 ,
status_forcelist =[ 500 , 502 , 503 , 504 ],
)
adapter = HTTPAdapter ( max_retries = retry_strategy )
self . session . mount ( " https:// " , adapter )
self . session . mount ( " http:// " , adapter )
def health ( self ) -> Dict [ str , Any ]:
""" Check server health. """
resp = self . session . get (
f " {self . base_url } /health" ,
timeout = 5
)
resp . raise_for_status ()
return resp . json ()
def run (
self ,
recipe_name : str ,
input_data : Dict [ str , Any ],
config : Dict [ str , Any ] = None ,
session_id : str = None ,
trace_id : str = None
) -> Dict [ str , Any ]:
""" Run a recipe with full error handling. """
body = {
" recipe " : recipe_name ,
" input " : input_data ,
}
if config :
body [ " config " ] = config
if session_id :
body [ " session_id " ] = session_id
headers = {}
if trace_id :
headers [ " X-Trace-ID " ] = trace_id
try :
resp = self . session . post (
f " {self . base_url } /v1/recipes/run" ,
json = body ,
headers = headers ,
timeout = self . timeout
)
if resp . status_code == 401 :
raise PermissionError ( " Invalid API key " )
elif resp . status_code == 404 :
raise ValueError ( f "Recipe not found: { recipe_name } " )
elif resp . status_code == 429 :
raise RuntimeError ( " Rate limit exceeded " )
resp . raise_for_status ()
return resp . json ()
except requests . exceptions . Timeout :
logger . error ( f "Request timeout for recipe { recipe_name } " )
raise
except requests . exceptions . ConnectionError as e :
logger . error ( f "Connection error: { e } " )
raise
# Usage with environment-based configuration
if __name__ == " __main__ " :
client = RemoteRecipeClient (
base_url = os . environ [ " PRAISONAI_ENDPOINTS_URL " ],
api_key = os . environ [ " PRAISONAI_ENDPOINTS_API_KEY " ],
timeout = 60 ,
retries = 3
)
# Health check
print ( client . health ())
# Run recipe
result = client . run (
" support-reply-drafter " ,
{ " ticket_id " : " T-123 " , " message " : " I need help " },
trace_id = " req-12345 "
)
print ( result [ " output " ])
CLI Client
# Set environment variables
export PRAISONAI_ENDPOINTS_URL = https :// api . example . com
export PRAISONAI_ENDPOINTS_API_KEY = your-api-key
# Check health
praisonai endpoints health
# List available recipes
praisonai endpoints list
# Invoke a recipe
praisonai endpoints invoke my-recipe \
--input-json ' {"query": "Hello"} ' \
--json
# Stream output
praisonai endpoints invoke my-recipe \
--input-json ' {"query": "Hello"} ' \
--stream
Troubleshooting
Verify your API key: # Check if key is set
echo $PRAISONAI_ENDPOINTS_API_KEY
# Test with curl
curl -H " X-API-Key: $PRAISONAI_ENDPOINTS_API_KEY " \
https://api.example.com/health
Check network connectivity and firewall rules: # Test connectivity
curl -v https://api.example.com/health
# Check DNS
nslookup api.example.com
Implement exponential backoff: import time
def run_with_backoff ( client , recipe , input_data , max_retries = 5 ):
for attempt in range ( max_retries ):
try :
return client . run ( recipe , input_data )
except RuntimeError as e :
if " Rate limit " in str ( e ):
wait = 2 ** attempt
time . sleep ( wait )
else :
raise
raise RuntimeError ( " Max retries exceeded " )
For self-signed certificates in development: # NOT recommended for production
client . session . verify = False
# Better: Add your CA certificate
client . session . verify = " /path/to/ca-bundle.crt "
Security & Ops Notes
TLS everywhere - Always use HTTPS in production
API key rotation - Rotate keys regularly
Rate limiting - Protect against abuse
IP allowlisting - Restrict access by IP if possible
Audit logging - Log all API calls with trace IDs
Secrets management - Use vault/secrets manager for keys
# serve.yaml - Production security configuration
host : 0.0.0.0
port : 8765
auth : api-key
api_key : ${PRAISONAI_API_KEY}
rate_limit : 100 # requests per minute
max_request_size : 10485760
enable_metrics : true
enable_admin : false # Disable admin endpoints in production
# Observability
trace_exporter : otlp
otlp_endpoint : http://otel-collector:4317
Monitoring
# prometheus.yml
scrape_configs :
- job_name : ' praisonai-runner '
static_configs :
- targets : [ ' runner1:8765 ' , ' runner2:8765 ' , ' runner3:8765 ' ]
metrics_path : /metrics
Key metrics to monitor:
praisonai_recipe_duration_seconds - Recipe execution time
praisonai_recipe_total - Total recipe invocations
praisonai_recipe_errors_total - Error count
praisonai_active_sessions - Active sessions
Next Steps