gemcode 0.3.70__tar.gz → 0.3.72__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 (128) hide show
  1. {gemcode-0.3.70/src/gemcode.egg-info → gemcode-0.3.72}/PKG-INFO +72 -1
  2. {gemcode-0.3.70 → gemcode-0.3.72}/README.md +71 -0
  3. {gemcode-0.3.70 → gemcode-0.3.72}/pyproject.toml +1 -1
  4. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/agent.py +39 -1
  5. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/callbacks.py +42 -2
  6. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/config.py +35 -0
  7. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/repl_slash.py +8 -0
  8. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/session_runtime.py +28 -0
  9. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/tool_result_store.py +67 -0
  10. {gemcode-0.3.70 → gemcode-0.3.72/src/gemcode.egg-info}/PKG-INFO +72 -1
  11. {gemcode-0.3.70 → gemcode-0.3.72}/LICENSE +0 -0
  12. {gemcode-0.3.70 → gemcode-0.3.72}/MANIFEST.in +0 -0
  13. {gemcode-0.3.70 → gemcode-0.3.72}/setup.cfg +0 -0
  14. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/__init__.py +0 -0
  15. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/__main__.py +0 -0
  16. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/audit.py +0 -0
  17. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/autocompact.py +0 -0
  18. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/capability_routing.py +0 -0
  19. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/cli.py +0 -0
  20. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/compaction.py +0 -0
  21. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/computer_use/__init__.py +0 -0
  22. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/computer_use/browser_computer.py +0 -0
  23. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/context_budget.py +0 -0
  24. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/context_warning.py +0 -0
  25. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/credentials.py +0 -0
  26. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/dynamic_policy.py +0 -0
  27. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/hitl_session.py +0 -0
  28. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/hooks.py +0 -0
  29. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/intent_classifier.py +0 -0
  30. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/interactions.py +0 -0
  31. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/invoke.py +0 -0
  32. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/kairos_daemon.py +0 -0
  33. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/limits.py +0 -0
  34. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/live_audio_engine.py +0 -0
  35. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/logging_config.py +0 -0
  36. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/mcp_loader.py +0 -0
  37. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/memory/__init__.py +0 -0
  38. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/memory/embedding_memory_service.py +0 -0
  39. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/memory/file_memory_service.py +0 -0
  40. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/modality_tools.py +0 -0
  41. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/model_errors.py +0 -0
  42. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/model_routing.py +0 -0
  43. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/openapi_loader.py +0 -0
  44. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/paths.py +0 -0
  45. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/permissions.py +0 -0
  46. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/plugins/__init__.py +0 -0
  47. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
  48. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
  49. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/policy_profile.py +0 -0
  50. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/pricing.py +0 -0
  51. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/prompt_suggestions.py +0 -0
  52. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/query/__init__.py +0 -0
  53. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/query/config.py +0 -0
  54. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/query/deps.py +0 -0
  55. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/query/engine.py +0 -0
  56. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/query/stop_hooks.py +0 -0
  57. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/query/token_budget.py +0 -0
  58. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/query/transitions.py +0 -0
  59. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/refine.py +0 -0
  60. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/repl_commands.py +0 -0
  61. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/review_agent.py +0 -0
  62. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/session_store.py +0 -0
  63. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/slash_commands.py +0 -0
  64. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/thinking.py +0 -0
  65. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/tool_prompt_manifest.py +0 -0
  66. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/tool_registry.py +0 -0
  67. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/tools/__init__.py +0 -0
  68. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/tools/bash.py +0 -0
  69. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/tools/browser.py +0 -0
  70. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/tools/edit.py +0 -0
  71. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/tools/filesystem.py +0 -0
  72. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/tools/notebook.py +0 -0
  73. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/tools/notes.py +0 -0
  74. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/tools/repo_map.py +0 -0
  75. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/tools/search.py +0 -0
  76. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/tools/shell.py +0 -0
  77. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/tools/shell_gate.py +0 -0
  78. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/tools/subtask.py +0 -0
  79. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/tools/tasks.py +0 -0
  80. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/tools/think.py +0 -0
  81. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/tools/todo.py +0 -0
  82. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/tools/web.py +0 -0
  83. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/tools/web_search.py +0 -0
  84. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/tools_inspector.py +0 -0
  85. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/trust.py +0 -0
  86. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/tui/input_handler.py +0 -0
  87. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/tui/scrollback.py +0 -0
  88. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/tui/spinner.py +0 -0
  89. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/tui/welcome_banner.py +0 -0
  90. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/tui/welcome_rich.py +0 -0
  91. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/version.py +0 -0
  92. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/vertex.py +0 -0
  93. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/web/__init__.py +0 -0
  94. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/web/claude_sse_adapter.py +0 -0
  95. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/web/terminal_repl.py +0 -0
  96. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode/workspace_hints.py +0 -0
  97. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode.egg-info/SOURCES.txt +0 -0
  98. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode.egg-info/dependency_links.txt +0 -0
  99. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode.egg-info/entry_points.txt +0 -0
  100. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode.egg-info/requires.txt +0 -0
  101. {gemcode-0.3.70 → gemcode-0.3.72}/src/gemcode.egg-info/top_level.txt +0 -0
  102. {gemcode-0.3.70 → gemcode-0.3.72}/tests/test_agent_instruction.py +0 -0
  103. {gemcode-0.3.70 → gemcode-0.3.72}/tests/test_autocompact.py +0 -0
  104. {gemcode-0.3.70 → gemcode-0.3.72}/tests/test_capability_routing.py +0 -0
  105. {gemcode-0.3.70 → gemcode-0.3.72}/tests/test_claude_web_adapter_sse.py +0 -0
  106. {gemcode-0.3.70 → gemcode-0.3.72}/tests/test_cli_init.py +0 -0
  107. {gemcode-0.3.70 → gemcode-0.3.72}/tests/test_computer_use_permissions.py +0 -0
  108. {gemcode-0.3.70 → gemcode-0.3.72}/tests/test_context_budget.py +0 -0
  109. {gemcode-0.3.70 → gemcode-0.3.72}/tests/test_context_warning.py +0 -0
  110. {gemcode-0.3.70 → gemcode-0.3.72}/tests/test_credentials.py +0 -0
  111. {gemcode-0.3.70 → gemcode-0.3.72}/tests/test_interactive_permission_ask.py +0 -0
  112. {gemcode-0.3.70 → gemcode-0.3.72}/tests/test_kairos_scheduler.py +0 -0
  113. {gemcode-0.3.70 → gemcode-0.3.72}/tests/test_modality_tools.py +0 -0
  114. {gemcode-0.3.70 → gemcode-0.3.72}/tests/test_model_error_retry.py +0 -0
  115. {gemcode-0.3.70 → gemcode-0.3.72}/tests/test_model_errors.py +0 -0
  116. {gemcode-0.3.70 → gemcode-0.3.72}/tests/test_model_routing.py +0 -0
  117. {gemcode-0.3.70 → gemcode-0.3.72}/tests/test_paths.py +0 -0
  118. {gemcode-0.3.70 → gemcode-0.3.72}/tests/test_permissions.py +0 -0
  119. {gemcode-0.3.70 → gemcode-0.3.72}/tests/test_prompt_suggestions.py +0 -0
  120. {gemcode-0.3.70 → gemcode-0.3.72}/tests/test_repl_commands.py +0 -0
  121. {gemcode-0.3.70 → gemcode-0.3.72}/tests/test_repl_slash.py +0 -0
  122. {gemcode-0.3.70 → gemcode-0.3.72}/tests/test_slash_commands.py +0 -0
  123. {gemcode-0.3.70 → gemcode-0.3.72}/tests/test_thinking_config.py +0 -0
  124. {gemcode-0.3.70 → gemcode-0.3.72}/tests/test_token_budget.py +0 -0
  125. {gemcode-0.3.70 → gemcode-0.3.72}/tests/test_tool_context_circulation.py +0 -0
  126. {gemcode-0.3.70 → gemcode-0.3.72}/tests/test_tools.py +0 -0
  127. {gemcode-0.3.70 → gemcode-0.3.72}/tests/test_tools_inspector.py +0 -0
  128. {gemcode-0.3.70 → gemcode-0.3.72}/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.70
