config/skills/managed-deep-agents/SKILL.md
INVOKE THIS SKILL when creating, running, or operating a Managed Deep Agent against the LangSmith /v1/deepagents private-preview REST API. Covers the agent → MCP server → thread → streamed run flow, tool/interrupt configuration, and the agent file tree (AGENTS.md, skills/, subagents/, tools.json).
npx skillsauth add langchain-ai/langchain-skills managed-deep-agentsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Managed Deep Agents is an API-first hosted runtime for creating, running, and operating Deep Agents in LangSmith. It packages the operational layer around the open-source Deep Agents harness — versioned agent repo in the Context Hub, durable threads, streamed runs, managed file tree, MCP credential storage — so you don't have to stand up your own agent server.
For self-hosted deployments or full Agent Server APIs, use a standard LangSmith Deployment via [[langgraph-cli]] instead.
Use this skill when the user wants to:
POST / PATCH /v1/deepagents/agents)httpx in Python, built-in fetch in JS/Node 18+)export LANGSMITH_API_KEY="<LANGSMITH_API_KEY>"
export DEEPAGENTS_BASE_URL="https://api.smith.langchain.com/v1/deepagents"
All requests authenticate via the X-Api-Key header:
X-Api-Key: <LANGSMITH_API_KEY>
1. Register MCP server(s) → POST /v1/deepagents/mcp-servers
2. Create the agent → POST /v1/deepagents/agents
3. Create a thread → POST /v1/deepagents/threads
4. Stream a run → POST /v1/deepagents/threads/{thread_id}/runs/stream
5. Inspect in LangSmith → Traces UI
Each step is independent — register MCP servers once per workspace, then point any number of agents at them.
| Group | Purpose |
|-------|---------|
| Agents (/agents) | Create, list, get, update (PATCH), and delete Managed Deep Agents. |
| Threads (/threads) | Create, search, count, and inspect durable thread state. |
| Runs (/threads/{thread_id}/runs/stream) | Start and stream runs on a thread (server-sent events). |
| MCP servers (/mcp-servers) | Register, list, rotate credentials, and delete MCP servers referenced by agent tools. |
Managed Deep Agents keeps the familiar Deep Agents project shape. Keep these in your source repo (so updates are reviewable) and submit them in POST / PATCH /agents payloads. The platform stores them as a versioned agent repo in the Context Hub, returns a revision on each update, and serves the tree to the agent on every run.
| File / directory | Purpose |
|------------------|---------|
| AGENTS.md | Agent instructions. |
| skills/ | Skill definitions the agent can use. |
| subagents/ | Subagent definitions for delegated work. |
| tools.json | Tool configuration (tools + interrupt_config). |
At runtime, the agent can read and write files — including a /memories/ directory for durable cross-run state.
tools configurationTools are configured with a tools array and an interrupt_config map. The same shape is used in tools.json and inline in agent create/update payloads:
{
"tools": [
{
"name": "tavily_search",
"mcp_server_url": "https://mcp.tavily.com/mcp/",
"mcp_server_name": "tavily",
"display_name": "tavily_search"
}
],
"interrupt_config": {
"https://mcp.tavily.com/mcp/::tavily-search::tavily": false
}
}
tools[].mcp_server_url must match an MCP server already registered for the workspace; credentials are attached automatically at invocation time.tools[].name is the tool name exposed by the MCP server itself, not the workspace's MCP-server display name. Confirm the name with tools/list against the server (e.g. Tavily's MCP exposes tavily_search with an underscore, even if you registered the server under any other name). The model will not see the tool if this name is wrong.interrupt_config keys use the form {mcp_server_url}::{tool_name} (two colons, trailing slashes on the URL stripped before matching). Additional ::-separated parts (e.g. the MCP server display name) are accepted but ignored when matching.true to require human approval before the tool runs, false to allow it without interrupt.Tools that need credentials (custom MCP servers, private endpoints, any bearer-authenticated API) are registered once per workspace via POST /v1/deepagents/mcp-servers. The platform stores the URL and credential headers (encrypted at rest) and re-attaches them on every invocation whose tools[].mcp_server_url matches.
import os, httpx
BASE_URL = os.environ["DEEPAGENTS_BASE_URL"]
HEADERS = {"X-Api-Key": os.environ["LANGSMITH_API_KEY"]}
response = httpx.post(
f"{BASE_URL}/mcp-servers",
headers=HEADERS,
json={
"name": "tavily",
"url": "https://mcp.tavily.com/mcp/",
"headers": [
{"key": "Authorization", "value": "Bearer tvly-..."},
],
},
)
response.raise_for_status()
mcp_server_id = response.json()["id"]
Other operations on the same resource:
| Action | Method + path |
|--------|---------------|
| List | GET /mcp-servers |
| Get one | GET /mcp-servers/{mcp_server_id} |
| Rotate credentials | PATCH /mcp-servers/{mcp_server_id} (replaces the entire headers array — partial diffs not supported) |
| Delete | DELETE /mcp-servers/{mcp_server_id} |
Only static-header auth (bearer tokens, custom API-key headers) is supported during preview. OAuth-backed MCP servers are planned.
response = httpx.post(
f"{BASE_URL}/agents",
headers=HEADERS,
json={
"name": "research-assistant",
"description": "Research assistant that can search the web and summarize sources.",
"runtime": {"model": {"model_id": "anthropic:claude-sonnet-4-6"}},
"instructions": (
"You are a careful research assistant. Search for sources, "
"keep notes, and return concise answers with citations."
),
"tools": {
"tools": [
{
"name": "tavily_search",
"mcp_server_url": "https://mcp.tavily.com/mcp/",
"mcp_server_name": "tavily",
"display_name": "tavily_search",
},
],
"interrupt_config": {
"https://mcp.tavily.com/mcp/::tavily-search::tavily": False,
},
},
},
)
response.raise_for_status()
agent_id = response.json()["id"]
Response includes the agent id (store it as AGENT_ID).
Other operations:
| Action | Method + path |
|--------|---------------|
| List | GET /agents |
| Get one | GET /agents/{agent_id} |
| Update | PATCH /agents/{agent_id} |
| Delete | DELETE /agents/{agent_id} |
PATCH replaces tools wholesale — to add a tool, pass the full new tool set, not just the additions. Each PATCH also creates a new revision on the agent's Context Hub repo.
Deleting an agent does NOT cascade to its threads. Existing threads remain queryable but starting new runs returns 502. Track and delete threads explicitly when cleaning up.
Pass model identifiers in {provider}:{model_id} form (e.g. anthropic:claude-sonnet-4-6, openai:gpt-5.4-mini). The runtime resolves models with init_chat_model, so any provider supported by init_chat_model works.
Values without a colon are interpreted as references to a saved Playground configuration, not a model ID — always supply the full {provider}:{model_id} form when configuring a model directly.
Threads preserve conversation and execution state for long-running work.
response = httpx.post(
f"{BASE_URL}/threads",
headers=HEADERS,
json={
"agent_id": agent_id,
"options": {
"test_run": False,
"skip_memory_write_protection": False,
},
},
)
response.raise_for_status()
thread_id = response.json()["id"]
payload = {
"agent_id": agent_id,
"messages": [
{
"role": "user",
"content": "Research recent approaches to agent memory and summarize the main tradeoffs.",
}
],
"stream_mode": ["values", "updates", "messages-tuple"],
"stream_subgraphs": True,
"user_timezone": "America/Los_Angeles",
}
with httpx.stream(
"POST",
f"{BASE_URL}/threads/{thread_id}/runs/stream",
headers={**HEADERS, "Accept": "text/event-stream"},
json=payload,
timeout=None,
) as response:
response.raise_for_status()
for line in response.iter_lines():
if line:
print(line)
Set Accept: text/event-stream so the client receives progress as SSE. stream_mode accepts the same values as the LangGraph runtime (values, updates, messages-tuple, etc.). stream_subgraphs: true emits events from subagent graphs as well as the parent.
Every run is traced in LangSmith — model calls, tool calls, subagent activity, files, and runtime state are all inspectable from the trace UI.
When interrupt_config flags a tool with true, the run pauses before the tool executes and the SSE stream emits an interrupt payload nested inside a values (or updates) event:
{
"__interrupt__": [
{
"value": {
"action_requests": [
{
"name": "tavily_search",
"args": { "query": "…", "max_results": 5 },
"description": "Tool execution requires approval\n\nTool: tavily_search\nArgs: {...}"
}
],
"review_configs": [
{
"action_name": "tavily_search",
"allowed_decisions": ["approve", "edit", "reject", "respond"]
}
]
},
"id": "7ae0be0e5105464d49a267e35083f422"
}
]
}
The stream closes after the interrupt is emitted. To act on it, post a follow-up run with command.resume on the same thread:
resume = httpx.stream(
"POST",
f"{BASE_URL}/threads/{thread_id}/runs/stream",
headers={**HEADERS, "Accept": "text/event-stream"},
json={
"agent_id": agent_id,
# The API requires a non-empty `messages` array on every call;
# an empty system message is accepted as a no-op when resuming.
"messages": [{"role": "system", "content": ""}],
"command": {
"resume": {
"decisions": [{"type": "approve"}]
}
},
"stream_mode": ["values", "updates", "messages-tuple"],
"stream_subgraphs": True,
},
timeout=None,
)
The resume value must be the HITLResponse dict {"decisions": [...]} — not a bare list. The middleware does decisions = interrupt(request)["decisions"] under the hood (see langchain.agents.middleware.HumanInTheLoopMiddleware), so a list at the top level crashes the run with TypeError: list indices must be integers or slices, not str.
Send exactly one decision per action_request, in the same order. Decision shapes:
| Decision | Shape | Effect |
|----------|-------|--------|
| Approve | {"type": "approve"} | Run the tool with the proposed args. |
| Edit | {"type": "edit", "edited_action": {"name": "...", "args": {...}}} | Run the tool with modified name/args (preserves the original tool_call.id). |
| Reject | {"type": "reject", "message": "..."} | Block the tool. The model receives an error ToolMessage with message (or a default). |
| Respond | {"type": "respond", "message": "..."} | Skip the tool; the model receives a success ToolMessage with message as the synthetic tool reply. |
Each entry's type must be present in the matching review_configs[i].allowed_decisions — otherwise the run raises ValueError.
POST /threads/{thread_id}/resolve-interrupt (cancel, not approve)There is also a POST /v1/deepagents/threads/{thread_id}/resolve-interrupt endpoint, which takes no body and returns 204. It is not an approve shortcut — it calls update_state(as_node="__end__") on the upstream LangGraph runtime, which terminates the run at the interrupt without running the pending tool. Use it only when you want to abandon the paused work and free the thread; for any "approve / edit / reject / respond" decision, send a command.resume to /runs/stream instead.
The same flow works with fetch (Node 18+ or browser) or curl. Replace httpx.post(url, headers=HEADERS, json=...) with:
const BASE_URL = process.env.DEEPAGENTS_BASE_URL;
const HEADERS = {
"X-Api-Key": process.env.LANGSMITH_API_KEY,
"Content-Type": "application/json",
};
const response = await fetch(`${BASE_URL}/agents`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({ /* same payload */ }),
});
if (!response.ok) throw new Error(await response.text());
const { id: agentId } = await response.json();
For streaming, read from response.body.pipeThrough(new TextDecoderStream()).getReader().
For curl, set --header "X-Api-Key: $LANGSMITH_API_KEY" and --header 'Content-Type: application/json'.
Use a standard LangSmith Deployment via [[langgraph-cli]] (langgraph deploy) instead when you need:
useStream SDK wrappers are in progress.PATCH is wholesale, not partial — both for agents (tools field is replaced) and for mcp-servers (headers array is replaced). Always send the full new value.interrupt_config key format — {mcp_server_url}::{tool_name} with two colons. Trailing slashes on the URL are stripped before matching.tools[].name doesn't match what the MCP server exposes from tools/list, the runtime silently drops the tool from the model's bound tool set. The model never sees it and never triggers the interrupt. List tools against the live server (e.g. via mcp.client.streamable_http) to confirm.command.resume = {"decisions": [...]} — a bare list of decisions crashes the run with TypeError: list indices must be integers or slices, not str because the middleware does interrupt(request)["decisions"] internally./threads/{tid}/runs/stream requires a non-empty messages array even when resuming — pass [{"role": "system", "content": ""}] alongside command.resume./resolve-interrupt advances to __end__ — the endpoint terminates the paused run without running the pending tool. It is a cancel/finalize escape hatch, not an approve.anthropic:claude-sonnet-4-6, not claude-sonnet-4-6 (the latter is treated as a Playground config reference).headers is omitted from response bodies for callers without invoke permission; treat list/get responses as sensitive and avoid logging them verbatim.502. Clean up threads explicitly./v1/deepagents but the surface may change in backwards-incompatible ways before GA. Breaking changes are communicated directly to preview customers.tools
INVOKE THIS SKILL when using the langgraph CLI to scaffold, develop, build, or deploy LangGraph applications. Covers langgraph new, dev, build, up, deploy, and langgraph.json configuration.
data-ai
Dispatches many independent items in parallel: create a table, fan out to subagents, aggregate results. One row = one unit of work.
testing
INVOKE THIS SKILL when your LangGraph needs to persist state, remember conversations, travel through history, or configure subgraph checkpointer scoping. Covers checkpointers, thread_id, time travel, Store, and subgraph persistence modes.
tools
INVOKE THIS SKILL when implementing human-in-the-loop patterns, pausing for approval, or handling errors in LangGraph. Covers interrupt(), Command(resume=...), approval/validation workflows, and the 4-tier error handling strategy.