gemcode 0.3.70__py3-none-any.whl → 0.3.72__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gemcode/agent.py +39 -1
- gemcode/callbacks.py +42 -2
- gemcode/config.py +35 -0
- gemcode/repl_slash.py +8 -0
- gemcode/session_runtime.py +28 -0
- gemcode/tool_result_store.py +67 -0
- {gemcode-0.3.70.dist-info → gemcode-0.3.72.dist-info}/METADATA +72 -1
- {gemcode-0.3.70.dist-info → gemcode-0.3.72.dist-info}/RECORD +12 -12
- {gemcode-0.3.70.dist-info → gemcode-0.3.72.dist-info}/WHEEL +0 -0
- {gemcode-0.3.70.dist-info → gemcode-0.3.72.dist-info}/entry_points.txt +0 -0
- {gemcode-0.3.70.dist-info → gemcode-0.3.72.dist-info}/licenses/LICENSE +0 -0
- {gemcode-0.3.70.dist-info → gemcode-0.3.72.dist-info}/top_level.txt +0 -0
gemcode/agent.py
CHANGED
|
@@ -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
|
|
gemcode/callbacks.py
CHANGED
|
@@ -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
|
|
370
|
-
|
|
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")
|
gemcode/config.py
CHANGED
|
@@ -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.
|
gemcode/repl_slash.py
CHANGED
|
@@ -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}%")
|
gemcode/session_runtime.py
CHANGED
|
@@ -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.
|
gemcode/tool_result_store.py
CHANGED
|
@@ -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.
|
|
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.
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
gemcode/__init__.py,sha256=l0DCRYqK7KM7Fb7u49fqh-5_SlpeIL7r3LjMeJWMgSg,112
|
|
2
2
|
gemcode/__main__.py,sha256=EX2s1hxq2Yvli_-tnBN3w5Qv4bOjsBBbjyISF0pDIQw,37
|
|
3
|
-
gemcode/agent.py,sha256=
|
|
3
|
+
gemcode/agent.py,sha256=EogvAa9qBHY_rYmC52wks8G4dTYrJK0li5_zHFF9yN8,53894
|
|
4
4
|
gemcode/audit.py,sha256=bh9uhXaeh8wqxqoZtz3ZAowd8Ndk1ss-mw9993Vlrgo,469
|
|
5
5
|
gemcode/autocompact.py,sha256=77h5tgFzJ2rjrhlCL2oIc28IHwLbP4Pqlo7cSNgDwiA,6727
|
|
6
|
-
gemcode/callbacks.py,sha256=
|
|
6
|
+
gemcode/callbacks.py,sha256=EYkJTRcHjCeqTgVZGat2Y3BYWg4tNtHl-cgl5cdUxpo,31076
|
|
7
7
|
gemcode/capability_routing.py,sha256=yvQXwKtrfHXbgbNunU0Dxh9GCDN4cbySXIeccrdzr2o,3471
|
|
8
8
|
gemcode/cli.py,sha256=kBXb4b4JCG3u0XewCJn8lCyOT62Y8bOvlVoDc2R-GKQ,25320
|
|
9
9
|
gemcode/compaction.py,sha256=9YtA_qa23_8dHWVHx7AJwUduuI7jJQtq-m6sT8jgPWI,1186
|
|
10
|
-
gemcode/config.py,sha256=
|
|
10
|
+
gemcode/config.py,sha256=20OnbYjcvwTZ9qwKF5Ih-NfHxrpzctQ-bSXQtAZjYWI,16726
|
|
11
11
|
gemcode/context_budget.py,sha256=Nhox9vFBtLbb7jtO7cyGW1MxtN7SVjlIeQ7d-cgGyKM,10544
|
|
12
12
|
gemcode/context_warning.py,sha256=Q8mg5Vojj7EglPhsGAVL7vb8ROLuHVPgdzw25yw-Q2c,4263
|
|
13
13
|
gemcode/credentials.py,sha256=04v-rLD8_Ams69FQdof2FwcL3ZgsroGUnMcHNQFuBZo,1296
|
|
@@ -33,15 +33,15 @@ gemcode/pricing.py,sha256=lftp0SwyDqOzHqC2-6XzgZZhjif5PLdCe1Q3wY-p6kQ,3558
|
|
|
33
33
|
gemcode/prompt_suggestions.py,sha256=h-W_9LlfagS91PyoMEjEjsCqoG4XmIh3QBypA59HyGw,2553
|
|
34
34
|
gemcode/refine.py,sha256=BijEZ4Z32wGa9aK_WottyAhZF-j0xEqRg5UpjedNv2A,7653
|
|
35
35
|
gemcode/repl_commands.py,sha256=AHjDUhlq7IuSL6Fla17y0aeGZyp6snxwbMG7gz1PrRc,11330
|
|
36
|
-
gemcode/repl_slash.py,sha256=
|
|
36
|
+
gemcode/repl_slash.py,sha256=L5KFu4uriijEtxYgFJIpgiOrqk_F7aGnc7pJheYUDlM,49355
|
|
37
37
|
gemcode/review_agent.py,sha256=4t7_5-aE60b4-EheJ_eSB_H2eQYf9GppKoui6jw0TME,5264
|
|
38
|
-
gemcode/session_runtime.py,sha256=
|
|
38
|
+
gemcode/session_runtime.py,sha256=mHZooqGPjaLwUcLzkTaHQ_i13OiRqHlPPQ9PYFmtDfM,20513
|
|
39
39
|
gemcode/session_store.py,sha256=POUT_QQf715c74jbXj0s5vCd4dlAgJz_CLsIWuEUoO0,6051
|
|
40
40
|
gemcode/slash_commands.py,sha256=Qylzsj1notk0xN_hvd3CR4HD8g-l99UENDMcg1pKeBA,794
|
|
41
41
|
gemcode/thinking.py,sha256=RanBf_x9fKv1o4DNyNXPLfOdn2xT0KybJb65nYgmMEE,4885
|
|
42
42
|
gemcode/tool_prompt_manifest.py,sha256=MS_eSJg2BTp6yv1Ih2p93okPmnK3B2dYAMjnG6yaEVY,8695
|
|
43
43
|
gemcode/tool_registry.py,sha256=ifqxtr2uLwEUwnJLKYLza_tIz-paaZediJa75y9MiyA,1795
|
|
44
|
-
gemcode/tool_result_store.py,sha256=
|
|
44
|
+
gemcode/tool_result_store.py,sha256=pkeV5ekvsp6v7679yOTB2Rof4vPv3RdSl8urzawofq4,6351
|
|
45
45
|
gemcode/tools_inspector.py,sha256=okmu4PDYAQQ7nthDvuzSHmy2zArFTG4ftIPRadzLnxA,4100
|
|
46
46
|
gemcode/trust.py,sha256=fxe57Xg6aL_KU24bQDUtD-rXjsNpaq7g-eQTInZnudE,1336
|
|
47
47
|
gemcode/version.py,sha256=uwynYS-RmK8CDoqGtt8976kFkJv0zELkEAlwebnp_io,380
|
|
@@ -87,9 +87,9 @@ gemcode/tui/welcome_rich.py,sha256=8FEZzLXrzqly5JWiDgV9ooRV1LNXDk-CXV1a7K6ua-U,4
|
|
|
87
87
|
gemcode/web/__init__.py,sha256=EysmUAWs6g-lmMk4VFljKfaHVrEgb_FiIzwQmBdORJc,40
|
|
88
88
|
gemcode/web/claude_sse_adapter.py,sha256=HcNp0Lh4DdBZBLOpstsqa-VzfqAUrRngZ6FSuJ-mIMg,8609
|
|
89
89
|
gemcode/web/terminal_repl.py,sha256=k2irvFGbCY8gDm_pbirR7b_cakaeafcctoTIvnJkVXk,3902
|
|
90
|
-
gemcode-0.3.
|
|
91
|
-
gemcode-0.3.
|
|
92
|
-
gemcode-0.3.
|
|
93
|
-
gemcode-0.3.
|
|
94
|
-
gemcode-0.3.
|
|
95
|
-
gemcode-0.3.
|
|
90
|
+
gemcode-0.3.72.dist-info/licenses/LICENSE,sha256=TD4524qn-W8Z07GTDnag-9jJPFutFZNB0a1WbMHPC54,8388
|
|
91
|
+
gemcode-0.3.72.dist-info/METADATA,sha256=Gc52r_EC_JeO0CEkJZN-zdfxrRkUWDHCToG4wHt5xmA,26402
|
|
92
|
+
gemcode-0.3.72.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
93
|
+
gemcode-0.3.72.dist-info/entry_points.txt,sha256=cZdLTLDiHbks7OSUCuxCh66dCWeQdpLR8BozoqfEjV4,45
|
|
94
|
+
gemcode-0.3.72.dist-info/top_level.txt,sha256=UYrjULLBY2bcgK6KI6flomJWmsbDXu7n0rvW2SWFrbo,8
|
|
95
|
+
gemcode-0.3.72.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|