gemcode 0.3.17__tar.gz → 0.3.18__tar.gz

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 (112) hide show
  1. {gemcode-0.3.17/src/gemcode.egg-info → gemcode-0.3.18}/PKG-INFO +1 -1
  2. {gemcode-0.3.17 → gemcode-0.3.18}/pyproject.toml +1 -1
  3. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/agent.py +51 -4
  4. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/tool_prompt_manifest.py +4 -1
  5. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/tool_registry.py +3 -1
  6. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/tools/__init__.py +13 -2
  7. gemcode-0.3.18/src/gemcode/tools/subtask.py +137 -0
  8. gemcode-0.3.18/src/gemcode/tools/think.py +41 -0
  9. {gemcode-0.3.17 → gemcode-0.3.18/src/gemcode.egg-info}/PKG-INFO +1 -1
  10. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode.egg-info/SOURCES.txt +2 -0
  11. {gemcode-0.3.17 → gemcode-0.3.18}/LICENSE +0 -0
  12. {gemcode-0.3.17 → gemcode-0.3.18}/MANIFEST.in +0 -0
  13. {gemcode-0.3.17 → gemcode-0.3.18}/README.md +0 -0
  14. {gemcode-0.3.17 → gemcode-0.3.18}/setup.cfg +0 -0
  15. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/__init__.py +0 -0
  16. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/__main__.py +0 -0
  17. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/audit.py +0 -0
  18. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/autocompact.py +0 -0
  19. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/callbacks.py +0 -0
  20. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/capability_routing.py +0 -0
  21. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/cli.py +0 -0
  22. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/compaction.py +0 -0
  23. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/computer_use/__init__.py +0 -0
  24. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/computer_use/browser_computer.py +0 -0
  25. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/config.py +0 -0
  26. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/context_budget.py +0 -0
  27. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/context_warning.py +0 -0
  28. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/credentials.py +0 -0
  29. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/hitl_session.py +0 -0
  30. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/interactions.py +0 -0
  31. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/invoke.py +0 -0
  32. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/kairos_daemon.py +0 -0
  33. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/limits.py +0 -0
  34. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/live_audio_engine.py +0 -0
  35. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/logging_config.py +0 -0
  36. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/mcp_loader.py +0 -0
  37. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/memory/__init__.py +0 -0
  38. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/memory/embedding_memory_service.py +0 -0
  39. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/memory/file_memory_service.py +0 -0
  40. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/modality_tools.py +0 -0
  41. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/model_errors.py +0 -0
  42. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/model_routing.py +0 -0
  43. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/paths.py +0 -0
  44. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/permissions.py +0 -0
  45. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/plugins/__init__.py +0 -0
  46. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
  47. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
  48. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/prompt_suggestions.py +0 -0
  49. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/query/__init__.py +0 -0
  50. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/query/config.py +0 -0
  51. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/query/deps.py +0 -0
  52. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/query/engine.py +0 -0
  53. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/query/stop_hooks.py +0 -0
  54. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/query/token_budget.py +0 -0
  55. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/query/transitions.py +0 -0
  56. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/repl_commands.py +0 -0
  57. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/repl_slash.py +0 -0
  58. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/session_runtime.py +0 -0
  59. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/slash_commands.py +0 -0
  60. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/thinking.py +0 -0
  61. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/tools/bash.py +0 -0
  62. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/tools/edit.py +0 -0
  63. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/tools/filesystem.py +0 -0
  64. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/tools/search.py +0 -0
  65. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/tools/shell.py +0 -0
  66. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/tools/shell_gate.py +0 -0
  67. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/tools/todo.py +0 -0
  68. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/tools/web.py +0 -0
  69. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/tools_inspector.py +0 -0
  70. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/trust.py +0 -0
  71. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/tui/input_handler.py +0 -0
  72. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/tui/scrollback.py +0 -0
  73. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/tui/spinner.py +0 -0
  74. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/tui/welcome_banner.py +0 -0
  75. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/tui/welcome_rich.py +0 -0
  76. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/version.py +0 -0
  77. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/vertex.py +0 -0
  78. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/web/__init__.py +0 -0
  79. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/web/claude_sse_adapter.py +0 -0
  80. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/web/terminal_repl.py +0 -0
  81. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode/workspace_hints.py +0 -0
  82. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode.egg-info/dependency_links.txt +0 -0
  83. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode.egg-info/entry_points.txt +0 -0
  84. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode.egg-info/requires.txt +0 -0
  85. {gemcode-0.3.17 → gemcode-0.3.18}/src/gemcode.egg-info/top_level.txt +0 -0
  86. {gemcode-0.3.17 → gemcode-0.3.18}/tests/test_agent_instruction.py +0 -0
  87. {gemcode-0.3.17 → gemcode-0.3.18}/tests/test_autocompact.py +0 -0
  88. {gemcode-0.3.17 → gemcode-0.3.18}/tests/test_capability_routing.py +0 -0
  89. {gemcode-0.3.17 → gemcode-0.3.18}/tests/test_claude_web_adapter_sse.py +0 -0
  90. {gemcode-0.3.17 → gemcode-0.3.18}/tests/test_cli_init.py +0 -0
  91. {gemcode-0.3.17 → gemcode-0.3.18}/tests/test_computer_use_permissions.py +0 -0
  92. {gemcode-0.3.17 → gemcode-0.3.18}/tests/test_context_budget.py +0 -0
  93. {gemcode-0.3.17 → gemcode-0.3.18}/tests/test_context_warning.py +0 -0
  94. {gemcode-0.3.17 → gemcode-0.3.18}/tests/test_credentials.py +0 -0
  95. {gemcode-0.3.17 → gemcode-0.3.18}/tests/test_interactive_permission_ask.py +0 -0
  96. {gemcode-0.3.17 → gemcode-0.3.18}/tests/test_kairos_scheduler.py +0 -0
  97. {gemcode-0.3.17 → gemcode-0.3.18}/tests/test_modality_tools.py +0 -0
  98. {gemcode-0.3.17 → gemcode-0.3.18}/tests/test_model_error_retry.py +0 -0
  99. {gemcode-0.3.17 → gemcode-0.3.18}/tests/test_model_errors.py +0 -0
  100. {gemcode-0.3.17 → gemcode-0.3.18}/tests/test_model_routing.py +0 -0
  101. {gemcode-0.3.17 → gemcode-0.3.18}/tests/test_paths.py +0 -0
  102. {gemcode-0.3.17 → gemcode-0.3.18}/tests/test_permissions.py +0 -0
  103. {gemcode-0.3.17 → gemcode-0.3.18}/tests/test_prompt_suggestions.py +0 -0
  104. {gemcode-0.3.17 → gemcode-0.3.18}/tests/test_repl_commands.py +0 -0
  105. {gemcode-0.3.17 → gemcode-0.3.18}/tests/test_repl_slash.py +0 -0
  106. {gemcode-0.3.17 → gemcode-0.3.18}/tests/test_slash_commands.py +0 -0
  107. {gemcode-0.3.17 → gemcode-0.3.18}/tests/test_thinking_config.py +0 -0
  108. {gemcode-0.3.17 → gemcode-0.3.18}/tests/test_token_budget.py +0 -0
  109. {gemcode-0.3.17 → gemcode-0.3.18}/tests/test_tool_context_circulation.py +0 -0
  110. {gemcode-0.3.17 → gemcode-0.3.18}/tests/test_tools.py +0 -0
  111. {gemcode-0.3.17 → gemcode-0.3.18}/tests/test_tools_inspector.py +0 -0
  112. {gemcode-0.3.17 → gemcode-0.3.18}/tests/test_workspace_hints.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemcode
