A production scaffold for running LangChain Deep Agents on Render. Deep Agents gives you the agent harness — planning, subagents, a task tool, human-in-the-loop. This repo adds the two pieces you need to run it for real, both backed by Render:
- Durable state on Render Postgres — every step of a run is checkpointed with LangGraph's
PostgresSaver. Runs survive restarts, work across replicas, and can pause for human approval and resume later. - Distributed execution on Render Workflows — when the agent spawns a subagent via the built-in
tasktool, it runs as its own Render Workflow task: isolated compute, retries, and timeouts, instead of running in-process.
The example agent is a small research-report generator (an orchestrator that delegates to a researcher and an editor subagent), but the point is the wiring — swap in your own agents in src/agents/.
┌──────────────────────────────────────────────┐
POST /run ───▶│ Web service (src/server.ts) │
│ • Orchestrator Deep Agent (the planner) │
│ • Postgres checkpointer (durable state) │
│ • Human-in-the-loop interrupt + resume │
└───────────────┬──────────────────────────────┘
│ task() tool intercepted by
│ WorkflowDispatchMiddleware
▼
┌──────────────────────────────────────────────┐
│ Workflow service (src/workflow.ts) │
│ • one Render task per subagent │
│ • researcher (isolated instance) │
│ • editor (isolated instance) │
└──────────────────────────────────────────────┘
Two Render services participate:
| Process | Source | Role |
|---|---|---|
| Web service | src/server.ts |
Runs the orchestrator, owns the durable checkpoint/interrupt state, exposes the HTTP API. |
| Workflow service | src/workflow.ts |
Registers one Render task per subagent. Each subagent runs on its own instance with retries + timeout. |
Render Workflows as the subagent backend. The orchestrator's built-in task tool (how Deep Agents delegate to subagents) is intercepted by WorkflowDispatchMiddleware. In production it dispatches the subagent to its Render Workflow task; locally (RENDER_USE_LOCAL_DEV=true) it runs the subagent in-process so you get a fast dev loop with zero infrastructure.
Render Postgres as the checkpointer. src/checkpointer.ts builds a PostgresSaver over an explicit pg.Pool sized to your Postgres plan. The orchestrator runs in the web service (not as a Workflow) so it can pause on an interruptOn tool, persist its state to Postgres, and resume on a later request — even after a restart or deploy. If DATABASE_URL is unset it falls back to an in-memory saver so a fresh clone still runs (state is not durable until you set it).
src/
server.ts web service: /run, /resume, /threads, /healthz
workflow.ts Render Workflow service: registers a task per subagent
config.ts env-driven runtime config (dispatch mode, pool size, ...)
model.ts model resolution from the MODEL env var
checkpointer.ts Postgres checkpointer + pg pool (sized to your plan)
agents/
index.ts registry of top-level orchestrators (/run/:agent)
orchestrator.ts the Deep Agent: subagents + HITL + checkpointer + middleware
subagents.ts ← THE customization surface: define your subagents here
run-subagent.ts how a subagent actually runs (shared by local + Workflow)
tools.ts example tools (replace these)
workflows/
dispatch-middleware.ts intercepts the `task` tool → Render Workflow dispatch
render-client.ts Render SDK wrapper: start a task run and await its result
Requires Node >=22.12.
git clone <your-fork-url>
cd deepagents-on-render-ts
npm install
cp .env.example .env
# edit .env: set ANTHROPIC_API_KEY (or OPENAI_API_KEY + MODEL=openai:gpt-5)With RENDER_USE_LOCAL_DEV=true (the default in .env.example), subagents run in-process — no Render account needed:
npm run dev # http://localhost:3000Trigger a run:
curl -s -X POST http://localhost:3000/run/research \
-H 'content-type: application/json' \
-d '{"input":"Research the history of espresso and write a short report."}'The orchestrator delegates to the subagents, then tries to call publish_report — a sensitive action gated by human-in-the-loop. The response will have status: "interrupted" and a threadId. Approve it to finish:
curl -s -X POST http://localhost:3000/resume/<threadId> \
-H 'content-type: application/json' \
-d '{"decisions":[{"type":"approve"}]}'Postgres locally (optional but recommended): set
DATABASE_URLin.envto any Postgres instance to get durable checkpointing. Without it, the app uses an in-memory saver and state is lost on restart. To prove durability: start a run, restart the web service, thenGET /threads/<threadId>/state— the pending interrupts are still there, loaded from Postgres — and/resumecontinues from the checkpoint.
To run each subagent as a real Render Workflow task locally (isolated instances, retries), use the Render CLI in a second terminal:
render workflows dev -- npm run dev:workflowSee the Render Workflows docs for CLI setup.
The web service and the Workflows service deploy through two complementary mechanisms.
-
Push your fork to GitHub.
-
Create the Blueprint. In the Render Dashboard, create a new Blueprint from your repo.
render.yamlprovisions:- the web service (
deepagents-web) - a managed Postgres database (
deepagents-db), auto-wired toDATABASE_URL
During sync, set the secret env vars (marked
sync: false):ANTHROPIC_API_KEY(orOPENAI_API_KEY) andRENDER_API_KEY. - the web service (
-
Create the Workflow service. Workflows are not yet provisionable from
render.yaml, so create one in the Dashboard:- New → Workflow, pointed at the same repo
- Build command:
npm ci - Start command:
npm run start:workflow - Add the same model key env vars (
ANTHROPIC_API_KEY/OPENAI_API_KEY,MODEL) - Note its slug (e.g.
deepagents-workflows)
See Triggering Task Runs for details.
-
Point the web service at the Workflow service. On
deepagents-web, set:RENDER_WORKFLOW_SLUG= the Workflow service slug from step 3RENDER_API_KEY= a Render API key
Redeploy the web service. In production (
NODE_ENV=production), subagents now dispatch to Workflow task runs you can watch in the Dashboard. -
Verify.
POST /run/researchon your web service URL and watch each subagent appear as a separate task run in the Workflow service's dashboard.
Replace the example agents without touching the infrastructure:
-
Define your subagents in
src/agents/subagents.ts. Each entry is a{ name, description, systemPrompt, tools? }. Thenamebecomes both thesubagent_typethe orchestrator delegates to and the Render Workflow task name — no infra changes needed. -
Swap the tools in
src/agents/tools.tsfor your real ones (search APIs, internal services, databases). -
Rewrite the orchestrator prompt in
src/agents/orchestrator.tsto describe your workflow, and adjustinterruptOnto gate whichever tools are sensitive in your domain. -
(Optional) Add another orchestrator by adding it to
src/agents/index.ts; it becomes reachable atPOST /run/<name>.
You should not need to touch checkpointer.ts, dispatch-middleware.ts, render-client.ts, or server.ts.
| Method & path | Description |
|---|---|
POST /run/:agent |
Start a run. Body: { "input": string, "threadId"?: string }. Returns a completed result or status: "interrupted" with the interrupts to approve. |
POST /resume/:threadId |
Resume a paused run. Body: { "agent"?: string, "decisions"?: [...] }. Decisions are passed to the HITL middleware (default: a single approve). |
GET /threads/:threadId/state |
Inspect the persisted state for a thread (proves durability). |
GET /healthz |
Liveness check. |
GET / |
Service info + available agents. |
See .env.example for the full, documented list. Summary:
| Variable | Required | Description |
|---|---|---|
MODEL |
no | Model id, "<provider>:<model>". Default anthropic:claude-sonnet-4-5. |
ANTHROPIC_API_KEY / OPENAI_API_KEY |
yes (one) | Provider key matching MODEL. |
DATABASE_URL |
prod | Postgres connection string for the checkpointer. Falls back to in-memory if unset. |
DATABASE_POOL_MAX |
no | Max pg pool connections. Size to your Postgres plan (default 8). |
RENDER_USE_LOCAL_DEV |
no | true runs subagents in-process (local dev default). |
RENDER_API_KEY |
prod | Render API key for dispatching Workflow task runs. |
RENDER_WORKFLOW_SLUG |
prod | Slug of the Workflow service hosting the subagent tasks. |
PORT |
no | Web service port (default 3000). |
NODE_ENV |
no | production switches the default dispatch mode to Render Workflows. |
MIT