gemcode 0.3.27__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.
Files changed (115) hide show
  1. {gemcode-0.3.27/src/gemcode.egg-info → gemcode-0.3.29}/PKG-INFO +1 -1
  2. {gemcode-0.3.27 → gemcode-0.3.29}/pyproject.toml +1 -1
  3. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/agent.py +161 -9
  4. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/config.py +16 -0
  5. gemcode-0.3.29/src/gemcode/refine.py +204 -0
  6. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/repl_commands.py +2 -0
  7. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/repl_slash.py +82 -0
  8. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/session_runtime.py +24 -1
  9. gemcode-0.3.29/src/gemcode/tool_prompt_manifest.py +168 -0
  10. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tools/__init__.py +24 -2
  11. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tui/input_handler.py +2 -0
  12. {gemcode-0.3.27 → gemcode-0.3.29/src/gemcode.egg-info}/PKG-INFO +1 -1
  13. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode.egg-info/SOURCES.txt +1 -0
  14. gemcode-0.3.27/src/gemcode/tool_prompt_manifest.py +0 -142
  15. {gemcode-0.3.27 → gemcode-0.3.29}/LICENSE +0 -0
  16. {gemcode-0.3.27 → gemcode-0.3.29}/MANIFEST.in +0 -0
  17. {gemcode-0.3.27 → gemcode-0.3.29}/README.md +0 -0
  18. {gemcode-0.3.27 → gemcode-0.3.29}/setup.cfg +0 -0
  19. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/__init__.py +0 -0
  20. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/__main__.py +0 -0
  21. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/audit.py +0 -0
  22. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/autocompact.py +0 -0
  23. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/callbacks.py +0 -0
  24. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/capability_routing.py +0 -0
  25. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/cli.py +0 -0
  26. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/compaction.py +0 -0
  27. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/computer_use/__init__.py +0 -0
  28. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/computer_use/browser_computer.py +0 -0
  29. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/context_budget.py +0 -0
  30. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/context_warning.py +0 -0
  31. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/credentials.py +0 -0
  32. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/hitl_session.py +0 -0
  33. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/interactions.py +0 -0
  34. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/invoke.py +0 -0
  35. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/kairos_daemon.py +0 -0
  36. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/limits.py +0 -0
  37. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/live_audio_engine.py +0 -0
  38. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/logging_config.py +0 -0
  39. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/mcp_loader.py +0 -0
  40. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/memory/__init__.py +0 -0
  41. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/memory/embedding_memory_service.py +0 -0
  42. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/memory/file_memory_service.py +0 -0
  43. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/modality_tools.py +0 -0
  44. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/model_errors.py +0 -0
  45. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/model_routing.py +0 -0
  46. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/paths.py +0 -0
  47. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/permissions.py +0 -0
  48. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/plugins/__init__.py +0 -0
  49. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
  50. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
  51. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/prompt_suggestions.py +0 -0
  52. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/query/__init__.py +0 -0
  53. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/query/config.py +0 -0
  54. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/query/deps.py +0 -0
  55. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/query/engine.py +0 -0
  56. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/query/stop_hooks.py +0 -0
  57. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/query/token_budget.py +0 -0
  58. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/query/transitions.py +0 -0
  59. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/slash_commands.py +0 -0
  60. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/thinking.py +0 -0
  61. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tool_registry.py +0 -0
  62. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tools/bash.py +0 -0
  63. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tools/browser.py +0 -0
  64. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tools/edit.py +0 -0
  65. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tools/filesystem.py +0 -0
  66. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tools/search.py +0 -0
  67. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tools/shell.py +0 -0
  68. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tools/shell_gate.py +0 -0
  69. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tools/subtask.py +0 -0
  70. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tools/think.py +0 -0
  71. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tools/todo.py +0 -0
  72. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tools/web.py +0 -0
  73. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tools_inspector.py +0 -0
  74. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/trust.py +0 -0
  75. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tui/scrollback.py +0 -0
  76. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tui/spinner.py +0 -0
  77. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tui/welcome_banner.py +0 -0
  78. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tui/welcome_rich.py +0 -0
  79. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/version.py +0 -0
  80. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/vertex.py +0 -0
  81. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/web/__init__.py +0 -0
  82. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/web/claude_sse_adapter.py +0 -0
  83. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/web/terminal_repl.py +0 -0
  84. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/workspace_hints.py +0 -0
  85. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode.egg-info/dependency_links.txt +0 -0
  86. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode.egg-info/entry_points.txt +0 -0
  87. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode.egg-info/requires.txt +0 -0
  88. {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode.egg-info/top_level.txt +0 -0
  89. {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_agent_instruction.py +0 -0
  90. {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_autocompact.py +0 -0
  91. {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_capability_routing.py +0 -0
  92. {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_claude_web_adapter_sse.py +0 -0
  93. {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_cli_init.py +0 -0
  94. {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_computer_use_permissions.py +0 -0
  95. {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_context_budget.py +0 -0
  96. {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_context_warning.py +0 -0
  97. {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_credentials.py +0 -0
  98. {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_interactive_permission_ask.py +0 -0
  99. {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_kairos_scheduler.py +0 -0
  100. {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_modality_tools.py +0 -0
  101. {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_model_error_retry.py +0 -0
  102. {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_model_errors.py +0 -0
  103. {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_model_routing.py +0 -0
  104. {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_paths.py +0 -0
  105. {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_permissions.py +0 -0
  106. {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_prompt_suggestions.py +0 -0
  107. {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_repl_commands.py +0 -0
  108. {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_repl_slash.py +0 -0
  109. {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_slash_commands.py +0 -0
  110. {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_thinking_config.py +0 -0
  111. {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_token_budget.py +0 -0
  112. {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_tool_context_circulation.py +0 -0
  113. {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_tools.py +0 -0
  114. {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_tools_inspector.py +0 -0
  115. {gemcode-0.3.27 → gemcode-0.3.29}/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.27
3
+ Version: 0.3.29
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.27"
7
+ version = "0.3.29"
8
8
  description = "Local-first coding agent on Google Gemini + ADK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -58,17 +58,97 @@ def _load_gemini_md(project_root: Path) -> str:
58
58
 
59
59
  def _build_runtime_facts(cfg: GemCodeConfig) -> str:
60
60
  """
61
- Injected every session so the model does not hallucinate deployment, permissions,
62
- or "how to switch Pro" the way a product-agnostic base prompt would.
61
+ Injected every session so the model is fully self-aware of its own capabilities,
62
+ limits, and the environment not just generic defaults.
63
63
  """
64
64
  root = cfg.project_root.resolve()
65
65
  model = (getattr(cfg, "model", None) or "").strip() or "(default)"
66
+
67
+ # ── Active capabilities ──────────────────────────────────────────────────
68
+ caps: list[str] = []
69
+ if getattr(cfg, "enable_deep_research", False):
70
+ dr_extras = " + google_maps_grounding" if getattr(cfg, "enable_maps_grounding", False) else ""
71
+ caps.append(f"deep_research ON (tools: google_search, url_context{dr_extras})")
72
+ if getattr(cfg, "enable_embeddings", False):
73
+ caps.append(f"embeddings ON (tool: semantic_search_files, model: {getattr(cfg, 'embeddings_model', 'default')})")
74
+ if getattr(cfg, "enable_memory", False):
75
+ mem_path = root / ".gemcode" / "memories.jsonl"
76
+ mem_kind = "embedding-backed" if getattr(cfg, "enable_embeddings", False) else "keyword-backed"
77
+ caps.append(f"memory ON ({mem_kind}, stored at {mem_path}; ADK preload_memory injects relevant memories before each turn)")
78
+ if getattr(cfg, "enable_computer_use", False):
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.")
84
+ if not caps:
85
+ caps.append("none enabled (use /research on, /embeddings on, /memory on, /computer on, /code on to enable)")
86
+ caps_text = "\n".join(f" - {c}" for c in caps)
87
+
88
+ # ── Limits ───────────────────────────────────────────────────────────────
89
+ max_calls = getattr(cfg, "max_llm_calls", 256) or 256
90
+ token_budget = getattr(cfg, "token_budget", None)
91
+ max_session_tokens = getattr(cfg, "max_session_tokens", None)
92
+ budget_line = f"{max_calls} model↔tool iterations per user message"
93
+ if token_budget:
94
+ budget_line += f" · token_budget={token_budget:,} per turn"
95
+ if max_session_tokens:
96
+ budget_line += f" · max_session_tokens={max_session_tokens:,}"
97
+
98
+ # ── Kairos ────────────────────────────────────────────────────────────────
99
+ # The user can run `gemcode kairos -C <project>` in a separate terminal to
100
+ # launch a long-lived scheduler. Jobs submitted to it run concurrently with
101
+ # the current session. This is useful for background / parallel heavy work.
102
+ kairos_section = (
103
+ "- **Kairos background scheduler** — `gemcode kairos -C <project>` launches a "
104
+ "long-lived daemon that reads prompts from stdin and runs each as an isolated job "
105
+ "(up to N concurrently). Each job gets `kairos_sleep_ms(ms)` and "
106
+ "`kairos_enqueue_prompt(prompt, priority, session_id)` tools so the model can "
107
+ "schedule follow-up work itself. Useful for: bulk file processing, repeated "
108
+ "polling loops, parallelising large independent tasks. "
109
+ "Tell the user to open a second terminal and run `gemcode kairos` if a task "
110
+ "would benefit from background parallelism."
111
+ )
112
+
66
113
  return f"""## Runtime facts (authoritative for this session)
67
114
  - **Project root** — every filesystem tool path is relative to: `{root}`
68
- - **Model id in use:** `{model}`. In this TUI/REPL you can override it for subsequent turns with `/model use <id>` (use `/model list` to browse IDs). For a full restart you can still use `--model <id>` or env `GEMCODE_MODEL`.
69
- - **UI banner** phrases such as "GemCode Pro" are **terminal marketing**, not a separate API tier or model you enable from chat.
70
- - **Env toggles** (`GEMCODE_ENABLE_COMPUTER_USE`, `GEMCODE_MODEL`, etc.) affect only the **OS process** that launched `gemcode`. Pasting `VAR=1` in chat does **not** reconfigure a running session—tell the user to export in their shell, use project `.env`, or restart the CLI.
71
- - **Working in subfolders** — use tools: e.g. `list_directory("Desktop")`, `glob_files("**/query.ts")`, `read_file("testing/ai-edtech-app/src/app/page.tsx")`, or `run_command` with `cwd_subdir`. Never claim the sandbox cannot reach a subpath unless a tool returned an explicit error."""
115
+ - **Model id in use:** `{model}`. Override mid-session with `/model use <id>` or `/mode fast|balanced|quality|auto`.
116
+ - **Execution budget:** {budget_line}.
117
+ - **Active capabilities:**
118
+ {caps_text}
119
+ - **Capability routing** (`capability_mode={getattr(cfg, 'capability_mode', 'auto')}`): in `auto` mode, GemCode automatically enables deep_research when it detects research-intent keywords in your prompt each turn. You can also type `/research on`, `/embeddings on`, `/memory on`, `/computer on` at the prompt.
120
+ - **Your tool palette can grow mid-session:** if the user enables a capability via a slash command, the runner rebuilds and you get new tools on the next turn.
121
+ - **Memory system:** when `memory ON`, ADK automatically searches `.gemcode/memories.jsonl` and injects relevant past context before each turn. Facts the user tells you in one session can appear in future sessions. You do not need to manage memory explicitly — it is loaded automatically.
122
+ {kairos_section}
123
+ - **UI banner** phrases like "GemCode Pro" are terminal marketing, not a separate API tier.
124
+ - **Env toggles** (`GEMCODE_ENABLE_COMPUTER_USE`, `GEMCODE_MODEL`, etc.) affect only the OS process that launched gemcode. Pasting `VAR=1` in chat does NOT reconfigure a running session—tell the user to export in their shell, use project `.env`, or restart the CLI.
125
+ - **Working in subfolders** — call `list_directory("Desktop")`, `glob_files("**/query.ts")`, `read_file("testing/ai-edtech-app/src/app/page.tsx")` directly. Never claim access is blocked unless a tool returned an explicit error."""
126
+
127
+
128
+ def _build_memory_section(cfg: GemCodeConfig) -> str:
129
+ """Injected when enable_memory=True so the agent understands and uses memory."""
130
+ mem_path = cfg.project_root / ".gemcode" / "memories.jsonl"
131
+ kind = "embedding-based (semantic cosine similarity)" if getattr(cfg, "enable_embeddings", False) else "keyword-based"
132
+ return f"""
133
+ ## Persistent Memory System
134
+ Memory is **ON** ({kind}). Stored at: `{mem_path}`
135
+
136
+ ### How it works
137
+ - Before each turn, ADK automatically searches the memory store for relevant past facts and injects them as context — you do not need to call a tool to load them.
138
+ - After each turn, the session is automatically added to memory by the post-turn plugin.
139
+ - The memory file persists across sessions (JSONL, one entry per session).
140
+
141
+ ### What to do with memory
142
+ - **Reference it naturally** — if the injected context mentions past facts ("last time the user's API key was X", "the user prefers TypeScript"), treat that as trusted context.
143
+ - **Update it proactively** — if the user tells you important facts about their project, preferences, or recurring patterns, note them in your response: "I'll remember that for future sessions."
144
+ - **Don't re-explain already-known context** — if the memory already contains the project structure or preferences, skip the discovery step and act on what's known.
145
+ - **Memory is scoped to this project root** — `{cfg.project_root}`. Different project roots have separate memories.
146
+
147
+ ### When memory helps most
148
+ - Long-running projects (user preferences, patterns, recurring tasks)
149
+ - Multi-session workflows (continuing work from a previous day)
150
+ - Team conventions stored once and reused automatically
151
+ """
72
152
 
73
153
 
74
154
  def _build_computer_use_section(cfg: GemCodeConfig) -> str:
@@ -350,6 +430,41 @@ Use `run_subtask` when the work is better done in an isolated context:
350
430
 
351
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.
352
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
+
353
468
  ## Evaluator-optimizer loop
354
469
  For tasks where quality matters:
355
470
  1. Complete the task (execute tools, write code, run commands)
@@ -378,10 +493,13 @@ For tasks where quality matters:
378
493
  ## Workspace scope
379
494
  All file tools use paths **relative to the project root** (where GemCode was started). The root may be the home folder — subfolders like `Desktop`, `Desktop/code`, `Documents` are inside the sandbox. Call `list_directory("Desktop")` or `glob_files("**/*name*.ts")` instead of assuming access is blocked. Only treat access as denied when a tool returns an explicit `error`."""
380
495
 
381
- # Inject computer use strategy when the browser is enabled.
496
+ # Inject capability-specific strategy sections only when those caps are on.
382
497
  if getattr(cfg, "enable_computer_use", False):
383
498
  base = f"{base}\n\n{_build_computer_use_section(cfg)}"
384
499
 
500
+ if getattr(cfg, "enable_memory", False):
501
+ base = f"{base}\n\n{_build_memory_section(cfg)}"
502
+
385
503
  tool_manifest = build_tool_manifest(cfg)
386
504
  if tool_manifest:
387
505
  base = f"{base}\n\n{tool_manifest}"
@@ -391,6 +509,17 @@ All file tools use paths **relative to the project root** (where GemCode was sta
391
509
  return base
392
510
 
393
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
+
394
523
  def build_root_agent(
395
524
  cfg: GemCodeConfig,
396
525
  extra_tools: list | None = None,
@@ -413,8 +542,15 @@ def build_root_agent(
413
542
  if getattr(cfg, "enable_memory", False):
414
543
  # ADK preload_memory injects retrieved memories into the next llm_request.
415
544
  from google.adk.tools import preload_memory
416
-
417
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
+
418
554
  if extra_tools:
419
555
  tools = [*tools, *extra_tools]
420
556
 
@@ -469,15 +605,31 @@ def build_root_agent(
469
605
  tool_config=tool_cfg,
470
606
  )
471
607
 
472
- return LlmAgent(
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(
473
618
  model=cfg.model,
474
619
  name="gemcode",
475
620
  instruction=build_instruction(cfg),
621
+ global_instruction=global_instr,
476
622
  tools=tools,
477
623
  generate_content_config=gen_cfg,
478
624
  **cb_kwargs,
479
625
  )
480
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
+
481
633
 
482
634
  def create_runner(cfg: GemCodeConfig, extra_tools: list | None = None):
483
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)
@@ -215,4 +215,6 @@ def slash_help_lines() -> list[str]:
215
215
  " Other:",
216
216
  " /permissions Show permission / HITL settings",
217
217
  " /hooks Show post-turn hook configuration",
218
+ " /kairos How to launch the background parallel job scheduler",
219
+ " /code [on|off] Toggle sandboxed Python executor (ADK BuiltInCodeExecutor)",
218
220
  ]
@@ -318,6 +318,88 @@ async def process_repl_slash(
318
318
  if name in ("exit", "quit"):
319
319
  return ReplSlashResult(exit_repl=True)
320
320
 
321
+ # ── /kairos ───────────────────────────────────────────────────────────────
322
+ if name == "kairos":
323
+ args_s = (sc.args or "").strip()
324
+ out("Kairos — background parallel job scheduler")
325
+ out()
326
+ out("What it does:")
327
+ out(" Runs a long-lived daemon that accepts prompts on stdin and executes")
328
+ out(" each as an isolated GemCode agent job. Jobs run concurrently (up to")
329
+ out(" --concurrency N, default 2) in a priority queue.")
330
+ out()
331
+ out("How to launch (in a separate terminal):")
332
+ out(f" gemcode kairos -C {cfg.project_root}")
333
+ out(" gemcode kairos -C <project> --concurrency 4 --yes")
334
+ out(" gemcode kairos -C <project> --model gemini-2.5-pro --deep-research")
335
+ out()
336
+ out("Options:")
337
+ out(" --concurrency N Max parallel jobs (default: 2)")
338
+ out(" --default-priority N Priority for stdin jobs (higher = runs first, default: 0)")
339
+ out(" --yes Auto-approve mutations (like gemcode --yes)")
340
+ out(" --model <id> Override model for all jobs")
341
+ out(" --model-mode <mode> fast|balanced|quality|auto")
342
+ out(" --deep-research Enable Google Search + URL Context for all jobs")
343
+ out(" --embeddings Enable semantic file search for all jobs")
344
+ out(" --max-llm-calls N Cap model↔tool iterations per job")
345
+ out(" --session <uuid> Share session history with a running gemcode session")
346
+ out()
347
+ out("Tools available to jobs (in addition to normal GemCode tools):")
348
+ out(" kairos_sleep_ms(duration_ms) — pause this job without blocking others")
349
+ out(" kairos_enqueue_prompt(prompt, priority, session_id)")
350
+ out(" — the model can queue MORE jobs itself")
351
+ out()
352
+ out("Use cases:")
353
+ out(" - Process N files in parallel: each file → one job")
354
+ out(" - Polling loop: job sleeps, re-enqueues itself with kairos_enqueue_prompt")
355
+ out(" - Bulk code generation, test runs, or research across many documents")
356
+ out(" - Background work while you continue chatting in this session")
357
+ out()
358
+ return ReplSlashResult(skip_model_turn=True)
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
+
321
403
  # ── /computer ────────────────────────────────────────────────────────────
322
404
  if name in ("computer", "browser"):
323
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
- return Runner(
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)