3
- Version: 0.3.17
3
+ Version: 0.3.18
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gemcode"
7
- version = "0.3.17"
7
+ version = "0.3.18"
8
8
  description = "Local-first coding agent on Google Gemini + ADK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -151,10 +151,24 @@ You have native deep thinking capability — use it actively:
151
151
  - `web_fetch("https://registry.npmjs.org/react/latest")` — npm package info
152
152
  - Use when you need to look up an API, check the latest version, or read documentation.
153
153
 
154
- ### Planning
154
+ ### Reasoning and planning
155
+ - **`think`** — private reasoning scratchpad. Write your analysis, plan, or hypothesis here before acting. Not shown to the user. Use before:
156
+ - A complex multi-file edit or refactor
157
+ - A debugging session where you need to trace logic before touching code
158
+ - Any destructive action (delete, force-push) — think first
159
+ - Choosing between approaches with real trade-offs
160
+
155
161
  - **`todo_write`** — track work items. Use for any task with 3+ steps.
156
162
  - Create at task start, mark completed as you finish, merge updates.
157
163
 
164
+ - **`run_subtask`** — spawn an isolated sub-agent with its own fresh context window.
165
+ - The sub-agent has the same tools (bash, read_file, grep, etc.) but starts from scratch.
166
+ - Use when a task would bloat your context too much: e.g. "read all 40 test files and find patterns"
167
+ - Use to parallelize: issue multiple `run_subtask` calls in one turn for concurrent exploration
168
+ - Use for verification passes: "check all files I edited for consistency and syntax errors"
169
+ - Always give the sub-agent enough context to operate independently.
170
+ - End your task prompt with "Summarise your findings clearly." so the result is useful.
171
+
158
172
  ## Multi-step task execution
