managed-deepagents 0.1.3.dev15__py3-none-win_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,158 @@
1
+ """Managed Deep Agents for the Python ecosystem.
2
+
3
+ This package provides two things:
4
+
5
+ * the ``define_deep_agent`` authoring interface (the v0 contract for MDA), and
6
+ * the prebuilt, cross-compiled ``mda`` CLI. Each published wheel bundles the
7
+ binary for a single platform under ``_binaries/`` and exposes it through the
8
+ ``mda`` console script.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import os
14
+ import sys
15
+ from pathlib import Path
16
+ from typing import Any
17
+
18
+ __all__ = [
19
+ "define_deep_agent",
20
+ "define_sandbox",
21
+ "DeepAgentDefinition",
22
+ "SandboxDefinition",
23
+ "MANAGED_KEYS",
24
+ "main",
25
+ "binary_path",
26
+ ]
27
+
28
+ #: Properties the managed runtime owns. Agent authors never set these — MDA
29
+ #: wires the backend, store, and checkpointer at deploy time.
30
+ MANAGED_KEYS = ("backend", "store", "checkpointer")
31
+
32
+
33
+ class DeepAgentDefinition:
34
+ """The pre-runtime spec returned by :func:`define_deep_agent`.
35
+
36
+ It is intentionally *not* a compiled graph: the CLI generates an entry
37
+ module that hands this definition to the managed runtime, which injects the
38
+ managed backend/store/checkpointer and compiles it into a Deep Agent.
39
+ """
40
+
41
+ kind = "deep-agent"
42
+
43
+ def __init__(self, config: dict[str, Any]) -> None:
44
+ self.config = config
45
+
46
+ def __repr__(self) -> str: # pragma: no cover - debug helper
47
+ keys = ", ".join(sorted(self.config))
48
+ return f"DeepAgentDefinition({keys})"
49
+
50
+
51
+ def define_deep_agent(**config: Any) -> DeepAgentDefinition:
52
+ """Define a Managed Deep Agent.
53
+
54
+ Accepts the full ``create_deep_agent`` keyword surface except the managed
55
+ keys (``backend``, ``store``, ``checkpointer``, ``system_prompt``) and
56
+ returns a pre-runtime spec. ``mda deploy`` generates an entry module that
57
+ hands this definition to the managed runtime, which injects the
58
+ platform-owned backend/store/checkpointer, embeds the system prompt from
59
+ ``instructions.md``, and compiles it into a Deep Agent.
60
+ """
61
+ if "system_prompt" in config:
62
+ raise TypeError(
63
+ "system_prompt is managed by MDA and cannot be set in "
64
+ "define_deep_agent(...). Write your agent's system prompt in "
65
+ "instructions.md next to agent.py; the CLI embeds it at deploy time."
66
+ )
67
+ managed = [key for key in MANAGED_KEYS if key in config]
68
+ if managed:
69
+ raise TypeError(
70
+ f"{', '.join(managed)} {'is' if len(managed) == 1 else 'are'} owned by "
71
+ "the managed runtime and cannot be set in define_deep_agent(...). "
72
+ "Configure the sandbox under sandbox/; the store and checkpointer are "
73
+ "wired automatically."
74
+ )
75
+ return DeepAgentDefinition(config)
76
+
77
+
78
+ class SandboxDefinition:
79
+ """The pre-runtime sandbox spec returned by :func:`define_sandbox`.
80
+
81
+ A Managed Deep Agent declares its execution environment in
82
+ ``sandbox/__init__.py`` by importing a real provider class (such as
83
+ ``deepagents``' ``LangSmithSandbox``) and passing it to
84
+ :func:`define_sandbox`. MDA owns how it is *resolved*: construction,
85
+ run-scoped naming, reuse across turns, the provisioning ``setup.sh``, and
86
+ lifecycle/TTL.
87
+ """
88
+
89
+ kind = "sandbox"
90
+
91
+ #: Option keys MDA owns; ``define_sandbox`` callers must not set these.
92
+ MANAGED_OPTION_KEYS = ("name", "image", "snapshot", "image_name")
93
+
94
+ def __init__(self, provider: Any, options: dict[str, Any]) -> None:
95
+ self.provider = provider
96
+ self.options = options
97
+
98
+ def __repr__(self) -> str: # pragma: no cover - debug helper
99
+ name = getattr(self.provider, "__name__", repr(self.provider))
100
+ return f"SandboxDefinition(provider={name}, options={sorted(self.options)})"
101
+
102
+
103
+ def define_sandbox(provider: Any, **options: Any) -> SandboxDefinition:
104
+ """Declare the managed sandbox provider for a Managed Deep Agent.
105
+
106
+ ``provider`` is a sandbox provider class (e.g.
107
+ ``from deepagents import LangSmithSandbox``). The keyword options are the
108
+ provider's own ``create(...)`` options, plus the managed reuse/lifecycle
109
+ knobs ``scope`` (``"thread"`` | ``"tenant"`` | ``"actor"``, default
110
+ ``"thread"``) and ``idle_ttl_seconds``.
111
+
112
+ MDA owns naming, scoping, lifecycle, image/snapshot construction, reuse, the
113
+ provisioning ``setup.sh``, and cleanup, so image-related keys cannot be set
114
+ here.
115
+ """
116
+ managed = [key for key in SandboxDefinition.MANAGED_OPTION_KEYS if key in options]
117
+ if managed:
118
+ raise TypeError(
119
+ f"{', '.join(managed)} {'is' if len(managed) == 1 else 'are'} owned by "
120
+ "the managed runtime and cannot be set in define_sandbox(...). MDA "
121
+ "supplies the sandbox image/snapshot and run-scoped name automatically."
122
+ )
123
+ return SandboxDefinition(provider, options)
124
+
125
+
126
+ def binary_path() -> Path:
127
+ """Return the path to the bundled ``mda`` binary for this platform."""
128
+ name = "mda.exe" if os.name == "nt" else "mda"
129
+ return Path(__file__).parent / "_binaries" / name
130
+
131
+
132
+ def main() -> int:
133
+ """Entry point for the ``mda`` console script.
134
+
135
+ Forwards all arguments to the bundled binary and returns its exit code.
136
+ """
137
+ binary = binary_path()
138
+ if not binary.exists():
139
+ sys.stderr.write(
140
+ "managed-deepagents: the prebuilt mda binary was not found in this "
141
+ "installation. Reinstall the platform-specific wheel.\n"
142
+ )
143
+ return 1
144
+
145
+ argv = [str(binary), *sys.argv[1:]]
146
+
147
+ # On POSIX, replace the current process so signals propagate cleanly.
148
+ if os.name == "posix":
149
+ os.execv(str(binary), argv)
150
+ return 0 # unreachable
151
+
152
+ import subprocess
153
+
154
+ return subprocess.run(argv).returncode
155
+
156
+
157
+ if __name__ == "__main__":
158
+ raise SystemExit(main())
@@ -0,0 +1,3 @@
1
+ from . import main
2
+
3
+ raise SystemExit(main())
Binary file
Binary file
@@ -0,0 +1,442 @@
1
+ """Managed runtime seam for compiled Managed Deep Agents.
2
+
3
+ This module is consumed only by the entry module that ``mda deploy`` generates —
4
+ agent authors never import it directly. It is the point where MDA injects the
5
+ platform-owned backend (the configured sandbox), plus the system prompt embedded
6
+ from ``instructions.md``, and — in a later milestone — the managed store and
7
+ checkpointer.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import inspect
13
+ import os
14
+ from dataclasses import dataclass
15
+ from typing import Any
16
+
17
+ from . import DeepAgentDefinition, SandboxDefinition
18
+
19
+ __all__ = ["compile_managed_agent", "announce_sandbox_plan"]
20
+
21
+ #: Absolute path the provisioning script is uploaded to inside the sandbox.
22
+ _SETUP_REMOTE_PATH = "/tmp/mda-setup.sh"
23
+
24
+ #: Created sandboxes, keyed by scope, reused for the lifetime of this runtime
25
+ #: process. This guarantees ``setup.sh`` runs only once per newly created
26
+ #: sandbox; reused sandboxes (same scope key) skip provisioning entirely.
27
+ _managed_sandboxes: dict[str, Any] = {}
28
+
29
+
30
+ def compile_managed_agent(
31
+ definition: DeepAgentDefinition,
32
+ config: Any | None = None,
33
+ *,
34
+ system_prompt: str | None = None,
35
+ sandbox: SandboxDefinition | None = None,
36
+ setup_script: str | None = None,
37
+ ) -> Any:
38
+ """Compile a :class:`DeepAgentDefinition` into a runnable Deep Agent.
39
+
40
+ ``system_prompt`` is the text the CLI embedded from ``instructions.md``.
41
+ ``sandbox`` is the ``sandbox/__init__.py`` declaration, and ``setup_script``
42
+ is the contents of ``sandbox/setup.sh`` (run once when a sandbox is first
43
+ provisioned). All are merged here so authors never set them directly.
44
+
45
+ The managed store/checkpointer wiring lands in a follow-up milestone; this
46
+ signature is the stable injection point.
47
+ """
48
+ # Imported lazily so the CLI wheel does not require ``deepagents`` at import
49
+ # time; it is present in the deployed runtime.
50
+ from deepagents import create_deep_agent
51
+
52
+ merged = dict(definition.config)
53
+ if system_prompt is not None:
54
+ merged["system_prompt"] = system_prompt
55
+
56
+ # TODO(mda): inject the managed store/checkpointer here, scoped by ``config``
57
+ # identity / thread_id.
58
+ if sandbox is not None:
59
+ merged["backend"] = _resolve_managed_sandbox(sandbox, setup_script, config)
60
+
61
+ return create_deep_agent(**merged)
62
+
63
+
64
+ #: Cache key for the single, process-wide sandbox used under ``mda dev``.
65
+ #:
66
+ #: Deploys key sandboxes per scope (thread/tenant/actor) for isolation. Dev is a
67
+ #: single developer iterating locally, so it shares one sandbox across every
68
+ #: thread: that lets :func:`announce_sandbox_plan` create it eagerly at startup
69
+ #: (to report local-vs-remote and the temp dir up front) and have the first real
70
+ #: run reuse it instead of provisioning a second one.
71
+ _DEV_SANDBOX_SCOPE_KEY = "__mda_dev__"
72
+
73
+
74
+ def announce_sandbox_plan(
75
+ sandbox: SandboxDefinition, setup_script: str | None = None
76
+ ) -> None:
77
+ """Eagerly resolve the ``mda dev`` sandbox at startup so the developer sees
78
+ the definitive decision — local vs. the configured provider, and the temp
79
+ dir path when local — right when the dev server boots, not on the first run.
80
+
81
+ The generated entry calls this at module load (graph registration); because
82
+ dev shares one sandbox (:data:`_DEV_SANDBOX_SCOPE_KEY`), the first real run
83
+ reuses what is created here. The authoritative "created" log (with the temp
84
+ dir) is emitted by :func:`_create_dev_sandbox`.
85
+
86
+ No-op outside ``mda dev``, so the generated entry can always call it.
87
+ """
88
+ if not _is_dev_mode():
89
+ return
90
+ _resolve_managed_sandbox(sandbox, setup_script, None)
91
+
92
+
93
+ def _resolve_managed_sandbox(
94
+ sandbox: SandboxDefinition,
95
+ setup_script: str | None,
96
+ config: Any | None,
97
+ ) -> Any:
98
+ """Resolve (and cache) the scoped sandbox backend, provisioning it with
99
+ ``setup.sh`` the first time it is created."""
100
+ key = _DEV_SANDBOX_SCOPE_KEY if _is_dev_mode() else _sandbox_scope_key(sandbox, config)
101
+ backend = _managed_sandboxes.get(key)
102
+ if backend is None:
103
+ backend = _create_managed_sandbox(sandbox, setup_script)
104
+ _managed_sandboxes[key] = backend
105
+ return backend
106
+
107
+
108
+ def _sandbox_scope_key(sandbox: SandboxDefinition, config: Any | None) -> str:
109
+ """Derive the reuse key from the run: by thread without identity, or by the
110
+ configured tenant/actor scope when identity is present."""
111
+ configurable = {}
112
+ if isinstance(config, dict):
113
+ configurable = config.get("configurable") or {}
114
+ identity = configurable.get("identity") or {}
115
+ tenant_id = (identity.get("tenant") or {}).get("id")
116
+ actor_id = (identity.get("actor") or {}).get("id")
117
+ thread_id = configurable.get("thread_id")
118
+
119
+ scope = sandbox.options.get("scope", "thread")
120
+ if scope == "tenant":
121
+ key = tenant_id
122
+ elif scope == "actor":
123
+ key = actor_id
124
+ else:
125
+ key = thread_id
126
+
127
+ return key or tenant_id or actor_id or thread_id or "default"
128
+
129
+
130
+ def _create_managed_sandbox(sandbox: SandboxDefinition, setup_script: str | None) -> Any:
131
+ # ``mda dev`` never provisions a real provider sandbox the way a deploy does
132
+ # (it builds no snapshot/image), so it gets its own resolution path with a
133
+ # local fallback. The flag gating ensures a real deployment can never be
134
+ # silently downgraded to local shell execution.
135
+ if _is_dev_mode():
136
+ return _create_dev_sandbox(sandbox, setup_script)
137
+ return _create_provider_sandbox(sandbox, setup_script)
138
+
139
+
140
+ def _create_provider_sandbox(
141
+ sandbox: SandboxDefinition, setup_script: str | None
142
+ ) -> Any:
143
+ """Production path: build the configured provider's sandbox and provision it.
144
+
145
+ Any failure propagates — a misconfigured deployment must surface, not
146
+ silently degrade to local shell execution.
147
+ """
148
+ # Strip MDA-managed knobs before handing the rest to the provider factory.
149
+ provider_options = {
150
+ key: value
151
+ for key, value in sandbox.options.items()
152
+ if key not in ("scope", "idle_ttl_seconds")
153
+ }
154
+
155
+ backend = _construct_sandbox_backend(sandbox.provider, provider_options)
156
+
157
+ if setup_script and setup_script.strip():
158
+ _run_setup_script(backend, setup_script)
159
+
160
+ return backend
161
+
162
+
163
+ def _create_dev_sandbox(sandbox: SandboxDefinition, setup_script: str | None) -> Any:
164
+ """Dev path: prefer the configured provider when its credentials are present,
165
+ but fall back to a local temp-directory sandbox when credentials are missing
166
+ *or* the provider cannot be created (e.g. ``LangSmithSandbox``, which needs a
167
+ snapshot that only ``mda deploy`` builds). This keeps ``mda dev`` working
168
+ offline and resilient to provider/config gaps while still exercising real
169
+ providers that can spin up from an API key alone.
170
+ """
171
+ cred = _provider_credential_status(sandbox.provider)
172
+
173
+ if cred.status != "missing":
174
+ try:
175
+ backend = _create_provider_sandbox(sandbox, setup_script)
176
+ if cred.status == "available":
177
+ _log_dev_sandbox_once(
178
+ f"[mda dev] using the configured {cred.provider} sandbox."
179
+ )
180
+ else:
181
+ _log_dev_sandbox_once(
182
+ "[mda dev] using the configured sandbox provider "
183
+ f"({cred.provider})."
184
+ )
185
+ return backend
186
+ except Exception as error: # noqa: BLE001 - dev fallback is intentional
187
+ return _create_local_sandbox(
188
+ setup_script,
189
+ f"the configured {cred.provider} sandbox could not be created "
190
+ f"({error}); using a local temporary directory instead",
191
+ )
192
+
193
+ missing = ", ".join(cred.missing) or "the provider credentials"
194
+ return _create_local_sandbox(
195
+ setup_script,
196
+ "no credentials found for the configured sandbox provider "
197
+ f"({cred.provider}); using a local temporary directory instead — set "
198
+ f"{missing} to use it",
199
+ )
200
+
201
+
202
+ def _create_local_sandbox(setup_script: str | None, reason: str) -> Any:
203
+ """Provision the local dev sandbox and run ``setup.sh`` inline inside it."""
204
+ backend = _create_local_dev_sandbox(reason)
205
+ if setup_script and setup_script.strip():
206
+ # Run the script inline rather than uploading it first: the local backend
207
+ # virtualizes file paths (uploads land under the temp root) but executes
208
+ # shell commands against the real filesystem, so an uploaded
209
+ # ``/tmp/mda-setup.sh`` would not be found by ``bash``. Executed inline,
210
+ # it runs with the temp root as its working directory.
211
+ _assert_setup_succeeded(_await_if_needed(backend.execute(setup_script)))
212
+ return backend
213
+
214
+
215
+ #: Guards the one-time dev sandbox notice so it logs at most once per process.
216
+ #:
217
+ #: ``_create_managed_sandbox`` runs once per sandbox *scope key*
218
+ #: (thread/tenant/actor), not once per process. The ``mda dev`` LangGraph server
219
+ #: is long-lived and serves many threads, so each new thread misses the
220
+ #: ``_managed_sandboxes`` cache and provisions a fresh sandbox — without this
221
+ #: guard the banner would reprint on the first turn of every new thread. The
222
+ #: message is session-level context, so once is enough.
223
+ _logged_dev_sandbox_notice = False
224
+
225
+
226
+ def _is_dev_mode() -> bool:
227
+ """Whether MDA is running under ``mda dev``.
228
+
229
+ The CLI sets ``MDA_DEV`` when it launches the local LangGraph dev server; it
230
+ is never set for ``mda deploy``.
231
+ """
232
+ return bool(os.environ.get("MDA_DEV"))
233
+
234
+
235
+ #: Per-provider environment variables MDA expects to hold the credentials each
236
+ #: sandbox provider needs. Keyed by the provider's ``provider_id`` (when set) or
237
+ #: its class name.
238
+ #:
239
+ #: The value is a list of alternative credential *sets*: a provider is
240
+ #: considered credentialed if **every** variable in **any one** set is present.
241
+ #: An empty list means the provider needs no credentials (e.g. it runs locally).
242
+ #:
243
+ #: Sources: the deepagents sandbox docs and each provider's own SDK docs.
244
+ #: - LangSmith: https://docs.langchain.com/langsmith/sandboxes
245
+ #: - Daytona / E2B / Modal / Runloop / Vercel / AgentCore: provider SDK docs.
246
+ _PROVIDER_CREDENTIAL_ENV_VARS: dict[str, list[list[str]]] = {
247
+ # LangSmith managed sandboxes.
248
+ "LangSmithSandbox": [["LANGSMITH_API_KEY"]],
249
+ # Daytona (``langchain-daytona`` / deepagents ``DaytonaSandbox``).
250
+ "DaytonaSandbox": [["DAYTONA_API_KEY"]],
251
+ # E2B (``langchain-e2b`` / deepagents ``E2BSandbox``).
252
+ "E2BSandbox": [["E2B_API_KEY"]],
253
+ # Modal requires both a token id and secret.
254
+ "ModalSandbox": [["MODAL_TOKEN_ID", "MODAL_TOKEN_SECRET"]],
255
+ # Runloop devboxes.
256
+ "RunloopSandbox": [["RUNLOOP_API_KEY"]],
257
+ # Vercel: either an OIDC token, or an access token + team + project ids.
258
+ "VercelSandbox": [
259
+ ["VERCEL_OIDC_TOKEN"],
260
+ ["VERCEL_TOKEN", "VERCEL_TEAM_ID", "VERCEL_PROJECT_ID"],
261
+ ],
262
+ # AWS Bedrock AgentCore: standard AWS credential resolution.
263
+ "AgentCoreSandbox": [
264
+ ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"],
265
+ ["AWS_PROFILE"],
266
+ ],
267
+ }
268
+
269
+
270
+ @dataclass(frozen=True)
271
+ class _ProviderCredentialStatus:
272
+ #: One of ``"available"`` (credentials present or none needed),
273
+ #: ``"missing"`` (a known provider's credentials are absent), or
274
+ #: ``"unknown"`` (provider not in ``_PROVIDER_CREDENTIAL_ENV_VARS``).
275
+ status: str
276
+ #: The provider id / class name used for the lookup.
277
+ provider: str
278
+ #: Env vars from the primary credential set, surfaced when ``missing``.
279
+ missing: tuple[str, ...] = ()
280
+
281
+
282
+ def _provider_key(provider: Any) -> str:
283
+ """Resolve the lookup key for a sandbox provider (its id, else class name)."""
284
+ provider_id = getattr(provider, "provider_id", None) or getattr(
285
+ provider, "providerId", None
286
+ )
287
+ if isinstance(provider_id, str) and provider_id.strip():
288
+ return provider_id.strip()
289
+ name = getattr(provider, "__name__", None) or type(provider).__name__
290
+ return str(name).strip()
291
+
292
+
293
+ def _credential_set_satisfied(env_vars: list[str]) -> bool:
294
+ """Whether every variable in a credential set is set and non-empty."""
295
+ return all((os.environ.get(name) or "").strip() for name in env_vars)
296
+
297
+
298
+ def _provider_credential_status(provider: Any) -> _ProviderCredentialStatus:
299
+ """Determine whether the configured provider's credentials are available.
300
+
301
+ Lets ``mda dev`` choose between the real provider and the local fallback.
302
+ """
303
+ key = _provider_key(provider)
304
+ sets = _PROVIDER_CREDENTIAL_ENV_VARS.get(key)
305
+ if sets is None:
306
+ return _ProviderCredentialStatus("unknown", key or "unknown")
307
+ if not sets or any(_credential_set_satisfied(s) for s in sets):
308
+ return _ProviderCredentialStatus("available", key)
309
+ return _ProviderCredentialStatus("missing", key, tuple(sets[0]))
310
+
311
+
312
+ def _create_local_dev_sandbox(reason: str) -> Any:
313
+ """Provision a local, throwaway sandbox rooted at a fresh OS temp directory.
314
+
315
+ Uses ``deepagents``' ``LocalShellBackend``, which runs commands and file
316
+ operations against the host within the temp dir — no remote sandbox, no
317
+ credentials, no network. Intended only for ``mda dev``.
318
+
319
+ ``reason`` explains *why* the local sandbox is being used (missing
320
+ credentials, provider creation failure, …) and is logged once alongside the
321
+ temp dir path.
322
+ """
323
+ import tempfile
324
+
325
+ from deepagents import LocalShellBackend
326
+
327
+ root_dir = tempfile.mkdtemp(prefix="mda-dev-sandbox-")
328
+ _log_dev_sandbox_once(f"[mda dev] {reason}:\n {root_dir}")
329
+
330
+ return LocalShellBackend(root_dir, virtual_mode=True, inherit_env=True)
331
+
332
+
333
+ def _log_dev_sandbox_once(message: str) -> None:
334
+ """Emit a dev sandbox notice at most once per process.
335
+
336
+ See ``_logged_dev_sandbox_notice`` for why the dedupe matters: the notice is
337
+ reached once per sandbox scope key, and the dev server serves many threads.
338
+ """
339
+ global _logged_dev_sandbox_notice
340
+ if _logged_dev_sandbox_notice:
341
+ return
342
+ _logged_dev_sandbox_notice = True
343
+ print(message)
344
+
345
+
346
+ def _construct_sandbox_backend(provider: Any, options: dict[str, Any]) -> Any:
347
+ """Construct the provider's sandbox backend.
348
+
349
+ Providers that expose a ``create(...)`` factory (the contract the TypeScript
350
+ SDK relies on, and the forward-compatible path for ``deepagents``) are used
351
+ directly. Otherwise, MDA falls back to building a LangSmith sandbox and
352
+ wrapping it with the provider class — the shape of today's
353
+ ``deepagents.LangSmithSandbox(sandbox=...)``.
354
+ """
355
+ create = getattr(provider, "create", None)
356
+ if callable(create):
357
+ result = create(**options)
358
+ return _await_if_needed(result)
359
+
360
+ return _wrap_langsmith_sandbox(provider, options)
361
+
362
+
363
+ def _wrap_langsmith_sandbox(provider: Any, options: dict[str, Any]) -> Any:
364
+ """Build a LangSmith sandbox and wrap it with ``provider``.
365
+
366
+ Used when the provider has no ``create(...)`` factory, which is the case for
367
+ the current ``deepagents.LangSmithSandbox`` whose constructor wraps an
368
+ existing ``langsmith.sandbox.Sandbox``.
369
+ """
370
+ try:
371
+ from langsmith.sandbox import SandboxClient
372
+ except ImportError as error: # pragma: no cover - depends on runtime deps
373
+ raise RuntimeError(
374
+ "The configured sandbox provider does not expose a create(...) "
375
+ "factory and LangSmith is not installed, so MDA cannot construct it. "
376
+ "Install `langsmith` with sandbox support or use a provider that "
377
+ "exposes `create(...)`."
378
+ ) from error
379
+
380
+ # Pass through only the options the LangSmith sandbox lifecycle understands;
381
+ # `default_timeout` is applied to the backend wrapper below, not the sandbox.
382
+ create_kwargs: dict[str, Any] = {}
383
+ if "idle_ttl_seconds" in options:
384
+ create_kwargs["idle_ttl_seconds"] = options["idle_ttl_seconds"]
385
+ if "snapshot_id" in options:
386
+ create_kwargs["snapshot_id"] = options["snapshot_id"]
387
+
388
+ client = SandboxClient()
389
+ sandbox = client.create_sandbox(**create_kwargs)
390
+ backend = provider(sandbox)
391
+
392
+ default_timeout = options.get("default_timeout")
393
+ if default_timeout is not None and hasattr(backend, "_default_timeout"):
394
+ backend._default_timeout = default_timeout # noqa: SLF001
395
+
396
+ return backend
397
+
398
+
399
+ def _run_setup_script(backend: Any, script: str) -> None:
400
+ """Provision a freshly created sandbox by running ``setup.sh`` inside it."""
401
+ upload_files = getattr(backend, "upload_files", None)
402
+ if callable(upload_files):
403
+ _await_if_needed(upload_files([(_SETUP_REMOTE_PATH, script.encode("utf-8"))]))
404
+ result = _await_if_needed(backend.execute(f"bash {_SETUP_REMOTE_PATH}"))
405
+ else:
406
+ result = _await_if_needed(backend.execute(script))
407
+
408
+ _assert_setup_succeeded(result)
409
+
410
+
411
+ def _assert_setup_succeeded(result: Any) -> None:
412
+ exit_code = getattr(result, "exit_code", None)
413
+ if isinstance(exit_code, int) and exit_code != 0:
414
+ detail = (getattr(result, "output", "") or "").strip()
415
+ message = f"sandbox setup.sh failed with exit code {exit_code}"
416
+ if detail:
417
+ message = f"{message}: {detail}"
418
+ raise RuntimeError(message)
419
+
420
+
421
+ def _await_if_needed(value: Any) -> Any:
422
+ """Resolve a value that may be awaitable.
423
+
424
+ The compiled Python entry runs synchronously, so if a provider returns a
425
+ coroutine we drive it to completion on a private event loop.
426
+ """
427
+ if not inspect.isawaitable(value):
428
+ return value
429
+
430
+ import asyncio
431
+
432
+ try:
433
+ asyncio.get_running_loop()
434
+ except RuntimeError:
435
+ return asyncio.run(value)
436
+
437
+ # Already inside a running loop: run the coroutine on a dedicated loop in a
438
+ # worker thread so we don't deadlock the caller's loop.
439
+ import concurrent.futures
440
+
441
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
442
+ return pool.submit(asyncio.run, value).result()
@@ -0,0 +1,114 @@
1
+ Metadata-Version: 2.4
2
+ Name: managed-deepagents
3
+ Version: 0.1.3.dev15
4
+ Summary: Managed Deep Agents — the define_deep_agent authoring interface plus the CLI that compiles and deploys a code-first Deep Agent repository to a managed LangGraph runtime.
5
+ Author: LangChain
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/langchain-ai/managed-deepagents-sdk
8
+ Project-URL: Repository, https://github.com/langchain-ai/managed-deepagents-sdk
9
+ Keywords: langchain,langgraph,deepagents,agents,cli
10
+ Classifier: Programming Language :: Rust
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+ Provides-Extra: test
16
+ Requires-Dist: pytest>=8; extra == "test"
17
+
18
+ <!-- markdownlint-disable MD033 MD041 -->
19
+
20
+ <div align="center">
21
+ <a href="https://www.langchain.com/langsmith-managed-deep-agents-waitlist">
22
+ <img alt="Managed Deep Agents logo" src="https://raw.githubusercontent.com/langchain-ai/managed-deepagents-sdk/main/.github/assets/logo.png" width="70%">
23
+ </a>
24
+ </div>
25
+
26
+ <div align="center">
27
+ <h3>Python authoring package and CLI launcher for Managed Deep Agents.</h3>
28
+ </div>
29
+
30
+ > [!IMPORTANT]
31
+ > **Active development / private beta.** Managed Deep Agents is in active
32
+ > development and currently in private beta. The PyPI package API and managed
33
+ > runtime contract may change. [Join the
34
+ > waitlist](https://www.langchain.com/langsmith-managed-deep-agents-waitlist)
35
+ > for access and updates.
36
+
37
+ `managed-deepagents` is the PyPI package for authoring Managed Deep Agents in
38
+ Python. It includes:
39
+
40
+ - `define_deep_agent`, the Python authoring contract for managed agents.
41
+ - `mda`, the CLI used to build and deploy your agent to LangSmith.
42
+ - `managed_deepagents.runtime`, the runtime helper used by generated managed
43
+ entry modules.
44
+
45
+ ## Install
46
+
47
+ ```bash
48
+ pip install managed-deepagents
49
+ ```
50
+
51
+ > [!NOTE]
52
+ > **Private beta: dev releases only.** We currently publish only PEP 440
53
+ > pre-release (dev) versions and no stable version yet. pip skips pre-releases
54
+ > by default, so install with `--pre`:
55
+ >
56
+ > ```bash
57
+ > pip install --pre managed-deepagents
58
+ > ```
59
+
60
+ This package requires Python 3.9 or newer. Each platform wheel bundles the
61
+ prebuilt `mda` binary for its OS and CPU architecture and exposes it through the
62
+ `mda` console script.
63
+
64
+ ## Define an Agent
65
+
66
+ Create an `agent.py` that defines an `agent`:
67
+
68
+ ```python
69
+ from pathlib import Path
70
+
71
+ from managed_deepagents import define_deep_agent
72
+
73
+ instructions = Path("instructions.md").read_text()
74
+
75
+ agent = define_deep_agent(
76
+ model="openai:gpt-5.5",
77
+ system_prompt=instructions,
78
+ tools=[query_db],
79
+ )
80
+ ```
81
+
82
+ `define_deep_agent` accepts the `create_deep_agent` keyword surface minus the
83
+ managed keys: `backend`, `store`, and `checkpointer`. Those are provided by the
84
+ managed runtime when your agent is deployed.
85
+
86
+ ## Project Shape
87
+
88
+ ```text
89
+ my-agent/
90
+ agent.py
91
+ instructions.md
92
+ tools/
93
+ middleware/
94
+ skills/
95
+ ```
96
+
97
+ The CLI copies your project files into the managed build and generates the entry
98
+ module that connects your definition to the hosted runtime.
99
+
100
+ ## CLI
101
+
102
+ Build locally:
103
+
104
+ ```bash
105
+ mda build ./my-agent
106
+ ```
107
+
108
+ Deploy to LangSmith:
109
+
110
+ ```bash
111
+ mda deploy ./my-agent
112
+ ```
113
+
114
+ The generated build is written to `<root>/.mda/build` by default.
@@ -0,0 +1,10 @@
1
+ managed_deepagents/__init__.py,sha256=m6twfLrHeO14y9-x5W4HQ5C92duYG3JJpHRliyKwmks,5737
2
+ managed_deepagents/__main__.py,sha256=BHLi_-4Bx1gsZn-z6x7wECPn1YTxdc74y0deRea9FzY,45
3
+ managed_deepagents/runtime.py,sha256=GDTC0-QAa5q6YrJBlj4V3v_naerRLgNiznXWxTCRLWk,17900
4
+ managed_deepagents/_binaries/mda,sha256=JvwHPx6G2MHgD6CjJMOfcKJwIzBt9gU0d6CLYLxuo1E,3200208
5
+ managed_deepagents/_binaries/mda.exe,sha256=imV_avbhmIbcQqxptTffjlW9c9NngNQ2OAhxEHH7REc,2435072
6
+ managed_deepagents-0.1.3.dev15.dist-info/METADATA,sha256=s-8Szgz151_o7XYgOF56JV2N8ep3of4znBlzI5q8PjI,3272
7
+ managed_deepagents-0.1.3.dev15.dist-info/WHEEL,sha256=84gBEa4fGSDR6CJj9KMeLIZ46AIp14gh61YGBn-qQNM,97
8
+ managed_deepagents-0.1.3.dev15.dist-info/entry_points.txt,sha256=Z7JjAzLk9VYdYVvN9xdG91cZ1-pCHj3GFfSRiT0SObw,48
9
+ managed_deepagents-0.1.3.dev15.dist-info/top_level.txt,sha256=fdBo4O0Hlol_w022tTLB5ftZpamb4yJoQ3MUvjgTGfA,19
10
+ managed_deepagents-0.1.3.dev15.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-win_arm64
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ mda = managed_deepagents:main
@@ -0,0 +1 @@
1
+ managed_deepagents