gemcode 0.3.68__tar.gz → 0.3.70__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gemcode-0.3.68/src/gemcode.egg-info → gemcode-0.3.70}/PKG-INFO +1 -1
- {gemcode-0.3.68 → gemcode-0.3.70}/pyproject.toml +1 -1
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/callbacks.py +69 -0
- gemcode-0.3.70/src/gemcode/policy_profile.py +135 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/repl_slash.py +21 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/session_runtime.py +10 -0
- {gemcode-0.3.68 → gemcode-0.3.70/src/gemcode.egg-info}/PKG-INFO +1 -1
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode.egg-info/SOURCES.txt +1 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/LICENSE +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/MANIFEST.in +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/README.md +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/setup.cfg +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/__init__.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/__main__.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/agent.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/audit.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/autocompact.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/capability_routing.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/cli.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/compaction.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/computer_use/__init__.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/computer_use/browser_computer.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/config.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/context_budget.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/context_warning.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/credentials.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/dynamic_policy.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/hitl_session.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/hooks.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/intent_classifier.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/interactions.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/invoke.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/kairos_daemon.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/limits.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/live_audio_engine.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/logging_config.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/mcp_loader.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/memory/__init__.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/memory/embedding_memory_service.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/memory/file_memory_service.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/modality_tools.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/model_errors.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/model_routing.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/openapi_loader.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/paths.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/permissions.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/plugins/__init__.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/pricing.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/prompt_suggestions.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/query/__init__.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/query/config.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/query/deps.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/query/engine.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/query/stop_hooks.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/query/token_budget.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/query/transitions.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/refine.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/repl_commands.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/review_agent.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/session_store.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/slash_commands.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/thinking.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/tool_prompt_manifest.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/tool_registry.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/tool_result_store.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/tools/__init__.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/tools/bash.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/tools/browser.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/tools/edit.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/tools/filesystem.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/tools/notebook.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/tools/notes.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/tools/repo_map.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/tools/search.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/tools/shell.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/tools/shell_gate.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/tools/subtask.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/tools/tasks.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/tools/think.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/tools/todo.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/tools/web.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/tools/web_search.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/tools_inspector.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/trust.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/tui/input_handler.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/tui/scrollback.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/tui/spinner.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/tui/welcome_banner.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/tui/welcome_rich.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/version.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/vertex.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/web/__init__.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/web/claude_sse_adapter.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/web/terminal_repl.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode/workspace_hints.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode.egg-info/dependency_links.txt +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode.egg-info/entry_points.txt +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode.egg-info/requires.txt +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/src/gemcode.egg-info/top_level.txt +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/tests/test_agent_instruction.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/tests/test_autocompact.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/tests/test_capability_routing.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/tests/test_claude_web_adapter_sse.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/tests/test_cli_init.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/tests/test_computer_use_permissions.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/tests/test_context_budget.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/tests/test_context_warning.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/tests/test_credentials.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/tests/test_interactive_permission_ask.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/tests/test_kairos_scheduler.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/tests/test_modality_tools.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/tests/test_model_error_retry.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/tests/test_model_errors.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/tests/test_model_routing.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/tests/test_paths.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/tests/test_permissions.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/tests/test_prompt_suggestions.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/tests/test_repl_commands.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/tests/test_repl_slash.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/tests/test_slash_commands.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/tests/test_thinking_config.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/tests/test_token_budget.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/tests/test_tool_context_circulation.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/tests/test_tools.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/tests/test_tools_inspector.py +0 -0
- {gemcode-0.3.68 → gemcode-0.3.70}/tests/test_workspace_hints.py +0 -0
|
@@ -43,6 +43,11 @@ _CTX_WARN_LEVEL_NOTIFIED = "gemcode:ctx_warn_level_notified"
|
|
|
43
43
|
_LAST_PROMPT_TOKENS = "gemcode:last_prompt_tokens"
|
|
44
44
|
_LAST_CONTEXT_PCT = "gemcode:last_context_percent_left"
|
|
45
45
|
_LAST_CONTEXT_LEVEL = "gemcode:last_context_alert_level"
|
|
46
|
+
_RISK_FILES_TOUCHED = "gemcode:risk_files_touched"
|
|
47
|
+
_RISK_TOOL_CALLS = "gemcode:risk_tool_calls"
|
|
48
|
+
_RISK_HAD_SHELL = "gemcode:risk_had_shell"
|
|
49
|
+
_RISK_HAD_WRITE = "gemcode:risk_had_write"
|
|
50
|
+
_RISK_HAD_FAILURE = "gemcode:risk_had_failure"
|
|
46
51
|
|
|
47
52
|
def _truthy_env(name: str, *, default: bool = False) -> bool:
|
|
48
53
|
v = os.environ.get(name)
|
|
@@ -141,6 +146,38 @@ def make_before_tool_callback(cfg: GemCodeConfig):
|
|
|
141
146
|
record = {"tool": name, "args": _redact_args(name, args)}
|
|
142
147
|
append_audit(cfg.project_root, record)
|
|
143
148
|
|
|
149
|
+
# Dynamic risk signals from actual repo interaction.
|
|
150
|
+
try:
|
|
151
|
+
if tool_context is not None:
|
|
152
|
+
st = tool_context.state
|
|
153
|
+
st[_RISK_TOOL_CALLS] = int(st.get(_RISK_TOOL_CALLS, 0) or 0) + 1
|
|
154
|
+
if name == "read_file":
|
|
155
|
+
p = (args or {}).get("path")
|
|
156
|
+
if isinstance(p, str) and p.strip():
|
|
157
|
+
touched: set[str] = set(st.get(_RISK_FILES_TOUCHED, []) or [])
|
|
158
|
+
touched.add(p.strip())
|
|
159
|
+
# Store as list for JSON-serializable session state.
|
|
160
|
+
st[_RISK_FILES_TOUCHED] = list(sorted(touched))[:200]
|
|
161
|
+
# More files touched => higher complexity.
|
|
162
|
+
n = len(touched)
|
|
163
|
+
cur = float(getattr(cfg, "_risk_score", 0.0) or 0.0)
|
|
164
|
+
if n >= 10:
|
|
165
|
+
cur = min(1.0, cur + 0.08)
|
|
166
|
+
elif n >= 5:
|
|
167
|
+
cur = min(1.0, cur + 0.04)
|
|
168
|
+
object.__setattr__(cfg, "_risk_score", cur)
|
|
169
|
+
# Writes / shell are inherently higher risk; allow more evidence.
|
|
170
|
+
if name in MUTATING_TOOLS:
|
|
171
|
+
st[_RISK_HAD_WRITE] = True
|
|
172
|
+
cur = float(getattr(cfg, "_risk_score", 0.0) or 0.0)
|
|
173
|
+
object.__setattr__(cfg, "_risk_score", min(1.0, cur + 0.12))
|
|
174
|
+
if name in SHELL_TOOLS:
|
|
175
|
+
st[_RISK_HAD_SHELL] = True
|
|
176
|
+
cur = float(getattr(cfg, "_risk_score", 0.0) or 0.0)
|
|
177
|
+
object.__setattr__(cfg, "_risk_score", min(1.0, cur + 0.08))
|
|
178
|
+
except Exception:
|
|
179
|
+
pass
|
|
180
|
+
|
|
144
181
|
# ── Shell hooks: pre_tool_use ─────────────────────────────────────────
|
|
145
182
|
# If the project has a .gemcode/hooks/pre_tool_use.sh, run it now.
|
|
146
183
|
# Non-zero exit or {"decision":"deny"} stdout will block the tool call.
|
|
@@ -391,10 +428,21 @@ def make_after_tool_callback(cfg: GemCodeConfig):
|
|
|
391
428
|
cur = float(getattr(cfg, "_risk_score", 0.0) or 0.0)
|
|
392
429
|
bump = 0.0
|
|
393
430
|
if err:
|
|
431
|
+
try:
|
|
432
|
+
st[_RISK_HAD_FAILURE] = True
|
|
433
|
+
except Exception:
|
|
434
|
+
pass
|
|
394
435
|
bump += 0.15
|
|
395
436
|
if isinstance(tool_response, dict) and isinstance(tool_response.get("exit_code"), int):
|
|
396
437
|
if int(tool_response["exit_code"]) != 0:
|
|
438
|
+
try:
|
|
439
|
+
st[_RISK_HAD_FAILURE] = True
|
|
440
|
+
except Exception:
|
|
441
|
+
pass
|
|
397
442
|
bump += 0.10
|
|
443
|
+
# Test/build failures should boost evidence allowance more.
|
|
444
|
+
if name in ("bash", "run_command"):
|
|
445
|
+
bump += 0.05
|
|
398
446
|
# decay slowly when things are healthy
|
|
399
447
|
if bump == 0.0:
|
|
400
448
|
cur = max(0.0, cur * 0.90)
|
|
@@ -403,6 +451,27 @@ def make_after_tool_callback(cfg: GemCodeConfig):
|
|
|
403
451
|
object.__setattr__(cfg, "_risk_score", cur)
|
|
404
452
|
except Exception:
|
|
405
453
|
pass
|
|
454
|
+
|
|
455
|
+
# Persist repo calibration profile (best-effort).
|
|
456
|
+
try:
|
|
457
|
+
files = st.get(_RISK_FILES_TOUCHED, []) or []
|
|
458
|
+
files_n = len(files) if isinstance(files, list) else 0
|
|
459
|
+
tool_calls = int(st.get(_RISK_TOOL_CALLS, 0) or 0)
|
|
460
|
+
had_shell = bool(st.get(_RISK_HAD_SHELL, False))
|
|
461
|
+
had_write = bool(st.get(_RISK_HAD_WRITE, False))
|
|
462
|
+
had_failure = bool(st.get(_RISK_HAD_FAILURE, False))
|
|
463
|
+
from gemcode.policy_profile import update_profile
|
|
464
|
+
prof = update_profile(
|
|
465
|
+
cfg.project_root,
|
|
466
|
+
files_touched=files_n,
|
|
467
|
+
tool_calls=tool_calls,
|
|
468
|
+
had_shell=had_shell,
|
|
469
|
+
had_write=had_write,
|
|
470
|
+
had_failure=had_failure,
|
|
471
|
+
)
|
|
472
|
+
object.__setattr__(cfg, "_policy_profile", prof.to_dict())
|
|
473
|
+
except Exception:
|
|
474
|
+
pass
|
|
406
475
|
# ── Shell hooks: post_tool_use ────────────────────────────────────────
|
|
407
476
|
try:
|
|
408
477
|
from gemcode.hooks import run_post_tool_use_hook
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Persistent per-repo policy profile.
|
|
3
|
+
|
|
4
|
+
Goal: make dynamic budgets self-tuning per repository without requiring manual
|
|
5
|
+
configuration. This stores lightweight rolling stats under `.gemcode/policy.json`.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import time
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _path(root: Path) -> Path:
|
|
18
|
+
d = root / ".gemcode"
|
|
19
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
20
|
+
return d / "policy.json"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _clamp(x: float, lo: float, hi: float) -> float:
|
|
24
|
+
return lo if x < lo else hi if x > hi else x
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _ema(prev: float, x: float, *, alpha: float) -> float:
|
|
28
|
+
return (alpha * x) + ((1.0 - alpha) * prev)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class PolicyProfile:
|
|
33
|
+
# Rolling averages in [0,1] where possible.
|
|
34
|
+
failure_rate_ema: float = 0.0
|
|
35
|
+
shell_rate_ema: float = 0.0
|
|
36
|
+
write_rate_ema: float = 0.0
|
|
37
|
+
files_touched_ema: float = 0.0 # scaled 0..1 (e.g. 0.5 ~ 10 files)
|
|
38
|
+
updated_at: int = 0
|
|
39
|
+
|
|
40
|
+
def to_dict(self) -> dict[str, Any]:
|
|
41
|
+
return {
|
|
42
|
+
"failure_rate_ema": self.failure_rate_ema,
|
|
43
|
+
"shell_rate_ema": self.shell_rate_ema,
|
|
44
|
+
"write_rate_ema": self.write_rate_ema,
|
|
45
|
+
"files_touched_ema": self.files_touched_ema,
|
|
46
|
+
"updated_at": self.updated_at,
|
|
47
|
+
"version": 1,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def from_dict(d: dict[str, Any]) -> "PolicyProfile":
|
|
52
|
+
try:
|
|
53
|
+
return PolicyProfile(
|
|
54
|
+
failure_rate_ema=float(d.get("failure_rate_ema", 0.0) or 0.0),
|
|
55
|
+
shell_rate_ema=float(d.get("shell_rate_ema", 0.0) or 0.0),
|
|
56
|
+
write_rate_ema=float(d.get("write_rate_ema", 0.0) or 0.0),
|
|
57
|
+
files_touched_ema=float(d.get("files_touched_ema", 0.0) or 0.0),
|
|
58
|
+
updated_at=int(d.get("updated_at", 0) or 0),
|
|
59
|
+
)
|
|
60
|
+
except Exception:
|
|
61
|
+
return PolicyProfile()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def load_profile(project_root: Path) -> PolicyProfile:
|
|
65
|
+
p = _path(project_root)
|
|
66
|
+
if not p.exists():
|
|
67
|
+
return PolicyProfile()
|
|
68
|
+
try:
|
|
69
|
+
raw = p.read_text(encoding="utf-8", errors="replace")
|
|
70
|
+
d = json.loads(raw) if raw.strip() else {}
|
|
71
|
+
if isinstance(d, dict):
|
|
72
|
+
return PolicyProfile.from_dict(d)
|
|
73
|
+
except Exception:
|
|
74
|
+
return PolicyProfile()
|
|
75
|
+
return PolicyProfile()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def save_profile(project_root: Path, profile: PolicyProfile) -> None:
|
|
79
|
+
p = _path(project_root)
|
|
80
|
+
p.write_text(
|
|
81
|
+
json.dumps(profile.to_dict(), ensure_ascii=False, indent=2),
|
|
82
|
+
encoding="utf-8",
|
|
83
|
+
errors="replace",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def update_profile(
|
|
88
|
+
project_root: Path,
|
|
89
|
+
*,
|
|
90
|
+
files_touched: int,
|
|
91
|
+
tool_calls: int,
|
|
92
|
+
had_shell: bool,
|
|
93
|
+
had_write: bool,
|
|
94
|
+
had_failure: bool,
|
|
95
|
+
alpha: float = 0.08,
|
|
96
|
+
) -> PolicyProfile:
|
|
97
|
+
"""
|
|
98
|
+
Update profile with a single-turn observation.
|
|
99
|
+
|
|
100
|
+
We scale files_touched into [0,1] via min(files/20, 1).
|
|
101
|
+
"""
|
|
102
|
+
prof = load_profile(project_root)
|
|
103
|
+
alpha = _clamp(alpha, 0.01, 0.3)
|
|
104
|
+
ft_scaled = _clamp(float(files_touched) / 20.0, 0.0, 1.0)
|
|
105
|
+
fail = 1.0 if had_failure else 0.0
|
|
106
|
+
shell = 1.0 if had_shell else 0.0
|
|
107
|
+
write = 1.0 if had_write else 0.0
|
|
108
|
+
# tool_calls unused for now, but reserved for future calibration.
|
|
109
|
+
_ = tool_calls
|
|
110
|
+
updated = PolicyProfile(
|
|
111
|
+
failure_rate_ema=_ema(prof.failure_rate_ema, fail, alpha=alpha),
|
|
112
|
+
shell_rate_ema=_ema(prof.shell_rate_ema, shell, alpha=alpha),
|
|
113
|
+
write_rate_ema=_ema(prof.write_rate_ema, write, alpha=alpha),
|
|
114
|
+
files_touched_ema=_ema(prof.files_touched_ema, ft_scaled, alpha=alpha),
|
|
115
|
+
updated_at=int(time.time()),
|
|
116
|
+
)
|
|
117
|
+
save_profile(project_root, updated)
|
|
118
|
+
return updated
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def calibrated_baseline_risk(profile: PolicyProfile) -> float:
|
|
122
|
+
"""
|
|
123
|
+
Convert profile into a baseline risk prior for a repo.
|
|
124
|
+
|
|
125
|
+
Repos with frequent failures, many writes, and lots of files touched tend to
|
|
126
|
+
benefit from higher evidence budgets by default.
|
|
127
|
+
"""
|
|
128
|
+
r = (
|
|
129
|
+
0.55 * profile.failure_rate_ema
|
|
130
|
+
+ 0.20 * profile.write_rate_ema
|
|
131
|
+
+ 0.15 * profile.shell_rate_ema
|
|
132
|
+
+ 0.10 * profile.files_touched_ema
|
|
133
|
+
)
|
|
134
|
+
return _clamp(r, 0.0, 0.8)
|
|
135
|
+
|
|
@@ -391,6 +391,27 @@ async def process_repl_slash(
|
|
|
391
391
|
out(f" thinking_budget: {cfg.thinking_budget if cfg.thinking_budget is not None else '(auto)'}")
|
|
392
392
|
out(f" show_full_thinking: {cfg.show_full_thinking}")
|
|
393
393
|
out()
|
|
394
|
+
# Dynamic policy telemetry
|
|
395
|
+
try:
|
|
396
|
+
risk = float(getattr(cfg, "_risk_score", 0.0) or 0.0)
|
|
397
|
+
pct = getattr(cfg, "_context_percent_left", None)
|
|
398
|
+
prof = getattr(cfg, "_policy_profile", None)
|
|
399
|
+
out(" Dynamic policy:")
|
|
400
|
+
out(f" dynamic_token_policy: {getattr(cfg, 'dynamic_token_policy', True)}")
|
|
401
|
+
out(f" dynamic_risk_policy: {getattr(cfg, 'dynamic_risk_policy', True)}")
|
|
402
|
+
out(f" dynamic_risk_boost: {getattr(cfg, 'dynamic_risk_boost', 0.6)}")
|
|
403
|
+
out(f" risk_score: {risk:.2f}")
|
|
404
|
+
if isinstance(pct, int):
|
|
405
|
+
out(f" context_percent_left: {pct}%")
|
|
406
|
+
if isinstance(prof, dict):
|
|
407
|
+
try:
|
|
408
|
+
out(f" profile.failure_rate_ema: {float(prof.get('failure_rate_ema', 0.0) or 0.0):.2f}")
|
|
409
|
+
out(f" profile.files_touched_ema: {float(prof.get('files_touched_ema', 0.0) or 0.0):.2f}")
|
|
410
|
+
except Exception:
|
|
411
|
+
pass
|
|
412
|
+
out()
|
|
413
|
+
except Exception:
|
|
414
|
+
pass
|
|
394
415
|
out(" Autocompact:")
|
|
395
416
|
out(f" GEMCODE_AUTOCOMPACT: {os.environ.get('GEMCODE_AUTOCOMPACT', '1')}")
|
|
396
417
|
out(f" GEMCODE_AUTOCOMPACT_BUFFER_CHARS: {os.environ.get('GEMCODE_AUTOCOMPACT_BUFFER_CHARS', '60000')}")
|
|
@@ -314,6 +314,16 @@ def _build_artifact_service(cfg: GemCodeConfig):
|
|
|
314
314
|
|
|
315
315
|
def create_runner(cfg: GemCodeConfig, extra_tools: list | None = None) -> Runner:
|
|
316
316
|
"""Construct Runner + SQLite session service + root LlmAgent."""
|
|
317
|
+
# Load per-repo calibration profile (self-tuning dynamic policy).
|
|
318
|
+
try:
|
|
319
|
+
from gemcode.policy_profile import calibrated_baseline_risk, load_profile
|
|
320
|
+
prof = load_profile(cfg.project_root)
|
|
321
|
+
base = calibrated_baseline_risk(prof)
|
|
322
|
+
cur = float(getattr(cfg, "_risk_score", 0.0) or 0.0)
|
|
323
|
+
object.__setattr__(cfg, "_risk_score", max(cur, base))
|
|
324
|
+
object.__setattr__(cfg, "_policy_profile", prof.to_dict())
|
|
325
|
+
except Exception:
|
|
326
|
+
pass
|
|
317
327
|
modality_tools = build_modality_extra_tools(cfg)
|
|
318
328
|
merged_extra_tools: list | None
|
|
319
329
|
if extra_tools:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|