159
173
  One user message = many model↔tool rounds (up to 256 LLM calls by default). This is intentional — you are expected to do complete tasks autonomously.
160
174
 
@@ -174,8 +188,25 @@ Issue independent tool calls in the same turn when outputs don't depend on each
174
188
  - Reading multiple files simultaneously ✓
175
189
  - Grepping for different patterns at once ✓
176
190
  - `list_directory` + `glob_files` in parallel ✓
191
+ - Multiple `run_subtask` calls in one turn for parallel sub-agent exploration ✓
177
192
  Sequential: when step B needs step A's result.
178
193
 
194
+ ## Sub-agent delegation (orchestrator-worker pattern)
195
+ Use `run_subtask` when the work is better done in an isolated context:
196
+ - **Context preservation**: offload reading/analysing large areas of the codebase so your own context stays clean and focused on the high-level task.
197
+ - **Parallel exploration**: launch multiple sub-agents simultaneously to research different subsystems ("analyse auth module", "analyse payment module") then synthesise.
198
+ - **Verification**: after completing work, spawn a sub-agent to review it independently — "verify the changes in src/ are syntactically correct and don't break imports."
199
+ - **Deep research**: when you need to exhaustively search something (50+ files, long documentation pages) delegate it rather than polluting the main conversation.
200
+
201
+ The sub-agent inherits your permission settings and returns its final text as `result`. Treat it as a trusted colleague returning a written summary.
202
+
203
+ ## Evaluator-optimizer loop
204
+ For tasks where quality matters:
205
+ 1. Complete the task (execute tools, write code, run commands)
206
+ 2. Spawn a verification `run_subtask` or use `bash` to run tests/lint
207
+ 3. If verification fails, read the error, fix, re-verify
208
+ 4. Report done only when verified
209
+
179
210
  ## Error recovery
180
211
  - **Test/build failures**: read the full error, identify the exact line, fix, re-run.
181
212
  - **Tool errors**: diagnose why it failed before retrying — don't repeat the exact same call.