3
+ Version: 0.3.72
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -217,6 +217,16 @@ gemcode --yes "Add a module docstring to src/foo.py"
217
217
  gemcode --session mysess --yes "Continue: run tests and fix failures"
218
218
  ```
219
219
 
220
+ ### What GemCode writes to `.gemcode/`
221
+
222
+ GemCode keeps project-local state under `.gemcode/`:
223
+
224
+ - **`sessions.sqlite`**: session events/history (ADK `SqliteSessionService`)
225
+ - **`audit.log`**: JSONL audit trail for tool usage + model usage + stop reasons
226
+ - **`tool-results/`**: oversized tool outputs offloaded to stable refs (`tool_result:<sha>`)
227
+ - **`artifacts/`**: file artifacts (ADK `FileArtifactService`)
228
+ - **`policy.json`**: self-tuning per-repo profile used to calibrate dynamic budgets
229
+
220
230
  - **`--yes`**: allow mutating tools (`write_file`, `search_replace`). Shell execution is still restricted by the `.env.example` allowlist.
221
231
  - **`--session`**: Conversation history is stored under `.gemcode/sessions.sqlite` (ADK `SqliteSessionService`). Reuse the same `--session` id to continue.
222
232
  - **`--max-llm-calls`**: cap model↔tool iterations for this message (maps to ADK `RunConfig.max_llm_calls`). You can also set `GEMCODE_MAX_LLM_CALLS`.
@@ -236,6 +246,11 @@ gemcode --session mysess --yes "Continue: run tests and fix failures"
236
246
  - `gemcode tools smoke` fails non-zero if any tool’s declaration compilation fails
237
247
  - Optional inspection flags: `--deep-research`, `--maps-grounding`, `--embeddings`, `--memory`
238
248
  - **Optional compaction**: set `GEMCODE_ENABLE_COMPACT=1` to trim old `Content` entries before each model call (MVP sliding window; can break complex tool chains if too aggressive—tune `GEMCODE_MAX_CONTENT_ITEMS`).
249
+ - **ADK multi-agent transfer (recommended)**: enabled by default; GemCode builds an ADK sub-agent tree (Explorer/Verifier) and allows the model to transfer with `transfer_to_agent` when specialization helps.
250
+ - Disable: `GEMCODE_ADK_AGENT_TRANSFER=0`
251
+ - **ADK event compaction (new)**: optional ADK-native summarization of older events to keep long sessions coherent without exploding context.
252
+ - Enable: `GEMCODE_ADK_EVENTS_COMPACTION=1`
253
+ - Tune: `GEMCODE_ADK_COMPACTION_INTERVAL`, `GEMCODE_ADK_COMPACTION_OVERLAP`, `GEMCODE_ADK_COMPACTION_MODEL`
239
254
  - **Session token ceiling**: set `GEMCODE_MAX_SESSION_TOKENS` to stop the next LLM call when cumulative `usage_metadata.total_token_count` exceeds the limit.
240
255
  - **Token budget tracking**: set `GEMCODE_TOKEN_BUDGET` to enforce continuation/stop decisions per user turn (token-budget audit in `.gemcode/audit.log`).
241
256
  - **Stop-the-loop hooks**: set `GEMCODE_POST_TURN_HOOK=/path/to/hook.sh` (or place an executable at `.gemcode/hooks/post_turn`) to run after each user message.
@@ -243,6 +258,33 @@ gemcode --session mysess --yes "Continue: run tests and fix failures"
243
258
  - **Recovery-loop**: ADK `ReflectAndRetryToolPlugin`-based retries on tool failures.
244
259
  - Set `GEMCODE_ENABLE_TOOL_RECOVERY_RETRY=0` to disable.
245
260
  - Set `GEMCODE_TOOL_REFLECT_MAX_RETRIES=1` to control retries per tool.
261
+
262
+ ### Token efficiency (dynamic + intelligent)
263
+
264
+ GemCode optimizes tokens without losing capability by using a dynamic policy:
265
+
266
+ - **Context-pressure aware**: tool caps tighten when context is tight, loosen when there is room.
267
+ - **Risk/complexity aware**: caps increase for risky tasks (writes, shell, failures, many files).
268
+ - **Self-tuning per repo**: `.gemcode/policy.json` calibrates baseline evidence budgets over time.
269
+
270
+ Key env toggles:
271
+
272
+ - `GEMCODE_DYNAMIC_TOKEN_POLICY=0|1`
273
+ - `GEMCODE_DYNAMIC_RISK_POLICY=0|1`
274
+ - `GEMCODE_DYNAMIC_RISK_BOOST=<float>` (default `0.6`)
275
+ - `GEMCODE_TOOL_RESULT_OFFLOAD=0|1` (default `1`)
276
+
277
+ Live telemetry:
278
+
279
+ - `/status` shows `risk_score`, `context_percent_left`, and profile EMAs.
280
+
281
+ ### Stable tool output offloading (OpenClaude-style)
282
+
283
+ Oversized tool outputs are automatically offloaded and replaced with stable refs:
284
+
285
+ - Stored in: `.gemcode/tool-results/`
286
+ - References: `tool_result:<sha256>`
287
+ - Load on demand: `load_tool_result(ref)`
246
288
  - **Gemini thinking controls (Claude-like)**:
247
289
  - By default GemCode lets Gemini use its dynamic/adaptive thinking behavior.
248
290
  - Set `GEMCODE_DISABLE_THINKING=1` to force a best-effort “low thinking” mode:
@@ -301,12 +343,24 @@ the user’s project.
301
343
  - `list_directory`
302
344
  - `glob_files`
303
345
  - `grep_content`
346
+ - `repo_map`
347
+ - `web_search`
348
+ - `web_fetch`
349
+ - `notebook_read`
350
+ - `notebook_edit`
304
351
  - Mutating tools (require `--yes` unless your policy blocks them):
305
352
  - `write_file`
306
353
  - `search_replace`
307
354
  - Shell execution:
308
355
  - `run_command` (guarded by `GEMCODE_ALLOW_COMMANDS` from `.env.example` and
309
356
  `GEMCODE_PERMISSION_MODE`).
357
+ - `bash` (pipelines + redirects; supports `background=True`)
358
+
359
+ - Background task management (for processes started via `bash(..., background=True)`):
360
+ - `list_tasks`, `task_output`, `kill_task`
361
+
362
+ - Tool offload loader:
363
+ - `load_tool_result(ref)`
310
364
 
311
365
  Tool execution is still controlled by permission gates and then governed by
312
366
  GemCode’s circuit breaker + recovery behavior.
@@ -434,6 +488,23 @@ pip install -e ".[dev]"
434
488
  pytest
435
489
  ```
