python-codex 0.1.13__py3-none-any.whl → 0.2.0__py3-none-any.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.
Files changed (50) hide show
  1. pycodex/agent.py +71 -11
  2. pycodex/cli.py +16 -356
  3. pycodex/context.py +12 -0
  4. pycodex/feishu_card.py +76 -30
  5. pycodex/feishu_link.py +131 -11
  6. pycodex/interactive_session.py +397 -0
  7. pycodex/model.py +11 -22
  8. pycodex/protocol.py +0 -5
  9. pycodex/runtime.py +23 -0
  10. pycodex/runtime_services.py +2 -2
  11. pycodex/tools/agent_tool_schemas.py +1 -1
  12. pycodex/tools/apply_patch_tool.py +1 -1
  13. pycodex/tools/base_tool.py +1 -27
  14. pycodex/tools/close_agent_tool.py +11 -4
  15. pycodex/tools/code_mode_manager.py +1 -1
  16. pycodex/tools/exec_command_tool.py +40 -16
  17. pycodex/tools/exec_tool.py +18 -2
  18. pycodex/tools/grep_files_tool.py +19 -6
  19. pycodex/tools/ipython_tool.py +3 -2
  20. pycodex/tools/list_dir_tool.py +19 -6
  21. pycodex/tools/read_file_tool.py +39 -9
  22. pycodex/tools/request_permissions_tool.py +12 -1
  23. pycodex/tools/request_user_input_tool.py +28 -1
  24. pycodex/tools/send_input_tool.py +4 -2
  25. pycodex/tools/shell_command_tool.py +23 -6
  26. pycodex/tools/shell_tool.py +13 -4
  27. pycodex/tools/spawn_agent_tool.py +31 -8
  28. pycodex/tools/unified_exec_manager.py +49 -93
  29. pycodex/tools/update_plan_tool.py +14 -6
  30. pycodex/tools/view_image_tool.py +17 -16
  31. pycodex/tools/wait_agent_tool.py +15 -3
  32. pycodex/tools/wait_tool.py +18 -4
  33. pycodex/tools/web_search_tool.py +2 -1
  34. pycodex/tools/write_stdin_tool.py +42 -10
  35. pycodex/utils/compactor.py +7 -1
  36. pycodex/utils/session_persist.py +42 -1
  37. pycodex/utils/truncation.py +206 -0
  38. pycodex/utils/visualize.py +34 -15
  39. {python_codex-0.1.13.dist-info → python_codex-0.2.0.dist-info}/METADATA +4 -1
  40. python_codex-0.2.0.dist-info/RECORD +88 -0
  41. {python_codex-0.1.13.dist-info → python_codex-0.2.0.dist-info}/entry_points.txt +1 -0
  42. workspace_server/__init__.py +23 -0
  43. workspace_server/__main__.py +5 -0
  44. workspace_server/app.py +1347 -0
  45. workspace_server/workspace.html +866 -0
  46. pycodex/prompts/exec_tools.json +0 -411
  47. pycodex/prompts/subagent_tools.json +0 -163
  48. python_codex-0.1.13.dist-info/RECORD +0 -84
  49. {python_codex-0.1.13.dist-info → python_codex-0.2.0.dist-info}/WHEEL +0 -0
  50. {python_codex-0.1.13.dist-info → python_codex-0.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -21,10 +21,17 @@ from .unified_exec_manager import (
21
21
  )
22
22
  import typing
23
23
 
24
+ MIN_EXEC_YIELD_TIME_MS = 250
25
+ MAX_EXEC_YIELD_TIME_MS = 30_000
26
+
24
27
 
25
28
  class ExecCommandTool(BaseTool):
26
29
  name = "exec_command"
27
- description = "Runs a command in a PTY, returning output or a session ID for ongoing interaction."
30
+ description = (
31
+ "Runs a command in a PTY, returning output or a session ID for ongoing interaction. "
32
+ "For long tasks, you can reply first; when the task finishes, you will be "
33
+ "invoked to continue."
34
+ )
28
35
  input_schema = {
29
36
  "type": "object",
30
37
  "properties": {
@@ -34,27 +41,27 @@ class ExecCommandTool(BaseTool):
34
41
  },
35
42
  "workdir": {
36
43
  "type": "string",
37
- "description": "Optional working directory to run the command in; defaults to the turn cwd.",
38
- },
39
- "shell": {
40
- "type": "string",
41
- "description": "Shell binary to launch. Defaults to the user's default shell.",
42
- },
43
- "login": {
44
- "type": "boolean",
45
- "description": "Whether to run the shell with -l/-i semantics. Defaults to true.",
44
+ "description": "Working directory for the command. Defaults to the turn cwd.",
46
45
  },
47
46
  "tty": {
48
47
  "type": "boolean",
49
- "description": "Whether to allocate a TTY for the command. Defaults to false (plain pipes); set to true to open a PTY and access TTY process.",
48
+ "description": "True allocates a PTY for the command; false or omitted uses plain pipes.",
50
49
  },
51
50
  "yield_time_ms": {
52
- "type": "integer",
53
- "description": "How long to wait (in milliseconds) for output before yielding.",
51
+ "type": "number",
52
+ "description": "Wait before yielding output. Defaults to 10000 ms; effective range is 250-30000 ms.",
54
53
  },
55
54
  "max_output_tokens": {
56
- "type": "integer",
57
- "description": "Maximum number of tokens to return. Excess output will be truncated.",
55
+ "type": "number",
56
+ "description": "Output token budget. Defaults to 10000 tokens; larger requests may be capped by policy.",
57
+ },
58
+ "shell": {
59
+ "type": "string",
60
+ "description": "Shell binary to launch. Defaults to the user's default shell.",
61
+ },
62
+ "login": {
63
+ "type": "boolean",
64
+ "description": "True runs the shell with -l/-i semantics; false disables them. Defaults to true.",
58
65
  },
59
66
  },
60
67
  "required": ["cmd"],
@@ -78,7 +85,13 @@ class ExecCommandTool(BaseTool):
78
85
  shell=self._optional_string(args, "shell"),
79
86
  login=bool(args.get("login", DEFAULT_LOGIN)),
80
87
  tty=bool(args.get("tty", DEFAULT_TTY)),
81
- yield_time_ms=int(args.get("yield_time_ms", DEFAULT_EXEC_YIELD_TIME_MS)),
88
+ yield_time_ms=self._bounded_int(
89
+ args,
90
+ "yield_time_ms",
91
+ DEFAULT_EXEC_YIELD_TIME_MS,
92
+ MIN_EXEC_YIELD_TIME_MS,
93
+ MAX_EXEC_YIELD_TIME_MS,
94
+ ),
82
95
  max_output_tokens=self._optional_int(args, "max_output_tokens"),
83
96
  )
