@@ -669,97 +669,6 @@ def factory(_runtime: Any) -> AgentSandboxBackend:
669669 return factory
670670
671671
672- def _get_thread_manager_class () -> type :
673- """Lazy import of ThreadedSandboxManager to avoid circular imports."""
674- from .thread_manager import ThreadedSandboxManager
675- return ThreadedSandboxManager
676-
677-
678- def create_threaded_backend_factory (
679- template_name : str ,
680- manager : Optional [Any ] = None ,
681- namespace : str = "default" ,
682- ** kwargs : Any ,
683- ) -> Callable [[Any ], AgentSandboxBackend ]:
684- """Create a BackendFactory that provides per-thread sandbox isolation.
685-
686- This factory reads the thread_id from the LangGraph config and uses a
687- ThreadedSandboxManager to provide isolated sandboxes per conversation thread.
688- Each thread gets its own sandbox with persistent filesystem.
689-
690- Usage:
691- from deepagents import create_deep_agent
692- from langgraph.checkpoint.memory import MemorySaver
693- from langchain_agent_sandbox import (
694- ThreadedSandboxManager,
695- create_threaded_backend_factory,
696- )
697-
698- # Create manager for lifecycle control
699- manager = ThreadedSandboxManager(
700- template_name="python-deepagent",
701- idle_ttl=timedelta(hours=1),
702- )
703-
704- # Create agent with threaded backend
705- agent = create_deep_agent(
706- model=model,
707- backend=create_threaded_backend_factory("python-deepagent", manager=manager),
708- checkpointer=MemorySaver(), # For message history
709- )
710-
711- # Same thread = same sandbox (filesystem persists)
712- agent.invoke(msg1, config={"configurable": {"thread_id": "user-123"}})
713- agent.invoke(msg2, config={"configurable": {"thread_id": "user-123"}})
714-
715- # Different thread = different sandbox
716- agent.invoke(msg3, config={"configurable": {"thread_id": "user-456"}})
717-
718- # Cleanup when done
719- manager.close()
720-
721- Args:
722- template_name: Name of the SandboxTemplate to claim.
723- manager: Optional ThreadedSandboxManager instance. If not provided,
724- one will be created (but you won't have lifecycle control).
725- namespace: Kubernetes namespace for the sandbox.
726- **kwargs: Additional arguments passed to ThreadedSandboxManager.
727-
728- Returns:
729- A factory callable that accepts a ToolRuntime and returns an
730- AgentSandboxBackend for the current thread.
731-
732- Note:
733- The thread_id is read from `runtime.config.get("configurable", {}).get("thread_id")`.
734- If no thread_id is found, a default "default-thread" is used.
735-
736- Warning:
737- If no manager is provided, an internal manager is created but you will
738- have no way to call close() or delete_thread(). For production use,
739- always pass an explicit manager instance for lifecycle control.
740- """
741- # Create manager if not provided
742- _manager = manager
743- if _manager is None :
744- ThreadedSandboxManager = _get_thread_manager_class ()
745- _manager = ThreadedSandboxManager (
746- template_name = template_name ,
747- namespace = namespace ,
748- ** kwargs ,
749- )
750-
751- def factory (runtime : Any ) -> AgentSandboxBackend :
752- # Extract thread_id from LangGraph config
753- config = getattr (runtime , "config" , {}) or {}
754- configurable = config .get ("configurable" , {}) or {}
755- thread_id = configurable .get ("thread_id" , "default-thread" )
756-
757- logger .debug ("Creating backend for thread_id: %s" , thread_id )
758- return _manager .get_backend (thread_id )
759-
760- return factory
761-
762-
763672class SandboxPolicyWrapper :
764673 """Wraps AgentSandboxBackend with policy enforcement.
765674
0 commit comments