Temporal
Run the Temporal server that SteelEngine deployments depend on
SteelEngine runs every workflow, schedule, and background job through Temporal. The Compose file and the Helm chart ship the Temporal Worker, not the Temporal server. You run the server and point the app and the Worker at it. SteelEngine publishes its own server image because a stock Temporal server cannot verify SteelEngine's tokens.
The Temporal server is a separate deployment with its own PostgreSQL databases. Size it in addition to the Requirements for the SteelEngine containers.
The Server Image
ghcr.io/steelengineai/temporal-server is Temporal Server 1.31.2 with SteelEngine's token verification built in. It accepts Temporal's standard environment configuration and --config-file. The image also carries the temporal CLI, two dynamic config files, and the namespace bootstrap script. Pull it with the same registry credentials as the app images.
The server keeps Temporal's own system Worker on an internal frontend (port 7236) that skips token verification. Never expose that port or any other internode port through a Service, load balancer, or ingress.
How Authentication Works
The app signs a short-lived token for every request to Temporal. The token names the issuer, the audience (steelengine-temporal-rpc), a subject, a role, and the namespaces and Task Queues it may touch. The signing key is the same one behind the app's JWKS endpoint, <app URL>/api/auth/jwks. The server verifies each token against that endpoint. It never receives the signing key.
There are three roles. The app uses client. The Worker uses worker. Bootstrap tooling uses operator, which is the only administrative role. A Worker token must carry the Worker's TEMPORAL_IDENTITY as its subject and list all eight product Task Queues:
steelengine-workflows
steelengine-knowledge-connectors
steelengine-webhook-ingestion
steelengine-notifications
steelengine-document-processing
steelengine-copy-backfill
steelengine-scheduled-tasks
steelengine-platformA second signed token travels inside each workflow and identifies the caller. Workers check it before they run Activities. It is not a Temporal Server credential and needs no server configuration.
Server Configuration
Required:
| Variable | Description |
|---|---|
TEMPORAL_AUTH_ISSUER | Exact token issuer. Must match TEMPORAL_AUTH_ISSUER on the app and the Worker |
TEMPORAL_AUTH_AUDIENCE | Token audience. Use steelengine-temporal-rpc unless you changed it everywhere |
TEMPORAL_AUTH_JWKS_URL | HTTPS JWKS endpoint, normally <app URL>/api/auth/jwks. Set this or TEMPORAL_AUTH_JWKS_FILE, not both |
TEMPORAL_AUTH_JWKS_FILE | Local JWKS file for development or a mounted secret |
Optional:
| Variable | Default | Description |
|---|---|---|
TEMPORAL_AUTH_ENVIRONMENT | production | development, test, or production. Production rejects every insecure exception |
TEMPORAL_AUTH_REQUIRE_TLS | true | Reject tokens on plaintext connections |
TEMPORAL_AUTH_ALLOW_INSECURE_JWKS | false | Permit an HTTP JWKS URL. Development only |
TEMPORAL_AUTH_CLOCK_SKEW | 30s | Tolerance for token time claims |
TEMPORAL_AUTH_JWKS_REFRESH_INTERVAL | 5m | Maximum age of cached keys |
TEMPORAL_AUTH_JWKS_HTTP_TIMEOUT | 5s | JWKS HTTP timeout |
TEMPORAL_DEPLOYMENT_MODE | local | local permits loopback. clustered requires a private broadcast address and internode TLS |
TEMPORAL_BROADCAST_ADDRESS | detected | Private IPv4 this node advertises. Detected when the host has one private interface |
TEMPORAL_TLS_INTERNODE_CA, TEMPORAL_TLS_INTERNODE_CERT, TEMPORAL_TLS_INTERNODE_KEY, TEMPORAL_TLS_INTERNODE_SERVER_NAME | - | Internode mutual TLS material. Required in clustered mode |
Persistence and frontend TLS use Temporal's own variables. For PostgreSQL, set DB=postgres12, POSTGRES_SEEDS, POSTGRES_USER, POSTGRES_PWD, DB_PORT, and DYNAMIC_CONFIG_FILE_PATH. For frontend TLS, set TEMPORAL_TLS_FRONTEND_CERT, TEMPORAL_TLS_FRONTEND_KEY, and TEMPORAL_TLS_FRONTEND_SERVER_NAME. SteelEngine's TEMPORAL_SERVER_NAME must match that certificate name.
The image ships two dynamic config files under /etc/temporal/config/dynamicconfig/. Both record the verified caller on workflow events and allow long IDs. Use development-sql.yaml for a single node and hosted-sql.yaml for a multi-node cluster, which also encrypts cluster membership traffic.
Bootstrap the Schema and Namespace
The server does not create its own tables. Against an empty PostgreSQL it refuses to start.
- Create the schema. Run
docker/temporal/scripts/setup-all.shfrom thetemporalio/admin-tools:1.31.2image. Give it the sameDB,POSTGRES_*, andSQL_PASSWORDvalues as the server. It creates thetemporalandtemporal_visibilitydatabases. No Elasticsearch is needed. Use a PostgreSQL instance dedicated to Temporal in production. - Create the namespace. Run
/etc/temporal/scripts/bootstrap-product-namespace.shfrom the server image as a one-shot task. SetTEMPORAL_PRODUCT_NAMESPACEandTEMPORAL_INTERNAL_ADDRESS. Inclusteredmode, also pass the internode TLS material. The script creates the namespace and registers eightKeywordsearch attributes:OrganizationId,WorkspaceId,WorkflowOwnerId,WorkflowTriggerSource,SteelEngineWorkflowId,SteelEngineExecutionId,SteelEngineDeploymentId, andSteelEngineRequestId. SteelEngine writes these attributes when it starts workflows, so register all eight before the first run. - Verify. Run
bash scripts/temporal-production-preflight.shfrom the repository with an operator token. It needsTEMPORAL_ADDRESS,TEMPORAL_NAMESPACE,TEMPORAL_API_KEY, and the TLS flags. It checks cluster health and creates any missing search attribute.
Namespace retention defaults to 7 days. Change it with TEMPORAL_NAMESPACE_RETENTION.
Connect SteelEngine
The app and the Worker read the same connection settings. Environment Variables → Temporal lists every variable. These values must agree across the server, the app, and the Worker:
| Setting | Server | App and Worker |
|---|---|---|
| Endpoint | frontend on 7233 | TEMPORAL_ADDRESS=host:7233, no scheme |
| Namespace | created in bootstrap | TEMPORAL_NAMESPACE |
| TLS name | TEMPORAL_TLS_FRONTEND_SERVER_NAME | TEMPORAL_SERVER_NAME with TEMPORAL_TLS_ENABLED=true |
| Issuer | TEMPORAL_AUTH_ISSUER | TEMPORAL_AUTH_ISSUER |
| Audience | TEMPORAL_AUTH_AUDIENCE | TEMPORAL_AUTH_AUDIENCE (default steelengine-temporal-rpc) |
| JWKS | TEMPORAL_AUTH_JWKS_URL | TEMPORAL_AUTH_JWKS_URL on the Worker |
The app signs its own tokens. The Worker gets its token from one of two sources:
- Credential broker. This is the Compose default and Helm
worker.auth.credentialBroker. The Worker calls the app withINTERNAL_API_SECRETand a proof signed withTEMPORAL_WORKER_BROKER_SECRET. The app returns a short-lived token for the identity inTEMPORAL_WORKER_IDENTITY. The Worker asks for a new token every 30 seconds by default. Production requires an HTTPSAPP_URL, which Compose sets fromTEMPORAL_WORKER_APP_URL. - File or static token. This is Helm
worker.auth.apiKeySecret, orTEMPORAL_API_KEY_FILEorTEMPORAL_API_KEYin Compose. You issue the token yourself with the Worker'sTEMPORAL_IDENTITYas subject and the eight queue names. Rotate it by rewriting the file. The Worker checks the signature, issuer, audience, subject, namespace, role, and queue list before it uses the token.
The two sources are mutually exclusive. If no valid token arrives before the current one expires, the Worker marks itself not ready and stops polling.
Scheduled Sub Agents
A scheduled prompt task runs its complete assistant loop in one Temporal Worker Activity. Model requests, platform tools, and integration tools execute in the Worker process using the shared assistant runtime and platform gateway. The Worker no longer starts these runs by calling /api/workspace-assistant/execute, and platform tool calls do not pass through MCP.
The Activity has a 30-minute start-to-close timeout, a 30-second heartbeat timeout, and one attempt. Heartbeats report liveness; they do not save a checkpoint after each tool. If the Worker stops partway through a run, a new run starts the task again rather than resuming at the next tool. Review external side effects before manually retrying. Persisting the run's final status has a separate retry policy and does not rerun the assistant.
Configure provider access, credential decryption keys, storage, and network access on the Worker as well as the app. Saved tasks use the same model and reasoning validation, BYOK resolution, and current authorization as Chat. See Environment Variables.
The app still provides a credential broker for assistant tools that start Temporal workflows. The Worker calls /api/internal/assistant-temporal-credentials with its internal-service token, a broker proof, and the persisted schedule and execution IDs. The app checks the occurrence and current permissions before signing credentials for the requested operation. The Worker cannot use this broker to request worker or operator authority.
Keep APP_URL, INTERNAL_API_SECRET, and TEMPORAL_WORKER_BROKER_SECRET configured for these operations, including when the Worker's own polling credential comes from a file or static token. Temporal signing private keys remain on the app. Removing the assistant execution HTTP call does not remove the Worker's need to reach the app for credential issuance and other internal callbacks.
Local Development
docker/local/compose.yml runs a single-node server on loopback. gRPC listens on 127.0.0.1:7233, HTTP on 7243, and the Temporal UI on 8233. bun run temporal:up generates keys under .temporal-auth.local/, starts the stack, and writes .env.temporal.local for bun run dev and the host Worker. bun run temporal:smoke runs a real workflow through it.
The local server accepts tokens over plaintext because every port is loopback-only. For a production-shaped rehearsal on one machine, run bun run selfhost:validate. It starts the same server behind TLS, deploys the Helm chart into kind, and checks the Worker end to end.
TEMPORAL_ALLOW_INSECURE_AUTH on the clients and TEMPORAL_AUTH_REQUIRE_TLS=false on the server are for loopback development only. The app and the Worker reject TEMPORAL_ALLOW_INSECURE_AUTH together with TEMPORAL_TLS_ENABLED=true. The server rejects an HTTP JWKS URL in production.
Production Checklist
- Frontend TLS on
7233. SetTEMPORAL_TLS_ENABLED=trueand a matchingTEMPORAL_SERVER_NAMEon the app and the Worker. - An HTTPS JWKS URL served by a highly available app. A fresh server or Worker cannot start while it is down.
- A stable
BETTER_AUTH_SECRETacross app replicas. Keep old public signing keys while running workflows still reference them. TEMPORAL_DEPLOYMENT_MODE=clusteredwith internode mutual TLS for any multi-node cluster. Keep ports 7234-7239 and 6933-6939 private.- Schema and namespace bootstrap completed before the Worker starts. Both scripts are safe to run twice.
bash scripts/temporal-production-preflight.shpassing before the first deployment and after every namespace change.