84
97
 
@@ -93,3 +106,14 @@ class ExecCommandTool(BaseTool):
93
106
  if value in (None, ""):
94
107
  return None
95
108
  return int(value)
109
+
110
+ def _bounded_int(
111
+ self,
112
+ args: 'JSONDict',
113
+ key: 'str',
114
+ default: 'int',
115
+ minimum: 'int',
116
+ maximum: 'int',
117
+ ) -> 'int':
118
+ value = int(args.get(key, default))
119
+ return min(max(value, minimum), maximum)
@@ -28,8 +28,24 @@ SOURCE: /[\s\S]+/
28
28
  class ExecTool(BaseTool):
29
29
  name = "exec"
30
30
  description = (
31
- "Runs raw JavaScript in an isolated context. Send raw JavaScript "
32
- "source text, not JSON, quoted strings, or markdown code fences."
31
+ "Run JavaScript code to orchestrate/compose tool calls\n"
32
+ "- Evaluates the provided JavaScript code in a fresh V8 isolate as an "
33
+ "async module.\n"
34
+ "- All nested tools are available on the global `tools` object, for "
35
+ "example `await tools.exec_command(...)`.\n"
36
+ "- Nested tool methods take either a string or an object as their input "
37
+ "argument.\n"
38
+ "- Runs raw JavaScript -- no Node, no file system, no network access, "
39
+ "no console.\n"
40
+ "- Accepts raw JavaScript source text, not JSON, quoted strings, or "
41
+ "markdown code fences.\n"
42
+ "- You may optionally start the tool input with a first-line pragma "
43
+ "like `// @exec: {\"yield_time_ms\": 10000, "
44
+ "\"max_output_tokens\": 1000}`.\n"
45
+ "- `yield_time_ms` asks `exec` to yield early if the script is still "
46
+ "running. Defaults to 10000 ms.\n"
47
+ "- `max_output_tokens` sets the token budget for direct `exec` results. "
48
+ "Defaults to 10000 tokens."
33
49
  )