436
490
 
491
+ ## Release workflow (GitHub Actions → PyPI)
492
+
493
+ This repository publishes to PyPI on tag pushes:
494
+
495
+ - `.github/workflows/publish-pypi.yml` triggers on tags matching `v*`
496
+
497
+ Typical release steps:
498
+
499
+ ```bash
500
+ # 1) bump gemcode/pyproject.toml version
501
+ git add -A
502
+ git commit -m "release: vX.Y.Z"
503
+ git tag -a vX.Y.Z -m "vX.Y.Z"
504
+ git push origin HEAD
505
+ git push origin vX.Y.Z
506
+ ```
507
+
437
508
  ## References (local only)
438
509
 
439
510
  Do not commit proprietary leaked trees into this package. Keep `claude-code-leaked/` and similar folders outside version control or in a private mirror.
@@ -28,6 +28,16 @@ gemcode --yes "Add a module docstring to src/foo.py"
28
28
  gemcode --session mysess --yes "Continue: run tests and fix failures"
29
29
  ```
30
30
 
31
+ ### What GemCode writes to `.gemcode/`
32
+
33
+ GemCode keeps project-local state under `.gemcode/`:
34
+
35
+ - **`sessions.sqlite`**: session events/history (ADK `SqliteSessionService`)
36
+ - **`audit.log`**: JSONL audit trail for tool usage + model usage + stop reasons
37
+ - **`tool-results/`**: oversized tool outputs offloaded to stable refs (`tool_result:<sha>`)
38
+ - **`artifacts/`**: file artifacts (ADK `FileArtifactService`)
39
+ - **`policy.json`**: self-tuning per-repo profile used to calibrate dynamic budgets
40
+
31
41
  - **`--yes`**: allow mutating tools (`write_file`, `search_replace`). Shell execution is still restricted by the `.env.example` allowlist.
32
42
  - **`--session`**: Conversation history is stored under `.gemcode/sessions.sqlite` (ADK `SqliteSessionService`). Reuse the same `--session` id to continue.
33
43
  - **`--max-llm-calls`**: cap model↔tool iterations for this message (maps to ADK `RunConfig.max_llm_calls`). You can also set `GEMCODE_MAX_LLM_CALLS`.
@@ -47,6 +57,11 @@ gemcode --session mysess --yes "Continue: run tests and fix failures"
47
57
  - `gemcode tools smoke` fails non-zero if any tool’s declaration compilation fails
48
58
  - Optional inspection flags: `--deep-research`, `--maps-grounding`, `--embeddings`, `--memory`
49
59
  - **Optional compaction**: set `GEMCODE_ENABLE_COMPACT=1` to trim old `Content` entries before each model call (MVP sliding window; can break complex tool chains if too aggressive—tune `GEMCODE_MAX_CONTENT_ITEMS`).
60
+ - **ADK multi-agent transfer (recommended)**: enabled by default; GemCode builds an ADK sub-agent tree (Explorer/Verifier) and allows the model to transfer with `transfer_to_agent` when specialization helps.
61
+ - Disable: `GEMCODE_ADK_AGENT_TRANSFER=0`
62
+ - **ADK event compaction (new)**: optional ADK-native summarization of older events to keep long sessions coherent without exploding context.
63
+ - Enable: `GEMCODE_ADK_EVENTS_COMPACTION=1`
64
+ - Tune: `GEMCODE_ADK_COMPACTION_INTERVAL`, `GEMCODE_ADK_COMPACTION_OVERLAP`, `GEMCODE_ADK_COMPACTION_MODEL`
50
65
  - **Session token ceiling**: set `GEMCODE_MAX_SESSION_TOKENS` to stop the next LLM call when cumulative `usage_metadata.total_token_count` exceeds the limit.
51
66
  - **Token budget tracking**: set `GEMCODE_TOKEN_BUDGET` to enforce continuation/stop decisions per user turn (token-budget audit in `.gemcode/audit.log`).
52
67
  - **Stop-the-loop hooks**: set `GEMCODE_POST_TURN_HOOK=/path/to/hook.sh` (or place an executable at `.gemcode/hooks/post_turn`) to run after each user message.
@@ -54,6 +69,33 @@ gemcode --session mysess --yes "Continue: run tests and fix failures"
54
69
  - **Recovery-loop**: ADK `ReflectAndRetryToolPlugin`-based retries on tool failures.
55
70
  - Set `GEMCODE_ENABLE_TOOL_RECOVERY_RETRY=0` to disable.
56
71
  - Set `GEMCODE_TOOL_REFLECT_MAX_RETRIES=1` to control retries per tool.
72
+
73
+ ### Token efficiency (dynamic + intelligent)
74
+
75
+ GemCode optimizes tokens without losing capability by using a dynamic policy:
76
+
77
+ - **Context-pressure aware**: tool caps tighten when context is tight, loosen when there is room.
78
+ - **Risk/complexity aware**: caps increase for risky tasks (writes, shell, failures, many files).
79
+ - **Self-tuning per repo**: `.gemcode/policy.json` calibrates baseline evidence budgets over time.
80
+
81
+ Key env toggles:
82
+
83
+ - `GEMCODE_DYNAMIC_TOKEN_POLICY=0|1`
84
+ - `GEMCODE_DYNAMIC_RISK_POLICY=0|1`
85
+ - `GEMCODE_DYNAMIC_RISK_BOOST=<float>` (default `0.6`)
86
+ - `GEMCODE_TOOL_RESULT_OFFLOAD=0|1` (default `1`)
87
+
88
+ Live telemetry:
89
+
90
+ - `/status` shows `risk_score`, `context_percent_left`, and profile EMAs.
91
+
92
+ ### Stable tool output offloading (OpenClaude-style)
93
+
94
+ Oversized tool outputs are automatically offloaded and replaced with stable refs:
95
+
96
+ - Stored in: `.gemcode/tool-results/`
97
+ - References: `tool_result:<sha256>`
98
+ - Load on demand: `load_tool_result(ref)`
57
99
  - **Gemini thinking controls (Claude-like)**:
58
100
  - By default GemCode lets Gemini use its dynamic/adaptive thinking behavior.
59
101
  - Set `GEMCODE_DISABLE_THINKING=1` to force a best-effort “low thinking” mode:
@@ -112,12 +154,24 @@ the user’s project.
112
154
  - `list_directory`
113
155
  - `glob_files`
114
156
  - `grep_content`
157
+ - `repo_map`
158
+ - `web_search`
159
+ - `web_fetch`
160
+ - `notebook_read`
161
+ - `notebook_edit`
115
162
  - Mutating tools (require `--yes` unless your policy blocks them):
116
163
  - `write_file`
117
164
  - `search_replace`
118
165
  - Shell execution:
119
166
  - `run_command` (guarded by `GEMCODE_ALLOW_COMMANDS` from `.env.example` and
120
167
  `GEMCODE_PERMISSION_MODE`).
168
+ - `bash` (pipelines + redirects; supports `background=True`)
169
+
170
+ - Background task management (for processes started via `bash(..., background=True)`):
171
+ - `list_tasks`, `task_output`, `kill_task`
172
+
173
+ - Tool offload loader:
174
+ - `load_tool_result(ref)`
121
175
 
122
176
  Tool execution is still controlled by permission gates and then governed by
123
177
  GemCode’s circuit breaker + recovery behavior.
@@ -245,6 +299,23 @@ pip install -e ".[dev]"
245
299
  pytest
246
300
  ```
