gemcode 0.3.28__tar.gz → 0.3.29__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.28/src/gemcode.egg-info → gemcode-0.3.29}/PKG-INFO +1 -1
- {gemcode-0.3.28 → gemcode-0.3.29}/pyproject.toml +1 -1
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/agent.py +77 -4
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/config.py +16 -0
- gemcode-0.3.29/src/gemcode/refine.py +204 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/repl_commands.py +1 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/repl_slash.py +43 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/session_runtime.py +24 -1
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/tool_prompt_manifest.py +3 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/tui/input_handler.py +1 -0
- {gemcode-0.3.28 → gemcode-0.3.29/src/gemcode.egg-info}/PKG-INFO +1 -1
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode.egg-info/SOURCES.txt +1 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/LICENSE +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/MANIFEST.in +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/README.md +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/setup.cfg +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/__init__.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/__main__.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/audit.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/autocompact.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/callbacks.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/capability_routing.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/cli.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/compaction.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/computer_use/__init__.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/computer_use/browser_computer.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/context_budget.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/context_warning.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/credentials.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/hitl_session.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/interactions.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/invoke.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/kairos_daemon.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/limits.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/live_audio_engine.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/logging_config.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/mcp_loader.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/memory/__init__.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/memory/embedding_memory_service.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/memory/file_memory_service.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/modality_tools.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/model_errors.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/model_routing.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/paths.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/permissions.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/plugins/__init__.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/prompt_suggestions.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/query/__init__.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/query/config.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/query/deps.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/query/engine.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/query/stop_hooks.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/query/token_budget.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/query/transitions.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/slash_commands.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/thinking.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/tool_registry.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/tools/__init__.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/tools/bash.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/tools/browser.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/tools/edit.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/tools/filesystem.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/tools/search.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/tools/shell.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/tools/shell_gate.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/tools/subtask.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/tools/think.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/tools/todo.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/tools/web.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/tools_inspector.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/trust.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/tui/scrollback.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/tui/spinner.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/tui/welcome_banner.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/tui/welcome_rich.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/version.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/vertex.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/web/__init__.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/web/claude_sse_adapter.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/web/terminal_repl.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode/workspace_hints.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode.egg-info/dependency_links.txt +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode.egg-info/entry_points.txt +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode.egg-info/requires.txt +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/src/gemcode.egg-info/top_level.txt +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/tests/test_agent_instruction.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/tests/test_autocompact.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/tests/test_capability_routing.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/tests/test_claude_web_adapter_sse.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/tests/test_cli_init.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/tests/test_computer_use_permissions.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/tests/test_context_budget.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/tests/test_context_warning.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/tests/test_credentials.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/tests/test_interactive_permission_ask.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/tests/test_kairos_scheduler.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/tests/test_modality_tools.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/tests/test_model_error_retry.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/tests/test_model_errors.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/tests/test_model_routing.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/tests/test_paths.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/tests/test_permissions.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/tests/test_prompt_suggestions.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/tests/test_repl_commands.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/tests/test_repl_slash.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/tests/test_slash_commands.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/tests/test_thinking_config.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/tests/test_token_budget.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/tests/test_tool_context_circulation.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/tests/test_tools.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/tests/test_tools_inspector.py +0 -0
- {gemcode-0.3.28 → gemcode-0.3.29}/tests/test_workspace_hints.py +0 -0
|
@@ -77,8 +77,12 @@ def _build_runtime_facts(cfg: GemCodeConfig) -> str:
|
|
|
77
77
|
caps.append(f"memory ON ({mem_kind}, stored at {mem_path}; ADK preload_memory injects relevant memories before each turn)")
|
|
78
78
|
if getattr(cfg, "enable_computer_use", False):
|
|
79
79
|
caps.append("computer_use ON (tools: navigate, click_at, type_text_at, browser_screenshot, browser_find_element, etc.)")
|
|
80
|
+
if getattr(cfg, "enable_code_executor", False):
|
|
81
|
+
caps.append("code_executor ON — you can write Python code blocks and they will be executed safely via Gemini's built-in sandboxed executor; results appear as code_execution_result events. Use this for math, data processing, quick tests, and anything that would otherwise require a shell command.")
|
|
82
|
+
if getattr(cfg, "enable_artifacts", True):
|
|
83
|
+
caps.append("artifacts ON — use save_artifact(filename, bytes, mime_type) / load_artifact(filename) to store large/binary outputs (screenshots, PDFs, generated files) outside session history. Artifacts are keyed by filename; prefix 'user:' for cross-session persistence.")
|
|
80
84
|
if not caps:
|
|
81
|
-
caps.append("none enabled (use /research on, /embeddings on, /memory on, /computer on to enable)")
|
|
85
|
+
caps.append("none enabled (use /research on, /embeddings on, /memory on, /computer on, /code on to enable)")
|
|
82
86
|
caps_text = "\n".join(f" - {c}" for c in caps)
|
|
83
87
|
|
|
84
88
|
# ── Limits ───────────────────────────────────────────────────────────────
|
|
@@ -426,6 +430,41 @@ Use `run_subtask` when the work is better done in an isolated context:
|
|
|
426
430
|
|
|
427
431
|
The sub-agent inherits your permission settings and returns its final text as `result`. Treat it as a trusted colleague returning a written summary.
|
|
428
432
|
|
|
433
|
+
## ADK Special Tools (always available when ADK supports them)
|
|
434
|
+
|
|
435
|
+
### `get_user_choice`
|
|
436
|
+
Present the user with a structured multi-option prompt rather than open-ended questions.
|
|
437
|
+
Use when you need the user to pick from 2–6 specific options (e.g. "Which framework would you like?", "Choose migration strategy: A, B, or C").
|
|
438
|
+
This provides a better UX than asking them to type a free-form answer.
|
|
439
|
+
|
|
440
|
+
### `load_artifacts`
|
|
441
|
+
Load binary/large artifacts that were saved in a previous turn or by a sub-agent.
|
|
442
|
+
Artifacts are keyed by filename (e.g. "report.pdf", "screenshot.png", "output.json").
|
|
443
|
+
Use `user:filename` prefix for user-scoped artifacts that persist across sessions.
|
|
444
|
+
After loading, the artifact bytes are available for further processing (display, analysis, transformation).
|
|
445
|
+
|
|
446
|
+
### `exit_loop`
|
|
447
|
+
Signal the surrounding LoopAgent to stop iterating and return the final result.
|
|
448
|
+
Only meaningful when this agent is running inside an ADK LoopAgent pipeline.
|
|
449
|
+
Call this when the task is complete and no further iterations are needed.
|
|
450
|
+
|
|
451
|
+
## Artifacts — storing large outputs
|
|
452
|
+
When `artifacts ON` (see Runtime facts above):
|
|
453
|
+
- **Save** large generated content as artifacts instead of printing them inline:
|
|
454
|
+
- Screenshots from computer_use: save as "screenshot.png" artifact
|
|
455
|
+
- Generated reports/PDFs: save as "report.pdf" artifact
|
|
456
|
+
- Large JSON data: save as "data.json" artifact
|
|
457
|
+
- **Reference** artifacts in instructions via `{artifact.filename?}` template syntax
|
|
458
|
+
- Artifacts are keyed by filename; `user:` prefix = cross-session persistence
|
|
459
|
+
|
|
460
|
+
## Code Executor (sandboxed Python)
|
|
461
|
+
When `code_executor ON` (see Runtime facts above):
|
|
462
|
+
- You can write Python code blocks in your response and the Gemini API executes them safely
|
|
463
|
+
- The result appears as a `code_execution_result` event with stdout and the outcome
|
|
464
|
+
- Best for: math calculations, data transformation, unit testing logic, quick experiments
|
|
465
|
+
- The sandbox does NOT have internet access or filesystem access — use for pure computation
|
|
466
|
+
- For file I/O or shell commands, use the standard tools (`bash`, `write_file`, etc.)
|
|
467
|
+
|
|
429
468
|
## Evaluator-optimizer loop
|
|
430
469
|
For tasks where quality matters:
|
|
431
470
|
1. Complete the task (execute tools, write code, run commands)
|
|
@@ -470,6 +509,17 @@ All file tools use paths **relative to the project root** (where GemCode was sta
|
|
|
470
509
|
return base
|
|
471
510
|
|
|
472
511
|
|
|
512
|
+
def _build_code_executor(cfg: GemCodeConfig):
|
|
513
|
+
"""Return an ADK BuiltInCodeExecutor when enable_code_executor=True, else None."""
|
|
514
|
+
if not getattr(cfg, "enable_code_executor", False):
|
|
515
|
+
return None
|
|
516
|
+
try:
|
|
517
|
+
from google.adk.code_executors import BuiltInCodeExecutor
|
|
518
|
+
return BuiltInCodeExecutor()
|
|
519
|
+
except Exception:
|
|
520
|
+
return None
|
|
521
|
+
|
|
522
|
+
|
|
473
523
|
def build_root_agent(
|
|
474
524
|
cfg: GemCodeConfig,
|
|
475
525
|
extra_tools: list | None = None,
|
|
@@ -488,12 +538,19 @@ def build_root_agent(
|
|
|
488
538
|
if _tools is not None:
|
|
489
539
|
tools = list(_tools)
|
|
490
540
|
else:
|
|
491
|
-
|
|
541
|
+
tools = build_function_tools(cfg)
|
|
492
542
|
if getattr(cfg, "enable_memory", False):
|
|
493
543
|
# ADK preload_memory injects retrieved memories into the next llm_request.
|
|
494
544
|
from google.adk.tools import preload_memory
|
|
495
|
-
|
|
496
545
|
tools = [preload_memory, *tools]
|
|
546
|
+
|
|
547
|
+
# ADK built-in interactive + artifact tools — always available when ADK supports them.
|
|
548
|
+
try:
|
|
549
|
+
from google.adk.tools import get_user_choice, load_artifacts, exit_loop
|
|
550
|
+
tools = [*tools, get_user_choice, load_artifacts, exit_loop]
|
|
551
|
+
except Exception:
|
|
552
|
+
pass
|
|
553
|
+
|
|
497
554
|
if extra_tools:
|
|
498
555
|
tools = [*tools, *extra_tools]
|
|
499
556
|
|
|
@@ -548,15 +605,31 @@ def build_root_agent(
|
|
|
548
605
|
tool_config=tool_cfg,
|
|
549
606
|
)
|
|
550
607
|
|
|
551
|
-
|
|
608
|
+
# global_instruction applies to the entire agent tree (including sub-agents
|
|
609
|
+
# spawned via run_subtask or multi-agent delegation). Keep it short — it's
|
|
610
|
+
# prepended to every agent's effective instruction.
|
|
611
|
+
global_instr = (
|
|
612
|
+
"You are GemCode, an expert software engineering agent powered by Google Gemini. "
|
|
613
|
+
"Act, don't advise. Complete tasks fully and autonomously. "
|
|
614
|
+
"Think before destructive actions. Use read-only tools before shell/write tools."
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
agent_kwargs: dict = dict(
|
|
552
618
|
model=cfg.model,
|
|
553
619
|
name="gemcode",
|
|
554
620
|
instruction=build_instruction(cfg),
|
|
621
|
+
global_instruction=global_instr,
|
|
555
622
|
tools=tools,
|
|
556
623
|
generate_content_config=gen_cfg,
|
|
557
624
|
**cb_kwargs,
|
|
558
625
|
)
|
|
559
626
|
|
|
627
|
+
code_executor = _build_code_executor(cfg)
|
|
628
|
+
if code_executor is not None:
|
|
629
|
+
agent_kwargs["code_executor"] = code_executor
|
|
630
|
+
|
|
631
|
+
return LlmAgent(**agent_kwargs)
|
|
632
|
+
|
|
560
633
|
|
|
561
634
|
def create_runner(cfg: GemCodeConfig, extra_tools: list | None = None):
|
|
562
635
|
"""Backward-compatible: prefer `gemcode.session_runtime.create_runner`."""
|
|
@@ -251,6 +251,22 @@ class GemCodeConfig:
|
|
|
251
251
|
default_factory=lambda: _truthy_env("GEMCODE_SHOW_FULL_THINKING", default=False)
|
|
252
252
|
)
|
|
253
253
|
|
|
254
|
+
# Enable ADK BuiltInCodeExecutor for safe sandboxed Python execution via
|
|
255
|
+
# Gemini's code execution API. When on, the agent can write and run Python
|
|
256
|
+
# snippets inline (math, data processing, quick tests) without requiring
|
|
257
|
+
# bash/shell permissions. Requires a Gemini model that supports code execution
|
|
258
|
+
# (gemini-2.5-flash, gemini-2.5-pro, gemini-3.x).
|
|
259
|
+
enable_code_executor: bool = field(
|
|
260
|
+
default_factory=lambda: _truthy_env("GEMCODE_ENABLE_CODE_EXECUTOR", default=False)
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
# Enable ADK artifact service for storing large/binary outputs (screenshots,
|
|
264
|
+
# generated files, reports) outside of session history.
|
|
265
|
+
# When on, the agent can save_artifact / load_artifact keyed by filename.
|
|
266
|
+
enable_artifacts: bool = field(
|
|
267
|
+
default_factory=lambda: _truthy_env("GEMCODE_ENABLE_ARTIFACTS", default=True)
|
|
268
|
+
)
|
|
269
|
+
|
|
254
270
|
def __post_init__(self) -> None:
|
|
255
271
|
self.project_root = self.project_root.resolve()
|
|
256
272
|
# Default agentic depth when env omits GEMCODE_MAX_LLM_CALLS (was: None → SDK default).
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Iterative refinement pipelines using ADK LoopAgent + SequentialAgent.
|
|
3
|
+
|
|
4
|
+
This module exposes factory functions for building multi-agent orchestration
|
|
5
|
+
patterns that GemCode can invoke for complex, multi-step tasks:
|
|
6
|
+
|
|
7
|
+
- build_refine_loop(): LoopAgent that loops write→test→fix until tests pass
|
|
8
|
+
or max_iterations is reached (escalates via exit_loop tool).
|
|
9
|
+
|
|
10
|
+
- build_sequential_pipeline(): SequentialAgent that runs N specialist agents
|
|
11
|
+
in order, passing results through session state via output_key.
|
|
12
|
+
|
|
13
|
+
- build_parallel_research(): ParallelAgent that fans out N research sub-agents
|
|
14
|
+
then collects their outputs.
|
|
15
|
+
|
|
16
|
+
These are "heavy" patterns; they are NOT wired by default — callers instantiate
|
|
17
|
+
them on demand (e.g. via /refine slash command or run_subtask).
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import logging
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
log = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
# Guard: check what's available in the installed ADK version
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
try:
|
|
31
|
+
from google.adk.agents import LoopAgent, SequentialAgent, ParallelAgent, LlmAgent # type: ignore
|
|
32
|
+
_ADK_WORKFLOW_AGENTS_OK = True
|
|
33
|
+
except ImportError:
|
|
34
|
+
_ADK_WORKFLOW_AGENTS_OK = False
|
|
35
|
+
log.warning("google.adk workflow agents (LoopAgent/SequentialAgent/ParallelAgent) "
|
|
36
|
+
"not available — refine pipelines disabled.")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _require_workflow() -> None:
|
|
40
|
+
if not _ADK_WORKFLOW_AGENTS_OK:
|
|
41
|
+
raise RuntimeError(
|
|
42
|
+
"ADK workflow agents (LoopAgent / SequentialAgent / ParallelAgent) are not "
|
|
43
|
+
"available in the installed google-adk version. "
|
|
44
|
+
"Try: pip install -U google-adk"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
# Write → Test → Fix Loop
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
def build_refine_loop(
|
|
53
|
+
*,
|
|
54
|
+
model: str,
|
|
55
|
+
task_description: str,
|
|
56
|
+
test_command: str,
|
|
57
|
+
max_iterations: int = 8,
|
|
58
|
+
extra_tools: list | None = None,
|
|
59
|
+
) -> Any:
|
|
60
|
+
"""
|
|
61
|
+
Build a LoopAgent that iteratively writes code, runs tests, and fixes
|
|
62
|
+
failures until all tests pass or ``max_iterations`` is exhausted.
|
|
63
|
+
|
|
64
|
+
Architecture:
|
|
65
|
+
LoopAgent(max_iterations=N)
|
|
66
|
+
├─ writer_agent — writes / edits code based on current task + test output
|
|
67
|
+
├─ tester_agent — runs the test command via bash, stores result in state
|
|
68
|
+
└─ checker_agent — inspects test result; calls exit_loop if tests pass
|
|
69
|
+
|
|
70
|
+
State keys used:
|
|
71
|
+
"task" — original task description (seeded by caller)
|
|
72
|
+
"test_command" — shell command to run tests
|
|
73
|
+
"test_output" — stdout/stderr from last test run
|
|
74
|
+
"test_passed" — "yes" if tests passed
|
|
75
|
+
"iteration" — current loop count (auto-incremented by writer)
|
|
76
|
+
|
|
77
|
+
Returns the LoopAgent instance, ready to run via a Runner.
|
|
78
|
+
|
|
79
|
+
Example usage:
|
|
80
|
+
loop = build_refine_loop(model="gemini-2.5-flash",
|
|
81
|
+
task_description="add unit tests for parser.py",
|
|
82
|
+
test_command="pytest tests/test_parser.py -x")
|
|
83
|
+
runner = Runner(agent=loop, app_name="refine", session_service=...)
|
|
84
|
+
async for event in runner.run_async(...):
|
|
85
|
+
...
|
|
86
|
+
"""
|
|
87
|
+
_require_workflow()
|
|
88
|
+
from google.adk.tools import exit_loop # type: ignore
|
|
89
|
+
|
|
90
|
+
from gemcode.tools import build_function_tools # type: ignore
|
|
91
|
+
|
|
92
|
+
# Shared tools that the sub-agents may use (no run_subtask to avoid recursion)
|
|
93
|
+
tools = extra_tools or []
|
|
94
|
+
|
|
95
|
+
writer = LlmAgent(
|
|
96
|
+
model=model,
|
|
97
|
+
name="writer_agent",
|
|
98
|
+
description="Writes or edits source code to fulfil the task, using test failure output as feedback.",
|
|
99
|
+
instruction=(
|
|
100
|
+
"You are a coding agent. Your goal: {task}.\n\n"
|
|
101
|
+
"Test command: {test_command}\n"
|
|
102
|
+
"Last test output:\n{test_output?}\n\n"
|
|
103
|
+
"Write or fix code to make the tests pass. "
|
|
104
|
+
"After making your edits, update state key 'iteration' by incrementing it."
|
|
105
|
+
),
|
|
106
|
+
tools=tools,
|
|
107
|
+
output_key="writer_notes",
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
tester = LlmAgent(
|
|
111
|
+
model=model,
|
|
112
|
+
name="tester_agent",
|
|
113
|
+
description="Runs the test suite and stores the output in session state.",
|
|
114
|
+
instruction=(
|
|
115
|
+
"Run the test command stored in state: {test_command}.\n"
|
|
116
|
+
"Use the bash tool to execute it. Store ALL stdout+stderr verbatim in "
|
|
117
|
+
"state key 'test_output'. "
|
|
118
|
+
"If the command exits with code 0 (success), set state key 'test_passed' to 'yes'. "
|
|
119
|
+
"If it fails, set 'test_passed' to 'no'."
|
|
120
|
+
),
|
|
121
|
+
tools=tools,
|
|
122
|
+
output_key="test_output",
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
checker = LlmAgent(
|
|
126
|
+
model=model,
|
|
127
|
+
name="checker_agent",
|
|
128
|
+
description=(
|
|
129
|
+
"Checks whether tests passed. If yes, calls exit_loop to terminate the "
|
|
130
|
+
"refinement cycle. If no, does nothing (loop continues)."
|
|
131
|
+
),
|
|
132
|
+
instruction=(
|
|
133
|
+
"Check the value of state key 'test_passed'.\n"
|
|
134
|
+
"If 'test_passed' is 'yes': call the exit_loop tool to stop the loop — "
|
|
135
|
+
"the task is complete.\n"
|
|
136
|
+
"If 'test_passed' is 'no': output a brief summary of the failures so the "
|
|
137
|
+
"writer knows what to fix next. Do NOT call exit_loop."
|
|
138
|
+
),
|
|
139
|
+
tools=[exit_loop, *tools],
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
return LoopAgent(
|
|
143
|
+
name="refine_loop",
|
|
144
|
+
description=(
|
|
145
|
+
f"Iterative write→test→fix loop for: {task_description[:120]}"
|
|
146
|
+
),
|
|
147
|
+
sub_agents=[writer, tester, checker],
|
|
148
|
+
max_iterations=max_iterations,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
# Sequential Pipeline
|
|
154
|
+
# ---------------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
def build_sequential_pipeline(
|
|
157
|
+
agents: list[Any],
|
|
158
|
+
*,
|
|
159
|
+
name: str = "sequential_pipeline",
|
|
160
|
+
description: str = "Runs specialist agents sequentially, passing results via state.",
|
|
161
|
+
) -> Any:
|
|
162
|
+
"""
|
|
163
|
+
Wrap a list of LlmAgents in a SequentialAgent.
|
|
164
|
+
|
|
165
|
+
Each agent should set ``output_key`` so subsequent agents can read its output
|
|
166
|
+
via {key} template substitution in their instructions.
|
|
167
|
+
|
|
168
|
+
Example:
|
|
169
|
+
pipeline = build_sequential_pipeline([
|
|
170
|
+
planner_agent, # output_key="plan"
|
|
171
|
+
coder_agent, # reads {plan}, output_key="code"
|
|
172
|
+
reviewer_agent, # reads {code}, output_key="review"
|
|
173
|
+
])
|
|
174
|
+
"""
|
|
175
|
+
_require_workflow()
|
|
176
|
+
return SequentialAgent(name=name, description=description, sub_agents=agents)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# ---------------------------------------------------------------------------
|
|
180
|
+
# Parallel Fan-Out
|
|
181
|
+
# ---------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
def build_parallel_research(
|
|
184
|
+
agents: list[Any],
|
|
185
|
+
*,
|
|
186
|
+
name: str = "parallel_research",
|
|
187
|
+
description: str = "Runs research agents in parallel, collecting results via state.",
|
|
188
|
+
) -> Any:
|
|
189
|
+
"""
|
|
190
|
+
Wrap a list of LlmAgents in a ParallelAgent.
|
|
191
|
+
|
|
192
|
+
Each agent MUST use a distinct ``output_key`` to avoid state key collisions.
|
|
193
|
+
|
|
194
|
+
Example:
|
|
195
|
+
research = build_parallel_research([
|
|
196
|
+
web_researcher, # output_key="web_results"
|
|
197
|
+
code_searcher, # output_key="code_results"
|
|
198
|
+
doc_reader, # output_key="doc_results"
|
|
199
|
+
])
|
|
200
|
+
# Wrap in a SequentialAgent to collect after:
|
|
201
|
+
pipeline = build_sequential_pipeline([research, synthesis_agent])
|
|
202
|
+
"""
|
|
203
|
+
_require_workflow()
|
|
204
|
+
return ParallelAgent(name=name, description=description, sub_agents=agents)
|
|
@@ -216,4 +216,5 @@ def slash_help_lines() -> list[str]:
|
|
|
216
216
|
" /permissions Show permission / HITL settings",
|
|
217
217
|
" /hooks Show post-turn hook configuration",
|
|
218
218
|
" /kairos How to launch the background parallel job scheduler",
|
|
219
|
+
" /code [on|off] Toggle sandboxed Python executor (ADK BuiltInCodeExecutor)",
|
|
219
220
|
]
|
|
@@ -357,6 +357,49 @@ async def process_repl_slash(
|
|
|
357
357
|
out()
|
|
358
358
|
return ReplSlashResult(skip_model_turn=True)
|
|
359
359
|
|
|
360
|
+
# ── /code ─────────────────────────────────────────────────────────────────
|
|
361
|
+
if name == "code":
|
|
362
|
+
args_s = (sc.args or "").strip().lower()
|
|
363
|
+
if args_s in ("on", "enable", "1", "true"):
|
|
364
|
+
cfg.enable_code_executor = True
|
|
365
|
+
out("Code executor: ON (sandboxed Python via Gemini built-in executor)")
|
|
366
|
+
out("The agent can now write Python code blocks and execute them safely,")
|
|
367
|
+
out("without requiring bash/shell permission. Best for: math, data, quick tests.")
|
|
368
|
+
out()
|
|
369
|
+
out("Note: the sandbox has no internet/filesystem access — use bash for I/O.")
|
|
370
|
+
return ReplSlashResult(skip_model_turn=True, force_rebuild_runner=True)
|
|
371
|
+
elif args_s in ("off", "disable", "0", "false"):
|
|
372
|
+
cfg.enable_code_executor = False
|
|
373
|
+
out("Code executor: OFF")
|
|
374
|
+
return ReplSlashResult(skip_model_turn=True, force_rebuild_runner=True)
|
|
375
|
+
else:
|
|
376
|
+
# Status
|
|
377
|
+
status = "ON" if getattr(cfg, "enable_code_executor", False) else "OFF"
|
|
378
|
+
out(f"Code executor: {status}")
|
|
379
|
+
out()
|
|
380
|
+
out("ADK BuiltInCodeExecutor — safe sandboxed Python execution via Gemini API.")
|
|
381
|
+
out()
|
|
382
|
+
out("What it does:")
|
|
383
|
+
out(" When ON, the agent can write Python code blocks directly in its response")
|
|
384
|
+
out(" and the Gemini API executes them in a sandboxed environment. The output")
|
|
385
|
+
out(" (stdout, result) is sent back so the agent can use it for further reasoning.")
|
|
386
|
+
out()
|
|
387
|
+
out("Best for:")
|
|
388
|
+
out(" - Math and numerical calculations")
|
|
389
|
+
out(" - Data processing and transformations")
|
|
390
|
+
out(" - Quick tests of logic or algorithms")
|
|
391
|
+
out(" - Anything that doesn't need filesystem or network access")
|
|
392
|
+
out()
|
|
393
|
+
out("Not for:")
|
|
394
|
+
out(" - Shell commands (use bash)")
|
|
395
|
+
out(" - Reading/writing files (use read_file / write_file)")
|
|
396
|
+
out(" - Internet requests (use web_fetch)")
|
|
397
|
+
out()
|
|
398
|
+
out("Toggle: /code on /code off")
|
|
399
|
+
out()
|
|
400
|
+
out("Supported models: gemini-2.5-flash, gemini-2.5-pro, gemini-3.x and newer.")
|
|
401
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
402
|
+
|
|
360
403
|
# ── /computer ────────────────────────────────────────────────────────────
|
|
361
404
|
if name in ("computer", "browser"):
|
|
362
405
|
args_s = (sc.args or "").strip().lower()
|
|
@@ -30,6 +30,23 @@ def session_db_path(cfg: GemCodeConfig) -> Path:
|
|
|
30
30
|
return cfg.project_root / ".gemcode" / "sessions.sqlite"
|
|
31
31
|
|
|
32
32
|
|
|
33
|
+
def _build_artifact_service(cfg: GemCodeConfig):
|
|
34
|
+
"""
|
|
35
|
+
Return an ADK ArtifactService for this session, or None if disabled.
|
|
36
|
+
|
|
37
|
+
Uses InMemoryArtifactService so artifacts are available within the session
|
|
38
|
+
without requiring GCS credentials. The agent can save screenshots, generated
|
|
39
|
+
files, large reports, etc. as artifacts to avoid bloating session history.
|
|
40
|
+
"""
|
|
41
|
+
if not getattr(cfg, "enable_artifacts", True):
|
|
42
|
+
return None
|
|
43
|
+
try:
|
|
44
|
+
from google.adk.artifacts import InMemoryArtifactService
|
|
45
|
+
return InMemoryArtifactService()
|
|
46
|
+
except Exception:
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
|
|
33
50
|
def create_runner(cfg: GemCodeConfig, extra_tools: list | None = None) -> Runner:
|
|
34
51
|
"""Construct Runner + SQLite session service + root LlmAgent."""
|
|
35
52
|
modality_tools = build_modality_extra_tools(cfg)
|
|
@@ -86,7 +103,9 @@ def create_runner(cfg: GemCodeConfig, extra_tools: list | None = None) -> Runner
|
|
|
86
103
|
else:
|
|
87
104
|
memory_service = FileMemoryService(mem_path)
|
|
88
105
|
|
|
89
|
-
|
|
106
|
+
artifact_service = _build_artifact_service(cfg)
|
|
107
|
+
|
|
108
|
+
runner_kwargs: dict = dict(
|
|
90
109
|
app_name="gemcode",
|
|
91
110
|
agent=agent,
|
|
92
111
|
session_service=session_service,
|
|
@@ -94,3 +113,7 @@ def create_runner(cfg: GemCodeConfig, extra_tools: list | None = None) -> Runner
|
|
|
94
113
|
memory_service=memory_service,
|
|
95
114
|
auto_create_session=True,
|
|
96
115
|
)
|
|
116
|
+
if artifact_service is not None:
|
|
117
|
+
runner_kwargs["artifact_service"] = artifact_service
|
|
118
|
+
|
|
119
|
+
return Runner(**runner_kwargs)
|
|
@@ -126,6 +126,9 @@ def build_tool_manifest(cfg: GemCodeConfig) -> str | None:
|
|
|
126
126
|
- Returns the sub-agent's final text as `result`.
|
|
127
127
|
- Use for: context-heavy exploration (reading 50+ files), parallel investigation of independent subsystems, verification passes after you finish work.
|
|
128
128
|
- Always give the sub-agent enough context to work independently; end the task with "Summarise your findings clearly."
|
|
129
|
+
- **`get_user_choice(question, choices)`** — present the user with a structured list of options (2–6 items). Returns the user's selected option. Use instead of open-ended questions when the set of valid responses is known and bounded (e.g. framework choice, migration strategy, naming convention).
|
|
130
|
+
- **`load_artifacts(filenames)`** — load one or more named artifacts (binary/large files) saved in this or a previous session. Returns the content of each. Use `user:filename` prefix for cross-session artifacts.
|
|
131
|
+
- **`exit_loop()`** — signal the surrounding LoopAgent to stop iterating and emit the final result. Only relevant when running inside a LoopAgent pipeline (e.g. the `/refine` write→test→fix loop).
|
|
129
132
|
|
|
130
133
|
### Read-only tools (no permission needed): {_fmt_list(read_only)}
|
|
131
134
|
Use proactively. Never use bash/run_command just to list or read files — these are instant and require zero approval.
|
|
@@ -56,6 +56,7 @@ SLASH_COMMANDS: list[tuple[str, str]] = [
|
|
|
56
56
|
("budget", "Set per-turn token budget · /budget <N> · /budget off"),
|
|
57
57
|
("limits", "Show/set execution limits (max_llm_calls, context, etc.)"),
|
|
58
58
|
("kairos", "Background parallel job scheduler — how to run gemcode kairos"),
|
|
59
|
+
("code", "Toggle sandboxed Python code executor (ADK BuiltInCodeExecutor)"),
|
|
59
60
|
("tools", "List all tools and their permission categories"),
|
|
60
61
|
("config", "Show full active configuration (all fields)"),
|
|
61
62
|
("permissions", "Show current permission mode (default / strict / yes)"),
|
|
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
|
|
File without changes
|