gemcode 0.3.17__tar.gz → 0.3.19__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.
- {gemcode-0.3.17/src/gemcode.egg-info → gemcode-0.3.19}/PKG-INFO +1 -1
- {gemcode-0.3.17 → gemcode-0.3.19}/pyproject.toml +1 -1
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/agent.py +73 -13
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/tool_prompt_manifest.py +4 -1
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/tool_registry.py +3 -1
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/tools/__init__.py +13 -2
- gemcode-0.3.19/src/gemcode/tools/subtask.py +137 -0
- gemcode-0.3.19/src/gemcode/tools/think.py +41 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/tui/scrollback.py +80 -9
- {gemcode-0.3.17 → gemcode-0.3.19/src/gemcode.egg-info}/PKG-INFO +1 -1
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode.egg-info/SOURCES.txt +2 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/LICENSE +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/MANIFEST.in +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/README.md +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/setup.cfg +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/__init__.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/__main__.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/audit.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/autocompact.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/callbacks.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/capability_routing.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/cli.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/compaction.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/computer_use/__init__.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/computer_use/browser_computer.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/config.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/context_budget.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/context_warning.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/credentials.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/hitl_session.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/interactions.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/invoke.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/kairos_daemon.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/limits.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/live_audio_engine.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/logging_config.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/mcp_loader.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/memory/__init__.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/memory/embedding_memory_service.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/memory/file_memory_service.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/modality_tools.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/model_errors.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/model_routing.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/paths.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/permissions.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/plugins/__init__.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/prompt_suggestions.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/query/__init__.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/query/config.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/query/deps.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/query/engine.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/query/stop_hooks.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/query/token_budget.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/query/transitions.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/repl_commands.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/repl_slash.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/session_runtime.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/slash_commands.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/thinking.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/tools/bash.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/tools/edit.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/tools/filesystem.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/tools/search.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/tools/shell.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/tools/shell_gate.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/tools/todo.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/tools/web.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/tools_inspector.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/trust.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/tui/input_handler.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/tui/spinner.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/tui/welcome_banner.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/tui/welcome_rich.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/version.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/vertex.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/web/__init__.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/web/claude_sse_adapter.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/web/terminal_repl.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode/workspace_hints.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode.egg-info/dependency_links.txt +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode.egg-info/entry_points.txt +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode.egg-info/requires.txt +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/src/gemcode.egg-info/top_level.txt +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/tests/test_agent_instruction.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/tests/test_autocompact.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/tests/test_capability_routing.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/tests/test_claude_web_adapter_sse.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/tests/test_cli_init.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/tests/test_computer_use_permissions.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/tests/test_context_budget.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/tests/test_context_warning.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/tests/test_credentials.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/tests/test_interactive_permission_ask.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/tests/test_kairos_scheduler.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/tests/test_modality_tools.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/tests/test_model_error_retry.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/tests/test_model_errors.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/tests/test_model_routing.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/tests/test_paths.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/tests/test_permissions.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/tests/test_prompt_suggestions.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/tests/test_repl_commands.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/tests/test_repl_slash.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/tests/test_slash_commands.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/tests/test_thinking_config.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/tests/test_token_budget.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/tests/test_tool_context_circulation.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/tests/test_tools.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/tests/test_tools_inspector.py +0 -0
- {gemcode-0.3.17 → gemcode-0.3.19}/tests/test_workspace_hints.py +0 -0
|
@@ -79,13 +79,26 @@ You run locally via the GemCode CLI. You are the same agent the user launched
|
|
|
79
79
|
|
|
80
80
|
## Core identity and approach
|
|
81
81
|
You are a senior engineer who *acts*, not just advises. When given a task:
|
|
82
|
-
1. **Orient** —
|
|
82
|
+
1. **Orient** — use `list_directory`, `glob_files`, `grep_content`, `read_file` to understand structure. These tools need **no permission** and are instant. Start here.
|
|
83
83
|
2. **Plan** — for complex tasks, call `todo_write` upfront to map out the work.
|
|
84
84
|
3. **Execute** — make the changes, run the checks, iterate.
|
|
85
85
|
4. **Verify** — confirm the result is correct before reporting done.
|
|
86
86
|
|
|
87
87
|
Never stop mid-task just because the first tool call succeeded. Keep going until the full task is complete or you hit a genuine blocker.
|
|
88
88
|
|
|
89
|
+
## CRITICAL: Read-only tools first — never bash for exploration
|
|
90
|
+
`bash` and `run_command` require permission confirmation by default. Always start with the **zero-permission** read-only tools:
|
|
91
|
+
|
|
92
|
+
| Instead of… | Use… |
|
|
93
|
+
|---|---|
|
|
94
|
+
| `bash("ls -la src/")` | `list_directory("src")` |
|
|
95
|
+
| `bash("find . -name '*.py'")` | `glob_files("**/*.py")` |
|
|
96
|
+
| `bash("cat file.py")` | `read_file("file.py")` |
|
|
97
|
+
| `bash("grep -r pattern .")` | `grep_content("pattern", "**/*")` |
|
|
98
|
+
| `bash("find . -type f \| head -50")` | `list_directory(".")` + `glob_files("**/*")` |
|
|
99
|
+
|
|
100
|
+
Only reach for `bash` or `run_command` when you actually need to **execute** something: run tests, build, git ops, start a server, install packages. **NEVER** use bash to list or read files.
|
|
101
|
+
|
|
89
102
|
## Thinking through hard problems
|
|
90
103
|
You have native deep thinking capability — use it actively:
|
|
91
104
|
- **Before** starting a complex refactor or architectural change, think: what are the dependencies? what could break? what is the minimal safe change?
|
|
@@ -94,11 +107,12 @@ You have native deep thinking capability — use it actively:
|
|
|
94
107
|
- **For trade-off decisions** (which library, which pattern, which approach): reason through the pros/cons given this specific codebase.
|
|
95
108
|
|
|
96
109
|
## Interpreting requests
|
|
97
|
-
- Treat every message as a software engineering task
|
|
110
|
+
- Treat every message as a software engineering task — start working immediately. Do NOT respond with just a greeting or introduction.
|
|
98
111
|
- If vague ("fix it", "the config", "rename that"), **infer from the repo**: search, read, then act. Do not give abstract advice when concrete files exist.
|
|
99
|
-
- If the user refers to symbols or behaviors, **find them** with
|
|
112
|
+
- If the user refers to symbols or behaviors, **find them** with `glob_files`/`grep_content`/`list_directory` — never ask them to paste paths you can discover yourself.
|
|
100
113
|
- **Never propose edits to files you haven't read.** Read first, then edit.
|
|
101
114
|
- When something fails, diagnose (re-read the error, check assumptions) before switching strategy. Do not repeat the same failed call.
|
|
115
|
+
- For analysis tasks ("analyse X", "explain X", "what does X do"): immediately start reading files with `list_directory` + `read_file` + `grep_content`. Produce concrete findings, not hypotheses.
|
|
102
116
|
|
|
103
117
|
## Tool selection guide
|
|
104
118
|
|
|
@@ -151,21 +165,34 @@ You have native deep thinking capability — use it actively:
|
|
|
151
165
|
- `web_fetch("https://registry.npmjs.org/react/latest")` — npm package info
|
|
152
166
|
- Use when you need to look up an API, check the latest version, or read documentation.
|
|
153
167
|
|
|
154
|
-
###
|
|
168
|
+
### Reasoning and planning
|
|
169
|
+
- **`think`** — private reasoning scratchpad. Write your analysis, plan, or hypothesis here before acting. Not shown to the user. Use before:
|
|
170
|
+
- A complex multi-file edit or refactor
|
|
171
|
+
- A debugging session where you need to trace logic before touching code
|
|
172
|
+
- Any destructive action (delete, force-push) — think first
|
|
173
|
+
- Choosing between approaches with real trade-offs
|
|
174
|
+
|
|
155
175
|
- **`todo_write`** — track work items. Use for any task with 3+ steps.
|
|
156
176
|
- Create at task start, mark completed as you finish, merge updates.
|
|
157
177
|
|
|
178
|
+
- **`run_subtask`** — spawn an isolated sub-agent with its own fresh context window.
|
|
179
|
+
- The sub-agent has the same tools (bash, read_file, grep, etc.) but starts from scratch.
|
|
180
|
+
- Use when a task would bloat your context too much: e.g. "read all 40 test files and find patterns"
|
|
181
|
+
- Use to parallelize: issue multiple `run_subtask` calls in one turn for concurrent exploration
|
|
182
|
+
- Use for verification passes: "check all files I edited for consistency and syntax errors"
|
|
183
|
+
- Always give the sub-agent enough context to operate independently.
|
|
184
|
+
- End your task prompt with "Summarise your findings clearly." so the result is useful.
|
|
185
|
+
|
|
158
186
|
## Multi-step task execution
|
|
159
187
|
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
188
|
|
|
161
189
|
**Standard workflow for complex tasks:**
|
|
162
190
|
1. `todo_write` — plan the work items
|
|
163
|
-
2. Explore
|
|
164
|
-
3.
|
|
165
|
-
4.
|
|
166
|
-
5.
|
|
167
|
-
6.
|
|
168
|
-
7. Update todos — mark done as you go
|
|
191
|
+
2. **Explore (read-only, no permission needed)** — `list_directory` + `glob_files` to map structure; `grep_content` to find symbols; `read_file` to understand code. Do NOT use `bash` for this step.
|
|
192
|
+
3. **Edit** — `write_file` / `search_replace` for changes
|
|
193
|
+
4. **Verify (shell needed)** — `bash("pytest ...")` or `bash("npm run build ...")` or `bash("git diff")`
|
|
194
|
+
5. Fix — iterate on failures, re-verify
|
|
195
|
+
6. Update todos — mark done as you go
|
|
169
196
|
|
|
170
197
|
**Do not stop after step 2 or 3** — complete the full task.
|
|
171
198
|
|
|
@@ -174,8 +201,25 @@ Issue independent tool calls in the same turn when outputs don't depend on each
|
|
|
174
201
|
- Reading multiple files simultaneously ✓
|
|
175
202
|
- Grepping for different patterns at once ✓
|
|
176
203
|
- `list_directory` + `glob_files` in parallel ✓
|
|
204
|
+
- Multiple `run_subtask` calls in one turn for parallel sub-agent exploration ✓
|
|
177
205
|
Sequential: when step B needs step A's result.
|
|
178
206
|
|
|
207
|
+
## Sub-agent delegation (orchestrator-worker pattern)
|
|
208
|
+
Use `run_subtask` when the work is better done in an isolated context:
|
|
209
|
+
- **Context preservation**: offload reading/analysing large areas of the codebase so your own context stays clean and focused on the high-level task.
|
|
210
|
+
- **Parallel exploration**: launch multiple sub-agents simultaneously to research different subsystems ("analyse auth module", "analyse payment module") then synthesise.
|
|
211
|
+
- **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."
|
|
212
|
+
- **Deep research**: when you need to exhaustively search something (50+ files, long documentation pages) delegate it rather than polluting the main conversation.
|
|
213
|
+
|
|
214
|
+
The sub-agent inherits your permission settings and returns its final text as `result`. Treat it as a trusted colleague returning a written summary.
|
|
215
|
+
|
|
216
|
+
## Evaluator-optimizer loop
|
|
217
|
+
For tasks where quality matters:
|
|
218
|
+
1. Complete the task (execute tools, write code, run commands)
|
|
219
|
+
2. Spawn a verification `run_subtask` or use `bash` to run tests/lint
|
|
220
|
+
3. If verification fails, read the error, fix, re-verify
|
|
221
|
+
4. Report done only when verified
|
|
222
|
+
|
|
179
223
|
## Error recovery
|
|
180
224
|
- **Test/build failures**: read the full error, identify the exact line, fix, re-run.
|
|
181
225
|
- **Tool errors**: diagnose why it failed before retrying — don't repeat the exact same call.
|
|
@@ -207,9 +251,25 @@ All file tools use paths **relative to the project root** (where GemCode was sta
|
|
|
207
251
|
return base
|
|
208
252
|
|
|
209
253
|
|
|
210
|
-
def build_root_agent(
|
|
211
|
-
|
|
212
|
-
|
|
254
|
+
def build_root_agent(
|
|
255
|
+
cfg: GemCodeConfig,
|
|
256
|
+
extra_tools: list | None = None,
|
|
257
|
+
*,
|
|
258
|
+
_tools: list | None = None,
|
|
259
|
+
) -> LlmAgent:
|
|
260
|
+
"""Create the root LlmAgent with tools and callbacks (no Runner).
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
cfg: Runtime configuration.
|
|
264
|
+
extra_tools: Additional tools to append (e.g. modality tools from session_runtime).
|
|
265
|
+
_tools: Override the entire tool list (used by run_subtask sub-agents to pass a
|
|
266
|
+
pre-built list that excludes run_subtask itself, preventing recursion).
|
|
267
|
+
When set, build_function_tools() is NOT called.
|
|
268
|
+
"""
|
|
269
|
+
if _tools is not None:
|
|
270
|
+
tools = list(_tools)
|
|
271
|
+
else:
|
|
272
|
+
tools = build_function_tools(cfg)
|
|
213
273
|
if getattr(cfg, "enable_memory", False):
|
|
214
274
|
# ADK preload_memory injects retrieved memories into the next llm_request.
|
|
215
275
|
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)}.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -236,7 +236,76 @@ async def run_gemcode_scrollback_tui(
|
|
|
236
236
|
pass
|
|
237
237
|
return tool_name, hint
|
|
238
238
|
|
|
239
|
+
# ── Live status indicator ─────────────────────────────────────────────────
|
|
240
|
+
# spinner_active[0] = True while "⟳ Working..." is on the current line.
|
|
241
|
+
# _clear_spinner() uses \r + ANSI erase to overwrite it before real output.
|
|
242
|
+
spinner_active = [False]
|
|
243
|
+
|
|
244
|
+
def _clear_spinner() -> None:
|
|
245
|
+
if spinner_active[0]:
|
|
246
|
+
sys.stdout.write("\r\033[K")
|
|
247
|
+
sys.stdout.flush()
|
|
248
|
+
spinner_active[0] = False
|
|
249
|
+
|
|
250
|
+
def _fmt_tool_result(resp: object) -> str:
|
|
251
|
+
"""One-line summary of a tool execution result."""
|
|
252
|
+
try:
|
|
253
|
+
d = resp if isinstance(resp, dict) else {}
|
|
254
|
+
# ADK may wrap the return value; try both the raw dict and a nested "result" key.
|
|
255
|
+
inner = d.get("result", d)
|
|
256
|
+
if not isinstance(inner, dict):
|
|
257
|
+
inner = d
|
|
258
|
+
err = inner.get("error") or d.get("error")
|
|
259
|
+
if err:
|
|
260
|
+
return f"\u2717 {str(err)[:80]}"
|
|
261
|
+
exit_code = inner.get("exit_code")
|
|
262
|
+
if exit_code is not None:
|
|
263
|
+
icon = "\u2713" if exit_code == 0 else f"\u2717 exit {exit_code}"
|
|
264
|
+
out = str(inner.get("stdout", "") or "").strip()
|
|
265
|
+
first = out.split("\n")[0][:80] if out else ""
|
|
266
|
+
if not first:
|
|
267
|
+
first = str(inner.get("stderr", "") or "").strip().split("\n")[0][:80]
|
|
268
|
+
return f"{icon} {first}" if first else icon
|
|
269
|
+
if inner.get("content") is not None:
|
|
270
|
+
lines = str(inner["content"]).count("\n") + 1
|
|
271
|
+
return f"\u2713 {lines} lines"
|
|
272
|
+
if inner.get("files") is not None:
|
|
273
|
+
return f"\u2713 {len(inner['files'])} files"
|
|
274
|
+
if inner.get("matches") is not None:
|
|
275
|
+
return f"\u2713 {len(inner['matches'])} matches"
|
|
276
|
+
if inner.get("ok") or d.get("ok"):
|
|
277
|
+
return "\u2713"
|
|
278
|
+
return ""
|
|
279
|
+
except Exception:
|
|
280
|
+
return ""
|
|
281
|
+
|
|
282
|
+
def _render_tool_results(ev) -> None:
|
|
283
|
+
"""Print a brief one-line summary when a tool finishes executing."""
|
|
284
|
+
try:
|
|
285
|
+
frs: list = []
|
|
286
|
+
try:
|
|
287
|
+
frs = ev.get_function_responses() or []
|
|
288
|
+
except Exception:
|
|
289
|
+
pass
|
|
290
|
+
if not frs and ev.content and ev.content.parts:
|
|
291
|
+
for part in ev.content.parts:
|
|
292
|
+
fr = getattr(part, "function_response", None)
|
|
293
|
+
if fr is not None:
|
|
294
|
+
frs.append(fr)
|
|
295
|
+
for fr in frs:
|
|
296
|
+
name = getattr(fr, "name", "") or ""
|
|
297
|
+
if name == _ADK_REQUEST_CONFIRMATION:
|
|
298
|
+
continue
|
|
299
|
+
_clear_spinner()
|
|
300
|
+
resp = getattr(fr, "response", {}) or {}
|
|
301
|
+
summary = _fmt_tool_result(resp)
|
|
302
|
+
if summary:
|
|
303
|
+
print(f" ⎿ {ansi.dim}\u21b3 {summary}{ansi.reset}")
|
|
304
|
+
except Exception:
|
|
305
|
+
pass
|
|
306
|
+
|
|
239
307
|
def _render_tool_calls(ev) -> None:
|
|
308
|
+
_clear_spinner()
|
|
240
309
|
try:
|
|
241
310
|
fcs = ev.get_function_calls() or []
|
|
242
311
|
except Exception:
|
|
@@ -331,9 +400,18 @@ async def run_gemcode_scrollback_tui(
|
|
|
331
400
|
kwargs["run_config"] = run_config
|
|
332
401
|
# (We don't handle token budget reset here; full-screen TUI does.)
|
|
333
402
|
|
|
403
|
+
# Show a live "Working…" indicator so the user never wonders if the
|
|
404
|
+
# agent froze. _clear_spinner() overwrites it with \r+ANSI-erase
|
|
405
|
+
# before the first real output (tool call, tool result, or response).
|
|
406
|
+
if ansi.enabled:
|
|
407
|
+
spinner_active[0] = True
|
|
408
|
+
sys.stdout.write(f" {ansi.dim}\u27f3 Working\u2026{ansi.reset}")
|
|
409
|
+
sys.stdout.flush()
|
|
410
|
+
|
|
334
411
|
async for ev in runner.run_async(**kwargs):
|
|
335
412
|
events.append(ev)
|
|
336
413
|
_render_tool_calls(ev)
|
|
414
|
+
_render_tool_results(ev)
|
|
337
415
|
try:
|
|
338
416
|
if not ev.content or not ev.content.parts:
|
|
339
417
|
continue
|
|
@@ -351,6 +429,8 @@ async def run_gemcode_scrollback_tui(
|
|
|
351
429
|
except Exception:
|
|
352
430
|
continue
|
|
353
431
|
|
|
432
|
+
_clear_spinner() # ensure "Working…" is gone before any response line
|
|
433
|
+
|
|
354
434
|
if not assistant_wrote_text and _events_had_non_confirmation_tools(events):
|
|
355
435
|
await typewrite(
|
|
356
436
|
f"{ansi.dim}(Tools ran without a text reply in this step; "
|
|
@@ -377,15 +457,6 @@ async def run_gemcode_scrollback_tui(
|
|
|
377
457
|
await typewrite(thought_text)
|
|
378
458
|
sys.stdout.write("\n")
|
|
379
459
|
sys.stdout.flush()
|
|
380
|
-
else:
|
|
381
|
-
# Keep thinking visible even if the model produced no separate
|
|
382
|
-
# "thought" channel output.
|
|
383
|
-
print(
|
|
384
|
-
f" ⎿ {ansi.dim}{ansi.bold}\u2234 Thinking{ansi.reset}: "
|
|
385
|
-
"(no thinking output)"
|
|
386
|
-
)
|
|
387
|
-
print("")
|
|
388
|
-
|
|
389
460
|
if buffered_final:
|
|
390
461
|
sys.stdout.write(f" ⎿ {ansi.bold}GemCode{ansi.reset}: ")
|
|
391
462
|
sys.stdout.flush()
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|