247
301
 
302
+ ## Release workflow (GitHub Actions → PyPI)
303
+
304
+ This repository publishes to PyPI on tag pushes:
305
+
306
+ - `.github/workflows/publish-pypi.yml` triggers on tags matching `v*`
307
+
308
+ Typical release steps:
309
+
310
+ ```bash
311
+ # 1) bump gemcode/pyproject.toml version
312
+ git add -A
313
+ git commit -m "release: vX.Y.Z"
314
+ git tag -a vX.Y.Z -m "vX.Y.Z"
315
+ git push origin HEAD
316
+ git push origin vX.Y.Z
317
+ ```
318
+
248
319
  ## References (local only)
249
320
 
250
321
  Do not commit proprietary leaked trees into this package. Keep `claude-code-leaked/` and similar folders outside version control or in a private mirror.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gemcode"
7
- version = "0.3.70"
7
+ version = "0.3.72"
8
8
  description = "Local-first coding agent on Google Gemini + ADK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -887,7 +887,6 @@ def build_root_agent(
887
887
  pre-built list that excludes run_subtask itself, preventing recursion).
888
888
  When set, build_function_tools() is NOT called.
889
889
  """
890
- return (base + tool_guide).strip() + "\n"
891
890
  if _tools is not None:
892
891
  tools = list(_tools)
893
892
  else:
@@ -966,12 +965,51 @@ def build_root_agent(
966
965
  tool_config=tool_cfg,
967
966
  )
968
967
 
968
+ # ── ADK multi-agent tree (LLM-controlled transfer) ───────────────────────
969
+ sub_agents = []
970
+ if getattr(cfg, "enable_adk_agent_transfer", True) and _tools is None:
971
+ try:
972
+ # Explorer: read-only, fast, low-risk. Keep instruction short.
973
+ explorer_tools = build_function_tools(cfg, include_subtask=False)
974
+ explorer_tools = [t for t in explorer_tools if getattr(t, "__name__", "") not in ("write_file", "search_replace", "delete_file", "move_file", "bash", "run_command")]
975
+ explorer = LlmAgent(
976
+ name="explorer",
977
+ model=getattr(cfg, "model_alt", None) or cfg.model,
978
+ instruction=(
979
+ "You are Explorer. Your job is to quickly map the codebase and answer: "
980
+ "what files/symbols matter and where to look next. Use read-only tools only. "
981
+ "Return concise findings with file paths and symbol names."
982
+ ),
983
+ tools=explorer_tools,
984
+ generate_content_config=gen_cfg,
985
+ **cb_kwargs,
986
+ )
987
+ # Verifier: focuses on checking, tests, and consistency.
988
+ verifier_tools = build_function_tools(cfg, include_subtask=False)
989
+ verifier_tools = [t for t in verifier_tools if getattr(t, "__name__", "") not in ("write_file", "search_replace", "delete_file", "move_file")]
990
+ verifier = LlmAgent(
991
+ name="verifier",
992
+ model=getattr(cfg, "model_alt", None) or cfg.model,
993
+ instruction=(
994
+ "You are Verifier. Your job is to verify changes: run checks/tests when needed, "
995
+ "spot inconsistencies, and report PASS/FAIL with concrete evidence. "
996
+ "Prefer minimal commands and short outputs."
997
+ ),
998
+ tools=verifier_tools,
999
+ generate_content_config=gen_cfg,
1000
+ **cb_kwargs,
1001
+ )
1002
+ sub_agents = [explorer, verifier]
1003
+ except Exception:
1004
+ sub_agents = []
1005
+
969
1006
  agent_kwargs: dict = dict(
970
1007
  model=cfg.model,
971
1008
  name="gemcode",
972
1009
  instruction=build_instruction(cfg),
973
1010
  tools=tools,
974
1011
  generate_content_config=gen_cfg,
1012
+ sub_agents=sub_agents or None,
975
1013
  **cb_kwargs,
976
1014
  )
977
1015
 
@@ -48,6 +48,9 @@ _RISK_TOOL_CALLS = "gemcode:risk_tool_calls"
48
48
  _RISK_HAD_SHELL = "gemcode:risk_had_shell"
49
49
  _RISK_HAD_WRITE = "gemcode:risk_had_write"
50
50
  _RISK_HAD_FAILURE = "gemcode:risk_had_failure"
51
+ _TOOL_GROUP_CHARS = "gemcode:tool_group_chars"
52
+ _TOOL_GROUP_EXCEEDED = "gemcode:tool_group_budget_exceeded"
53
+ _TOOL_SEQ = "gemcode:tool_seq"
51
54
 
52
55
  def _truthy_env(name: str, *, default: bool = False) -> bool:
53
56
  v = os.environ.get(name)
@@ -150,6 +153,8 @@ def make_before_tool_callback(cfg: GemCodeConfig):
150
153
  try:
151
154
  if tool_context is not None:
152
155
  st = tool_context.state
156
+ # Per-turn tool sequence (used for stable tool-result replacement keys).
157
+ st[_TOOL_SEQ] = int(st.get(_TOOL_SEQ, 0) or 0) + 1
153
158
  st[_RISK_TOOL_CALLS] = int(st.get(_RISK_TOOL_CALLS, 0) or 0) + 1
154
159
  if name == "read_file":
155
160
  p = (args or {}).get("path")
@@ -360,18 +365,40 @@ def make_after_tool_callback(cfg: GemCodeConfig):
360
365
  except Exception:
361
366
  pass
362
367
 
368
+ # Aggregate per-turn tool-result budget: if we already exceeded the budget,
369
+ # tighten caps further for the rest of this user message.
370
+ try:
371
+ if tool_context is not None:
372
+ st = tool_context.state
373
+ if bool(st.get(_TOOL_GROUP_EXCEEDED, False)):
374
+ effective_tool_chars = max(1500, int(effective_tool_chars * 0.5))
375
+ except Exception:
376
+ pass
377
+
363
378
  if (
364
379
  isinstance(tool_response, dict)
365
380
  and getattr(cfg, "tool_result_offload_enabled", False)
366
381
  and effective_tool_chars > 0
367
382
  ):
368
383
  try:
369
- from gemcode.tool_result_store import maybe_offload_tool_result
370
- new_payload, did = maybe_offload_tool_result(
384
+ from gemcode.tool_result_store import maybe_offload_tool_result_stable
385
+ seq = None
386
+ st = None
387
+ try:
388
+ if tool_context is not None:
389
+ st = tool_context.state
390
+ seq = int(st.get(_TOOL_SEQ, 0) or 0)
391
+ except Exception:
392
+ st = None
393
+ seq = None
394
+ new_payload, did = maybe_offload_tool_result_stable(
371
395
  project_root=cfg.project_root,
372
396
  tool_name=name,
397
+ args=args or {},
373
398
  payload=tool_response,
374
399
  max_inline_chars=int(effective_tool_chars),
400
+ state=st,
401
+ seq=seq,
375
402
  )
376
403
  if did and isinstance(new_payload, dict):
377
404
  tool_response = new_payload
@@ -392,6 +419,19 @@ def make_after_tool_callback(cfg: GemCodeConfig):
392
419
  st = tool_context.state
393
420
  except Exception:
394
421
  return tool_response if (truncated or offloaded) else None
422
+
423
+ # Update aggregate per-turn budget counters (best-effort).
424
+ try:
425
+ from gemcode.context_budget import estimate_obj_string_chars
426
+ budget = int(getattr(cfg, "tool_result_group_budget_chars", 0) or 0)
427
+ if budget > 0:
428
+ used = int(st.get(_TOOL_GROUP_CHARS, 0) or 0)
429
+ used += int(estimate_obj_string_chars(tool_response))
430
+ st[_TOOL_GROUP_CHARS] = used
431
+ if used >= budget:
432
+ st[_TOOL_GROUP_EXCEEDED] = True
433
+ except Exception:
434
+ pass
395
435
  err = isinstance(tool_response, dict) and tool_response.get("error")
396
436
  err_kind = (
397
437
  isinstance(tool_response, dict) and tool_response.get("error_kind")
@@ -60,6 +60,10 @@ def token_budget_invocation_reset() -> dict:
60
60
  "gemcode:bt_t0": t,
61
61
  "gemcode:bt_base_total_tokens": -1,
62
62
  "gemcode:bt_token_budget_stop": False,
63
+ # Tool-result aggregate budget (per user message)
64
+ "gemcode:tool_group_chars": 0,
65
+ "gemcode:tool_group_budget_exceeded": False,
66
+ "gemcode:tool_seq": 0,
63
67
  }
64
68
 
65
69
 
@@ -131,6 +135,37 @@ class GemCodeConfig:
131
135
  )
132
136
  )
133
137
 
138
+ # Aggregate tool-result budget per user message (approx. characters of tool payloads).
139
+ # When exceeded, GemCode tightens subsequent tool output caps for the remainder of the turn.
140
+ tool_result_group_budget_chars: int = field(
141
+ default_factory=lambda: int(os.environ.get("GEMCODE_TOOL_RESULT_GROUP_BUDGET_CHARS", "60000"))
142
+ )
143
+
144
+ # ADK App-level event compaction (sliding-window summarization).
145
+ enable_adk_events_compaction: bool = field(
146
+ default_factory=lambda: _truthy_env("GEMCODE_ADK_EVENTS_COMPACTION", default=False)
147
+ )
148
+ adk_compaction_interval: int = field(
149
+ default_factory=lambda: int(os.environ.get("GEMCODE_ADK_COMPACTION_INTERVAL", "6"))
150
+ )
151
+ adk_compaction_overlap: int = field(
152
+ default_factory=lambda: int(os.environ.get("GEMCODE_ADK_COMPACTION_OVERLAP", "1"))
153
+ )
154
+ adk_compaction_token_threshold: int | None = field(
155
+ default_factory=lambda: _opt_positive_int("GEMCODE_ADK_COMPACTION_TOKEN_THRESHOLD")
156
+ )
157
+ adk_compaction_event_retention_size: int | None = field(
158
+ default_factory=lambda: _opt_positive_int("GEMCODE_ADK_COMPACTION_EVENT_RETENTION")
159
+ )
160
+ adk_compaction_summarizer_model: str = field(
161
+ default_factory=lambda: os.environ.get("GEMCODE_ADK_COMPACTION_MODEL", "gemini-2.5-flash")
162
+ )
163
+
164
+ # ADK multi-agent transfer (Explorer/Implementer/Verifier sub-agents).
165
+ enable_adk_agent_transfer: bool = field(
166
+ default_factory=lambda: _truthy_env("GEMCODE_ADK_AGENT_TRANSFER", default=True)
167
+ )
168
+
134
169
  # When enabled, oversized tool outputs are offloaded to disk under
135
170
  # .gemcode/tool-results/ and replaced in history with stable refs + previews.
136
171
  # This reduces context bloat and improves prompt-cache stability.
@@ -400,6 +400,14 @@ async def process_repl_slash(
400
400
  out(f" dynamic_token_policy: {getattr(cfg, 'dynamic_token_policy', True)}")
401
401
  out(f" dynamic_risk_policy: {getattr(cfg, 'dynamic_risk_policy', True)}")
402
402
  out(f" dynamic_risk_boost: {getattr(cfg, 'dynamic_risk_boost', 0.6)}")
403
+ out()
404
+ out(" ADK advanced:")
405
+ out(f" adk_agent_transfer: {getattr(cfg, 'enable_adk_agent_transfer', True)}")
406
+ out(f" adk_events_compaction: {getattr(cfg, 'enable_adk_events_compaction', False)}")
407
+ if getattr(cfg, "enable_adk_events_compaction", False):
408
+ out(f" compaction_interval: {getattr(cfg, 'adk_compaction_interval', 6)}")
409
+ out(f" compaction_overlap: {getattr(cfg, 'adk_compaction_overlap', 1)}")
410
+ out(f" compaction_model: {getattr(cfg, 'adk_compaction_summarizer_model', 'gemini-2.5-flash')}")
403
411
  out(f" risk_score: {risk:.2f}")
404
412
  if isinstance(pct, int):
405
413
  out(f" context_percent_left: {pct}%")
@@ -66,11 +66,39 @@ def _build_app(agent, plugins, cfg: GemCodeConfig):
66
66
  """