34
50
  tool_type = "custom"
35
51
  format = {
@@ -26,18 +26,31 @@ COMMAND_TIMEOUT_SECONDS = 30
26
26
  class GrepFilesTool(BaseTool):
27
27
  name = "grep_files"
28
28
  description = (
29
- "Finds files whose contents match the pattern and lists them by "
30
- "modification time."
29
+ "Search file contents with ripgrep and return matching file paths, "
30
+ "newest first."
31
31
  )
32
32
  input_schema = {
33
33
  "type": "object",
34
34
  "properties": {
35
- "pattern": {"type": "string"},
36
- "include": {"type": "string"},
37
- "path": {"type": "string"},
38
- "limit": {"type": "integer"},
35
+ "pattern": {
36
+ "type": "string",
37
+ "description": "Regular expression to search for.",
38
+ },
39
+ "include": {
40
+ "type": "string",
41
+ "description": "Optional glob pattern to limit searched file names.",
42
+ },
43
+ "path": {
44
+ "type": "string",
45
+ "description": "File or directory to search. Defaults to the tool cwd.",
46
+ },
47
+ "limit": {
48
+ "type": "integer",
49
+ "description": "Maximum number of matching paths to return.",
50
+ },
39
51
  },
40
52
  "required": ["pattern"],
53
+ "additionalProperties": False,
41
54
  }
42
55
 
43
56
  def __init__(self, cwd: 'typing.Union[typing.Union[str, Path], None]' = None) -> 'None':
@@ -7,8 +7,9 @@ from .base_tool import BaseTool, ToolContext
7
7
  class IPythonTool(BaseTool):
8
8
  name = "ipython"
9
9
  description = (
10
- "Execute Python code in the current IPython kernel namespace. "
11
- "Use this to inspect live Python variables in the user's IPython session."
10
+ "Execute Python code in the current IPython kernel namespace. Use this "
11
+ "only when running inside IPython and live notebook/session variables "
12
+ "need to be inspected."
12
13
  )
13
14
  input_schema = {
14
15
  "type": "object",
@@ -23,18 +23,31 @@ INDENTATION_SPACES = 2
23
23
  class ListDirTool(BaseTool):
24
24
  name = "list_dir"
25
25
  description = (
26
- "Lists entries in a local directory with 1-indexed entry numbers and "
27
- "simple type labels."
26
+ "List entries in a local directory as a stable tree slice with simple "
27
+ "type labels."
28
28
  )
29
29
  input_schema = {
30
30
  "type": "object",
31
31
  "properties": {
32
- "dir_path": {"type": "string"},
33
- "offset": {"type": "integer"},
34
- "limit": {"type": "integer"},
35
- "depth": {"type": "integer"},
32
+ "dir_path": {
33
+ "type": "string",
34
+ "description": "Absolute path to the directory to list.",
35
+ },
36
+ "offset": {
37
+ "type": "integer",
38
+ "description": "1-indexed entry offset for pagination. Defaults to 1.",
39
+ },
40
+ "limit": {
41
+ "type": "integer",
42
+ "description": "Maximum number of entries to return. Defaults to 25.",
43
+ },
44
+ "depth": {
45
+ "type": "integer",
46
+ "description": "Maximum directory depth to include. Defaults to 2.",
47
+ },
36
48
  },
37
49
  "required": ["dir_path"],
50
+ "additionalProperties": False,
38
51
  }
39
52
 
40
53
  async def run(self, context: 'ToolContext', args: 'JSONDict') -> 'JSONValue':
@@ -30,22 +30,52 @@ class ReadFileTool(BaseTool):
30
30
  input_schema = {
31
31
  "type": "object",
32
32
  "properties": {
33
- "file_path": {"type": "string"},
34
- "offset": {"type": "integer"},
35
- "limit": {"type": "integer"},
36
- "mode": {"type": "string"},
33
+ "file_path": {
34
+ "type": "string",
35
+ "description": "Absolute path to the file to read.",
36
+ },
37
+ "offset": {
38
+ "type": "integer",
39
+ "description": "1-indexed starting line number. Defaults to 1.",
40
+ },
41
+ "limit": {
42
+ "type": "integer",
43
+ "description": "Maximum number of lines to return. Defaults to 2000.",
44
+ },
45
+ "mode": {
46
+ "type": "string",
47
+ "description": "Read mode. Use `slice` for a line range or `indentation` for an indentation-aware block.",
48
+ },
37
49
  "indentation": {
38
50
  "type": "object",
51
+ "description": "Options for indentation-aware block mode.",
39
52
  "properties": {
40
- "anchor_line": {"type": "integer"},
41
- "max_levels": {"type": "integer"},
42
- "include_siblings": {"type": "boolean"},
43
- "include_header": {"type": "boolean"},
44
- "max_lines": {"type": "integer"},
53
+ "anchor_line": {
54
+ "type": "integer",
55
+ "description": "1-indexed anchor line for block expansion.",
56
+ },
57
+ "max_levels": {
58
+ "type": "integer",
59
+ "description": "How many indentation levels above the anchor to include. Use 0 for file-root scope.",
60
+ },
61
+ "include_siblings": {
62
+ "type": "boolean",
63
+ "description": "Whether to include sibling blocks at the same indentation level.",
64
+ },
65
+ "include_header": {
66
+ "type": "boolean",
67
+ "description": "Whether to include immediately preceding header/comment lines.",
68
+ },
69
+ "max_lines": {
70
+ "type": "integer",
71
+ "description": "Maximum number of lines to return in indentation mode.",
72
+ },
45
73
  },
74
+ "additionalProperties": False,
46
75
  },
47
76
  },
48
77
  "required": ["file_path"],
78
+ "additionalProperties": False,
49
79
  }
50
80
 
51
81
  async def run(self, context: 'ToolContext', args: 'JSONDict') -> 'JSONValue':
@@ -55,7 +55,13 @@ class RequestPermissionsTool(BaseTool):
55
55
  name = "request_permissions"
56
56
  description = (
57
57
  "Request additional filesystem or network permissions from the user "
58
- "and wait for a response."
58
+ "and wait for the client to grant a subset of the requested permission "
59
+ "profile. Use environment_id to target a specific attached "
60
+ "environment; omit it to use the primary environment. Relative "
61
+ "filesystem paths resolve against the selected environment cwd. "
62
+ "Granted permissions apply automatically to later shell-like commands "
63
+ "in the current turn, or for the rest of the session if the client "
64
+ "approves them at session scope."
59
65
  )
60
66
  input_schema = {
61
67
  "type": "object",
@@ -64,6 +70,10 @@ class RequestPermissionsTool(BaseTool):
64
70
  "type": "string",
65
71
  "description": "Optional short explanation for why additional permissions are needed.",
66
72
  },
73
+ "environment_id": {
74
+ "type": "string",
75
+ "description": "Environment id from <environment_context>. Omit to use the primary environment.",
76
+ },
67
77
  "permissions": REQUEST_PERMISSION_PROFILE_SCHEMA,
68
78
  },
69
79
  "required": ["permissions"],
@@ -85,6 +95,7 @@ class RequestPermissionsTool(BaseTool):
85
95
  response = await self._request_manager.request(
86
96
  {
87
97
  "reason": None if args.get("reason") in (None, "") else str(args.get("reason")),
98
+ "environment_id": None if args.get("environment_id") in (None, "") else str(args.get("environment_id")),
88
99
  "permissions": permissions,
89
100
  }
90
101
  )
@@ -20,6 +20,9 @@ from ..runtime_services import RequestUserInputManager
20
20
  from .base_tool import BaseTool, StructuredToolOutput, ToolContext
21
21
  import typing
22
22
 
23
+ MIN_AUTO_RESOLUTION_MS = 60_000
24
+ MAX_AUTO_RESOLUTION_MS = 240_000
25
+
23
26
  REQUEST_USER_INPUT_QUESTION_SCHEMA = {
24
27
  "type": "object",
25
28
  "properties": {
@@ -96,7 +99,11 @@ def request_user_input_tool_description(
96
99
  allowed_modes = "Plan mode"
97
100
  return (
98
101
  "Request user input for one to three short questions and wait for the "
99
- f"response. This tool is only available in {allowed_modes}."
102
+ f"response. Set autoResolutionMs, from {MIN_AUTO_RESOLUTION_MS} to "
103
+ f"{MAX_AUTO_RESOLUTION_MS} milliseconds, only when the question is "
104
+ "useful but non-blocking and continuing with best judgment is "
105
+ "acceptable if the user does not answer; omit it when explicit user "
106
+ f"input is required. This tool is only available in {allowed_modes}."
100
107
  )
101
108
 
102
109
 
@@ -110,6 +117,20 @@ class RequestUserInputTool(BaseTool):
110
117
  "type": "array",
111
118
  "description": "Questions to show the user. Prefer 1 and do not exceed 3",
112
119
  "items": REQUEST_USER_INPUT_QUESTION_SCHEMA,
120
+ },
121
+ "autoResolutionMs": {
122
+ "type": "number",
123
+ "description": (
124
+ "Optional auto-resolution window in milliseconds, from "
125
+ f"{MIN_AUTO_RESOLUTION_MS} to {MAX_AUTO_RESOLUTION_MS}. "
126
+ "Include this only when the question is useful but "
127
+ "non-blocking and continuing with best judgment is "
128
+ "acceptable if the user does not answer; omit it when "
129
+ "explicit user input is required before continuing. Use "
130
+ f"{MIN_AUTO_RESOLUTION_MS} for lightly helpful context and "
131
+ f"up to {MAX_AUTO_RESOLUTION_MS} when the answer would "
132
+ "materially unblock better work."
133
+ ),
113
134
  }
114
135
  },
115
136
  "required": ["questions"],
@@ -157,6 +178,12 @@ class RequestUserInputTool(BaseTool):
157
178
  for question in questions
158
179
  ]
159
180
  }
