@tiens.nguyen/gu-cli 1.0.747 → 1.0.748
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.
- package/gonext_agent_chat.py +66 -28
- package/package.json +1 -1
package/gonext_agent_chat.py
CHANGED
|
@@ -2140,8 +2140,34 @@ def _usage_reports_cache(usage) -> bool:
|
|
|
2140
2140
|
# against the corrected counter so that the OBSERVED GOOD BEHAVIOUR is preserved: one pass in
|
|
2141
2141
|
# 86 steps, firing where it fired before. Raising the counter without raising the threshold
|
|
2142
2142
|
# would fire earlier and more often — the nibbling this whole mechanism exists to stop.
|
|
2143
|
-
_TRIM_FIRST_AT_CHARS =
|
|
2143
|
+
_TRIM_FIRST_AT_CHARS = 90_000 # was 125k: a first trim frees most of the history, so it pays
|
|
2144
|
+
# back fast — the payback gate below keeps that honest
|
|
2144
2145
|
_TRIM_AGAIN_AFTER_CHARS = 60_000 # …and not again until it has grown this much further
|
|
2146
|
+
# A trim REWRITES the history into the prompt cache, so it is only worth it when the smaller
|
|
2147
|
+
# prompt repays that within a few steps (task #279). Prices relative to a normal input token:
|
|
2148
|
+
# Anthropic cache write 1.25x (5-minute TTL), cache read 0.1x. Measured 2026-09-25 on one turn:
|
|
2149
|
+
# trim 1 (69.7k → 28.3k tokens) paid back in ~2 steps; trim 2 (54.8k → 38.6k) needed ~15 and
|
|
2150
|
+
# only 3 followed, so it cost ~18k more than it saved.
|
|
2151
|
+
_TRIM_PAYBACK_STEPS = 5
|
|
2152
|
+
_CACHE_WRITE_X = 1.25
|
|
2153
|
+
_CACHE_READ_X = 0.1
|
|
2154
|
+
|
|
2155
|
+
|
|
2156
|
+
def _trim_payback_steps(history_chars: int, freed_chars: int) -> float:
|
|
2157
|
+
"""How many later steps a trim needs to repay its cache cost. inf when it frees nothing.
|
|
2158
|
+
|
|
2159
|
+
After a trim, the history that remains is written to the cache once instead of read
|
|
2160
|
+
(costing WRITE - READ more per token); every later step then reads `freed` fewer tokens
|
|
2161
|
+
(saving READ each). Chars stand in for tokens — only the ratio matters.
|
|
2162
|
+
"""
|
|
2163
|
+
if freed_chars <= 0:
|
|
2164
|
+
return float("inf")
|
|
2165
|
+
remaining = max(0, history_chars - freed_chars)
|
|
2166
|
+
return ((_CACHE_WRITE_X - _CACHE_READ_X) * remaining) / (_CACHE_READ_X * freed_chars)
|
|
2167
|
+
|
|
2168
|
+
|
|
2169
|
+
def _trim_pays_back(history_chars: int, freed_chars: int, limit: float = _TRIM_PAYBACK_STEPS) -> bool:
|
|
2170
|
+
return _trim_payback_steps(history_chars, freed_chars) <= limit
|
|
2145
2171
|
|
|
2146
2172
|
|
|
2147
2173
|
def _step_memory_chars(steps) -> int:
|
|
@@ -7999,11 +8025,13 @@ def run_agent_chat(cfg):
|
|
|
7999
8025
|
"truncated and slow every later step) → edit_lines to change it → run_command to "
|
|
8000
8026
|
"VERIFY. edit_file only for a byte-exact unique string; create_file(overwrite=True) "
|
|
8001
8027
|
"replaces a whole file in ONE call (no read+edit just to rewrite it).\n"
|
|
8002
|
-
" EXPLORE FIRST
|
|
8003
|
-
"work, what would a change to Z
|
|
8004
|
-
"
|
|
8005
|
-
"
|
|
8006
|
-
"
|
|
8028
|
+
" EXPLORE FIRST — before CHANGING code you have not seen yet in this conversation, and "
|
|
8029
|
+
"for any broad question (where is X handled, how does Y work, what would a change to Z "
|
|
8030
|
+
"touch): call explore(question) ONCE to map the files and lines involved. It searches in "
|
|
8031
|
+
"a SEPARATE context and returns a short summary with path:line references, so its greps "
|
|
8032
|
+
"and reads do not pile up here and are not re-sent on every later step. Then read only "
|
|
8033
|
+
"the exact ranges you will edit. Use grep_repo/read_file_lines directly when you already "
|
|
8034
|
+
"know the file or the exact string.\n"
|
|
8007
8035
|
" run_command CHAINS: put dependent commands in ONE call with && — e.g. "
|
|
8008
8036
|
"run_command(\"cd app && ./gradlew assembleDebug && adb install -r app/build/outputs/apk/debug/app-debug.apk\") "
|
|
8009
8037
|
"— they run in order, `cd` sets the folder for the rest, and the first failure stops the "
|
|
@@ -8232,7 +8260,8 @@ def run_agent_chat(cfg):
|
|
|
8232
8260
|
# the cached reference above. Workspace/coding mode only.
|
|
8233
8261
|
+ ("Remember: FINISH what was asked yourself — fix blockers, VERIFY the outcome, then "
|
|
8234
8262
|
"final_answer with evidence; never hand the task back. If it only asks to RECORD work "
|
|
8235
|
-
"(create a task, a note, a plan), recording it IS the task — do not start the work
|
|
8263
|
+
"(create a task, a note, a plan), recording it IS the task — do not start the work. "
|
|
8264
|
+
"To change code you have not seen yet, map it with ONE explore() call first.\n"
|
|
8236
8265
|
if _WS_AVAILABLE else "")
|
|
8237
8266
|
+ "\nTASK (answer THIS, choose the tool that fits it):\n"
|
|
8238
8267
|
f"{task_text}\n"
|
|
@@ -9142,38 +9171,47 @@ def run_agent_chat(cfg):
|
|
|
9142
9171
|
# context is not big enough for that trade to pay, so history is left ALONE and
|
|
9143
9172
|
# every step appends cleanly onto a cached prefix.
|
|
9144
9173
|
if _steps and len(_steps) > 2 and _trim_due(_mem_chars, _trim_state):
|
|
9145
|
-
|
|
9174
|
+
# PLAN FIRST, then apply only if it pays back its cache cost (task #279): a
|
|
9175
|
+
# trim rewrites the history into the cache, and one that frees little costs
|
|
9176
|
+
# more than it saves. Each edit is (object, attribute, new value, chars freed).
|
|
9177
|
+
_plan = []
|
|
9146
9178
|
for _st in _steps[:-2]:
|
|
9147
9179
|
_obs = getattr(_st, "observations", None)
|
|
9148
9180
|
if isinstance(_obs, str) and len(_obs) > 900:
|
|
9149
|
-
|
|
9150
|
-
|
|
9151
|
-
|
|
9152
|
-
|
|
9153
|
-
"re-read the file/dir with a small range if you still need it]")
|
|
9154
|
-
_trim_n += 1
|
|
9155
|
-
_trim_saved += _before - len(_st.observations)
|
|
9181
|
+
_new = (_obs[:400].rstrip()
|
|
9182
|
+
+ "\n…[earlier observation trimmed to save context — "
|
|
9183
|
+
"re-read the file/dir with a small range if you still need it]")
|
|
9184
|
+
_plan.append((_st, "observations", _new, len(_obs) - len(_new)))
|
|
9156
9185
|
_mo = getattr(_st, "model_output", None)
|
|
9157
9186
|
if isinstance(_mo, str) and len(_mo) > 700:
|
|
9158
|
-
_before = len(_mo)
|
|
9159
9187
|
# Keep the Thought (everything before the code block); the code is
|
|
9160
9188
|
# redundant with the observation that already came back.
|
|
9161
9189
|
_head = re.split(r"<code[\s>]|```", _mo, maxsplit=1)[0].rstrip()
|
|
9162
9190
|
_kept = (_head[:500].rstrip() if _head else _mo[:300].rstrip())
|
|
9163
9191
|
_new = _kept + "\n…[code trimmed to save context — its result is in the Observation]"
|
|
9164
|
-
if len(_new) <
|
|
9165
|
-
_st
|
|
9166
|
-
|
|
9167
|
-
|
|
9168
|
-
#
|
|
9169
|
-
#
|
|
9170
|
-
|
|
9171
|
-
|
|
9172
|
-
|
|
9173
|
-
|
|
9174
|
-
|
|
9192
|
+
if len(_new) < len(_mo):
|
|
9193
|
+
_plan.append((_st, "model_output", _new, len(_mo) - len(_new)))
|
|
9194
|
+
# The SAME code, a second time: smolagents keeps the parsed code action in
|
|
9195
|
+
# step.tool_calls and renders it as its own message on every request, so
|
|
9196
|
+
# stripping <code> from model_output above saves nothing on its own. See
|
|
9197
|
+
# _trim_tool_call_args for why trimming the duplicate is safe.
|
|
9198
|
+
for _tc in (getattr(_st, "tool_calls", None) or []):
|
|
9199
|
+
_a = getattr(_tc, "arguments", None)
|
|
9200
|
+
if isinstance(_a, str) and len(_a) > 300:
|
|
9201
|
+
_new = _a[:300].rstrip() + _CODE_TRIMMED_MARK
|
|
9202
|
+
if len(_new) < len(_a):
|
|
9203
|
+
_plan.append((_tc, "arguments", _new, len(_a) - len(_new)))
|
|
9204
|
+
_freed = sum(p[3] for p in _plan)
|
|
9205
|
+
_trim_n, _trim_saved = 0, 0
|
|
9206
|
+
if _trim_pays_back(_mem_chars, _freed):
|
|
9207
|
+
for _obj, _attr, _new, _gain in _plan:
|
|
9208
|
+
setattr(_obj, _attr, _new)
|
|
9175
9209
|
_trim_n += 1
|
|
9176
|
-
_trim_saved +=
|
|
9210
|
+
_trim_saved += _gain
|
|
9211
|
+
else:
|
|
9212
|
+
_log(f"context trim skipped: would free ~{_freed} of {_mem_chars} chars — "
|
|
9213
|
+
f"repays its cache rewrite only after ~{_trim_payback_steps(_mem_chars, _freed):.0f} "
|
|
9214
|
+
f"steps (> {_TRIM_PAYBACK_STEPS})")
|
|
9177
9215
|
# Arm the next pass off the size we actually reached, so a big win buys a
|
|
9178
9216
|
# long quiet period and a small one cannot re-fire next step.
|
|
9179
9217
|
_note_trimmed(_step_memory_chars(_steps), _trim_state)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiens.nguyen/gu-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.748",
|
|
4
4
|
"description": "Gu CLI — the gu terminal plus the local worker that runs agent / OCR / PDF / embedding jobs on your Mac (Ollama / MLX / OpenAI-compatible).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|