67
67
  try:
68
68
  from google.adk.apps.app import App
69
+ events_compaction_config = None
70
+ if getattr(cfg, "enable_adk_events_compaction", False):
71
+ try:
72
+ from google.adk.apps.app import EventsCompactionConfig
73
+ summarizer = None
74
+ try:
75
+ from google.adk.apps.llm_event_summarizer import LlmEventSummarizer
76
+ from google.adk.models import Gemini
77
+ summarizer_llm = Gemini(model=getattr(cfg, "adk_compaction_summarizer_model", "gemini-2.5-flash"))
78
+ summarizer = LlmEventSummarizer(llm=summarizer_llm)
79
+ except Exception:
80
+ summarizer = None
81
+
82
+ kw = dict(
83
+ compaction_interval=max(2, int(getattr(cfg, "adk_compaction_interval", 6) or 6)),
84
+ overlap_size=max(0, int(getattr(cfg, "adk_compaction_overlap", 1) or 1)),
85
+ )
86
+ tt = getattr(cfg, "adk_compaction_token_threshold", None)
87
+ rs = getattr(cfg, "adk_compaction_event_retention_size", None)
88
+ if tt is not None and rs is not None:
89
+ kw["token_threshold"] = int(tt)
90
+ kw["event_retention_size"] = int(rs)
91
+ if summarizer is not None:
92
+ kw["summarizer"] = summarizer
93
+ events_compaction_config = EventsCompactionConfig(**kw)
94
+ except Exception:
95
+ events_compaction_config = None
69
96
  return App(
70
97
  name="gemcode",
71
98
  root_agent=agent,
72
99
  plugins=plugins,
73
100
  context_cache_config=_build_context_cache_config(),
101
+ events_compaction_config=events_compaction_config,
74
102
  )
