Do not commit real secrets to a .env file in your repository. Put credentials under secrets and non-secret config under env, then read them with os.environ in your Python code.
How to Run a LangGraph Agent on DigitalOcean Harness Runtimepublic
Last verified 13 Oct 2026
DigitalOcean Harness Runtime provides managed, hardware-isolated microVM sandboxes with built-in tools such as Chromium to run harnesses and execute arbitrary code. Rich lifecycle APIs preserve conversational history and working state across sessions, letting you pause, resume, and fork work to control costs and adapt to the nonlinear nature of agentic workflows. Scale complete agents such as Claude Code or use sandboxes independently for code execution, all through the same service. See What You Can Build for example use cases.
Managed Agents runs your LangGraph application in an isolated sandbox. Unlike coding adapters, the LangGraph runtime runs your graph, rather than a general-purpose coding agent. At session start, we clone your repository at a pinned commit, install your Python dependencies, and start the LangGraph Agent Server (langgraph dev) against your graph.
Your application stays a standard LangGraph app. You do not need platform-specific imports or an SDK.
To run a LangGraph agent, you need a GitHub repository that contains your LangGraph app, with langgraph.json at the repository root (or in a subdirectory you point to) and the exact commit SHA you want to run.
Set Up the Repository
A deployable LangGraph app needs a compiled graph, a langgraph.json manifest, and a dependency file. We support both of the following layouts:
my-app/
|-- my_agent/
| |-- __init__.py
| `-- agent.py
|-- requirements.txt
`-- langgraph.jsonmy-app/
|-- my_agent/
| |-- __init__.py
| `-- agent.py
|-- pyproject.toml
`-- langgraph.jsonlanggraph.json must be at the repository root, or in the directory you set with FRAMEWORK_SUBDIR for monorepos.
Create the langgraph.json Manifest
Create langgraph.json at the root of your repository. This file declares your dependencies and the compiled graph the Agent Server should load. The session fails to start if the file is missing.
{
"dependencies": ["langchain_openai", "."],
"graphs": {
"my_agent": "./my_agent/agent.py:agent"
}
}The manifest supports the following fields:
dependencies(required): Packages and local paths your graph needs. Use"."or"./your_package"to include your repository code.graphs(required): Maps a graph name to a compiled graph as"./path/to/file.py:variable_name". The variable must be the object returned by.compile().env(optional): Path to a local.envfile for local development. Use environment variables in the environment spec instead.python_version(optional): Python version your graph expects.pip_config_file(optional): Path to a custom pip configuration file.dockerfile_lines(optional): Extra image build lines for system-level binaries your dependencies need.
Declare Dependencies
At boot, we install the following dependencies from your checked-out repository:
requirements.txt:pip install --no-cache-dir -r requirements.txtpyproject.toml:pip install --no-cache-dir -e .- Neither: No install step. Your graph can only use packages preinstalled in the image.
If both requirements.txt and pyproject.toml exist, requirements.txt takes precedence and pyproject.toml is ignored.
For a typical LLM-backed graph, use a requirements.txt similar to:
langgraph>=0.2
langchain-core>=0.3
langchain-openai>=0.2Expose a Compiled Graph
Point the graphs key at a variable produced by .compile(). The following example defines a minimal chat-style graph with a messages array in state. The last assistant message is shown as streamed reply text. Graphs without messages still run; you receive node-level events instead of a chat transcript.
Create or open my_agent/agent.py:
import os
from typing import Annotated, TypedDict
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
llm = ChatOpenAI(
api_key=os.environ["HARNESS_INFERENCE_API_KEY"],
base_url=os.environ["HARNESS_INFERENCE_BASE_URL"],
model=os.environ["HARNESS_INFERENCE_MODEL"],
temperature=0,
)
class AgentState(TypedDict):
messages: Annotated[list, add_messages]
def respond(state: AgentState):
return {"messages": [llm.invoke(state["messages"])]}
workflow = StateGraph(AgentState)
workflow.add_node("respond", respond)
workflow.add_edge(START, "respond")
workflow.add_edge("respond", END)
agent = workflow.compile()Write the Environment Spec
Create a file such as langgraph-agent.yaml that points at your repository and commit:
name: my-langgraph-agent
agent: langgraph
size: mars-2vcpu-4gb
idle_timeout: 10m
env:
FRAMEWORK_REPO: "https://github.com/<owner>/<repo>.git"
FRAMEWORK_REPO_SHA: "<exact-commit-sha>"
HARNESS_INFERENCE_MODEL: "<model-name>"
secrets:
HARNESS_INFERENCE_API_KEY: ${HARNESS_INFERENCE_API_KEY}
permissions:
default: askExport the inference API key so doctl can expand ${HARNESS_INFERENCE_API_KEY}:
export HARNESS_INFERENCE_API_KEY=<your-inference-api-key>The spec uses the following fields and environment variables:
agent(required): Must belanggraph.FRAMEWORK_REPO(required): Fullhttps://github.com/...URL. HTTP URLs and non-GitHub hosts are rejected.FRAMEWORK_REPO_SHA(required): Exact commit SHA to check out. Boot fails without it.FRAMEWORK_SUBDIR(optional): Repository-relative directory that containslanggraph.json, for monorepos. Defaults to the repository root.HARNESS_INFERENCE_*(required if your graph calls a model): Inference credentials your code reads fromos.environ. Put the API key undersecrets. Non-secret values such as base URL and model may stay inenv.GITHUB_TOKEN(required for private repositories): Authenticate the clone with an OAuth slot that names your GitHub connection’s actor ID, or with a personal access token. See Connect GitHub.permissions.default(recommended):askpauses for approval when the agent requests a human-in-the-loop decision.
Always pin FRAMEWORK_REPO_SHA to an exact commit so every session boots the same code. Do not use a floating branch name.
Declare every credential under secrets.
Connect a Model
Your graph owns model configuration. We do not inject a model client for you. Pass credentials in the environment spec (API key under secrets), then read them from os.environ in Python.
Use the following environment variables:
HARNESS_INFERENCE_API_KEY: API key for your inference endpoint. Prefersecrets.HARNESS_INFERENCE_MODEL: Model name to request from that endpoint.
Any OpenAI-compatible endpoint works with langchain-openai when you set these variables and pass base_url explicitly. You can use other environment variable names if your code expects them (for example OPENAI_API_KEY under secrets). Prefer os.environ["KEY"] over silent defaults so missing configuration fails at import time.
Access a Private Repository
If FRAMEWORK_REPO is private, declare GITHUB_TOKEN under secrets. Harness Runtime uses the token only for a shallow clone of the pinned commit and does not write the token into the repository URL or git remotes.
env:
FRAMEWORK_REPO: "https://github.com/<owner>/<private-repo>.git"
FRAMEWORK_REPO_SHA: "<exact-commit-sha>"
secrets:
GITHUB_TOKEN:
source: oauth
provider: github
actor: <actor-id>Create the GitHub connection first with doctl harness-runtime connections create --actor <actor-id>, and use the same actor ID in the spec. You can also declare a personal access token instead. See Connect GitHub.
Start and Attach to the Session
Start the session from your spec:
doctl harness-runtime launch ./langgraph-agent.yamlCold start typically takes about 20 seconds while the sandbox clones the repository, installs dependencies, and starts the Agent Server. When the status is ready, launch attaches the session to your terminal:
Detach with Ctrl+D. The session stays alive until you pause or remove it. To inspect the event stream, run:
doctl harness-runtime logs <session-id-or-name>Remove the session when you are finished:
doctl harness-runtime remove <session-id-or-name>Session names must be unique within your team. Remove the previous session or change name before reusing a name. The LangGraph guest idle timeout is 10 minutes between turns. Send input within that window, or start a new session after an idle timeout ends the run.
Troubleshoot LangGraph Sessions
Use this table to diagnose common LangGraph deployment errors:
| Symptom | Likely cause | What to do |
|---|---|---|
| Session fails to boot; no graph running | langgraph.json missing at the resolved directory |
Add langgraph.json at the repository root, or set FRAMEWORK_SUBDIR to the directory that holds it. |
| Boot fails immediately | FRAMEWORK_REPO or FRAMEWORK_REPO_SHA missing |
Set both values. Boot fails closed without your code. |
FRAMEWORK_REPO rejected |
URL is not an HTTPS github.com address |
Use a full https://github.com/<owner>/<repo>.git URL. |
| Clone or fetch fails | Private repository, bad SHA, or invalid token | Verify the URL, confirm the commit exists on the remote, and set a valid GITHUB_TOKEN. |
ModuleNotFoundError at run time |
Dependency missing from your dependency file | Add the package, push a new commit, and update FRAMEWORK_REPO_SHA. |
| Unexpected dependency install | Both dependency files present | Consolidate into one file. requirements.txt wins over pyproject.toml. |
| Authentication error from the model | Credentials absent or not read by your code | Confirm HARNESS_INFERENCE_* keys are injected (API key via secrets) and your code reads them from os.environ. |
| Wrong model endpoint | base_url not passed to the client |
Pass base_url=os.environ["HARNESS_INFERENCE_BASE_URL"], including the /v1 suffix. |
| Session create fails on name | Session name already in use | Remove the old session or change name. |