Create an isolated ToolContext for subagents. Mirrors createSubagentContext() from typescript/src/utils/forkedAgent.ts. By default, ALL mutable state is isolated to prevent interference: - read_file_fingerprints: cloned from parent - abort_controller: new controller linked to paren
(
parent_context: ToolContext,
overrides: SubagentContextOverrides | None = None,
)
| 52 | |
| 53 | |
| 54 | def create_subagent_context( |
| 55 | parent_context: ToolContext, |
| 56 | overrides: SubagentContextOverrides | None = None, |
| 57 | ) -> ToolContext: |
| 58 | """Create an isolated ToolContext for subagents. |
| 59 | |
| 60 | Mirrors createSubagentContext() from typescript/src/utils/forkedAgent.ts. |
| 61 | |
| 62 | By default, ALL mutable state is isolated to prevent interference: |
| 63 | - read_file_fingerprints: cloned from parent |
| 64 | - abort_controller: new controller linked to parent (parent abort propagates) |
| 65 | - permission_context: wrapped to set should_avoid_permission_prompts |
| 66 | - Mutation callbacks: no-op |
| 67 | - Fresh collections: todos, tasks, outbox |
| 68 | |
| 69 | Callers can: |
| 70 | - Override specific fields via the overrides parameter |
| 71 | - Explicitly opt-in to sharing specific callbacks |
| 72 | """ |
| 73 | if overrides is None: |
| 74 | overrides = SubagentContextOverrides() |
| 75 | |
| 76 | # --- Abort controller --- |
| 77 | # Priority: explicit override > share parent's > new child linked to parent. |
| 78 | # ``parent_context.abort_controller`` is now non-optional on the |
| 79 | # ``ToolContext`` dataclass, so the legacy "parent has no controller" |
| 80 | # branch is gone — every parent context carries a real controller. |
| 81 | if overrides.abort_controller is not None: |
| 82 | abort_controller = overrides.abort_controller |
| 83 | elif overrides.share_abort_controller: |
| 84 | abort_controller = parent_context.abort_controller |
| 85 | else: |
| 86 | abort_controller = create_child_abort_controller(parent_context.abort_controller) |
| 87 | |
| 88 | # --- Permission context --- |
| 89 | # If sharing abort controller, it's interactive and can show UI. |
| 90 | # Otherwise, set should_avoid_permission_prompts. |
| 91 | if overrides.permission_context is not None: |
| 92 | permission_context = overrides.permission_context |
| 93 | elif overrides.share_abort_controller: |
| 94 | # Interactive agent — share parent's permission context |
| 95 | permission_context = parent_context.permission_context |
| 96 | else: |
| 97 | # Background agent — suppress permission prompts |
| 98 | permission_context = _wrap_avoid_prompts(parent_context.permission_context) |
| 99 | |
| 100 | # --- Read file state (fingerprints) --- |
| 101 | # TS behaviour: subagents start with an EMPTY fingerprint cache. |
| 102 | # They have NOT read any files yet, so inheriting the parent's cache |
| 103 | # causes the Read tool to return "file_unchanged" for files the |
| 104 | # subagent has never seen, forcing wasteful Bash fallbacks. |
| 105 | if overrides.read_file_state is not None: |
| 106 | read_file_fingerprints = dict(overrides.read_file_state) |
| 107 | else: |
| 108 | read_file_fingerprints: dict[Any, Any] = {} |
| 109 | |
| 110 | # --- Options --- |
| 111 | options = overrides.options if overrides.options is not None else parent_context.options |