181
+ auto_resolution_ms = args.get("autoResolutionMs")
182
+ if auto_resolution_ms not in (None, ""):
183
+ request_payload["autoResolutionMs"] = min(
184
+ max(int(auto_resolution_ms), MIN_AUTO_RESOLUTION_MS),
185
+ MAX_AUTO_RESOLUTION_MS,
186
+ )
160
187
  response = await self._request_manager.request(request_payload)
161
188
  if response is None:
162
189
  return "request_user_input was cancelled before receiving a response"
@@ -32,7 +32,9 @@ class SendInputTool(BaseTool):
32
32
  name = "send_input"
33
33
  description = (
34
34
  "Send a message to an existing agent. Use interrupt=true to redirect "
35
- "work immediately."
35
+ "work immediately. You should reuse the agent by send_input if you "
36
+ "believe your assigned task is highly dependent on the context of a "
37
+ "previous task."
36
38
  )
37
39
  input_schema = {
38
40
  "type": "object",
@@ -48,7 +50,7 @@ class SendInputTool(BaseTool):
48
50
  "items": COLLAB_INPUT_ITEMS_SCHEMA,
49
51
  "interrupt": {
50
52
  "type": "boolean",
51
- "description": "When true, stop the agent's current task and handle this immediately. When false (default), queue this message.",
53
+ "description": "True interrupts the current task and handles this message immediately; false or omitted queues it.",
52
54
  },
53
55
  },