75
103
  except Exception:
76
104
  # Fall back silently — Runner still accepts the legacy kwargs.
@@ -18,6 +18,73 @@ from pathlib import Path
18
18
  from typing import Any
19
19
 
20
20
  _REF_PREFIX = "tool_result:"
21
+ _REPL_STATE_KEY = "gemcode:tool_replacement_state"
22
+
23
+
24
+ def _stable_key(tool_name: str, args: dict[str, Any] | None, seq: int | None) -> str:
25
+ """
26
+ Build a stable key for replacement decisions.
27
+
28
+ ADK does not expose tool_use_id directly to callbacks in all versions, so we use:
29
+ - per-turn tool sequence number (preferred when available)
30
+ - tool name
31
+ - a stable hash of args (best-effort)
32
+ """
33
+ import json
34
+ try:
35
+ args_s = json.dumps(args or {}, sort_keys=True, ensure_ascii=False)
36
+ except Exception:
37
+ args_s = str(args or {})
38
+ b = (f"{seq or 0}:{tool_name}:{args_s}").encode("utf-8", errors="replace")
39
+ return _sha256_bytes(b)
40
+
41
+
42
+ def maybe_offload_tool_result_stable(
43
+ *,
44
+ project_root: Path,
45
+ tool_name: str,
46
+ args: dict[str, Any] | None,
47
+ payload: Any,
48
+ max_inline_chars: int,
49
+ state: dict[str, Any] | None,
50
+ seq: int | None,
51
+ ) -> tuple[Any, bool]:
52
+ """
53
+ Stable offload wrapper.
54
+
55
+ - If we've already processed an identical tool call in this session, we re-apply
56
+ the exact same replacement structure to preserve prompt byte stability.
57
+ - Otherwise, we apply offload and remember the replacement result.
58
+ """
59
+ if state is None:
60
+ return maybe_offload_tool_result(
61
+ project_root=project_root,
62
+ tool_name=tool_name,
63
+ payload=payload,
64
+ max_inline_chars=max_inline_chars,
65
+ )
66
+
67
+ repl_state = state.get(_REPL_STATE_KEY)
68
+ if not isinstance(repl_state, dict):
69
+ repl_state = {}
70
+ state[_REPL_STATE_KEY] = repl_state
71
+
72
+ key = _stable_key(tool_name, args, seq)
73
+ if key in repl_state:
74
+ return repl_state[key], False
75
+
76
+ new_payload, did = maybe_offload_tool_result(
77
+ project_root=project_root,
78
+ tool_name=tool_name,
79
+ payload=payload,
80
+ max_inline_chars=max_inline_chars,
81
+ )
82
+ if did:
83
+ repl_state[key] = new_payload
84
+ else:
85
+ # Freeze "no replacement" decision too (prevents later shape drift).
86
+ repl_state[key] = payload
87
+ return new_payload, did
21
88
 
