Home / Articles

Managed Deep Agents Beta: A Directory-Driven Way to Deploy LangChain Agents

LangChain's Managed Deep Agents turns a project folder into a hosted agent. What each file does, how mda deploy behaves, and the beta gaps worth knowing.

Publisher: REACTAPP.TOP12 min read2849 words

This article is published in English.

Sources: LangChain Just Shipped Managed Deep Agents — And Your Agent Is Now a Directory

Teams building on LangGraph kept running into the same limit. LangGraph is a runtime, a low-level primitive, yet plenty of people treated it like an application framework. Deep Agents was LangChain's response: planning, a virtual filesystem, subagents and context compression bundled into a harness so nobody had to rebuild that plumbing.

It helped, and then a second limit showed up. A harness ends at the process boundary. As soon as an agent has to survive a restart, pick up again after a human signs off on a tool call, fire at 8am Monday to Friday, reply to a Slack mention, or keep any memory past the current thread, the team is writing infrastructure again. That means checkpointers, cron jobs, signature verification, thread ownership and secret routing.

Anyone who has pushed an agent into a cloud container knows how lopsided this gets. Think of an MCP-powered document agent on ECS Fargate: IAM policies, a VPC with private subnets, Terraform state in S3 with a DynamoDB lock table, and task definitions that fail health checks until someone spots a container port mismatch. The agent logic can take a couple of days while the surrounding infrastructure eats a couple of weeks.

Managed Deep Agents is LangChain's answer to that second limit. It entered public beta on August 7, 2026. After reading through the 21 documentation pages, the most striking thing is not the one-command deploy everybody mentions. It's that the agent stops being a Python object. It becomes a directory.

What the product actually is

The word "managed" is vague, so a precise definition helps. Managed Deep Agents, invoked as mda in a terminal, is a Python package. It takes a project folder, compiles it into a hosted LangGraph application and ships that to LangSmith's Agent Server. The Deep Agents harness itself is unchanged, and LangSmith takes over running it, handling persistence, durable threads, the lifecycle of sandboxes, loading skills and mounting memory.

The workflow fits in four commands:

uv tool install managed-deepagents
mda init research-assistant
cd research-assistant
uv sync
mda dev .        # run locally in LangSmith Studio
mda deploy .     # deploy to LangSmith

The announcement leads with that, and it's accurate, but it undersells the real design choice one level below.

Capabilities are controlled by ls

mda init scaffolds a project layout that the documentation treats as the full surface area:

my-agent/
├── agent.py              # required — the model and core config
├── instructions.md       # system prompt, synced to Context Hub
├── skills/
│   └── research/
│       └── SKILL.md      # task-specific playbooks
├── tools/                # your LangChain tools
├── middleware/           # logic around model and tool calls
├── connectors/
│   └── mcp.py            # remote MCP servers
├── channels/
│   └── slack.py          # Slack, GitHub, other entry points
├── schedules/
│   └── daily_digest.py   # managed cron jobs
├── sandbox/
│   └── __init__.py       # isolated filesystem and shell
├── identity.py           # auth and thread scoping
├── memory.py             # durable cross-thread memory
├── pyproject.toml
├── .env
└── evals/                # Harbor tasks

Only agent.py is mandatory. Everything else is optional, and here's the part to dwell on: whether a file exists is what switches a capability on or off.

Remove the sandbox/ directory and the sandbox disappears. Put a memory.py at the root and durable memory turns on. Add schedules/daily_digest.py and the next deploy provisions a LangSmith cron job; delete the file and the cron job is destroyed.

Anyone who has written Terraform will recognise the model. It is declarative infrastructure in Python syntax. You describe the state you want, and mda deploy reconciles reality with it.

The layout only makes sense once you've seen what the files contain, so here they are one by one.

agent.py, the one required file

from managed_deepagents import define_deep_agent
from middleware.audit import log_tool_calls
from tools.search import internet_search