54
56
  "required": ["id"],
@@ -18,22 +18,39 @@ from ..protocol import JSONDict, JSONValue
18
18
  from .base_tool import BaseTool, ToolContext
19
19
  import typing
20
20
 
21
- DEFAULT_SHELL_TIMEOUT_MS = 30_000
21
+ DEFAULT_SHELL_TIMEOUT_MS = 10_000
22
22
  MAX_OUTPUT_CHARS = 12_000
23
23
 
24
24
 
25
25
  class ShellCommandTool(BaseTool):
26
26
  name = "shell_command"
27
- description = "Runs a shell command string and returns its output."
27
+ description = (
28
+ "Runs a shell command and returns its output.\n"
29
+ "- Always set the `workdir` param when using the shell_command "
30
+ "function. Do not use `cd` unless absolutely necessary."
31
+ )
28
32
  input_schema = {
29
33
  "type": "object",
30
34
  "properties": {
31
- "command": {"type": "string"},
32
- "workdir": {"type": "string"},
33
- "timeout_ms": {"type": "integer"},
34
- "login": {"type": "boolean"},
35
+ "command": {
36
+ "type": "string",
37
+ "description": "Shell script to run in the user's default shell.",
38
+ },
39
+ "workdir": {
40
+ "type": "string",
41
+ "description": "Working directory for the command. Defaults to the turn cwd.",
42
+ },
43
+ "timeout_ms": {
44
+ "type": "integer",
45
+ "description": "Maximum command runtime. Defaults to 10000 ms.",
46
+ },
47
+ "login": {
48
+ "type": "boolean",
49
+ "description": "True runs with login shell semantics; false disables them. Defaults to true.",
50
+ },
35
51
  },
36
52
  "required": ["command"],
53
+ "additionalProperties": False,
37
54
  }
