Skip to main content
Forest’s orchestrator coordinates your workflows but never executes the steps itself — that runs on Forest Runtime, deployed on your own infrastructure. Forest Runtime polls the orchestrator for pending steps, runs them locally, and reports only the results back. Setting it up is a one-time addition, not a migration: you deploy Forest Runtime and either point your agent at it or embed it in the agent — your collections and existing workflows are untouched. Because it talks to your data through your own Forest agent, the Forest orchestrator never sees your records — data read and written by data steps stays within your infrastructure.
One exception: AI steps and MCP Tasks send prompt content, which can include record data, to an LLM provider and remote tools — so workflows aren’t fully air-gapped. See AI provider.

Do I need Forest Runtime?

Yes — Forest Runtime is what executes your workflow steps. Automatically-triggered workflows especially: a webhook call or an AI assistant can fire at any time, with no guarantee anyone has Forest open in a browser, so those runs can only execute server-side. Until a runtime has reported in, the workflow settings page shows a “Your Forest Runtime can’t be reached” screen and neither automated trigger can be enabled. Running on infrastructure you control also keeps the records handled by data steps out of Forest’s infrastructure — decisive for compliance or data-residency requirements, or when a step needs access to systems reachable only from within your network.
Upgrade Forest Runtime before you enable the MCP trigger. A runtime older than the release that added MCP triggering rejects MCP-started runs when it validates them: the run is created, picked up, and immediately errored — its first step carries a validation message, and getWorkflowRun reports that error rather than a run in progress. There is no dedicated failed state: the run stays in started, and it is routed to the fallback inbox, so that inbox fills up with runs that never began. Because started counts as active, each affected record is then blocked from being triggered again on the same workflow until you abort its run. Nothing warns you up front, and there is no version check: the older runtime simply rejects triggerType: "mcp" as an unknown value when it validates the step, so the message recorded there is the generic “Internal validation error occurred while preparing the step. Please contact support.” — it names neither the version nor the trigger type. Upgrade first, then turn the toggle on.

How it works

  1. A workflow is triggered — by a user, a webhook, or an AI assistant. The Forest orchestrator queues the pending steps.
  2. Forest Runtime polls the orchestrator and pulls the steps assigned to it.
  3. Each step runs locally, reaching your data and actions through your Forest agent.
  4. It reports the step outcome back to the orchestrator, which advances the workflow.

Prerequisites

  • A recent Forest Admin agent — the minimum version depends on how you run Forest Runtime (below).
  • A PostgreSQL database for production: Forest Runtime persists its run state there. A database-free mode exists for testing only.

Running Forest Runtime

Run Forest Runtime one of two ways:
  • Embedded in your Node.js agent — one line in your agent, nothing separate to deploy. The simplest option.
  • Standalone — a separate process (Docker or CLI), to scale it independently of your agent or to use it with a Ruby agent.

Embedded in the Node.js agent

Requirements:
  • @forestadmin/agent — embedded mode arrived in 1.84.0, but the options shown below (schema, ai, encryptionKey, the tuning knobs) landed in later releases, so use a recent version.
  • Node.js ≥ 22.12.0 — required by the executor package.
  • The executor package, installed at the exact version your agent pins. It’s an optional peer dependency the agent loads dynamically at runtime, so tsc won’t flag it when it’s missing — but agent.start() throws “The embedded workflow executor requires the @forestadmin/workflow-executor package”. The pin is exact (a ^ range conflicts), so install that specific version — find it in your lockfile or with npm info @forestadmin/agent@<your-agent-version> peerDependencies (pin the version you actually run — without it, npm answers for latest, whose pin may differ):
  • TypeScript 5.5+ — the package ships zod 4, which is only tested against TypeScript 5.5 and later. On TypeScript 4.x its typings can’t even be parsed, and skipLibCheck won’t help (it suppresses type errors, not the syntax errors these typings trigger).
Then add one line — the executor runs inside the agent process, so there’s nothing else to deploy:
Safe to reuse the database your agent already reads: the executor keeps its tables in a dedicated forest Postgres schema, so they stay out of the public schema your datasource introspects and never show up as collections in your panel. Pass a schema if you’d rather name it differently.Creating that schema needs the CREATE privilege on the database. If your role doesn’t have it, have an administrator create the schema and grant the role access to it — the executor checks whether the schema already exists before trying to create one, so a pre-created schema boots fine with schema-level privileges only.
It inherits your agent’s secrets and Forest connection, so you only configure:
Embedded has full configuration parity with standalone: your own AI provider (ai), the encryption key, and every tuning knob are all settable here. It only inherits your agent’s secrets and Forest connection; set nothing and AI steps use Forest’s AI server.

Standalone (Docker or CLI)

Run Forest Runtime as its own service — the way to run it with any agent other than the v2 Node.js one (which can also embed it), and to scale or deploy it separately. 1. Point your agent at it with the workflow executor URL, so the agent mounts the route that forwards workflow requests to Forest Runtime (relaying the JWT). It’s supported across both agent generations:
The route itself lives in forest-express, which both wrappers pin to an exact version. The minimums above pull in forest-express 10.7.0, so that’s the number to look for in a lockfile. You never install or configure it yourself.
The Python agents (agent-python, django-forestadmin) don’t support Forest Runtime yet.
If the workflow executor URL is left unset, the agent returns 404 on those routes and Forest Runtime never receives any work. When the agent and Forest Runtime run on separate hosts, use an internal address the agent can reach on Forest Runtime’s HTTP port (default 3400). 2. Run Forest Runtime as a Docker image or via the CLI:
When Forest Runtime runs in Docker and your agent runs on the host machine, use host.docker.internal instead of localhost in AGENT_URL and DATABASE_URL. On Linux Docker Engine that hostname doesn’t exist by default, so the --add-host=host.docker.internal:host-gateway flag above is required to resolve it (on Docker Desktop it’s already provided and the flag is harmless).
The image was previously published as ghcr.io/forestadmin/workflow-executor. That name is still published, so existing deployments keep working — new ones should use ghcr.io/forestadmin/runtime. The npm package name is unchanged.