agent = define_deep_agent(
    name="research-assistant",
    model="openai:gpt-5.5",
    tools=[internet_search],
    middleware=[log_tool_calls],
    interrupt_on={"internet_search": True},
)

name is the one required argument, and it serves as both the LangGraph assistant ID and the default name of the deployment. Beyond it, define_deep_agent takes tools, middleware, subagents, permissions, interrupt_on (for human-in-the-loop approval) and response_format (structured output).

Look at what's missing: no system prompt, no memory configuration, no sandbox, no schedules. Those are driven by files alone, and there's no way to configure them in code even if you'd like to.

The built-in server-side search tools need no provider key, although each provider expects a differently shaped tool dictionary, and that's an easy place to slip:

tools=[{"type": "web_search"}]                                # OpenAI
tools=[{"google_search": {}}]                                 # Google
tools=[{"type": "web_search_20260209", "name": "web_search"}] # Anthropic

When search never appears in a trace, this mismatch is nearly always the cause.

instructions.md and skills/

Context that syncs to the cloud lives here. The instructions file might read:

# Research assistant

You are a careful research assistant. Use internet search to find sources,
keep notes, and return concise answers with citations.

It becomes the system prompt, inserted into every run. Skills behave differently. Each lives in its own directory, with frontmatter:

---
name: research
description: Gather and synthesize context before answering complex questions.
---
# Research
Use this skill when a task needs more than a direct answer.
1. Identify what information is missing.
2. Use `query_db` to look up relevant records.
3. Summarize findings before responding to the user.

When the agent starts it sees just the name and description of every skill. A matching task makes it read the whole SKILL.md, and any supporting files are fetched lazily. This is progressive disclosure, as in the open-source harness.

The useful operational detail: at deploy time instructions.md and the whole skills/ tree are pushed to LangSmith Context Hub. They can then be changed in the UI with no new deployment, so editing a prompt no longer means a build. Anyone who has sat through a CI run just to correct a typo in a system prompt gets the point.

memory.py: durable memory, opt-in

from managed_deepagents import define_memory
memory = define_memory(scope="agent")

Those two lines are all it takes, and without them the agent has no durable memory whatsoever. That may surprise people who assume memory comes standard.

Enabling it mounts one Context Hub tree at /memories/agent/. The AGENTS.md file in that tree is hot memory, loaded into context on each run. Everything else is cold memory that the agent reads only when relevant. Reads and writes go through the usual read_file, edit_file and write_file tools, and writes to other locations, including other paths under /memories/, aren't durable. A design consequence follows, covered further down.

sandbox/ and its snapshot step

from managed_deepagents import define_sandbox
sandbox = define_sandbox(
    idle_ttl_seconds=600,
    default_timeout=600,
    docker_image="python:3.12-slim",
)

Every durable thread receives its own sandbox, backed by LangSmith Sandboxes. An optional sandbox/setup.sh runs once under bash -e and the result is saved as a snapshot, which new threads clone instead of repeating setup:

#!/usr/bin/env bash
set -euo pipefail

apt-get update && apt-get install -y jq
mkdir -p /workspace

Two traps are worth knowing. Values from .env are available while setup.sh runs, which helps for cloning a private repository, but threads cloning the snapshot never receive them. In addition, whatever setup leaves on disk ends up in the image of every thread, which rules out keeping secrets there.

schedules/: cron with a compile-time rule

from managed_deepagents import define_schedule
schedule = define_schedule(
    cron="0 8 * * 1-5",
    timezone="America/Los_Angeles",
    prompt="Review durable memory for reusable research rules. List open questions for today.",
)

There's one file per schedule, and the filename becomes the schedule's name. Exactly one of prompt or input must be given. Only five-field cron is accepted, so a seconds-based expression makes compilation fail. Without a timezone, UTC applies.

The constraint most likely to cause trouble is that schedule declarations are extracted at compile time and have to be statically serialisable. No environment variables, no function calls, no **kwargs, no computed values. Literals and top-level constants are fine. Anything dynamic belongs in the agent, tools or middleware instead.