38
55
  supports_parallel = False
39
56
 
@@ -25,8 +25,9 @@ MAX_OUTPUT_CHARS = 12_000
25
25
  class ShellTool(BaseTool):
26
26
  name = "shell"
27
27
  description = (
28
- "Runs a shell command and returns its output. The command must be passed "
29
- "as argv, typically prefixed with ['bash', '-lc'] for shell syntax."
28
+ "Runs a process from an argv array and returns its output. Use "
29
+ "`shell_command` for shell-script strings; use this tool when the exact "
30
+ "argv vector matters."
30
31
  )
31
32
  input_schema = {
32
33
  "type": "object",
@@ -34,11 +35,19 @@ class ShellTool(BaseTool):
34
35
  "command": {
35
36
  "type": "array",
36
37
  "items": {"type": "string"},
38
+ "description": "Command argv to execute.",
39
+ },
40
+ "workdir": {
41
+ "type": "string",
42
+ "description": "Working directory for the command. Defaults to the tool cwd.",
43
+ },
44
+ "timeout_ms": {
45
+ "type": "integer",
46
+ "description": "Maximum command runtime in milliseconds.",
37
47
  },
38
- "workdir": {"type": "string"},
39
- "timeout_ms": {"type": "integer"},
40
48
  },
41
49
  "required": ["command"],
50
+ "additionalProperties": False,
42
51
  }