Network requirements

A standalone Forest Runtime opens these connections (an embedded executor makes the same outbound calls from the agent process, with no extra inbound port): On first boot Forest Runtime auto-creates its workflow_step_executions table, then polls the orchestrator every 30 seconds (POLLING_INTERVAL_S) for work. Forest Runtime is stateless apart from its Postgres database: you can run several instances against the same database for high availability — each pending step is claimed by exactly one instance. If you use OAuth-protected MCP connectors, give every instance the same encryption key.

Health check

GET /health is public (no auth) and returns the runtime’s current state:
For liveness probes, treat any 200 as healthy. For readiness probes, route traffic only on {"state":"running"} so a draining instance stops receiving new work while it finishes in-flight steps.

AI provider

Several step types rely on an LLM: guidance, decisions, MCP Tasks, and AI-assisted data steps. By default Forest Runtime uses Forest’s AI server — no configuration required, AI steps work out of the box. To keep AI calls off Forest’s server and use your own provider instead, set these variables:
AI_PROVIDER and AI_MODEL always go together. Add AI_API_KEY for openai and anthropic; leave it out for bedrock — setting it there stops Forest Runtime at startup instead of being ignored. Set none of them to keep using Forest’s AI server.

Amazon Bedrock

With AI_PROVIDER=bedrock, Forest Runtime reaches the model through Amazon Bedrock, so the calls stay inside your AWS account. Set AI_MODEL to a Bedrock model id, a cross-region inference profile id, or an inference profile ARN — and AWS_REGION to the region the model is enabled in. To find the id, open AWS’s Models at a glance and pick your Claude model: its page carries the model id, and an Inference profile IDs section with the eu. and us. ones. Either can be copied straight into AI_MODEL. Supported models: Anthropic Claude Sonnet, Haiku and Opus. Any other Bedrock model is refused at startup, with a message naming what is accepted. AI steps ask the model to answer in a strict, structured format, and the rest of the Bedrock catalogue either can’t do that reliably or hasn’t been tested on our side — so a model we can’t vouch for is rejected immediately, rather than breaking halfway through a workflow. A cross-region inference profile id works and is the usual choice, since it keeps your data in one geography: eu. stays in the EU, us. in the US. Avoid the global. prefix if data residency is the reason you are on Bedrock, as it routes worldwide. Bedrock takes no AI_API_KEY. Forest Runtime signs in to AWS exactly the way the AWS CLI does, so whatever already works for your other AWS tools works here too — you don’t configure anything specific to Forest. Whichever method you use, it needs permission to call bedrock:InvokeModel on the models you picked. That includes assuming a role, so you never have to hand long-lived keys to the container:
Always set AWS_REGION (or AWS_DEFAULT_REGION), even if your AWS profile already has a region — that one isn’t picked up here. Forest Runtime checks this at startup, so a missing region stops it right away instead of breaking the first workflow that uses AI.
Mounting an AWS profile into Docker: the image runs as the node user, so mount your AWS directory to /home/node/.aws, not /root/.aws. Mounting it to /root/.aws fails quietly — the container starts, and the first AI step reports that no credentials could be loaded.
To mount somewhere else, point AWS_CONFIG_FILE and AWS_SHARED_CREDENTIALS_FILE at the files directly.

OAuth-protected MCP connectors

If your workflows include MCP Tasks backed by OAuth-protected connectors, Forest Runtime stores each user’s OAuth credentials in its database, encrypted at rest. Provide the encryption key:
  • Required only for OAuth-protected MCP connectors, and read lazily — an instance that stores no such credentials runs fine without it.
  • Use the same value on every instance that shares a database, or an instance won’t decrypt credentials written by another.
  • Treat it as permanent: there is no managed rotation. Changing it forces every affected user to reconnect their connectors.

Observability

The Docker image ships with OpenTelemetry APM built in, compatible with any OTLP backend (Datadog, Grafana Tempo, Jaeger, Honeycomb…). It is off by default and turns on as soon as you set OTEL_EXPORTER_OTLP_ENDPOINT. OpenTelemetry is bundled only in the Docker image, not the npm package.

When an MCP Task can’t load its tools

Forest Runtime logs the reason at Error, so it is in your logs without changing LOG_LEVEL. This is the JSON the Docker image writes to stdout; running in a terminal you get the same fields in the pretty single-line format instead.
kind tells you where to look:
  • auth — the server rejected the credential (HTTP 401). For an OAuth connector the runtime refreshes the token and retries once on its own, so act only if the failure repeats without a follow-up MCP tools loaded after refreshing the credential line. For a static credential, renew it in the connector’s configuration.
  • connection — unreachable, refused, or slower than the 15-second per-server load timeout.
  • unknown — the server answered but the load failed anyway, including HTTP 403 permission or scope errors that no token refresh can fix; error carries the reason.
A server that answers but exposes no tools is not a failure: you get an empty tool list and no error.
If your logs show a failedConfigNames list instead of failures, your runtime predates this change: it names which server failed but not why, and reports a healthy server exposing no tools as a failure. Upgrade to get the cause.

Tuning

Beyond the required variables, these optional knobs have sensible defaults and rarely need changing:
For the remaining variables (individual database parts, DATABASE_SSL, in-memory testing mode, full OTel configuration), see the package README on npm.

Learn more

Workflows overview

Build and manage workflows in the no-code editor

MCP Servers

Configure the connectors used by MCP Tasks