Each run normally gets a throwaway thread that is removed when it finishes. Scheduled runs that should build up state need thread={"mode": "persistent", "id": "nightly-memory"} instead.

channels/ and connectors/

A channel receives messages that start runs, while a connector gives the agent tools. Each is a single file:

# channels/slack.py
from managed_deepagents import channels
channel = channels.slack(
    auto_reply=True,
    mention_behavior="strip",
    conversation={"app_mention": "thread", "direct_message": "conversation"},
)
# connectors/mcp.py
from managed_deepagents import connectors
connector = connectors.mcp(
    mcp_servers={
        "langchainDocs": {
            "transport": "http",
            "url": "https://docs.langchain.com/mcp",
            "include_tools": ["search_docs_by_lang_chain"],
        },
    },
)

Routing follows the filename. channels/slack.py exposes its Events endpoint at /channels/slack/events, and MCP tools carry the server name as a prefix unless told otherwise, so this connector shows up as langchainDocs__search_docs_by_lang_chain. Stdio MCP servers aren't an option, only Streamable HTTP and legacy SSE.

identity.py: who may call the agent

from managed_deepagents import auth, define_identity
identity = define_identity(auth=auth.langsmith_api_key())

This is what mda init generates as the safe default. Callers send a LangSmith workspace key in the x-api-key header. That settles whether someone may call the agent, but everyone then shares the same threads. Per-user privacy requires a swap to Supabase:

identity = define_identity(auth=auth.supabase(project_ref="your-project-ref"))

Clients then send Authorization: Bearer <access_token>, and MDA checks the JWT against the project's JWKS endpoint. The documentation includes one migration warning: adding this to an existing deployment doesn't backfill owner metadata onto threads that already exist.

What mda deploy does

The documentation lists eleven ordered steps. The ones that matter:

  1. Checks the project and loads agent.py.
  2. Finds the LangSmith key in a fixed order (LANGGRAPH_HOST_API_KEY, LANGSMITH_API_KEY, LANGCHAIN_API_KEY) and reads .env ahead of the shell environment.
  3. Collects each non-reserved .env value and sends it along as a hosted-deployment secret.
  4. Uploads instructions.md and the skills/ folder into Context Hub.
  5. Compiles to .mda/build and uploads the archive, which can be at most 200 MB.
  6. Waits, polling the revision, until its status is DEPLOYED.
  7. Reconciles cron jobs by locating every schedule it created earlier, deleting them all, and rebuilding the set from the current schedules/ directory.

The seventh step is the point at which comparing this to Terraform is no longer just a figure of speech. It's drift correction, and it behaves like terraform apply.

So, as with Terraform, a flag exists that quietly skips it. Adding --no-wait makes the CLI kick off the remote build and quit before the status turns DEPLOYED, which means that run never gets to the schedule reconciliation step. The new cron doesn't exist, the deleted cron still fires, and the deploy reports success. Whenever a schedule changes, run mda deploy . without --no-wait. A line in a team runbook is cheap insurance.

Where it sits in the LangChain stack

There are now four layers, and people tend to blur them together.

LangChain supplies the building blocks: models, tools, prompts. It's the foundation.

LangGraph is the runtime that provides durable, stateful, graph-shaped execution (persistence, streaming, interrupts). Think of it as the engine.

Deep Agents sits above both as the harness, with a planner, filesystem, subagents, context compression and opinionated defaults.

Managed Deep Agents is the hosted runtime for that harness. It's the same Agent Server that LangSmith customers already run in production, packaged more prescriptively for Deep Agents projects.

A car analogy captures it: LangGraph supplies an engine and a transmission, Deep Agents supplies a car, and Managed Deep Agents supplies the car, the garage and somebody else changing the oil. The price is losing the ability to open the hood. That's an honest trade, and the documentation is frank about it. Anyone needing custom routes, application code beside the graph, or direct control of persistence is told to use LangSmith Deployment directly.