43
52
  supports_parallel = False
44
53
 
@@ -34,11 +34,34 @@ SPAWN_AGENT_OUTPUT_SCHEMA = {
34
34
 
35
35
  class SpawnAgentTool(BaseTool):
36
36
  name = "spawn_agent"
37
- description = (
38
- "Spawn a sub-agent for a well-scoped task. Returns the agent id (and "
39
- "user-facing nickname when available) to use to communicate with this "
40
- "agent."
41
- )
37
+ description = """
38
+ Spawn a sub-agent for a well-scoped task. Returns the spawned agent id plus the user-facing nickname when available. Spawned agents inherit your current model by default. Omit `model` to use that preferred default; set `model` only when an explicit override is needed.
39
+ This spawn_agent tool provides you access to sub-agents that inherit your current model by default. Do not set the `model` field unless the user explicitly asks for a different model or there is a clear task-specific reason. You should follow the rules and guidelines below to use this tool.
40
+
41
+ Do not spawn sub-agents unless the user explicitly asks for sub-agents, delegation, or parallel agent work.
42
+
43
+ ### Designing delegated subtasks
44
+ - Subtasks must be concrete, well-defined, and self-contained.
45
+ - Delegated subtasks must materially advance the main task.
46
+ - Do not duplicate work between the main rollout and delegated subtasks.
47
+ - Avoid issuing multiple delegate calls on the same unresolved thread unless the new delegated task is genuinely different and necessary.
48
+ - Narrow the delegated ask to the concrete output you need next.
49
+ - For coding tasks, prefer delegating concrete code-change worker subtasks over read-only explorer analysis when the subagent can make a bounded patch in a clear write scope.
50
+ - When delegating coding work, instruct the submodel to edit files directly in its forked workspace and list the file paths it changed in the final answer.
51
+ - For code-edit subtasks, decompose work so each delegated task has a disjoint write set.
52
+
53
+ ### After you delegate
54
+ - Call wait_agent very sparingly. Only call wait_agent when you need the result immediately for the next critical-path step and you are blocked until it returns.
55
+ - Do not redo delegated subagent tasks yourself; focus on integrating results or tackling non-overlapping work.
56
+ - While the subagent is running in the background, do meaningful non-overlapping work immediately.
57
+ - Do not repeatedly wait by reflex.
58
+ - When a delegated coding task returns, quickly review the uploaded changes, then integrate or refine them.
59
+
60
+ ### Parallel delegation patterns
61
+ - Run multiple independent information-seeking subtasks in parallel when you have distinct questions that can be answered independently.
62
+ - Split implementation into disjoint codebase slices and spawn multiple agents in parallel when the write scopes do not overlap.
63
+ - Delegate verification only when it can run in parallel with ongoing implementation and is likely to catch a concrete risk before final integration.
64
+ - The key is to find opportunities to spawn multiple independent subtasks in parallel within the same round, while ensuring each subtask is well-defined, self-contained, and materially advances the main task."""
42
65
  input_schema = {
43
66
  "type": "object",
44
67
  "properties": {
@@ -53,15 +76,15 @@ class SpawnAgentTool(BaseTool):
53
76
  },
54
77
  "fork_context": {
55
78
  "type": "boolean",
56
- "description": "When true, fork the current thread history into the new agent before sending the initial prompt.",
79
+ "description": "True forks the current thread history into the new agent; false or omitted starts with only the initial prompt.",
57
80
  },
58
81
  "model": {
59
82
  "type": "string",
60
- "description": "Optional model override for the new agent.",
83
+ "description": "Model override for the new agent. Omit unless an explicit override is needed.",
61
84
  },
62
85
  "reasoning_effort": {
63
86
  "type": "string",
64
- "description": "Optional reasoning effort override for the new agent.",
87
+ "description": "Reasoning effort override for the new agent. Omit to inherit the parent effort.",
65
88
  },
66
89
  },
67
90
  "additionalProperties": False,