# Resonate Documentation — Full Content --- url: https://docs.resonatehq.io/debug/errors title: Errors --- This page documents known errors that may be encountered when using Resonate and how to troubleshoot them. If you believe your issue is not covered here, reach out in [Discord](https://resonatehq.io/discord) to discuss. Known errors will likely have an error code associated with them and a short error message. Troubleshooting steps are categorized by error code ranges: - [Server request errors (HTTP 4XX)](#server-request-errors) - [Server errors (HTTP 5XX)](#server-errors) - [Python SDK errors (100.x, 200, 201, 300)](#python-sdk-errors) - [TypeScript SDK errors (1100-1199)](#typescript-sdk-errors) ## Server request errors These are the errors you may encounter when making requests to Resonate Server v0.9.8. Request errors signify problems with the request itself — retrying the same request will not resolve the issue. **Range: HTTP 4XX** **Error response shape** Every response from the server (success or error) carries this envelope. On error, `head.status` is the HTTP status code and `data` is the human-readable message. ```json { "kind": "promises.get", "head": { "corrId": "", "status": 404, "version": "2026-04-01" }, "data": "Promise not found" } ``` If the problem persists, open a [GitHub issue](https://github.com/resonatehq/resonate/issues/new) with details, including the response envelope and steps to reproduce. Or reach out via [Discord](https://resonatehq.io/discord) for support. ### 400 — Bad Request The request is malformed. Common causes: - Invalid JSON body or missing required fields - Validation failure (`data` carries the failed field and reason) - Unrecognized operation kind - Invalid `resonate:target` address - Invalid listener address - `limit` outside the allowed range (1–1000) ### 401 — Unauthorized The request did not carry a valid bearer token, or the token failed JWT verification. ### 403 — Forbidden The request carried a valid token, but the principal is not permitted to perform the requested action. ### 404 — Not Found The requested resource does not exist. Common cases: promise not found, schedule not found, task not found, awaited promise not found. ### 409 — Conflict The request would violate an existing resource's lifecycle invariants. Common cases: - Promise already resolved, rejected, canceled, or timed out (cannot be modified again) - Schedule with the requested id already exists - Promise with the requested id already exists - Lock already acquired - Task already claimed or completed - Task claimed with the wrong counter ### 422 — Unprocessable Entity The request was syntactically valid but semantically unprocessable. Common cases: - Awaiter promise not found or has no `resonate:target` tag - Task in invalid state for the requested transition ## Server errors **Range: HTTP 5XX** Server errors represent transient or systemic problems that prevent the request from being processed. Retrying the request may resolve the issue. If the problem persists, open a [GitHub issue](https://github.com/resonatehq/resonate/issues/new) with details, including the response envelope and steps to reproduce. Or reach out via [Discord](https://resonatehq.io/discord) for support. ### 500 — Internal Server Error An unexpected server-side failure. Includes failures in the persistence layer (e.g. unrecoverable database lock contention), the scheduler, or any unhandled internal error path. The `data` field carries the failure description; the server logs carry the underlying cause. ## Python SDK errors These are the exception classes raised by the Python SDK (`resonate-sdk` v0.6.7). Each exception has a numeric `code` attribute. Codes are not contiguous integers — they fall into a few ranges keyed off the underlying failure domain. If the problem persists, open a [GitHub issue](https://github.com/resonatehq/resonate-sdk-py/issues/new) with details, including the error code and steps to reproduce. Or reach out via [Discord](https://resonatehq.io/discord) for support. ### `ResonateStoreError` (codes `100.x`) Raised when a store operation against the Resonate Server (or the in-process local store) fails. The fractional part embeds a structured sub-code from the **legacy Resonate Server** (which Python SDK v0.6.7 targets — see the legacy-server callout on the [Python SDK guide](/develop/python)). Common sub-codes: - `100.40400` — promise/schedule not found - `100.40303` — promise already timed out - `100.40305` — task already claimed - `100.40403` — task not found - `100.40399` — state-machine transition error - `100.0` — transport-level failure (request timed out, failed to connect, unknown exception) The current Rust server (v0.9.8) does not emit these structured sub-codes — it returns standard HTTP status codes (see [Server request errors](#server-request-errors) above). When the Python SDK adds Rust-server compatibility, this section will be updated. ### `ResonateCanceledError` (code `200`) Raised when a promise is canceled. The `promise_id` attribute identifies the canceled promise. ### `ResonateTimedoutError` (code `201`) Raised when a promise times out before it is resolved or rejected. Carries `promise_id` and `timeout` attributes. ### `ResonateShutdownError` (code `300`) Raised when the Resonate client is shutting down and an in-flight operation cannot proceed. ## TypeScript SDK errors **Range: 1100 - 1199** These are known errors you may encounter when using the TypeScript SDK. If the problem persists, open a [GitHub issue](https://github.com/resonatehq/resonate-sdk-ts/issues/new) with details, including the error code and steps to reproduce. Or reach out via [Discord](https://resonatehq.io/discord) for support. ### 1100 **REGISTRY_VERSION_INVALID** Function version must be greater than zero (x provided) ### 1101 **REGISTRY_NAME_REQUIRED** Function name is required ### 1102 **REGISTRY_FUNCTION_ALREADY_REGISTERED** Function 'x' (version y) is already registered [under 'u'] ### 1103 **REGISTRY_FUNCTION_NOT_REGISTERED** Function 'x' (version y) is not registered. Will drop. ### 1104 **DEPENDENCY_ALREADY_REGISTERED** Dependency 'x' is already registered ### 1105 **DEPENDENCY_NOT_REGISTERED** Dependency 'x' is not registered. Will drop. ### 1106 **ENCODING_ARGS_UNENCODEABLE** Function arguments cannot be encoded. ### 1107 **ENCODING_ARGS_UNDECODEABLE** Function arguments cannot be decoded. ### 1108 **ENCODING_RETV_UNENCODEABLE** The return value from a function cannot be encoded. ### 1109 **ENCODING_RETV_UNDECODEABLE** The return value from a function cannot be decoded. ### 1198 **PANIC** An unrecoverable internal error occurred in the SDK. ### 1199 **SERVER_ERROR** An error occurred on the server. Please check for additional error codes for more details. --- --- url: https://docs.resonatehq.io/debug title: Debug a Resonate application --- Debug Resonate applications section Debug Resonate applications section Something's not working. This section helps you figure out why and fix it. ## When things go wrong Debugging distributed systems is hard. Resonate's durable execution model helps by preserving execution state across failures, but you still need to know where to look when something breaks. **Start here:** 1. **Check the logs** - Server and SDK logs show what's happening at each step 2. **Look for error codes** - Resonate returns structured errors with codes you can search 3. **Use the troubleshooting guide** - Common problems and solutions ## What you'll find here ### [Errors](/debug/errors) Resonate's error codes and what they mean. When you see an error, look it up here to understand what went wrong and how to fix it. ### [Troubleshooting](/debug/troubleshooting) Common problems and their solutions. Start here if you're seeing unexpected behavior but not sure what's causing it. ## Debug workflow **When something fails:** 1. **Reproduce it** - Can you make it happen again? Consistent failures are easier to debug 2. **Check the logs** - Server logs (see [Logging](/deploy/logging)) show internal state changes 3. **Look up the error** - Find the error code in the [Errors](/debug/errors) reference 4. **Check common issues** - See if your problem matches something in [Troubleshooting](/debug/troubleshooting) 5. **Ask for help** - If you're stuck, share logs + error codes in [Discord](https://discord.gg/R4ad7T4U) ## What debugging looks like **Durable execution changes debugging:** In traditional systems, when a function fails, its execution state is lost. You see an error but can't inspect where it failed or what it was doing. With Resonate, execution state is preserved in durable promises. When something fails: - The promise shows the last successful checkpoint - You can inspect input/output of each step - You can retry from the failure point (not from scratch) - Logs show the full execution history This makes debugging **incremental** rather than blind. ## Beyond this section - **Deployment issues** → See [Deploy](/deploy) for server startup, configuration, and infrastructure problems - **Performance tuning** → See [Scaling](/deploy/scaling) for capacity and throughput optimization - **Observability setup** → See [Logging](/deploy/logging), [Metrics](/deploy/metrics), and [Tracing](/deploy/tracing) to instrument your system This section is about **fixing what's broken**. The other sections are about **building what works**. --- --- url: https://docs.resonatehq.io/debug/troubleshooting title: Troubleshooting --- This guide covers common issues you might encounter when using Resonate and how to resolve them. If your problem isn't listed here, check the [Errors](/debug/errors) reference for error codes or ask in [Discord](https://discord.gg/R4ad7T4U). ## Server won't start ### Database connection failures **Symptoms:** - Server fails to start with connection errors - Logs show `failed to connect to database` or similar **Causes:** - PostgreSQL not running - Wrong connection credentials - Network/firewall blocking connection - Database doesn't exist **Solutions:** 1. **Check PostgreSQL is running:** ```bash # PostgreSQL status pg_isready -h localhost -p 5432 # For managed services, check cloud console ``` 2. **Verify connection string:** ```toml title="resonate.toml" [storage] type = "postgres" [storage.postgres] url = "postgres://:@localhost:5432/resonate" ``` Or set `RESONATE_STORAGE__POSTGRES__URL` in the environment. Check the host, port, database, user, and password all match what your Postgres instance expects. 3. **Create database if missing:** ```bash psql -h localhost -U postgres -c "CREATE DATABASE resonate;" ``` 4. **Check firewall/security groups:** - Ensure server can reach database on port 5432 - Check cloud security group rules - Verify VPC/network configuration ### Port already in use **Symptoms:** - Server fails to start - Logs show `address already in use` or `bind: address already in use` **Cause:** Another process is using port 8001 (HTTP). **Solutions:** 1. **Find what's using the port:** ```bash # macOS/Linux lsof -i :8001 # Kill the process if needed kill -9 ``` 2. **Use a different port:** Pass `--server-port` to `resonate serve` to bind a different port: ```bash resonate serve --server-port 8003 ``` ### Configuration file errors **Symptoms:** - Server fails to start - Logs show a TOML parse error from `figment` or an unknown field name **Cause:** Invalid TOML syntax in `resonate.toml`, or a field name that doesn't match the schema. **Solutions:** 1. **Validate TOML syntax** with any TOML linter, or paste it into [toml-lint.com](https://www.toml-lint.com/). 2. **Check section and field names** against [Run a server](/deploy/run-server#configuration). Sections are nested with dots: `[storage.postgres]`, not `[storage_postgres]`. 3. **Check env var nesting.** Environment variables use the `RESONATE_` prefix and `__` (double underscore) for nesting: ```bash # WRONG — single underscore RESONATE_STORAGE_POSTGRES_URL=... # RIGHT — double underscore between sections RESONATE_STORAGE__POSTGRES__URL=... ``` ## Workers not receiving tasks ### Workers not connecting **Symptoms:** - Workers start but never process tasks - No worker registration in server logs - Tasks remain pending indefinitely **Causes:** - Wrong server URL - Network/firewall blocking connection - Server not running - Authentication misconfigured **Solutions:** 1. **Verify server URL:** ```typescript const resonate = new Resonate({ url: "http://resonate-server:8001", // Must match server address group: "workers", }); ``` 2. **Test connectivity:** ```bash # From worker machine, test server reachability curl -s -o /dev/null -w "%{http_code}\n" http://resonate-server:8001/health # Should return 200 when the server is alive ``` 3. **Check authentication:** If server has auth enabled, workers must provide credentials: ```typescript const resonate = new Resonate({ url: "http://resonate-server:8001", group: "workers", token: "your-jwt-token", }); ``` 4. **Check server logs:** ```bash # Look for worker registration messages resonate serve --level debug # Should see: "worker registered" or similar ``` ### Tasks not reaching workers **Symptoms:** - Workers connected but not processing tasks - Tasks created but remain pending - No task distribution happening **Causes:** - Wrong worker group name - Worker polling misconfigured - Task routing rules don't match workers **Solutions:** 1. **Verify group names match:** ```typescript // When registering worker: const resonate = new Resonate({ group: "workers", // Note the group name }); // When creating task: resonate.rpc( taskId, "processOrder", data, resonate.options({ target: "poll://any@workers", // Must match worker group }) ); ``` 2. **Check worker is polling:** Workers must actively poll for tasks. Ensure your worker code calls functions that poll: ```typescript // Worker polls when you register functions resonate.register("processOrder", async (ctx, data) => { // Task execution }); // Worker polls automatically after registration ``` 3. **Inspect promise state:** ```bash # Query promises to see if tasks are being created curl http://localhost:8001/promises?state=pending # Check if tasks exist for those promises curl http://localhost:8001/tasks?state=pending ``` ## Promises not resolving ### Promise stuck in pending state **Symptoms:** - Promise created but never completes - No worker picks up the task - `resonate.promises.get()` shows `state: "pending"` indefinitely **Causes:** - No workers available for the task's group - Worker crashed mid-execution and task not reassigned - Task timeout not configured (waits forever) - Routing misconfiguration **Solutions:** 1. **Confirm workers are running:** ```bash # Check worker processes ps aux | grep worker # In Kubernetes: kubectl get pods -l app=resonate-worker ``` 2. **Check promise/task state:** ```bash # Get promise details curl http://localhost:8001/promises/{promiseId} # Check if task exists curl http://localhost:8001/tasks?promiseId={promiseId} ``` 3. **Set task timeouts:** ```typescript resonate.rpc( taskId, "processOrder", data, resonate.options({ target: "poll://any@workers", timeout: 60000, // 60 second timeout }) ); ``` 4. **Check worker heartbeats:** If worker crashed, server should detect via heartbeat timeout (default: 60s) and reassign. Check server logs for heartbeat failures. ### Promise failed but retry not working **Symptoms:** - Promise fails once and doesn't retry - Expected automatic retry but it didn't happen **Cause:** Resonate doesn't automatically retry failed promises unless you configure retry logic. **Solutions:** 1. **Implement retry logic explicitly:** ```typescript async function processOrderWithRetry(ctx, data) { let attempts = 0; const maxAttempts = 3; while (attempts < maxAttempts) { try { const result = await ctx.run(() => processOrder(data)); return result; } catch (error) { attempts++; if (attempts >= maxAttempts) throw error; await ctx.sleep(1000 * attempts); // Exponential backoff } } } ``` 2. **Check error type:** Some errors shouldn't retry (e.g., invalid input). Handle appropriately: ```typescript catch (error) { if (error.code === "INVALID_INPUT") { throw error; // Don't retry } // Retry for transient errors } ``` ## Performance issues ### Slow task execution **Symptoms:** - Tasks complete but take longer than expected - High latency between task creation and completion **Causes:** - Not enough workers (tasks queue up) - Worker resource constraints (CPU/memory) - Database performance issues - Network latency **Solutions:** 1. **Scale workers horizontally:** ```bash # Docker Compose docker-compose up -d --scale worker=10 # Kubernetes kubectl scale deployment resonate-workers --replicas=20 ``` See [Scaling](/deploy/scaling) for details. 2. **Monitor worker resources:** ```bash # Check CPU/memory usage top htop # Kubernetes: kubectl top pods -l app=resonate-worker ``` 3. **Optimize database:** - Add indexes for frequently queried promise/task fields - Increase PostgreSQL connection pool size - Use managed PostgreSQL with IOPS scaling 4. **Check network latency:** ```bash # Measure round-trip time to server ping resonate-server # Test HTTP latency time curl -s http://resonate-server:8001/health ``` ### High database load **Symptoms:** - Slow promise creation/resolution - Database CPU/IOPS maxed out - Connection pool exhausted **Causes:** - Too many concurrent promises - Inefficient queries (missing indexes) - Insufficient database resources **Solutions:** 1. **Upgrade database resources:** - Increase CPU/RAM - Add IOPS capacity (for cloud databases) - Use managed PostgreSQL with auto-scaling 2. **Tune connection pool:** ```toml title="resonate.toml" [storage.postgres] url = "postgres://:@localhost:5432/resonate" pool_size = 50 # Increase pool size (default: 10) ``` 3. **Add database indexes:** Check PostgreSQL slow query log and add indexes for common queries. 4. **Batch operations:** If creating many promises, batch them when possible to reduce database round-trips. ## Authentication issues ### Unauthorized errors **Symptoms:** - Workers can't connect - API requests return `401 Unauthorized` - Logs show authentication failures **Causes:** - Wrong credentials - Auth enabled on server but not configured in client - Token expired (JWT) **Solutions:** 1. **Verify credentials:** ```typescript const resonate = new Resonate({ url: "http://resonate-server:8001", token: "your-jwt-token", // Check this matches server config }); ``` 2. **Check server auth config:** The server uses JWT bearer auth signed with an Ed25519 key. Start the server with the public key path: ```bash resonate serve --auth-publickey /etc/resonate/auth/public.pem ``` Or in `resonate.toml`: ```toml [auth] publickey = "/etc/resonate/auth/public.pem" ``` In the SDK, pass a JWT signed by the matching private key: ```typescript const resonate = new Resonate({ url: "http://resonate-server:8001", token: process.env.RESONATE_TOKEN, }); ``` 3. **For JWT tokens, verify:** - Token hasn't expired - Signature was produced with the private key matching the server's public key - `iss` / `aud` claims match what the server expects (if configured) See [Security](/deploy/security) for the full auth setup. ## Development workflow issues ### Changes not taking effect **Symptoms:** - Code changes don't appear when running - Old behavior persists after updates **Causes:** - Using wrong binary (old version still running) - Cache issues - Docker image not rebuilt **Solutions:** 1. **Verify process is new:** ```bash # Kill old processes pkill -f resonate # Restart with fresh binary resonate serve ``` 2. **Rebuild Docker images:** ```bash docker-compose build --no-cache docker-compose up -d ``` 3. **Clear SDK caches (if applicable):** ```bash # Node.js rm -rf node_modules && npm install # Python rm -rf __pycache__ && pip install -r requirements.txt ``` ### SQLite "database is locked" errors **Symptoms:** - SQLite errors about locked database - Concurrent access failures **Cause:** SQLite doesn't handle high concurrency well. Multiple processes/threads trying to write simultaneously. **Solution:** Use PostgreSQL for any deployment with concurrent writers: ```toml title="resonate.toml" [storage] type = "postgres" [storage.postgres] url = "postgres://:@localhost:5432/resonate" ``` SQLite is best for development and single-tenant deployments where there is no concurrent write contention against the server. ## Getting more help If these troubleshooting steps don't resolve your issue: 1. **Check error codes:** See [Errors](/debug/errors) for detailed error information 2. **Enable debug logging:** ```bash resonate serve --level debug ``` 3. **Collect diagnostics:** - Server logs - Worker logs - Promise/task state from API - Database connection status - Network connectivity tests 4. **Ask in Discord:** Share diagnostics in the [Resonate Discord](https://discord.gg/R4ad7T4U) 5. **File a bug:** If you've found a bug, [open an issue on GitHub](https://github.com/resonatehq/resonate/issues) ## Quick diagnostic checklist When debugging, check these in order: - [ ] Server running and reachable (`curl http://server:8001/health` returns `200`) - [ ] Storage reachable (`curl http://server:8001/ready` returns `200`) - [ ] Workers registered with server (check logs) - [ ] Worker group names match task routing - [ ] Authentication configured (if enabled) - [ ] Network/firewall allows communication - [ ] Adequate resources (CPU, memory, IOPS) - [ ] No port conflicts - [ ] `resonate.toml` syntax valid (and env-var nesting uses `__`) - [ ] Using recent Resonate version Most issues fall into one of these categories. Work through the checklist systematically. --- --- url: https://docs.resonatehq.io/deploy/availability title: Availability --- Resonate's availability depends primarily on the availability of its persistent storage. Since all execution state is stored in the database, database availability directly determines system reliability. ## Architecture and availability **The Resonate server coordinates work** but doesn't execute your functions. This separation means: - Worker failures don't impact the server - Worker restarts don't lose execution state - Workers can be added/removed dynamically - The database is the single source of truth for all execution state A single Resonate server can coordinate thousands of workers and millions of promises because it's a coordination layer, not a computational bottleneck. ## Persistent storage ### PostgreSQL (recommended) **Use PostgreSQL for production deployments** when you need: - High availability and replication - Multi-tenant deployments (multiple teams/projects sharing one server) - High write throughput - Standard HA patterns and tooling ### SQLite **SQLite works for specific use cases:** - Single-tenant micro deployments (one server per user/project) - Embedded use cases where the server runs alongside your app - Low-traffic production workloads - Deployments where simplicity matters more than scale Resonate's server and SDK are lightweight enough to support micro deployments. You can run isolated server instances with SQLite for specific users or projects. For shared, multi-tenant deployments, PostgreSQL provides better concurrency and HA options. ### Configure PostgreSQL ```bash title="Start server with PostgreSQL" resonate serve \ --storage-type postgres \ --storage-postgres-url "postgres://:@localhost:5432/resonate" \ --storage-postgres-pool-size 20 ``` For SSL connections, include `sslmode` in the connection string: ```bash --storage-postgres-url "postgres://:@postgres.example.com:5432/resonate?sslmode=require" ``` ## PostgreSQL high availability Since the server stores all promise state in PostgreSQL, database availability directly impacts system reliability. Use standard PostgreSQL HA patterns: ### Managed services (recommended) Managed database services handle replication, failover, and backups automatically: - **AWS RDS** with Multi-AZ deployment - **Google Cloud SQL** with high availability configuration - **Azure Database for PostgreSQL** with zone redundancy - **Supabase** with automatic backups and replication These services provide: - Automatic failover (typically <1 minute) - Automated backups with point-in-time recovery - Read replicas for scaling read traffic - Monitoring and alerting built-in Let your cloud provider handle database operations. They're better at it than you are, and their HA patterns are battle-tested. ### Self-managed replication If you need to manage PostgreSQL yourself: **Primary-replica setup:** - Use Patroni or similar for automatic failover - Configure streaming replication between primary and replicas - Set up health checks and automatic promotion **Point-in-time recovery (PITR):** - Enable WAL (write-ahead logging) archiving - Store WAL files in durable storage (S3, GCS, etc.) - Test recovery procedures regularly **Example backup strategy:** ```bash title="Automated PostgreSQL backups" # Daily full backup pg_dump -h postgres.example.com -U resonate resonate > backup-$(date +%Y%m%d).sql # Continuous WAL archiving for PITR # In postgresql.conf: archive_mode = on archive_command = 'cp %p /mnt/wal_archive/%f' ``` ## Server monitoring Monitor server health to detect issues before they impact availability: ### Health check The server exposes two endpoints on the HTTP API port (`:8001`): - `GET /health` — returns `200 OK` whenever the process is up. Use for liveness probes. - `GET /ready` — returns `200 OK` when storage is reachable, `503` otherwise. Use for readiness probes. ```bash title="Check server health" curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8001/health # 200 ``` ### Prometheus metrics ```bash title="Scrape metrics" curl http://localhost:9090/metrics ``` **Key metrics to watch:** ```text # Promise rate (workload indicator) rate(promises_total[5m]) # Pending promises (backlog indicator) promises_total{state="pending"} # API request latency (performance indicator) histogram_quantile(0.95, rate(api_duration_seconds_bucket[5m])) # Server internal queue (capacity indicator) sum(coroutines_in_flight) ``` See [Metrics](/deploy/metrics) for the full metrics catalog. ### Alerting Set up alerts for critical conditions: ```yaml title="alerting-rules.yml" groups: - name: resonate-availability rules: - alert: ResonateServerDown expr: up{job="resonate-server"} == 0 for: 1m annotations: summary: "Resonate server is unreachable" - alert: HighAPIErrorRate expr: rate(api_requests_total{status=~"5.."}[5m]) > 10 for: 5m annotations: summary: "High server error rate indicates availability issues" - alert: PromiseBacklogGrowing expr: increase(promises_total{state="pending"}[5m]) > 100 for: 10m annotations: summary: "Promise backlog growing - may indicate processing issues" ``` ## Server restart procedures The Resonate server can be restarted safely without losing work: 1. **State preserved in PostgreSQL** - All promise state persists across restarts 2. **Workers handle disconnection** - Workers detect server disconnect and retry connections automatically 3. **Graceful shutdown** - Server responds to `SIGTERM` and attempts graceful cleanup (configurable timeout, default 10s) 4. **Workers resume** - When the server comes back online, workers reconnect and continue from checkpoints ### Restart the server ```bash title="Graceful server restart" # Stop the server (sends SIGTERM for graceful shutdown) kill -TERM $(pgrep resonate) # Wait for shutdown (respects timeout config, default 10s) sleep 12 # Start server again resonate serve ``` See [Server configuration](/deploy/run-server#configuration) for the full configuration reference. ### Rolling updates For zero-downtime updates, the current architecture requires: 1. Upgrade the database schema (if needed) in a backward-compatible way 2. Deploy the new server version 3. Restart the server (brief downtime: ~10s) 4. Workers reconnect automatically **Multi-server deployments** (where multiple server instances share the same database) are not yet supported. The server-to-server coordination protocol is not implemented. ## When to upgrade server resources The server's resource needs grow slowly compared to workers. Consider upgrading when you observe: - **Database connections exhausted** - Increase connection pool size or upgrade server RAM - **CPU sustained >80%** - Rare, but indicates heavy coordination load - **Network bandwidth saturated** - Large payloads moving between workers and server For most deployments, a modest server (2-4 CPUs, 4-8GB RAM) can coordinate hundreds of workers processing thousands of tasks per second. ## What's not available yet Some features you might expect in a high-availability guide aren't implemented today: **Multi-server coordination** - Resonate doesn't support running multiple server instances that coordinate with each other. You run one server that coordinates many workers. **Automatic server failover** - No built-in automatic failover between multiple server instances. Use PostgreSQL HA/replication for state persistence, and restart the server if it crashes. **Cross-region disaster recovery** - For multi-region setups, use standard PostgreSQL replication patterns and manual failover procedures. These features aren't implemented because **worker horizontal scaling** handles the vast majority of scale and availability needs. The server coordinates but doesn't execute work, so it's rarely a bottleneck or single point of failure (state lives in PostgreSQL). If your use case exceeds single-server capacity, contact the Resonate team to discuss your requirements. ## Summary **Availability in Resonate depends on:** 1. PostgreSQL availability (use managed HA services) 2. Server monitoring and alerting 3. Worker fault tolerance (automatic via heartbeats) **The pattern:** - Use managed PostgreSQL with HA configuration - Monitor server health and database connections - Scale workers horizontally for capacity - Scale server vertically if coordination becomes a bottleneck **Resonate's architecture makes availability simpler** because execution state lives in the database, not in-memory. Worker failures don't lose work, and server restarts are safe. --- --- url: https://docs.resonatehq.io/deploy/deployment-patterns title: Deployment patterns --- Resonate works in any environment where you can run a server and workers. This guide covers common deployment patterns for different infrastructure choices. ## Development and staging ### Docker Compose Use Docker Compose for local development and staging environments. This gives you a complete Resonate stack (server + database + workers) with one command: ```yaml title="docker-compose.yml" version: "3.8" services: postgres: image: postgres:15 environment: POSTGRES_DB: resonate POSTGRES_USER: resonate POSTGRES_PASSWORD: secret volumes: - postgres-data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U resonate"] interval: 10s timeout: 5s retries: 5 resonate-server: image: resonatehqio/resonate:v0.9.8 ports: - "8001:8001" - "9090:9090" environment: RESONATE_SERVER__BIND: "0.0.0.0" RESONATE_STORAGE__TYPE: postgres RESONATE_STORAGE__POSTGRES__URL: postgres://:@postgres:5432/resonate depends_on: postgres: condition: service_healthy healthcheck: test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8001/health || exit 1"] interval: 5s retries: 10 start_period: 30s worker: image: your-app:latest environment: RESONATE_URL: http://resonate-server:8001 deploy: replicas: 3 volumes: postgres-data: ``` **Start the stack:** ```bash docker-compose up -d ``` **Scale workers:** ```bash docker-compose up -d --scale worker=10 ``` This pattern works for: - Local development - CI/CD testing - Staging environments - Small production deployments ## Kubernetes ### Server deployment The Resonate server runs as a single replica (multi-server coordination is not yet implemented). Use a Deployment with health checks: ```yaml title="server-deployment.yaml" apiVersion: apps/v1 kind: Deployment metadata: name: resonate-server spec: replicas: 1 # Single server coordinates all workers selector: matchLabels: app: resonate-server template: metadata: labels: app: resonate-server spec: containers: - name: server image: resonatehqio/resonate:v0.9.8 ports: - containerPort: 8001 name: http - containerPort: 9090 name: metrics env: - name: RESONATE_SERVER__BIND value: "0.0.0.0" - name: RESONATE_STORAGE__TYPE value: "postgres" - name: RESONATE_STORAGE__POSTGRES__URL valueFrom: secretKeyRef: name: postgres-credentials key: url livenessProbe: httpGet: path: /health port: 8001 initialDelaySeconds: 5 periodSeconds: 10 readinessProbe: httpGet: path: /ready port: 8001 initialDelaySeconds: 5 periodSeconds: 5 --- apiVersion: v1 kind: Service metadata: name: resonate-server spec: selector: app: resonate-server ports: - name: http port: 8001 targetPort: 8001 - name: metrics port: 9090 targetPort: 9090 ``` ### Worker deployment Workers scale horizontally. Use a Deployment with HorizontalPodAutoscaler: ```yaml title="worker-deployment.yaml" apiVersion: apps/v1 kind: Deployment metadata: name: resonate-workers spec: replicas: 10 # Initial worker count selector: matchLabels: app: resonate-worker template: metadata: labels: app: resonate-worker spec: containers: - name: worker image: your-app:latest env: - name: RESONATE_URL value: "http://resonate-server:8001" - name: WORKER_GROUP value: "workers" resources: requests: cpu: "500m" memory: "512Mi" limits: cpu: "1000m" memory: "1Gi" --- apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: resonate-workers-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: resonate-workers minReplicas: 5 maxReplicas: 50 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 ``` The HPA automatically scales worker pods based on CPU utilization. ## Serverless platforms ### Google Cloud Run Cloud Run workers can scale to zero and handle variable load automatically: ```dockerfile title="Dockerfile" FROM node:22-slim WORKDIR /app COPY package*.json ./ RUN npm ci --production COPY . . CMD ["node", "worker.js"] ``` ```bash title="Build and deploy" # Build container docker build -t gcr.io/your-project/resonate-worker:latest . # Push to GCR docker push gcr.io/your-project/resonate-worker:latest # Deploy to Cloud Run gcloud run deploy resonate-worker \ --image gcr.io/your-project/resonate-worker:latest \ --set-env-vars RESONATE_URL=https://resonate.example.com \ --min-instances 1 \ --max-instances 100 \ --cpu 1 \ --memory 512Mi \ --region us-central1 ``` Cloud Run workers stay alive polling for tasks and scale automatically based on load. ### AWS Lambda Lambda workers can run as functions that poll for tasks or respond to events: ```typescript title="lambda-worker.ts" import { Resonate } from "@resonatehq/sdk"; const resonate = new Resonate({ url: process.env.RESONATE_URL!, group: "lambda-workers", }); // Lambda handler polls for tasks export async function handler(event: any) { // Poll and process tasks // Return when done or Lambda timeout approaches } ``` **Deployment (using AWS CDK):** ```typescript title="worker-stack.ts" import * as cdk from "aws-cdk-lib"; import * as lambda from "aws-cdk-lib/aws-lambda"; export class ResonateWorkerStack extends cdk.Stack { constructor(scope: cdk.App, id: string) { super(scope, id); new lambda.Function(this, "ResonateWorker", { runtime: lambda.Runtime.NODEJS_22_X, handler: "lambda-worker.handler", code: lambda.Code.fromAsset("dist"), environment: { RESONATE_URL: "https://resonate.example.com", }, timeout: cdk.Duration.minutes(15), memorySize: 512, }); } } ``` Lambda has a 15-minute execution limit. Cloud Run supports longer executions (up to 60 minutes). Design your functions to complete within these limits, or use containerized workers for long-running workflows. ### AWS Fargate Fargate runs containers without managing servers, similar to Cloud Run: ```json title="task-definition.json" { "family": "resonate-worker", "networkMode": "awsvpc", "requiresCompatibilities": ["FARGATE"], "cpu": "512", "memory": "1024", "containerDefinitions": [ { "name": "worker", "image": "your-account.dkr.ecr.us-east-1.amazonaws.com/resonate-worker:latest", "environment": [ { "name": "RESONATE_URL", "value": "https://resonate.example.com" } ], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/ecs/resonate-worker", "awslogs-region": "us-east-1", "awslogs-stream-prefix": "ecs" } } } ] } ``` Create a Fargate service that runs the task definition, and Fargate handles scheduling and scaling. ## Bare metal / VMs ### Systemd service Run Resonate server as a systemd service on bare metal or VMs: ```ini title="/etc/systemd/system/resonate.service" [Unit] Description=Resonate Server After=network.target postgresql.service [Service] Type=simple User=resonate WorkingDirectory=/etc/resonate ExecStart=/usr/local/bin/resonate serve Restart=on-failure RestartSec=10 # Configuration is loaded from /etc/resonate/resonate.toml (the working directory). # Secrets can be injected as environment variables, e.g. RESONATE_STORAGE__POSTGRES__URL. [Install] WantedBy=multi-user.target ``` **Enable and start:** ```bash sudo systemctl enable resonate sudo systemctl start resonate ``` ### Worker processes Run workers as separate systemd services or use a process manager like PM2: ```bash title="PM2 worker management" # Start 10 workers pm2 start worker.js -i 10 --name "resonate-worker" # Scale to 20 workers pm2 scale resonate-worker 20 # Monitor pm2 monit ``` ## Hybrid deployments You can mix deployment patterns: **Example:** Server on Kubernetes, workers on Cloud Run - Central server in GKE for stability - Workers on Cloud Run for auto-scaling and cost efficiency **Example:** Server on VM, workers in Lambda - Self-hosted server for control - Serverless workers for variable load Resonate doesn't care where workers run as long as they can reach the server. ## Which pattern to choose? **Start simple, scale as needed:** | Use case | Recommended pattern | | ---------------------------------- | --------------------------- | | Local development | Docker Compose | | Small production (<10 workers) | Docker Compose or single VM | | Medium production (10-100 workers) | Kubernetes or Cloud Run | | Large production (>100 workers) | Kubernetes with HPA | | Variable/unpredictable load | Cloud Run or Fargate | | Event-driven workloads | Lambda workers | | Cost-sensitive | Cloud Run (scales to zero) | **General guidance:** - Use managed services for PostgreSQL (RDS, Cloud SQL, etc.) - Start with containers (easier debugging than serverless) - Add auto-scaling when you understand your load patterns - Use serverless for unpredictable or bursty workloads ## Summary Resonate works in any environment: - **Containers:** Docker Compose, Kubernetes, Fargate, Cloud Run - **Serverless:** Lambda, Cloud Functions - **Bare metal:** Systemd, PM2, manual processes **The pattern is always the same:** 1. Run one Resonate server (coordinates work) 2. Run N workers (execute your code) 3. Connect workers to server via `RESONATE_URL` Choose your infrastructure based on your operational preferences and scale requirements. Resonate adapts to where you want to run. --- --- url: https://docs.resonatehq.io/deploy title: Deploy a Resonate application --- Deploy Resonate applications section Deploy Resonate applications section Deployment is how you take Resonate from your laptop to production. This section covers everything you need to run Resonate reliably at any scale. ## What you're deploying Resonate has two components: 1. **Resonate Server** - Coordinates work, stores durable promise state, routes tasks to workers 2. **Workers** - Execute your application code (functions you write) **Key insight:** The server coordinates. Workers execute. This separation means you scale execution capacity (workers) independently from coordination (server). ## The deployment spectrum "Production" means different things depending on your scale: ### Development **Local testing** - Start with `resonate dev`: - Single command starts server with SQLite - No Docker or PostgreSQL required - Logs to stdout, hot-reload friendly - Perfect for development iteration **When you need more:** - Docker Compose for full stack (server + database + workers together) - Useful for testing worker scaling or database behavior **What matters:** Fast iteration, easy debugging. ### Staging **Pre-production testing** - Similar to production but isolated: - PostgreSQL for realistic behavior - Multiple workers to test load distribution - Observability setup (logs, metrics) - Authentication enabled **What matters:** Catch integration issues before they hit production. ### Production **Real workloads** - Scale and reliability matter: - PostgreSQL with high availability - Workers scaled to handle load (5-100+) - Full observability (logging, metrics, tracing) - Authentication enabled - Monitoring and alerting **What matters:** Uptime, performance, security. ## How to deploy Resonate ### 1. Choose your infrastructure Where do you want to run Resonate? - **Containers** (Docker, Kubernetes) → See [Deployment patterns](/deploy/deployment-patterns) - **Serverless** (Cloud Run, Lambda, Fargate) → See [Deployment patterns](/deploy/deployment-patterns) - **Bare metal / VMs** → See [Deployment patterns](/deploy/deployment-patterns) Resonate works anywhere. Choose based on your operational preferences. ### 2. Run the server Start the Resonate server with persistent storage: - **Development:** SQLite is fine - **Production:** Use PostgreSQL (see [Run server](/deploy/run-server)) The server coordinates work for all your workers. Already run ScyllaDB, NATS, or Postgres and would rather not add a database beside it? Resonate publishes a separate server implementation of the protocol for each. The ScyllaDB and NATS providers are source-available under BUSL-1.1; the Postgres provider is Apache 2.0. See [Server providers](/deploy/providers). ### 3. Deploy workers Workers execute your application code: - Start with 2-4 workers - Scale horizontally based on load (see [Scaling](/deploy/scaling)) - Workers connect to the server via `RESONATE_URL` ### 4. Set up observability You can't manage what you can't measure: - **Logging** → See [Logging](/deploy/logging) - **Metrics** → See [Metrics](/deploy/metrics) - **Tracing** → See [Tracing](/deploy/tracing) ### 5. Secure it Enable authentication for production: - **Authentication** → Token-based (JWT) or basic auth - **Secrets** → Store credentials safely See [Security](/deploy/security) for details. ### 6. Ensure availability Make your deployment resilient: - **PostgreSQL HA** → Use managed services (RDS, Cloud SQL) - **Worker fault tolerance** → Automatic via heartbeats - **Server monitoring** → Health checks and alerts See [Availability](/deploy/availability) for HA patterns. ## What to read first **If you're just starting:** 1. [Run server](/deploy/run-server) - Get the server running locally 2. [Deployment patterns](/deploy/deployment-patterns) - See your infrastructure options 3. [Scaling](/deploy/scaling) - Understand how workers scale **If you're going to production:** 1. [Deployment patterns](/deploy/deployment-patterns) - Choose your infrastructure 2. [Production readiness](/deploy/production-readiness) - Configuration checklist before sending real traffic 3. [Availability](/deploy/availability) - Set up PostgreSQL HA 4. [Security](/deploy/security) - Enable authentication 5. [Scaling](/deploy/scaling) - Plan for load 6. [Logging](/deploy/logging), [Metrics](/deploy/metrics), [Tracing](/deploy/tracing) - Observe everything **If you're debugging issues:** Go to [Debug](/debug) - that section is for troubleshooting. ## Decision tree Not sure where to start? Follow this: ``` Are you developing locally? └─ Yes → Start with `resonate dev` (simplest, no Docker needed) └─ No → Continue Do you need production reliability? └─ No → Use Docker Compose with PostgreSQL for staging └─ Yes → Continue What infrastructure do you prefer? ├─ Kubernetes → See Kubernetes deployment pattern ├─ Serverless (Cloud Run, Lambda) → See serverless deployment pattern ├─ Bare metal / VMs → See systemd deployment pattern └─ Not sure → Start with Docker Compose, migrate later How many workers do you need? ├─ <10 → Start with Docker Compose or simple VM deployment ├─ 10-100 → Use Kubernetes with HPA or Cloud Run with auto-scaling └─ >100 → Use Kubernetes with horizontal pod autoscaling Do you have variable/unpredictable load? └─ Yes → Use serverless (Cloud Run, Fargate) for auto-scaling └─ No → Use containers with manual scaling ``` ## Common patterns ### Small production (1-10 workers) **Pattern:** Docker Compose + managed PostgreSQL ```yaml services: resonate-server: image: resonatehqio/resonate:v0.9.8 environment: RESONATE_STORAGE__TYPE: postgres RESONATE_STORAGE__POSTGRES__URL: postgres://:@your-postgres.rds.amazonaws.com:5432/resonate worker: image: your-app:latest environment: RESONATE_URL: http://resonate-server:8001 deploy: replicas: 5 ``` **Why:** Simple, low-cost, easy to manage. Perfect for small teams. See [Deploy to Railway](/deploy/railway) for a step-by-step walkthrough of the same pattern on a PaaS. ### Medium production (10-100 workers) **Pattern:** Kubernetes + Cloud SQL + HPA - Resonate server: 1 replica (single server coordinates all workers) - Workers: Deployment with HorizontalPodAutoscaler - PostgreSQL: Managed service (Cloud SQL, RDS) **Why:** Kubernetes handles scaling, managed services handle ops. ### Large production (100+ workers) **Pattern:** Kubernetes + replicated PostgreSQL + regional deployment - Same as medium, but with PostgreSQL replication for HA - Regional worker deployments for latency - Advanced monitoring and alerting **Why:** Scale requires automation and redundancy. ### Variable load **Pattern:** Cloud Run or Fargate workers + managed PostgreSQL - Workers scale to zero when idle - Auto-scale to 100+ during load spikes - Pay only for what you use **Why:** Serverless auto-scaling handles unpredictable workloads. ## Architecture principles Understanding these principles helps you deploy Resonate effectively: ### 1. Server coordinates, workers execute The server never runs your code. It coordinates work and stores durable promise state. This means: - Server resource needs are modest (2-4 CPUs, 4-8GB RAM) - Workers scale to handle load - You can restart workers without losing work ### 2. State lives in the database All execution state is stored in PostgreSQL (or SQLite for dev). This means: - Server restarts don't lose work - Worker failures don't lose work - Database availability = system availability ### 3. Workers are stateless and interchangeable Workers poll for tasks and execute them. Any worker in a group can handle any task. This means: - Workers scale horizontally (add as many as you need) - Workers can be restarted safely - Load balances automatically ### 4. Fault tolerance is built-in Resonate handles worker failures automatically via heartbeat timeouts. This means: - No manual intervention for worker crashes - Work is reassigned automatically - Execution resumes from last checkpoint ## What's not covered yet Some features you might expect in a production deployment: **Multi-server coordination** - Resonate doesn't support running multiple server instances that coordinate with each other. One server coordinates many workers. **Automatic server failover** - No built-in failover between multiple servers. Use PostgreSQL HA/replication for state persistence. **Cross-region disaster recovery** - Use standard PostgreSQL replication patterns and manual failover procedures. These features aren't implemented because **worker horizontal scaling** handles the vast majority of scale needs. The server coordinates work but doesn't execute it, so it's rarely a bottleneck. ## Summary **To deploy Resonate:** 1. Run one server (coordinates all work) 2. Run N workers (execute your code) 3. Use PostgreSQL for production 4. Scale workers horizontally for capacity 5. Enable observability and security **Start simple, scale as needed.** You can begin with Docker Compose on a single VM and grow to Kubernetes with 100+ workers when your workload requires it. --- **Next:** Ready to run the server? See [Run server](/deploy/run-server). --- --- url: https://docs.resonatehq.io/deploy/kafka title: Kafka integration --- ## TL;DR Resonate does not ship a native Kafka transport in the current server. The supported integration today is the **Kafka worker pattern**: your code consumes Kafka with a normal client library and dispatches a Resonate workflow per message. Native `kafka://` transport is specified in the [Message Passing Protocol](https://distributed-async-await.io/spec/execution-model/message-passing#address-schemes) and is on the roadmap — when it ships, the server will deliver task messages over Kafka directly. ## The Kafka worker pattern Use a regular Kafka consumer (`kafkajs`, `confluent-kafka-python`, `rdkafka`, etc.) and call `beginRun` on each message. A stable per-message identifier becomes the Resonate promise ID, so the dispatch is idempotent — duplicate deliveries reconnect to the in-flight execution rather than starting over. ```ts title="src/consumer.ts" import { Kafka } from "kafkajs"; import { resonate } from "./workflow"; const kafka = new Kafka({ brokers: ["localhost:9092"] }); const consumer = kafka.consumer({ groupId: "workers" }); await consumer.connect(); await consumer.subscribe({ topic: "records_to_process", fromBeginning: true }); await consumer.run({ eachMessage: async ({ topic, partition, message }) => { // topic-partition-offset uniquely identifies the message even when no key is set const messageId = `${topic}-${partition}-${message.offset}`; await resonate.beginRun(messageId, "workflow", messageId, message.offset); }, }); ``` If your producer sets a stable message key (or your payload carries a domain ID like a record UUID), that's also a valid promise ID — the only requirement is that the same logical message always resolves to the same string. Full TypeScript / Python / Rust walkthroughs and runnable repos are on the [Kafka worker example page](/get-started/examples/kafka-worker). Source repos: - [example-kafka-worker-ts](https://github.com/resonatehq-examples/example-kafka-worker-ts) - [example-kafka-worker-py](https://github.com/resonatehq-examples/example-kafka-worker-py) - [example-kafka-worker-rs](https://github.com/resonatehq-examples/example-kafka-worker-rs) ## Idempotent dispatch Every Kafka message has a stable identifier you can derive from its coordinates: `${topic}-${partition}-${offset}` is unique by construction, and a producer-supplied key (or a domain ID inside the payload) works equally well when present. Pass that identifier as the Resonate promise ID: ```ts await resonate.beginRun(messageId, "workflow", ...args); ``` Resonate dedupes by promise ID. A consumer that crashes after starting a workflow but before committing the offset will redeliver the same message on restart. The redelivery hits `beginRun` with the same ID, Resonate recognizes the in-flight execution, and the workflow resumes from its last checkpoint instead of starting over. No per-message dedup table, no idempotency key bookkeeping — the workflow ID is the idempotency key. This is the property that makes the worker pattern crash-safe: the consumer can be at-least-once, and the dispatch into Resonate stays exactly-once. ## What about `@resonatehq/kafka`? The `@resonatehq/kafka` NPM package (and the [resonatehq/resonate-transport-kafka-ts](https://github.com/resonatehq/resonate-transport-kafka-ts) repo) targets the **legacy Go server**, not the current Rust server. It expects server flags like `--api-kafka-enable` that don't exist in the current server binary. Don't install it expecting it to wire up against a current Resonate Server — it won't. If you want native Kafka transport, watch: - The [`kafka://` address scheme](https://distributed-async-await.io/spec/execution-model/message-passing#address-schemes) in the protocol specification. - The [Planned section](/deploy/message-transports#planned) of the Message transports page — it gets updated when transports land in the server. ## Roadmap Native Kafka transport is on the roadmap. See [Message transports — Planned](/deploy/message-transports#planned) for status, and the [Kafka worker example](/get-started/examples/kafka-worker) for the supported integration today. --- --- url: https://docs.resonatehq.io/deploy/logging title: Logging --- Both the Resonate Server and Resonate SDK emit structured logs that can help you observe and diagnose the behavior of your application. ## Server logs Resonate emits structured logs via the Rust `tracing` ecosystem. On startup the server installs a text subscriber that writes key/value records to standard output at the operator-selected minimum log level. ### Configuring the log level Log levels: `debug`, `info`, `warn`, or `error`. The default is `info`. Set it in `resonate.toml`: ```toml title="resonate.toml" level = "debug" ``` Or via environment variable: ```shell RESONATE_LEVEL=debug ``` Or via CLI flag (takes precedence over the file and env): ```shell resonate serve --level debug ``` ### Log levels and common messages #### Debug – detailed flow diagnostics Enabled with `RESONATE_LEVEL=debug` or `--level debug`. Useful for tracing individual request outcomes, especially around promise lifecycle and task dispatch. - Promise lifecycle: messages like `Promise not found`, `Promise created already timedout`, `Promise settle: promise not found`, and `Promise settle: TOCTOU race detected, treating as not found` explain why an incoming promise request returned what it did. - Task lifecycle: `Task acquire: task not found`, `Task continue: not found`, `Task fulfill rejected: version mismatch or invalid state`, `Task fence rejected: task not found`. - Listener / callback registration: `Listener registration: awaited promise not found`, `Callback registration: awaited promise not found`. - Schedule lookups: `Schedule not found`, `Schedule delete: not found`. Every debug line includes structured fields such as `promise_id`, `task_id`, `schedule_id`, `version`, and (where relevant) `fenced_action`. #### Info – lifecycle and service announcements Emitted by default (the default `level` is `info`). - Server startup: `Resonate Server starting` reports the listener port; `Operational config` and `Transport config` follow with the resolved configuration. - Storage initialization: `SQLite initialized` or `PostgreSQL initialized` (`PostgreSQL pool configured` lists the pool size). - Auth state: `Auth disabled — all requests accepted`, or `Auth enabled` with the public key path (and `Auth issuer configured` / `Auth audience configured` when those claims are enforced). - Transport state: `GCP Pub/Sub transport enabled` when `[transports.gcps]` is configured. - Task recovery: `Task continued from halted state` indicates a previously halted task resumed. #### Warn – recoverable or throttling conditions Warnings surface when Resonate recovers automatically but an operator may want to know. - Auth in unsigned mode: `Auth enabled — unsigned mode (no signature verification)` when `[auth].publickey = "none"`. Fine for dev, dangerous in prod. - Task dispatch quirk: `Task fulfilled but promise not found` — the task completed but its promise record has since disappeared. - Shutdown pressure: `Background tasks did not finish within shutdown timeout, forcing exit` when the graceful shutdown window was exceeded. #### Error – actionable failures Errors identify conditions that usually require operator action. - Startup failures: `Fatal: ...` to stderr plus an `ERROR` record describing what aborted `resonate serve` (for example, `storage.type=postgres requires RESONATE_STORAGE__POSTGRES__URL`). - Metrics bind failures: `Failed to bind metrics port` with the port that was in use. - Background loop failures: `Background timeout processing failed: storage error` indicates the background timeout scanner hit a storage-layer problem. - Readiness probe failures: `Readiness check failed: storage database unavailable` is emitted each time `GET /ready` returns 503. ### Log output format Logs are written to stdout in `tracing`'s default key-value text format: ``` 2026-04-15T10:30:00.123Z INFO resonate: Resonate Server starting port=8001 2026-04-15T10:30:00.456Z INFO resonate: Using SQLite backend path="resonate.db" 2026-04-15T10:30:00.789Z INFO resonate: Auth disabled — all requests accepted ``` **Fields:** - ISO 8601 timestamp with millisecond precision - Level (`DEBUG`, `INFO`, `WARN`, `ERROR`) - Target (e.g. `resonate`) - Human-readable message plus structured key=value fields ## SDK logs The Resonate SDKs also emit logs for observing application behavior. ### TypeScript SDK The TypeScript SDK uses console logging by default: ```typescript import { Resonate } from "@resonatehq/sdk"; const resonate = new Resonate({ url: "http://localhost:8001", logLevel: "debug", // debug | info | warn | error }); ``` **What gets logged:** - Function execution (start, completion, errors) - Context operations (`ctx.run()`, `ctx.sleep()`, etc.) - RPC calls to workers - Promise resolution attempts - Retry attempts and failures ### Python SDK The Python SDK uses Python's standard `logging` module: ```python import logging from resonate import Resonate # Configure Python logging logging.basicConfig(level=logging.INFO) resonate = Resonate.remote( host="http://localhost", store_port="8001", message_source_port="8001", log_level="DEBUG", # DEBUG | INFO | WARNING | ERROR | CRITICAL (or a logging.* int) ) ``` ## Production logging patterns ### Log aggregation In production, collect logs from all servers and workers into a centralized system: **Common patterns:** - **ELK Stack** (Elasticsearch, Logstash, Kibana) - **Datadog** logs - **CloudWatch Logs** (AWS) - **Google Cloud Logging** - **Azure Monitor Logs** - **Grafana Loki** (lightweight alternative) ### Docker / Kubernetes **Docker Compose:** ```yaml services: resonate-server: image: resonatehqio/resonate:v0.9.8 logging: driver: "json-file" options: max-size: "10m" max-file: "3" ``` **Kubernetes:** Logs are automatically collected from stdout. Use a log aggregation solution like: ```yaml apiVersion: v1 kind: Pod metadata: name: resonate-server annotations: # Datadog log collection ad.datadoghq.com/resonate.logs: '[{"source":"resonate","service":"resonate-server"}]' spec: containers: - name: server image: resonatehqio/resonate:v0.9.8 ``` Or use Fluent Bit / Fluentd as a DaemonSet to forward logs to your aggregation system. ### Structured logging for analysis Parse structured logs into fields for querying: **Logstash filter example:** ```ruby filter { grok { match => { "message" => "time=%{TIMESTAMP_ISO8601:timestamp} level=%{WORD:level} msg=\"%{DATA:message}\"" } } } ``` **Query examples (CloudWatch Insights):** ```sql # Find all errors fields @timestamp, level, msg | filter level = "ERROR" | sort @timestamp desc # Count warnings by type fields msg | filter level = "WARN" | stats count() by msg ``` ## What to log and monitor ### Critical events Always monitor these log messages: **Server startup failures:** ``` ERROR resonate: Fatal: storage.type=postgres requires RESONATE_STORAGE__POSTGRES__URL ERROR resonate: Failed to bind metrics port port=9090 ``` **Action:** Check configuration and that required ports are free. **Readiness failures:** ``` ERROR resonate: Readiness check failed: storage database unavailable ``` **Action:** Investigate database health — `GET /ready` is returning 503 until storage recovers. **Background loop failures:** ``` ERROR resonate: Background timeout processing failed: storage error ``` **Action:** Check database health and connection pool; sustained failures block timeout handling. **Shutdown pressure:** ``` WARN resonate: Background tasks did not finish within shutdown timeout, forcing exit ``` **Action:** Consider raising `[server].shutdown_timeout` or investigating what's blocking shutdown. ### Normal operational events These logs indicate healthy operation: ``` INFO resonate: Resonate Server starting port=8001 INFO resonate: PostgreSQL initialized INFO resonate: Auth disabled — all requests accepted ``` ## Log retention and storage ### Development - **Retention:** 1-7 days - **Level:** `debug` or `info` - **Storage:** Local files or stdout ### Staging - **Retention:** 7-30 days - **Level:** `info` - **Storage:** Centralized log aggregation ### Production - **Retention:** 30-90 days (or per compliance requirements) - **Level:** `info` (use `debug` temporarily for troubleshooting) - **Storage:** Centralized log aggregation with archival to object storage (S3, GCS) ## Performance considerations ### Log volume **Debug logging produces significant volume.** In production: - Use `info` by default - Enable `debug` temporarily when troubleshooting - Monitor log storage costs **Estimate:** Debug logging can produce 10-100x more log data than info level. ### Log sampling For very high-throughput systems, consider sampling: ```yaml # Hypothetical config (not currently supported) logSampling: enabled: true rate: 0.1 # Log 10% of requests at debug level ``` **Alternative:** Use tracing (see [Tracing](/deploy/tracing)) for detailed execution visibility without overwhelming logs. ## Correlating logs across components Use **request IDs** to trace requests across server and workers: **Server logs:** ``` level=INFO msg="api:sqe:enqueue" requestId="req-abc123" method="POST" path="/promises" ``` **SDK logs:** ``` level=INFO msg="promise created" requestId="req-abc123" promiseId="order.123" ``` Search logs by `requestId` to see the full request lifecycle. ## Common debugging scenarios ### Task not being processed **Look for:** 1. Worker registration: `starting poll server` (server) + connection logs (worker) 2. Task creation: `api:sqe:enqueue` with promise/task IDs 3. Task routing: Check for `failed to match promise` warnings 4. Worker heartbeat: Look for heartbeat timeout warnings ### Promise stuck pending **Look for:** 1. Promise creation: `api:sqe:enqueue` with promiseId 2. Task assignment: Check if task was created and routed 3. Worker processing: Worker should log function execution start 4. Completion: Look for promise resolution logs ### Slow performance **Look for:** 1. `scheduler queue full` - Capacity exhausted 2. Database query latency - Check database logs 3. High request volume - Count `api:sqe:enqueue` per second ## Best practices 1. **Start with `info` level** - Debug is too verbose for production 2. **Use structured logging** - Parse key-value pairs for analysis 3. **Aggregate centrally** - Don't rely on local log files 4. **Set up alerts** - Monitor critical error patterns 5. **Retain logs adequately** - Balance cost vs troubleshooting needs 6. **Correlate with metrics** - Cross-reference logs with metrics for complete picture 7. **Test log queries** - Ensure you can find what you need during incidents ## Summary **For development:** - Use `debug` or `info` level - Logs to stdout are fine - Focus on understanding normal behavior **For production:** - Use `info` level (enable `debug` only when troubleshooting) - Centralize logs (ELK, Datadog, CloudWatch, etc.) - Alert on critical errors (startup failures, database errors) - Retain logs 30-90 days minimum - Correlate logs with metrics and traces Logs are your debugging lifeline. Set them up properly from day one. --- --- url: https://docs.resonatehq.io/deploy/message-transports title: Message transports --- The Resonate Server delivers task messages to workers over a configurable set of transports. As of v0.9.8, four transports ship in the server binary: | Transport | Address scheme | Enable flag | Default | |---|---|---|---| | HTTP push | `http://` / `https://` | `--transports-http-push-enabled` | `true` | | HTTP poll (SSE) | (used by SDK clients that long-poll the server) | `--transports-http-poll-enabled` | `true` | | GCP Pub/Sub | `gcps://` | `--transports-gcps-enabled` | `false` | | Bash exec | `bash://` | `--transports-bash-exec-enabled` | `false` | Each transport can be turned on or off independently. The full CLI flag list is on the [Run a server](/deploy/run-server#cli-flags) page; the equivalent `resonate.toml` keys live under `[transports.]`. ## HTTP push The HTTP push transport delivers task messages by `POST`ing to a worker URL embedded in the task address (for example `https://workers.example.com/tasks`). It's enabled by default and needs no further configuration for unauthenticated targets. For deployments where the target service requires authentication, the server can sign outbound requests with a static bearer token or a GCP OIDC ID token. See [Outbound HTTP push authentication](/deploy/security#outbound-http-push-authentication) on the Security page. ## HTTP poll (SSE) The HTTP poll transport lets SDK clients receive tasks over a long-lived Server-Sent Events stream instead of an inbound HTTP push. It's enabled by default and requires no per-deployment configuration; concurrency limits are tunable via `--transports-http-poll-max-connections` and `--transports-http-poll-buffer-size`. ## GCP Pub/Sub The GCP Pub/Sub transport puts task messages onto Pub/Sub topics for consumption by SDK clients using the matching client-side plugin. Enable it at runtime with the GCP project ID: ```shell resonate serve \ --transports-gcps-enabled true \ --transports-gcps-project my-gcp-project-id ``` Or in `resonate.toml`: ```toml [transports.gcps] enabled = true project = "my-gcp-project-id" ``` Or via env var: `RESONATE_TRANSPORTS__GCPS__ENABLED=true` and `RESONATE_TRANSPORTS__GCPS__PROJECT=my-gcp-project-id`. Authentication uses Application Default Credentials (ADC) — make sure the server process has access to credentials with publish rights on the target project. ## Bash exec The bash exec transport runs an inline shell script for each delivered task. It's intended for local-process workers, lightweight wrappers around CLI tools, agent sandboxes, and self-hosted single-host deployments where running a separate worker process is overkill. The transport is **disabled by default**. Enable it explicitly: ```shell resonate serve --transports-bash-exec-enabled true ``` Or in `resonate.toml`: ```toml [transports.bash_exec] enabled = true ``` Or via env var: `RESONATE_TRANSPORTS__BASH_EXEC__ENABLED=true`. Enabling is the only configuration the transport takes — there are no script-directory or working-directory settings. The bash exec transport runs arbitrary shell scripts. The **local** backend runs them as the user running the server, so only enable it on hosts where every promise creator is trusted. The **docker** and **tensorlake** backends isolate execution in a container or remote sandbox. ### Backends The script body is always **inline** — it travels in the promise's `param.data` (base64-encoded). The task address selects which backend runs it: | Address | Backend | What runs | |---|---|---| | `bash://` | Local | `bash -c "