The shared memory scope

The beta offers a single memory scope, agent: one read/write tree that every caller able to reach the deployment shares.

The documentation is unusually blunt about what follows. Memory is shared by all callers, and every caller can influence it. So store only knowledge that everyone may read and modify, and treat existing memory as untrusted input that must never grant authority, change tool permissions or bypass approvals.

Read that as a security stance. AGENTS.md is loaded into system context on every run, so whatever caller A writes on Tuesday ends up in caller B's context on Wednesday, persistently. For a single-team internal tool that's fine. For anything multi-tenant it amounts to a prompt-injection channel with a persistence layer attached.

What makes this worse for people who care about evaluation and observability is that the failure won't show up in evals. The Harbor tasks grade workspace state after a run, and they don't model an adversary who wrote to hot memory a week earlier.

The documentation's own advice is right: skip shared memory when callers shouldn't influence each other. Until per-user memory scoping arrives, that's a real limit on what can be built.

A surface that's still moving

A practical warning before copying any code. The launch post is dated August 7, while the documentation keeps changing. Within about three weeks, four public APIs shifted, and anyone building from the announcement's samples runs into all four.

  • Memory: the announcement says every deployment begins with agent-scoped memory, whereas the docs describe durable memory as opt-in and disabled by default.
  • Sandboxes: the announcement uses define_sandbox(provider="langsmith", scope="thread") and hints at a shared agent scope; the reference page lists idle_ttl_seconds and default_timeout and says one sandbox always maps to one thread.
  • Identity: the announcement talks about configuring an OIDC provider inside identity.py, but the identity page covers only LangSmith API keys and Supabase.
  • Slack: the announcement calls channels.slack(on=[...]), while the Slack guide has no on argument and places event subscriptions in the Slack app manifest.

The documentation is not always consistent with itself either. One page names the command mda channel init slack and another says mda channel add slack .; the --configure-slack flag for mda deploy . is described in the Slack guide yet absent from the CLI reference table.

That doesn't make the product poor. It simply means a beta of a declarative tool, where the surface is the one thing nobody should guess. When docs and tables disagree, mda --help wins.

When it fits and when it doesn't

Managed Deep Agents makes sense when:

  • you already build on the Deep Agents harness and infrastructure is the bottleneck
  • the agent needs durable threads, scheduled jobs or a Slack front door
  • prompts and skills should be editable without a redeploy
  • all callers share a single trust boundary
  • you're in the US and fine driving deploys from a terminal

Choose LangSmith Deployment itself, or host the open-source harness yourself, when:

  • Your own backend has to call the agent. The beta is CLI-first while the API gets finished, and resuming an interrupted run programmatically means asking your LangChain contact, per the docs. That blocks the use case outright.
  • Callers must be kept away from one another's memory.
  • Custom routes or hands-on control of persistence are required.
  • Data has to stay outside the US.
  • Slack runs across several replicas. Event deduplication is process-local at the moment, so a retry from Slack can trigger the agent twice, and tools reachable from a channel must be idempotent if they have side effects.

Trying it out

Install and scaffold:

uv tool install managed-deepagents
mda init test-agent && cd test-agent

Then put keys in .env:

LANGSMITH_API_KEY=<your-key>
OPENAI_API_KEY=<your-key>

Before writing anything, run ls on what was scaffolded. Five minutes with the directory teaches more about the product's model than the announcement does.

Next, uv sync && mda dev . builds the project, stands up a local stand-in for Context Hub (covering instructions, skills and memory) and launches LangSmith Studio in a browser tab. It doesn't hot-reload project files, so stop and rerun after changes. The official quickstart sits in the LangSmith Python docs under the Managed Deep Agents section.

Teams already running Deep Agents and hand-building their deployment plumbing should give it an afternoon. The directory model is a good idea, and the beta gaps can be worked around once they're known.