@@ -207,9 +238,25 @@ All file tools use paths **relative to the project root** (where GemCode was sta
207
238
  return base
208
239
 
209
240
 
210
- def build_root_agent(cfg: GemCodeConfig, extra_tools: list | None = None) -> LlmAgent:
211
- """Create the root LlmAgent with tools and callbacks (no Runner)."""
212
- tools = build_function_tools(cfg)
241
+ def build_root_agent(
242
+ cfg: GemCodeConfig,
243
+ extra_tools: list | None = None,
244
+ *,
245
+ _tools: list | None = None,
246
+ ) -> LlmAgent:
247
+ """Create the root LlmAgent with tools and callbacks (no Runner).
248
+
249
+ Args:
250
+ cfg: Runtime configuration.
251
+ extra_tools: Additional tools to append (e.g. modality tools from session_runtime).
252
+ _tools: Override the entire tool list (used by run_subtask sub-agents to pass a
253
+ pre-built list that excludes run_subtask itself, preventing recursion).
254
+ When set, build_function_tools() is NOT called.
255
+ """
256
+ if _tools is not None:
257
+ tools = list(_tools)
258
+ else:
259
+ tools = build_function_tools(cfg)
213
260
  if getattr(cfg, "enable_memory", False):
214
261
  # ADK preload_memory injects retrieved memories into the next llm_request.
215
262
  from google.adk.tools import preload_memory
@@ -108,7 +108,10 @@ Permission policy:
108
108
  - session_sticky_hitl={sticky_hitl}: when true, after the user approves **one** tool in this session, further mutating/shell tools may run without re-prompting (set GEMCODE_HITL_STICKY_SESSION=0 to prompt every time).
109
109
 
110
110
  You may call tools as follows:
111
- - Session planning: {_fmt_list(planning)}. In-memory task list for this session (no disk writes). Use for non-trivial multi-step work; set merge=true to upsert by id.
111
+ - Session planning / reasoning: {_fmt_list(planning)}.
112
+ - `todo_write`: in-memory task list (no disk writes). Use for non-trivial multi-step work; merge=true to upsert by id.
113
+ - `think`: private scratchpad — write reasoning/analysis before risky or complex actions. Not shown to user.
114
+ - `run_subtask(task, context)`: spawn an isolated sub-agent to handle context-heavy work in parallel or in sequence. Sub-agent inherits your permissions and returns its final text as `result`. Exclude run_subtask from sub-agent by design (no recursion).
112
115
  - Read-only tools: {_fmt_list(read_only)}.
113
116
  Use these proactively to locate files and code before asking the user for paths.
114
117
  - Mutating tools (WRITE/EDIT/DELETE): {_fmt_list(mutating)}.
@@ -36,7 +36,9 @@ MUTATING_TOOLS: frozenset[str] = frozenset(
36
36
  SHELL_TOOLS: frozenset[str] = frozenset({"run_command", "bash"})
37
37
 
38
38
  # Session planning only (no disk / shell; no extra permission)
39
- PLANNING_TOOLS: frozenset[str] = frozenset({"todo_write"})
39
+ # think in-context reasoning scratchpad (no-op, no side effects)
40
+ # run_subtask — spawns a sub-agent; inherits parent permission settings
41
+ PLANNING_TOOLS: frozenset[str] = frozenset({"todo_write", "think", "run_subtask"})
40
42
 
41
43
  ToolConcurrency = Literal["parallel_safe", "serial_mutating", "shell"]
42
44
 
@@ -8,20 +8,25 @@ from gemcode.tools.edit import make_edit_tools
8
8
  from gemcode.tools.filesystem import make_filesystem_tools
9
9
  from gemcode.tools.search import make_grep_tool
10
10
  from gemcode.tools.shell import make_run_command
11
+ from gemcode.tools.subtask import make_run_subtask_tool
12
+ from gemcode.tools.think import make_think_tool
11
13
  from gemcode.tools.todo import make_todo_tool
12
14
  from gemcode.tools.web import make_web_fetch_tool
13
15
 
14
16
 
15
- def build_function_tools(cfg: GemCodeConfig) -> list:
17
+ def build_function_tools(cfg: GemCodeConfig, *, include_subtask: bool = True) -> list:
16
18
  read_file, list_directory, glob_files, delete_file, move_file = make_filesystem_tools(cfg)
17
19
  grep_content = make_grep_tool(cfg)
18
20
  run_command = make_run_command(cfg)
19
21
  bash = make_bash_tool(cfg)
20
22
  write_file, search_replace = make_edit_tools(cfg)
21
23
  todo_write = make_todo_tool(cfg)
24
+ think = make_think_tool()
22
25
  web_fetch = make_web_fetch_tool()
23
- return [
26
+
27
+ tools = [
24
28
  todo_write,
29
+ think,
25
30
  read_file,
26
31
  list_directory,
27
32
  glob_files,
@@ -34,3 +39,9 @@ def build_function_tools(cfg: GemCodeConfig) -> list:
34
39
  delete_file,
35
40
  web_fetch,
36
41
  ]
42
+
43
+ # run_subtask is excluded when building sub-agent tools (prevents recursion).
44
+ if include_subtask:
45
+ tools.append(make_run_subtask_tool(cfg))
46
+
47
+ return tools
@@ -0,0 +1,137 @@
1
+ """
2
+ Sub-agent tool — spawn an isolated focused agent for context-heavy tasks.
3
+
4
+ Analogous to OpenClaude's AgentTool / AI SDK's ToolLoopAgent subagent pattern.
5
+
6
+ Key insight (from AI SDK docs "Subagents"):
7
+ "Some tasks require exploring large amounts of information — reading files,
8
+ searching codebases, or researching topics. Running these in the main agent
9
+ consumes context quickly, making the agent less coherent over time.
10
+ With subagents, you can spin up a dedicated agent that uses hundreds of
11
+ thousands of tokens and have it return only a focused summary."
12
+
13
+ The sub-agent:
14
+ - Gets the same tools as the parent (read_file, bash, grep, etc.) EXCEPT
15
+ run_subtask itself (prevents infinite recursion).
16
+ - Uses an in-memory session (no DB writes, fully isolated).
17
+ - Respects the same permission settings (yes_to_all, permission_mode) as the parent.
18
+ - Is depth-capped (max 48 LLM calls) to prevent runaway cost.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from gemcode.config import GemCodeConfig
24
+
25
+
26
+ def make_run_subtask_tool(cfg: GemCodeConfig):
27
+ async def run_subtask(task: str, context: str = "") -> dict:
28
+ """
29
+ Spawn an isolated sub-agent to complete a focused, self-contained task.
30
+
31
+ The sub-agent starts with a fresh context window — it does NOT inherit this
32
+ conversation's history. It has access to all the same tools (read_file, bash,
33
+ grep_content, web_fetch, etc.) and returns its final response as `result`.
34
+
35
+ Use when:
36
+ - Exploring a large codebase section that would bloat your context
37
+ ("read all 40 test files and summarise what each group tests")
38
+ - Running deep analysis in parallel — issue multiple run_subtask calls
39
+ in the same turn for genuine parallel execution
40
+ - Delegating a focused research or investigation task while staying
41
+ high-level yourself
42
+ - Getting a verification or review pass on your own changes
43
+ ("check my edits in src/ for syntax errors and consistency")
44
+ - Any task that requires 10+ file reads or multiple bash commands but
45
+ whose output can be summarised in a paragraph
46
+
47
+ The sub-agent reasons and acts like a full Gemini coding agent.
48
+ Give it a clear, self-contained task — include all context it needs
49
+ since it cannot see this conversation.
50
+
51
+ Tip: end your task description with "Summarise your findings clearly
52
+ since your full output will be returned to the main agent."
53
+
54
+ Args:
55
+ task: Clear, self-contained description of what to do.
56
+ context: Extra context the sub-agent needs (file paths, relevant
57
+ code snippets, constraints, acceptance criteria).
58
+ """
59
+ import uuid
60
+
61
+ try:
62
+ from google.adk.runners import Runner
63
+ from google.adk.sessions.in_memory_session_service import (
64
+ InMemorySessionService,
65
+ )
66
+ except ImportError:
67
+ try:
68
+ from google.adk.runners import Runner
69
+ from google.adk.sessions import InMemorySessionService
70
+ except ImportError:
71
+ return {"error": "google-adk not available for sub-agent"}
72
+
73
+ from gemcode.agent import build_root_agent
74
+ from gemcode.tools import build_function_tools
75
+ from gemcode.invoke import run_turn
76
+
77
+ # Build the sub-agent tool set WITHOUT run_subtask (no recursion).
78
+ sub_tools = build_function_tools(cfg, include_subtask=False)
79
+
80
+ # Build a standalone LlmAgent with the limited tool set.
81
+ sub_agent = build_root_agent(cfg, _tools=sub_tools)
82
+
83
+ # Isolated in-memory session — never writes to the parent SQLite DB.
84
+ sub_runner = Runner(
85
+ app_name="gemcode_sub",
86
+ agent=sub_agent,
87
+ session_service=InMemorySessionService(),
88
+ )
89
+ sub_session_id = str(uuid.uuid4())
90
+
91
+ # Compose the sub-agent prompt.
92
+ prompt = task.strip()
93
+ if context and context.strip():
94
+ prompt = f"{task.strip()}\n\nAdditional context:\n{context.strip()}"
95
+
96
+ # Cap sub-agent depth to avoid runaway API cost.
97
+ sub_max_calls = min(int(cfg.max_llm_calls or 48), 48)
98
+
99
+ try:
100
+ events = await run_turn(
101
+ sub_runner,
102
+ user_id="local",
103
+ session_id=sub_session_id,
104
+ prompt=prompt,
105
+ max_llm_calls=sub_max_calls,
106
+ cfg=cfg,
107
+ )
108
+ except Exception as e:
109
+ return {"error": f"Sub-agent error: {type(e).__name__}: {e}"}
110
+
111
+ # Extract only non-thinking text parts from the sub-agent's output.
112
+ parts: list[str] = []
113
+ for ev in events:
114
+ try:
115
+ if not ev.content or not ev.content.parts:
116
+ continue
117
+ if getattr(ev, "author", None) == "user":
118
+ continue
119
+ for part in ev.content.parts:
120
+ t = getattr(part, "text", None)
121
+ is_thought = getattr(part, "thought", None)
122
+ if isinstance(t, str) and t.strip() and not is_thought:
123
+ parts.append(t)
124
+ except Exception:
125
+ continue
126
+
127
+ result_text = "".join(parts).strip()
128
+ if not result_text:
129
+ result_text = (
130
+ "(Sub-agent completed the task but produced no text output. "
131
+ "It may have only called tools. Consider a more explicit task "
132
+ "that asks the sub-agent to summarise its findings.)"
133
+ )
134
+
135
+ return {"result": result_text}
136
+
137
+ return run_subtask
@@ -0,0 +1,41 @@
1
+ """
2
+ Think tool — structured reasoning scratchpad before acting.
3
+
4
+ Inspired by Anthropic's documented "think" tool pattern:
5
+ https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
6
+
7
+ Even though Gemini 2.5 has native dynamic thinking, an explicit think() call
8
+ improves quality on hard tasks because:
9
+ 1. It forces a structured reasoning checkpoint before irreversible actions.
10
+ 2. The written thought persists in the conversation history so later tool calls
11
+ can reference it.
12
+ 3. It creates a visible chain-of-reasoning the model can revise.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+
18
+ def make_think_tool():
19
+ def think(thought: str) -> dict:
20
+ """
21
+ Write out your reasoning, plan, or analysis before taking action.
22
+
23
+ This is a private scratchpad — the content is NOT shown to the user.
24
+ Use it when you need to think clearly before executing tools:
25
+
26
+ - Planning a multi-step edit: map out what files to touch and in what order
27
+ - Debugging an error: trace the execution path before deciding on a fix
28
+ - Weighing approaches: compare trade-offs before committing to one
29
+ - Verifying understanding: articulate your model of the code before editing
30
+ - Decomposing a complex task: break it into concrete sub-steps
31
+ - After reading multiple files: synthesise before writing
32
+
33
+ Call think() BEFORE a sequence of tool calls, especially before destructive
34
+ or irreversible actions (deletes, overwrites, force-pushes).
35
+
36
+ The return value is intentionally empty — the value is in the reasoning
37
+ itself being part of the conversation context.
38
+ """
39
+ return {"ok": True}
40
+
41
+ return think
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemcode
3
- Version: 0.3.17
3
+ Version: 0.3.18
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -69,6 +69,8 @@ src/gemcode/tools/filesystem.py
69
69
  src/gemcode/tools/search.py
70
70
  src/gemcode/tools/shell.py
71
71
  src/gemcode/tools/shell_gate.py
72
+ src/gemcode/tools/subtask.py
73
+ src/gemcode/tools/think.py
72
74
  src/gemcode/tools/todo.py
73
75
  src/gemcode/tools/web.py
74
76
  src/gemcode/tui/input_handler.py
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes