gemcode 0.3.70__tar.gz → 0.3.71__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.71}/PKG-INFO +67 -1
  2. {gemcode-0.3.70 → gemcode-0.3.71}/README.md +66 -0
  3. {gemcode-0.3.70 → gemcode-0.3.71}/pyproject.toml +1 -1
  4. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/callbacks.py +42 -2
  5. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/config.py +10 -0
  6. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/tool_result_store.py +67 -0
  7. {gemcode-0.3.70 → gemcode-0.3.71/src/gemcode.egg-info}/PKG-INFO +67 -1
  8. {gemcode-0.3.70 → gemcode-0.3.71}/LICENSE +0 -0
  9. {gemcode-0.3.70 → gemcode-0.3.71}/MANIFEST.in +0 -0
  10. {gemcode-0.3.70 → gemcode-0.3.71}/setup.cfg +0 -0
  11. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/__init__.py +0 -0
  12. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/__main__.py +0 -0
  13. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/agent.py +0 -0
  14. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/audit.py +0 -0
  15. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/autocompact.py +0 -0
  16. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/capability_routing.py +0 -0
  17. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/cli.py +0 -0
  18. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/compaction.py +0 -0
  19. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/computer_use/__init__.py +0 -0
  20. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/computer_use/browser_computer.py +0 -0
  21. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/context_budget.py +0 -0
  22. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/context_warning.py +0 -0
  23. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/credentials.py +0 -0
  24. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/dynamic_policy.py +0 -0
  25. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/hitl_session.py +0 -0
  26. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/hooks.py +0 -0
  27. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/intent_classifier.py +0 -0
  28. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/interactions.py +0 -0
  29. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/invoke.py +0 -0
  30. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/kairos_daemon.py +0 -0
  31. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/limits.py +0 -0
  32. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/live_audio_engine.py +0 -0
  33. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/logging_config.py +0 -0
  34. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/mcp_loader.py +0 -0
  35. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/memory/__init__.py +0 -0
  36. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/memory/embedding_memory_service.py +0 -0
  37. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/memory/file_memory_service.py +0 -0
  38. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/modality_tools.py +0 -0
  39. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/model_errors.py +0 -0
  40. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/model_routing.py +0 -0
  41. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/openapi_loader.py +0 -0
  42. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/paths.py +0 -0
  43. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/permissions.py +0 -0
  44. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/plugins/__init__.py +0 -0
  45. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
  46. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
  47. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/policy_profile.py +0 -0
  48. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/pricing.py +0 -0
  49. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/prompt_suggestions.py +0 -0
  50. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/query/__init__.py +0 -0
  51. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/query/config.py +0 -0
  52. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/query/deps.py +0 -0
  53. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/query/engine.py +0 -0
  54. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/query/stop_hooks.py +0 -0
  55. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/query/token_budget.py +0 -0
  56. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/query/transitions.py +0 -0
  57. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/refine.py +0 -0
  58. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/repl_commands.py +0 -0
  59. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/repl_slash.py +0 -0
  60. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/review_agent.py +0 -0
  61. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/session_runtime.py +0 -0
  62. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/session_store.py +0 -0
  63. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/slash_commands.py +0 -0
  64. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/thinking.py +0 -0
  65. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/tool_prompt_manifest.py +0 -0
  66. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/tool_registry.py +0 -0
  67. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/tools/__init__.py +0 -0
  68. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/tools/bash.py +0 -0
  69. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/tools/browser.py +0 -0
  70. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/tools/edit.py +0 -0
  71. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/tools/filesystem.py +0 -0
  72. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/tools/notebook.py +0 -0
  73. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/tools/notes.py +0 -0
  74. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/tools/repo_map.py +0 -0
  75. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/tools/search.py +0 -0
  76. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/tools/shell.py +0 -0
  77. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/tools/shell_gate.py +0 -0
  78. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/tools/subtask.py +0 -0
  79. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/tools/tasks.py +0 -0
  80. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/tools/think.py +0 -0
  81. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/tools/todo.py +0 -0
  82. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/tools/web.py +0 -0
  83. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/tools/web_search.py +0 -0
  84. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/tools_inspector.py +0 -0
  85. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/trust.py +0 -0
  86. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/tui/input_handler.py +0 -0
  87. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/tui/scrollback.py +0 -0
  88. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/tui/spinner.py +0 -0
  89. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/tui/welcome_banner.py +0 -0
  90. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/tui/welcome_rich.py +0 -0
  91. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/version.py +0 -0
  92. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/vertex.py +0 -0
  93. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/web/__init__.py +0 -0
  94. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/web/claude_sse_adapter.py +0 -0
  95. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/web/terminal_repl.py +0 -0
  96. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode/workspace_hints.py +0 -0
  97. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode.egg-info/SOURCES.txt +0 -0
  98. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode.egg-info/dependency_links.txt +0 -0
  99. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode.egg-info/entry_points.txt +0 -0
  100. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode.egg-info/requires.txt +0 -0
  101. {gemcode-0.3.70 → gemcode-0.3.71}/src/gemcode.egg-info/top_level.txt +0 -0
  102. {gemcode-0.3.70 → gemcode-0.3.71}/tests/test_agent_instruction.py +0 -0
  103. {gemcode-0.3.70 → gemcode-0.3.71}/tests/test_autocompact.py +0 -0
  104. {gemcode-0.3.70 → gemcode-0.3.71}/tests/test_capability_routing.py +0 -0
  105. {gemcode-0.3.70 → gemcode-0.3.71}/tests/test_claude_web_adapter_sse.py +0 -0
  106. {gemcode-0.3.70 → gemcode-0.3.71}/tests/test_cli_init.py +0 -0
  107. {gemcode-0.3.70 → gemcode-0.3.71}/tests/test_computer_use_permissions.py +0 -0
  108. {gemcode-0.3.70 → gemcode-0.3.71}/tests/test_context_budget.py +0 -0
  109. {gemcode-0.3.70 → gemcode-0.3.71}/tests/test_context_warning.py +0 -0
  110. {gemcode-0.3.70 → gemcode-0.3.71}/tests/test_credentials.py +0 -0
  111. {gemcode-0.3.70 → gemcode-0.3.71}/tests/test_interactive_permission_ask.py +0 -0
  112. {gemcode-0.3.70 → gemcode-0.3.71}/tests/test_kairos_scheduler.py +0 -0
  113. {gemcode-0.3.70 → gemcode-0.3.71}/tests/test_modality_tools.py +0 -0
  114. {gemcode-0.3.70 → gemcode-0.3.71}/tests/test_model_error_retry.py +0 -0
  115. {gemcode-0.3.70 → gemcode-0.3.71}/tests/test_model_errors.py +0 -0
  116. {gemcode-0.3.70 → gemcode-0.3.71}/tests/test_model_routing.py +0 -0
  117. {gemcode-0.3.70 → gemcode-0.3.71}/tests/test_paths.py +0 -0
  118. {gemcode-0.3.70 → gemcode-0.3.71}/tests/test_permissions.py +0 -0
  119. {gemcode-0.3.70 → gemcode-0.3.71}/tests/test_prompt_suggestions.py +0 -0
  120. {gemcode-0.3.70 → gemcode-0.3.71}/tests/test_repl_commands.py +0 -0
  121. {gemcode-0.3.70 → gemcode-0.3.71}/tests/test_repl_slash.py +0 -0
  122. {gemcode-0.3.70 → gemcode-0.3.71}/tests/test_slash_commands.py +0 -0
  123. {gemcode-0.3.70 → gemcode-0.3.71}/tests/test_thinking_config.py +0 -0
  124. {gemcode-0.3.70 → gemcode-0.3.71}/tests/test_token_budget.py +0 -0
  125. {gemcode-0.3.70 → gemcode-0.3.71}/tests/test_tool_context_circulation.py +0 -0
  126. {gemcode-0.3.70 → gemcode-0.3.71}/tests/test_tools.py +0 -0
  127. {gemcode-0.3.70 → gemcode-0.3.71}/tests/test_tools_inspector.py +0 -0
  128. {gemcode-0.3.70 → gemcode-0.3.71}/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.71
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`.
@@ -243,6 +253,33 @@ gemcode --session mysess --yes "Continue: run tests and fix failures"
243
253
  - **Recovery-loop**: ADK `ReflectAndRetryToolPlugin`-based retries on tool failures.
244
254
  - Set `GEMCODE_ENABLE_TOOL_RECOVERY_RETRY=0` to disable.
245
255
  - Set `GEMCODE_TOOL_REFLECT_MAX_RETRIES=1` to control retries per tool.
256
+
257
+ ### Token efficiency (dynamic + intelligent)
258
+
259
+ GemCode optimizes tokens without losing capability by using a dynamic policy:
260
+
261
+ - **Context-pressure aware**: tool caps tighten when context is tight, loosen when there is room.
262
+ - **Risk/complexity aware**: caps increase for risky tasks (writes, shell, failures, many files).
263
+ - **Self-tuning per repo**: `.gemcode/policy.json` calibrates baseline evidence budgets over time.
264
+
265
+ Key env toggles:
266
+
267
+ - `GEMCODE_DYNAMIC_TOKEN_POLICY=0|1`
268
+ - `GEMCODE_DYNAMIC_RISK_POLICY=0|1`
269
+ - `GEMCODE_DYNAMIC_RISK_BOOST=<float>` (default `0.6`)
270
+ - `GEMCODE_TOOL_RESULT_OFFLOAD=0|1` (default `1`)
271
+
272
+ Live telemetry:
273
+
274
+ - `/status` shows `risk_score`, `context_percent_left`, and profile EMAs.
275
+
276
+ ### Stable tool output offloading (OpenClaude-style)
277
+
278
+ Oversized tool outputs are automatically offloaded and replaced with stable refs:
279
+
280
+ - Stored in: `.gemcode/tool-results/`
281
+ - References: `tool_result:<sha256>`
282
+ - Load on demand: `load_tool_result(ref)`
246
283
  - **Gemini thinking controls (Claude-like)**:
247
284
  - By default GemCode lets Gemini use its dynamic/adaptive thinking behavior.
248
285
  - Set `GEMCODE_DISABLE_THINKING=1` to force a best-effort “low thinking” mode:
@@ -301,12 +338,24 @@ the user’s project.
301
338
  - `list_directory`
302
339
  - `glob_files`
303
340
  - `grep_content`
341
+ - `repo_map`
342
+ - `web_search`
343
+ - `web_fetch`
344
+ - `notebook_read`
345
+ - `notebook_edit`
304
346
  - Mutating tools (require `--yes` unless your policy blocks them):
305
347
  - `write_file`
306
348
  - `search_replace`
307
349
  - Shell execution:
308
350
  - `run_command` (guarded by `GEMCODE_ALLOW_COMMANDS` from `.env.example` and
309
351
  `GEMCODE_PERMISSION_MODE`).
352
+ - `bash` (pipelines + redirects; supports `background=True`)
353
+
354
+ - Background task management (for processes started via `bash(..., background=True)`):
355
+ - `list_tasks`, `task_output`, `kill_task`
356
+
357
+ - Tool offload loader:
358
+ - `load_tool_result(ref)`
310
359
 
311
360
  Tool execution is still controlled by permission gates and then governed by
312
361
  GemCode’s circuit breaker + recovery behavior.
@@ -434,6 +483,23 @@ pip install -e ".[dev]"
434
483
  pytest
435
484
  ```
436
485
 
486
+ ## Release workflow (GitHub Actions → PyPI)
487
+
488
+ This repository publishes to PyPI on tag pushes:
489
+
490
+ - `.github/workflows/publish-pypi.yml` triggers on tags matching `v*`
491
+
492
+ Typical release steps:
493
+
494
+ ```bash
495
+ # 1) bump gemcode/pyproject.toml version
496
+ git add -A
497
+ git commit -m "release: vX.Y.Z"
498
+ git tag -a vX.Y.Z -m "vX.Y.Z"
499
+ git push origin HEAD
500
+ git push origin vX.Y.Z
501
+ ```
502
+
437
503
  ## References (local only)
438
504
 
439
505
  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`.
@@ -54,6 +64,33 @@ gemcode --session mysess --yes "Continue: run tests and fix failures"
54
64
  - **Recovery-loop**: ADK `ReflectAndRetryToolPlugin`-based retries on tool failures.
55
65
  - Set `GEMCODE_ENABLE_TOOL_RECOVERY_RETRY=0` to disable.
56
66
  - Set `GEMCODE_TOOL_REFLECT_MAX_RETRIES=1` to control retries per tool.
67
+
68
+ ### Token efficiency (dynamic + intelligent)
69
+
70
+ GemCode optimizes tokens without losing capability by using a dynamic policy:
71
+
72
+ - **Context-pressure aware**: tool caps tighten when context is tight, loosen when there is room.
73
+ - **Risk/complexity aware**: caps increase for risky tasks (writes, shell, failures, many files).
74
+ - **Self-tuning per repo**: `.gemcode/policy.json` calibrates baseline evidence budgets over time.
75
+
76
+ Key env toggles:
77
+
78
+ - `GEMCODE_DYNAMIC_TOKEN_POLICY=0|1`
79
+ - `GEMCODE_DYNAMIC_RISK_POLICY=0|1`
80
+ - `GEMCODE_DYNAMIC_RISK_BOOST=<float>` (default `0.6`)
81
+ - `GEMCODE_TOOL_RESULT_OFFLOAD=0|1` (default `1`)
82
+
83
+ Live telemetry:
84
+
85
+ - `/status` shows `risk_score`, `context_percent_left`, and profile EMAs.
86
+
87
+ ### Stable tool output offloading (OpenClaude-style)
88
+
89
+ Oversized tool outputs are automatically offloaded and replaced with stable refs:
90
+
91
+ - Stored in: `.gemcode/tool-results/`
92
+ - References: `tool_result:<sha256>`
93
+ - Load on demand: `load_tool_result(ref)`
57
94
  - **Gemini thinking controls (Claude-like)**:
58
95
  - By default GemCode lets Gemini use its dynamic/adaptive thinking behavior.
59
96
  - Set `GEMCODE_DISABLE_THINKING=1` to force a best-effort “low thinking” mode:
@@ -112,12 +149,24 @@ the user’s project.
112
149
  - `list_directory`
113
150
  - `glob_files`
114
151
  - `grep_content`
152
+ - `repo_map`
153
+ - `web_search`
154
+ - `web_fetch`
155
+ - `notebook_read`
156
+ - `notebook_edit`
115
157
  - Mutating tools (require `--yes` unless your policy blocks them):
116
158
  - `write_file`
117
159
  - `search_replace`
118
160
  - Shell execution:
119
161
  - `run_command` (guarded by `GEMCODE_ALLOW_COMMANDS` from `.env.example` and
120
162
  `GEMCODE_PERMISSION_MODE`).
163
+ - `bash` (pipelines + redirects; supports `background=True`)
164
+
165
+ - Background task management (for processes started via `bash(..., background=True)`):
166
+ - `list_tasks`, `task_output`, `kill_task`
167
+
168
+ - Tool offload loader:
169
+ - `load_tool_result(ref)`
121
170
 
122
171
  Tool execution is still controlled by permission gates and then governed by
123
172
  GemCode’s circuit breaker + recovery behavior.
@@ -245,6 +294,23 @@ pip install -e ".[dev]"
245
294
  pytest
246
295
  ```
247
296
 
297
+ ## Release workflow (GitHub Actions → PyPI)
298
+
299
+ This repository publishes to PyPI on tag pushes:
300
+
301
+ - `.github/workflows/publish-pypi.yml` triggers on tags matching `v*`
302
+
303
+ Typical release steps:
304
+
305
+ ```bash
306
+ # 1) bump gemcode/pyproject.toml version
307
+ git add -A
308
+ git commit -m "release: vX.Y.Z"
309
+ git tag -a vX.Y.Z -m "vX.Y.Z"
310
+ git push origin HEAD
311
+ git push origin vX.Y.Z
312
+ ```
313
+
248
314
  ## References (local only)
249
315
 
250
316
  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.71"
8
8
  description = "Local-first coding agent on Google Gemini + ADK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -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,12 @@ 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
+
134
144
  # When enabled, oversized tool outputs are offloaded to disk under
135
145
  # .gemcode/tool-results/ and replaced in history with stable refs + previews.
136
146
  # This reduces context bloat and improves prompt-cache stability.
@@ -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.71
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`.
@@ -243,6 +253,33 @@ gemcode --session mysess --yes "Continue: run tests and fix failures"
243
253
  - **Recovery-loop**: ADK `ReflectAndRetryToolPlugin`-based retries on tool failures.
244
254
  - Set `GEMCODE_ENABLE_TOOL_RECOVERY_RETRY=0` to disable.
245
255
  - Set `GEMCODE_TOOL_REFLECT_MAX_RETRIES=1` to control retries per tool.
256
+
257
+ ### Token efficiency (dynamic + intelligent)
258
+
259
+ GemCode optimizes tokens without losing capability by using a dynamic policy:
260
+
261
+ - **Context-pressure aware**: tool caps tighten when context is tight, loosen when there is room.
262
+ - **Risk/complexity aware**: caps increase for risky tasks (writes, shell, failures, many files).
263
+ - **Self-tuning per repo**: `.gemcode/policy.json` calibrates baseline evidence budgets over time.
264
+
265
+ Key env toggles:
266
+
267
+ - `GEMCODE_DYNAMIC_TOKEN_POLICY=0|1`
268
+ - `GEMCODE_DYNAMIC_RISK_POLICY=0|1`
269
+ - `GEMCODE_DYNAMIC_RISK_BOOST=<float>` (default `0.6`)
270
+ - `GEMCODE_TOOL_RESULT_OFFLOAD=0|1` (default `1`)
271
+
272
+ Live telemetry:
273
+
274
+ - `/status` shows `risk_score`, `context_percent_left`, and profile EMAs.
275
+
276
+ ### Stable tool output offloading (OpenClaude-style)
277
+
278
+ Oversized tool outputs are automatically offloaded and replaced with stable refs:
279
+
280
+ - Stored in: `.gemcode/tool-results/`
281
+ - References: `tool_result:<sha256>`
282
+ - Load on demand: `load_tool_result(ref)`
246
283
  - **Gemini thinking controls (Claude-like)**:
247
284
  - By default GemCode lets Gemini use its dynamic/adaptive thinking behavior.
248
285
  - Set `GEMCODE_DISABLE_THINKING=1` to force a best-effort “low thinking” mode:
@@ -301,12 +338,24 @@ the user’s project.
301
338
  - `list_directory`
302
339
  - `glob_files`
303
340
  - `grep_content`
341
+ - `repo_map`
342
+ - `web_search`
343
+ - `web_fetch`
344
+ - `notebook_read`
345
+ - `notebook_edit`
304
346
  - Mutating tools (require `--yes` unless your policy blocks them):
305
347
  - `write_file`
306
348
  - `search_replace`
307
349
  - Shell execution:
308
350
  - `run_command` (guarded by `GEMCODE_ALLOW_COMMANDS` from `.env.example` and
309
351
  `GEMCODE_PERMISSION_MODE`).
352
+ - `bash` (pipelines + redirects; supports `background=True`)
353
+
354
+ - Background task management (for processes started via `bash(..., background=True)`):
355
+ - `list_tasks`, `task_output`, `kill_task`
356
+
357
+ - Tool offload loader:
358
+ - `load_tool_result(ref)`
310
359
 
311
360
  Tool execution is still controlled by permission gates and then governed by
312
361
  GemCode’s circuit breaker + recovery behavior.
@@ -434,6 +483,23 @@ pip install -e ".[dev]"
434
483
  pytest
435
484
  ```
436
485
 
486
+ ## Release workflow (GitHub Actions → PyPI)
487
+
488
+ This repository publishes to PyPI on tag pushes:
489
+
490
+ - `.github/workflows/publish-pypi.yml` triggers on tags matching `v*`
491
+
492
+ Typical release steps:
493
+
494
+ ```bash
495
+ # 1) bump gemcode/pyproject.toml version
496
+ git add -A
497
+ git commit -m "release: vX.Y.Z"
498
+ git tag -a vX.Y.Z -m "vX.Y.Z"
499
+ git push origin HEAD
500
+ git push origin vX.Y.Z
501
+ ```
502
+
437
503
  ## References (local only)
438
504
 
439
505
  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
File without changes