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.json
my-app/
|-- my_agent/
|   |-- __init__.py
|   `-- agent.py
|-- pyproject.toml
`-- langgraph.json

langgraph.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 .env file 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.
Warning

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.

Declare Dependencies

At boot, we install the following dependencies from your checked-out repository:

  • requirements.txt: pip install --no-cache-dir -r requirements.txt
  • pyproject.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.2

Expose 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: ask

Export 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 be langgraph.
  • FRAMEWORK_REPO (required): Full https://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 contains langgraph.json, for monorepos. Defaults to the repository root.
  • HARNESS_INFERENCE_* (required if your graph calls a model): Inference credentials your code reads from os.environ. Put the API key under secrets. Non-secret values such as base URL and model may stay in env.
  • 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): ask pauses for approval when the agent requests a human-in-the-loop decision.
Note

Always pin FRAMEWORK_REPO_SHA to an exact commit so every session boots the same code. Do not use a floating branch name.

Warning

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. Prefer secrets.
  • 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.yaml

Cold 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.

We can't find any results for your search.

Try using different keywords or simplifying your search terms.