22
89
 
23
90
  def _store_dir(project_root: Path) -> Path:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemcode
3
- Version: 0.3.70
3
+ Version: 0.3.72
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -217,6 +217,16 @@ gemcode --yes "Add a module docstring to src/foo.py"
217
217
  gemcode --session mysess --yes "Continue: run tests and fix failures"
218
218
  ```
219
219
 
220
+ ### What GemCode writes to `.gemcode/`
221
+
222
+ GemCode keeps project-local state under `.gemcode/`:
223
+
224
+ - **`sessions.sqlite`**: session events/history (ADK `SqliteSessionService`)
225
+ - **`audit.log`**: JSONL audit trail for tool usage + model usage + stop reasons
226
+ - **`tool-results/`**: oversized tool outputs offloaded to stable refs (`tool_result:<sha>`)
227
+ - **`artifacts/`**: file artifacts (ADK `FileArtifactService`)
228
+ - **`policy.json`**: self-tuning per-repo profile used to calibrate dynamic budgets
229
+
220
230
  - **`--yes`**: allow mutating tools (`write_file`, `search_replace`). Shell execution is still restricted by the `.env.example` allowlist.
221
231
  - **`--session`**: Conversation history is stored under `.gemcode/sessions.sqlite` (ADK `SqliteSessionService`). Reuse the same `--session` id to continue.
222
232
  - **`--max-llm-calls`**: cap model↔tool iterations for this message (maps to ADK `RunConfig.max_llm_calls`). You can also set `GEMCODE_MAX_LLM_CALLS`.
@@ -236,6 +246,11 @@ gemcode --session mysess --yes "Continue: run tests and fix failures"
236
246
  - `gemcode tools smoke` fails non-zero if any tool’s declaration compilation fails
237
247
  - Optional inspection flags: `--deep-research`, `--maps-grounding`, `--embeddings`, `--memory`
238
248
  - **Optional compaction**: set `GEMCODE_ENABLE_COMPACT=1` to trim old `Content` entries before each model call (MVP sliding window; can break complex tool chains if too aggressive—tune `GEMCODE_MAX_CONTENT_ITEMS`).
249
+ - **ADK multi-agent transfer (recommended)**: enabled by default; GemCode builds an ADK sub-agent tree (Explorer/Verifier) and allows the model to transfer with `transfer_to_agent` when specialization helps.
250
+ - Disable: `GEMCODE_ADK_AGENT_TRANSFER=0`
251
+ - **ADK event compaction (new)**: optional ADK-native summarization of older events to keep long sessions coherent without exploding context.
252
+ - Enable: `GEMCODE_ADK_EVENTS_COMPACTION=1`
253
+ - Tune: `GEMCODE_ADK_COMPACTION_INTERVAL`, `GEMCODE_ADK_COMPACTION_OVERLAP`, `GEMCODE_ADK_COMPACTION_MODEL`
239
254
  - **Session token ceiling**: set `GEMCODE_MAX_SESSION_TOKENS` to stop the next LLM call when cumulative `usage_metadata.total_token_count` exceeds the limit.
240
255
  - **Token budget tracking**: set `GEMCODE_TOKEN_BUDGET` to enforce continuation/stop decisions per user turn (token-budget audit in `.gemcode/audit.log`).
241
256
  - **Stop-the-loop hooks**: set `GEMCODE_POST_TURN_HOOK=/path/to/hook.sh` (or place an executable at `.gemcode/hooks/post_turn`) to run after each user message.
@@ -243,6 +258,33 @@ gemcode --session mysess --yes "Continue: run tests and fix failures"
243
258
  - **Recovery-loop**: ADK `ReflectAndRetryToolPlugin`-based retries on tool failures.
244
259
  - Set `GEMCODE_ENABLE_TOOL_RECOVERY_RETRY=0` to disable.
245
260
  - Set `GEMCODE_TOOL_REFLECT_MAX_RETRIES=1` to control retries per tool.
261
+
262
+ ### Token efficiency (dynamic + intelligent)
263
+
264
+ GemCode optimizes tokens without losing capability by using a dynamic policy:
265
+
266
+ - **Context-pressure aware**: tool caps tighten when context is tight, loosen when there is room.
267
+ - **Risk/complexity aware**: caps increase for risky tasks (writes, shell, failures, many files).
268
+ - **Self-tuning per repo**: `.gemcode/policy.json` calibrates baseline evidence budgets over time.
269
+
270
+ Key env toggles:
271
+
272
+ - `GEMCODE_DYNAMIC_TOKEN_POLICY=0|1`
273
+ - `GEMCODE_DYNAMIC_RISK_POLICY=0|1`
274
+ - `GEMCODE_DYNAMIC_RISK_BOOST=<float>` (default `0.6`)
275
+ - `GEMCODE_TOOL_RESULT_OFFLOAD=0|1` (default `1`)
276
+
277
+ Live telemetry:
278
+
279
+ - `/status` shows `risk_score`, `context_percent_left`, and profile EMAs.
280
+
281
+ ### Stable tool output offloading (OpenClaude-style)
282
+
283
+ Oversized tool outputs are automatically offloaded and replaced with stable refs:
284
+
285
+ - Stored in: `.gemcode/tool-results/`
286
+ - References: `tool_result:<sha256>`
287
+ - Load on demand: `load_tool_result(ref)`
246
288
  - **Gemini thinking controls (Claude-like)**:
247
289
  - By default GemCode lets Gemini use its dynamic/adaptive thinking behavior.
248
290
  - Set `GEMCODE_DISABLE_THINKING=1` to force a best-effort “low thinking” mode:
@@ -301,12 +343,24 @@ the user’s project.
301
343
  - `list_directory`
302
344
  - `glob_files`
303
345
  - `grep_content`
346
+ - `repo_map`
347
+ - `web_search`
348
+ - `web_fetch`
349
+ - `notebook_read`
350
+ - `notebook_edit`
304
351
  - Mutating tools (require `--yes` unless your policy blocks them):
305
352
  - `write_file`
306
353
  - `search_replace`
307
354
  - Shell execution:
308
355
  - `run_command` (guarded by `GEMCODE_ALLOW_COMMANDS` from `.env.example` and
309
356
  `GEMCODE_PERMISSION_MODE`).
357
+ - `bash` (pipelines + redirects; supports `background=True`)
358
+
359
+ - Background task management (for processes started via `bash(..., background=True)`):
360
+ - `list_tasks`, `task_output`, `kill_task`
361
+
362
+ - Tool offload loader:
363
+ - `load_tool_result(ref)`
310
364
 
311
365
  Tool execution is still controlled by permission gates and then governed by
312
366
  GemCode’s circuit breaker + recovery behavior.
@@ -434,6 +488,23 @@ pip install -e ".[dev]"
434
488
  pytest
435
489
  ```
436
490
 
491
+ ## Release workflow (GitHub Actions → PyPI)
492
+
493
+ This repository publishes to PyPI on tag pushes:
494
+
495
+ - `.github/workflows/publish-pypi.yml` triggers on tags matching `v*`
496
+
497
+ Typical release steps:
498
+
499
+ ```bash
500
+ # 1) bump gemcode/pyproject.toml version
501
+ git add -A
502
+ git commit -m "release: vX.Y.Z"
503
+ git tag -a vX.Y.Z -m "vX.Y.Z"
504
+ git push origin HEAD
505
+ git push origin vX.Y.Z
506
+ ```
507
+
437
508
  ## References (local only)
438
509
 
439
510
  Do not commit proprietary leaked trees into this package. Keep `claude-code-leaked/` and similar folders outside version control or in a private mirror.
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