@@ -337,6 +337,7 @@ def __init__(
337337 auto_reload : bool = False ,
338338 auto_reload_interval : float = 5.0 ,
339339 config_path : Path | str | None = None ,
340+ yolo_mode : bool = False ,
340341 ) -> None :
341342 """
342343 Initialize the agent loop.
@@ -362,6 +363,7 @@ def __init__(
362363 session_store_dsn: PostgreSQL DSN for 'postgres' backend.
363364 session_store_db_path: SQLite DB path for 'sqlite' backend.
364365 context_window: Override context window in tokens (auto-resolved from model if None).
366+ yolo_mode: Operate directly in user's path without sandbox isolation.
365367 """
366368 # Validate parameters
367369 try :
@@ -374,13 +376,15 @@ def __init__(
374376 session_key = session_key ,
375377 skill_paths = skill_paths ,
376378 mcp_config = mcp_config ,
379+ yolo_mode = yolo_mode ,
377380 )
378381 except Exception as e :
379382 logger .error (f"Configuration validation failed: { e } " )
380383 raise ValueError (f"Invalid AgentLoop configuration: { e } " ) from e
381384
382385 # Store config — callers must provide model/provider explicitly
383386 self .workspace = self ._config .workspace
387+ self .yolo_mode = self ._config .yolo_mode
384388 self .model = model
385389 self .provider = provider
386390 self .api_key = api_key
@@ -405,9 +409,12 @@ def __init__(
405409 self ._mcp_tools : list [MCPTool ] = []
406410
407411 # spoon-bot components
408- self .context = ContextBuilder (self .workspace )
412+ self .context = ContextBuilder (self .workspace , yolo_mode = self . yolo_mode )
409413 self .tools = ToolRegistry ()
410414
415+ if self .yolo_mode :
416+ logger .info (f"YOLO mode enabled — operating directly in: { self .workspace } " )
417+
411418 # Session persistence — configurable backend
412419 _store_backend = session_store_backend or "file"
413420 _store_db_path = session_store_db_path
@@ -712,7 +719,12 @@ def _register_native_tools(self) -> None:
712719 # skill-managed data (e.g. ~/.agent-wallet, ~/.spoon-bot/skills) is
713720 # accessible. The PathValidator blocklist still blocks truly sensitive
714721 # paths (.ssh, .aws, etc.).
715- _extra_read = [Path .home ()]
722+ #
723+ # In YOLO mode the workspace IS the user's directory, so we add its
724+ # parents as extra read paths to let the agent navigate freely.
725+ _extra_read : list [Path ] = [Path .home ()]
726+ if self .yolo_mode :
727+ _extra_read .extend (p for p in self .workspace .parents if p != Path .home ())
716728 self .tools .register (ReadFileTool (workspace = self .workspace , additional_read_paths = _extra_read , max_output = 15000 ))
717729 self .tools .register (WriteFileTool (workspace = self .workspace ))
718730 self .tools .register (EditFileTool (workspace = self .workspace ))
@@ -2079,7 +2091,10 @@ async def stream(
20792091 async def _run_and_signal () -> None :
20802092 nonlocal run_result_text
20812093 try :
2082- result = await self ._agent .run ()
2094+ run_kwargs : dict [str , Any ] = {}
2095+ if thinking and self ._callable_accepts_kwarg (self ._agent .run , "thinking" ):
2096+ run_kwargs ["thinking" ] = True
2097+ result = await self ._agent .run (** run_kwargs )
20832098 if hasattr (result , "content" ):
20842099 run_result_text = result .content or ""
20852100 elif isinstance (result , str ):
@@ -2109,14 +2124,27 @@ async def _run_and_signal() -> None:
21092124 oq = self ._agent .output_queue
21102125 td = self ._agent .task_done
21112126 logger .debug (f"output_queue type={ type (oq ).__name__ } , task_done type={ type (td ).__name__ } " )
2112- stream_timeout = 120 .0
2127+ stream_timeout = 600.0 if thinking else 300 .0
21132128 deadline = asyncio .get_event_loop ().time () + stream_timeout
21142129 chunk_count = 0
21152130
21162131 logger .debug (f"Entering stream loop: td={ td .is_set ()} , qempty={ oq .empty ()} , qsize={ oq .qsize ()} " )
21172132 while not (td .is_set () and oq .empty ()):
21182133 if asyncio .get_event_loop ().time () > deadline :
2119- logger .warning ("Streaming deadline reached, stopping" )
2134+ logger .warning (
2135+ f"Streaming deadline reached ({ stream_timeout } s), "
2136+ f"stopping after { chunk_count } chunks"
2137+ )
2138+ yield {
2139+ "type" : "error" ,
2140+ "delta" : f"Stream timeout after { int (stream_timeout )} s" ,
2141+ "metadata" : {
2142+ "error" : "STREAM_TIMEOUT" ,
2143+ "error_code" : "STREAM_TIMEOUT" ,
2144+ "timeout_seconds" : stream_timeout ,
2145+ "chunks_received" : chunk_count ,
2146+ },
2147+ }
21202148 break
21212149
21222150 try :
@@ -2815,6 +2843,7 @@ async def create_agent(
28152843 auto_reload : bool = False ,
28162844 auto_reload_interval : float = 5.0 ,
28172845 config_path : Path | str | None = None ,
2846+ yolo_mode : bool = False ,
28182847 ** kwargs : Any ,
28192848) -> AgentLoop :
28202849 """
@@ -2839,6 +2868,7 @@ async def create_agent(
28392868 auto_commit: Whether to auto-commit workspace changes after each message.
28402869 enabled_tools: Explicit set of tool names to enable. None = core only.
28412870 tool_profile: Named profile ('core', 'coding', 'web3', 'research', 'full').
2871+ yolo_mode: Operate directly in user's path without sandbox isolation.
28422872 **kwargs: Additional arguments for AgentLoop.
28432873
28442874 Returns:
@@ -2851,9 +2881,8 @@ async def create_agent(
28512881 >>> # Load all tools
28522882 >>> agent = await create_agent(tool_profile="full")
28532883
2854- >>> # Dynamically add a tool after creation
2855- >>> agent = await create_agent()
2856- >>> agent.add_tool("web_search")
2884+ >>> # YOLO mode — work in /home/user/project directly
2885+ >>> agent = await create_agent(yolo_mode=True, workspace="/home/user/project")
28572886 """
28582887 agent = AgentLoop (
28592888 model = model ,
@@ -2871,6 +2900,7 @@ async def create_agent(
28712900 auto_reload = auto_reload ,
28722901 auto_reload_interval = auto_reload_interval ,
28732902 config_path = config_path ,
2903+ yolo_mode = yolo_mode ,
28742904 ** kwargs ,
28752905 )
28762906
0 commit comments