@@ -323,10 +323,13 @@ def _peer_work_requires_isolated_workspace(
323323 agent_todo_summary : dict [str , Any ] | None ,
324324 * ,
325325 selected_todo : dict [str , Any ] | None = None ,
326+ local_goal_workspace : bool = False ,
326327) -> bool :
327328 explicit = workspace_guard_policy .get ("peer_independent_worktree_required" )
328329 if explicit is not None :
329330 return explicit is True
331+ if local_goal_workspace :
332+ return False
330333 candidate = (
331334 selected_todo
332335 if isinstance (selected_todo , dict ) and selected_todo
@@ -344,13 +347,47 @@ def _peer_work_requires_isolated_workspace(
344347 )
345348
346349
350+ def observe_goal_local_workspace (
351+ goal : dict [str , Any ],
352+ selected_todo : dict [str , Any ] | None ,
353+ allowed_write_scopes : list [str ] | None = None ,
354+ ) -> dict [str , Any ]:
355+ """Reuse the registered workspace identity and project its existing grants."""
356+ empty : dict [str , Any ] = {}
357+ if not selected_todo or selected_todo .get ("task_repository" ):
358+ return empty
359+ repo = goal .get ("repo" ) or goal .get ("project" ) or goal .get ("root" )
360+ goal_id = goal .get ("goal_id" ) or goal .get ("id" )
361+ if not repo or not goal_id :
362+ return empty
363+ root = Path (str (repo )).expanduser ()
364+ if not root .is_absolute ():
365+ return empty
366+ snapshot = capture_delivery_workspace (
367+ root , local_goal_id = str (goal_id ), local_project_root = root
368+ )
369+ if not snapshot or snapshot .get ("identity_kind" ) != "local_goal" :
370+ return empty
371+ boundary = goal .get ("coordination" ) or {}
372+ raw_scopes = boundary .get ("write_scope" ) if isinstance (boundary , dict ) else None
373+ scopes = allowed_write_scopes if allowed_write_scopes is not None else raw_scopes
374+ if not isinstance (scopes , list ) or any (not isinstance (scope , str ) for scope in scopes ):
375+ return empty
376+ from ..quota .settlement_workspace_causality import project_goal_write_scopes
377+
378+ # Physical identity is resolved above. Scope authority stays relative to the
379+ # registered spelling, including an explicit symlink alias of that root.
380+ return {"workspace" : snapshot , ** project_goal_write_scopes (str (root ), scopes )}
381+
382+
347383def build_agent_workspace_guard (
348384 goal : dict [str , Any ],
349385 agent_identity : dict [str , Any ] | None ,
350386 * ,
351387 agent_todo_summary : dict [str , Any ] | None = None ,
352388 selected_todo : dict [str , Any ] | None = None ,
353389 current_path : Path | None = None ,
390+ local_workspace : dict [str , Any ] | None = None ,
354391) -> dict [str , Any ] | None :
355392 if not isinstance (agent_identity , dict ):
356393 return None
@@ -361,18 +398,27 @@ def build_agent_workspace_guard(
361398 )
362399 if len (agent_identity .get ("registered_agents" ) or []) <= 1 :
363400 return None
364- if not _peer_work_requires_isolated_workspace (
365- workspace_guard_policy ,
366- agent_todo_summary ,
367- selected_todo = selected_todo ,
368- ):
369- return None
370401 current_path = current_path or Path .cwd ()
371402 candidate = (
372403 selected_todo
373404 if isinstance (selected_todo , dict ) and selected_todo
374405 else next (iter (_peer_candidate_items (agent_todo_summary )), {})
375406 )
407+ local = (
408+ local_workspace
409+ if local_workspace is not None
410+ else observe_goal_local_workspace (goal , candidate )
411+ )
412+ # Local declarations are relative to the registered Goal target, not the
413+ # caller cwd. Causal accounting still names the actual delivery workspace.
414+ local_admitted = (local .get ("workspace" ) or {}).get ("identity_kind" ) == "local_goal"
415+ if not _peer_work_requires_isolated_workspace (
416+ workspace_guard_policy ,
417+ agent_todo_summary ,
418+ selected_todo = selected_todo ,
419+ local_goal_workspace = local_admitted ,
420+ ):
421+ return None
376422 task_repository = normalize_todo_task_repository (candidate .get ("task_repository" ))
377423 current_workspace = ""
378424 repository_source = "goal.repo"
0 commit comments