chad-code 1.0.9__tar.gz → 1.12.0__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.
- {chad_code-1.0.9 → chad_code-1.12.0}/PKG-INFO +1 -1
- {chad_code-1.0.9 → chad_code-1.12.0}/pyproject.toml +1 -1
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/__init__.py +1 -1
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/agent.py +76 -5
- chad_code-1.12.0/src/chad/ambient.py +453 -0
- chad_code-1.12.0/src/chad/checkpoint.py +150 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/cli.py +5 -4
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/compaction.py +4 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/guardrails.py +38 -7
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/levers.py +180 -30
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/profiles.py +1 -5
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/prompt.py +20 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/render.py +3 -1
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/repomap.py +100 -0
- chad_code-1.12.0/src/chad/seatbelt.py +255 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/symbols.py +4 -1
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/syntaxgate.py +7 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/tools.py +87 -8
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/tui.py +30 -1
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad_code.egg-info/PKG-INFO +1 -1
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad_code.egg-info/SOURCES.txt +7 -0
- chad_code-1.12.0/tests/test_ambient.py +277 -0
- chad_code-1.12.0/tests/test_checkpoint.py +132 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_lever_bite.py +118 -0
- chad_code-1.12.0/tests/test_lever_instrumentation.py +95 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_levers.py +37 -11
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_repomap.py +87 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_repomap_polyglot.py +31 -0
- chad_code-1.12.0/tests/test_seatbelt.py +336 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/LICENSE +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/README.md +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/setup.cfg +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/atif.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/base_engine.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/bench.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/completion_engine.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/config.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/diag.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/engine.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/ignore.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/lsp.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/lspclient.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/lspservers.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/mcp.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/mcp_oauth.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/mlx_fastpath.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/mlx_moe_fused.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/mlx_qsdpa.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/parakeet/LICENSE +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/parakeet/__init__.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/parakeet/alignment.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/parakeet/attention.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/parakeet/audio.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/parakeet/cache.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/parakeet/conformer.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/parakeet/ctc.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/parakeet/parakeet.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/parakeet/rnnt.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/parakeet/tokenizer.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/parakeet/utils.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/prove.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/serve.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/session.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/skills.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/speech.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/toolcall_parse.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad/validate.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad_code.egg-info/dependency_links.txt +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad_code.egg-info/entry_points.txt +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad_code.egg-info/requires.txt +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/src/chad_code.egg-info/top_level.txt +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_agent.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_agent_e2e.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_agent_guards.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_atif.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_bench.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_cli.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_compact_notice.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_compaction.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_completion_engine.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_config.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_confirm_preview.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_done_audit.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_drift_warn.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_edit.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_edit_corruption.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_engine.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_engine_kvquant.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_engine_pld_hybrid.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_feel_pack.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_garble_invariant.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_gate.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_ignore.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_intent.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_log_redaction.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_lsp.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_lsp_live.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_lspclient.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_mcp.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_mcp_oauth.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_mlx_fastpath.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_mlx_moe_fused.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_mlx_qsdpa.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_plan_review.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_prove.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_render.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_replace_lines.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_serve.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_session.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_skills.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_speech.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_speech_tui.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_subagent.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_symbols.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_syntaxgate.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_toolcall_parse.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_tools.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_tui.py +0 -0
- {chad_code-1.0.9 → chad_code-1.12.0}/tests/test_validate.py +0 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# import name, and command name are independent. `uvx chad-code` runs the alias
|
|
5
5
|
# script added under [project.scripts].
|
|
6
6
|
name = "chad-code"
|
|
7
|
-
version = "1.0
|
|
7
|
+
version = "1.12.0"
|
|
8
8
|
description = "Local MLX-backed, Claude-Code-style coding agent (Apple Silicon, Ornith 35B/9B)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -4,7 +4,7 @@ A flat collection of cooperating modules behind one console script (``chad``):
|
|
|
4
4
|
the inference engine, the tool layer, the agent loop, and the terminal UI.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
__version__ = "1.0
|
|
7
|
+
__version__ = "1.12.0"
|
|
8
8
|
|
|
9
9
|
# chad sets no MLX_* runtime vars. MLX_METAL_FAST_SYNCH, MLX_MAX_OPS_PER_BUFFER
|
|
10
10
|
# and MLX_MAX_MB_PER_BUFFER were each measured end-to-end on the 35B and every
|
|
@@ -14,7 +14,18 @@ import re
|
|
|
14
14
|
import sys
|
|
15
15
|
import time
|
|
16
16
|
|
|
17
|
-
from . import
|
|
17
|
+
from . import (
|
|
18
|
+
ambient,
|
|
19
|
+
atif,
|
|
20
|
+
checkpoint,
|
|
21
|
+
compaction,
|
|
22
|
+
config,
|
|
23
|
+
guardrails,
|
|
24
|
+
levers,
|
|
25
|
+
seatbelt,
|
|
26
|
+
session,
|
|
27
|
+
syntaxgate,
|
|
28
|
+
)
|
|
18
29
|
from .base_engine import BackendError, BaseEngine
|
|
19
30
|
from .diag import args_preview, log, redact, result_preview
|
|
20
31
|
from .prompt import build_subagent_prompt, build_system_prompt, classify_intent
|
|
@@ -47,7 +58,7 @@ SUBAGENT_MAX_STEPS = 24
|
|
|
47
58
|
SUBAGENT_CTX_LIMIT = 32000
|
|
48
59
|
SUBAGENT_READ_ONLY = {
|
|
49
60
|
"read", "grep", "glob", "repo_map", "overview", "view_symbol",
|
|
50
|
-
"find_symbol", "find_refs", "done", "finish", "stop",
|
|
61
|
+
"find_symbol", "definition", "find_refs", "done", "finish", "stop",
|
|
51
62
|
}
|
|
52
63
|
# `write_todos` is deliberately absent: a sub-agent that plans its own work would mutate
|
|
53
64
|
# the process-global `_TODOS` and clobber the parent's pinned todo panel (and `_sub_emit`
|
|
@@ -346,6 +357,8 @@ class Agent:
|
|
|
346
357
|
skills.reset_session()
|
|
347
358
|
from . import mcp
|
|
348
359
|
mcp.reset_session()
|
|
360
|
+
ambient.reset() # fresh session-state ledger; sub-agents share the
|
|
361
|
+
# parent's and never feed it (see run_turn)
|
|
349
362
|
self.mode = mode or ("yolo" if yolo else "normal")
|
|
350
363
|
self.thinking = thinking # Ornith is a reasoning model; toggles <think> blocks
|
|
351
364
|
# Steps per WINDOW, not a hard kill: a window that landed+verified a change
|
|
@@ -625,6 +638,7 @@ class Agent:
|
|
|
625
638
|
# measured trace: a model re-phrasing the same delete 30 times). The
|
|
626
639
|
# guard names itself and the fix — narrow the target.
|
|
627
640
|
if levers.enabled("scoped_destructive_guard"):
|
|
641
|
+
levers.fired("scoped_destructive_guard", deny_text=True)
|
|
628
642
|
self._deny_reason = (
|
|
629
643
|
"[blocked by the destructive-command guard, not by a person: "
|
|
630
644
|
"the command matches a catastrophic pattern (recursive delete "
|
|
@@ -722,6 +736,7 @@ class Agent:
|
|
|
722
736
|
note = sub.budget_note or guardrails.progress_note(sub.messages)
|
|
723
737
|
if not note:
|
|
724
738
|
return res
|
|
739
|
+
levers.fired("subagent_budget_note")
|
|
725
740
|
return (res.rstrip() + "\n[sub-agent progress before it "
|
|
726
741
|
f"stopped: {note}]").strip()
|
|
727
742
|
if sub.interrupted:
|
|
@@ -1196,6 +1211,7 @@ class Agent:
|
|
|
1196
1211
|
or self._backend_retries >= _MAX_BACKEND_RETRIES):
|
|
1197
1212
|
raise
|
|
1198
1213
|
self._backend_retries += 1
|
|
1214
|
+
levers.fired("backend_retry", attempt=self._backend_retries)
|
|
1199
1215
|
log.warning("backend error (retry %d/%d): %s",
|
|
1200
1216
|
self._backend_retries, _MAX_BACKEND_RETRIES, e)
|
|
1201
1217
|
self._emit("status", f"Backend error; retrying "
|
|
@@ -1285,6 +1301,8 @@ class Agent:
|
|
|
1285
1301
|
# the first branch already credits; an unclosed generation that
|
|
1286
1302
|
# stopped SHORT of the cap is a truncation of some other kind, not
|
|
1287
1303
|
# the reasoning overspend this counts.
|
|
1304
|
+
levers.fired("capped_think_credit", step=step,
|
|
1305
|
+
tokens=stats.generated_tokens)
|
|
1288
1306
|
_think_delta = stats.generated_tokens
|
|
1289
1307
|
else:
|
|
1290
1308
|
_think_delta = 0
|
|
@@ -1312,6 +1330,7 @@ class Agent:
|
|
|
1312
1330
|
if _tt_decision == "half":
|
|
1313
1331
|
log.info("THINK-BUDGET half at step %d: %d/%d cumulative think tok "
|
|
1314
1332
|
"this turn", step, turn_think_tokens, _tt_budget)
|
|
1333
|
+
levers.fired("turn_think_budget", step=step, decision="half")
|
|
1315
1334
|
self.messages.append({"role": "tool", "name": "steer",
|
|
1316
1335
|
"content": guardrails.TURN_THINK_BUDGET_STEER})
|
|
1317
1336
|
elif _tt_decision == "exhausted":
|
|
@@ -1319,6 +1338,7 @@ class Agent:
|
|
|
1319
1338
|
"tok this turn — throttling <think> (one action step per "
|
|
1320
1339
|
"%d further think tok)", step, turn_think_tokens,
|
|
1321
1340
|
_tt_budget, guardrails.TURN_THINK_REARM_TOK)
|
|
1341
|
+
levers.fired("turn_think_budget", step=step, decision="exhausted")
|
|
1322
1342
|
self._emit("info", " [reasoning budget exhausted for this turn — "
|
|
1323
1343
|
"throttling further <think>]")
|
|
1324
1344
|
|
|
@@ -1388,6 +1408,8 @@ class Agent:
|
|
|
1388
1408
|
hard_wrapup_fired = True
|
|
1389
1409
|
landing_no_think = True
|
|
1390
1410
|
_remaining = self._turn_budget_s - (time.monotonic() - turn_start)
|
|
1411
|
+
levers.fired("hard_wrapup", step=step,
|
|
1412
|
+
remaining_s=int(_remaining))
|
|
1391
1413
|
log.info("HARD-WRAPUP abort at step %d: %.0fs left, gen was %d tok",
|
|
1392
1414
|
step, _remaining, stats.generated_tokens)
|
|
1393
1415
|
self._emit("info", " [wall deadline reached — landing the best answer now]")
|
|
@@ -1452,6 +1474,7 @@ class Agent:
|
|
|
1452
1474
|
capped_stall_streak += 1
|
|
1453
1475
|
if (self.think_ceiling and self.thinking and capped_stall_streak >= 2
|
|
1454
1476
|
and levers.enabled("no_think_escalation")):
|
|
1477
|
+
levers.fired("no_think_escalation", step=step)
|
|
1455
1478
|
no_think_next = True
|
|
1456
1479
|
else:
|
|
1457
1480
|
capped_stall_streak = 0
|
|
@@ -1481,6 +1504,7 @@ class Agent:
|
|
|
1481
1504
|
# last). Costs one prefix-cache invalidation on this rare path.
|
|
1482
1505
|
if (levers.enabled("garble_never_final")
|
|
1483
1506
|
and consecutive_garbles >= 2 and last_garble_idx is not None):
|
|
1507
|
+
levers.fired("garble_never_final", step=step, action="scrub")
|
|
1484
1508
|
self.messages[last_garble_idx]["content"] = \
|
|
1485
1509
|
guardrails.GARBLE_SCRUBBED
|
|
1486
1510
|
log.info("GARBLE scrub at step %d: previous garbled message "
|
|
@@ -1516,6 +1540,7 @@ class Agent:
|
|
|
1516
1540
|
# the audit latch, and the last one ships as the answer with most of the
|
|
1517
1541
|
# wall budget still unspent).
|
|
1518
1542
|
if garbled and levers.enabled("garble_never_final"):
|
|
1543
|
+
levers.fired("garble_never_final", step=step, action="hard-stop")
|
|
1519
1544
|
self.budget_note = guardrails.progress_note(self.messages)
|
|
1520
1545
|
log.info("END step %d: GARBLE hard-stop (%d garble nudges spent) — "
|
|
1521
1546
|
"a garbled tool call is never a final answer",
|
|
@@ -1558,6 +1583,8 @@ class Agent:
|
|
|
1558
1583
|
if audit:
|
|
1559
1584
|
done_audit_fired = True
|
|
1560
1585
|
done_audit_bounces += 1
|
|
1586
|
+
levers.fired("done_audit", step=step, entry="churn-handoff")
|
|
1587
|
+
levers.fired("audit_churn_handoff", step=step)
|
|
1561
1588
|
audit_absent_list = guardrails.audit_absent_paths(audit_task)
|
|
1562
1589
|
_runway = ((self._turn_budget_s
|
|
1563
1590
|
- (time.monotonic() - turn_start))
|
|
@@ -1607,6 +1634,7 @@ class Agent:
|
|
|
1607
1634
|
if audit:
|
|
1608
1635
|
done_audit_fired = True
|
|
1609
1636
|
done_audit_bounces += 1
|
|
1637
|
+
levers.fired("done_audit", step=step, entry="final-answer")
|
|
1610
1638
|
audit_absent_list = guardrails.audit_absent_paths(audit_task)
|
|
1611
1639
|
_runway = ((self._turn_budget_s
|
|
1612
1640
|
- (time.monotonic() - turn_start))
|
|
@@ -1681,10 +1709,15 @@ class Agent:
|
|
|
1681
1709
|
log.info("DONE rejected: edits not verified -> nudge #%d", verify_nudges)
|
|
1682
1710
|
self.messages.append({
|
|
1683
1711
|
"role": "tool", "name": "done",
|
|
1712
|
+
# The session-ledger facts (what changed, what last ran) ride
|
|
1713
|
+
# the bounce when the lever is on — the model's stale state
|
|
1714
|
+
# model IS the wrong-verify bug.
|
|
1684
1715
|
"content": "[not done yet: you changed files but have not run anything "
|
|
1685
1716
|
"to verify them. Run the project's tests (or the code) with "
|
|
1686
1717
|
"bash, check the output is correct, then call done. If a test "
|
|
1687
|
-
"fails, fix the code first.]"
|
|
1718
|
+
"fails, fix the code first.]"
|
|
1719
|
+
+ ("" if self._subagent else
|
|
1720
|
+
ambient.ledger_suffix(step=step, force=True)),
|
|
1688
1721
|
})
|
|
1689
1722
|
continue
|
|
1690
1723
|
if action_task and not read_only_intent and self.mode != "plan" \
|
|
@@ -1706,6 +1739,9 @@ class Agent:
|
|
|
1706
1739
|
if audit:
|
|
1707
1740
|
done_audit_fired = True
|
|
1708
1741
|
done_audit_bounces += 1
|
|
1742
|
+
levers.fired("done_audit", step=step,
|
|
1743
|
+
entry="churn-handoff-done")
|
|
1744
|
+
levers.fired("audit_churn_handoff", step=step)
|
|
1709
1745
|
audit_absent_list = guardrails.audit_absent_paths(audit_task)
|
|
1710
1746
|
_runway = ((self._turn_budget_s
|
|
1711
1747
|
- (time.monotonic() - turn_start))
|
|
@@ -1768,14 +1804,18 @@ class Agent:
|
|
|
1768
1804
|
if audit:
|
|
1769
1805
|
done_audit_fired = True
|
|
1770
1806
|
done_audit_bounces += 1
|
|
1807
|
+
levers.fired("done_audit", step=step, entry="done")
|
|
1771
1808
|
audit_absent_list = guardrails.audit_absent_paths(audit_task)
|
|
1772
1809
|
_runway = ((self._turn_budget_s
|
|
1773
1810
|
- (time.monotonic() - turn_start))
|
|
1774
1811
|
if self._turn_budget_s else float("inf"))
|
|
1775
1812
|
log.info("DONE-AUDIT bounce: paths=%s runway=%.0fs",
|
|
1776
1813
|
guardrails.audit_extract_paths(audit_task), _runway)
|
|
1777
|
-
self.messages.append({
|
|
1778
|
-
|
|
1814
|
+
self.messages.append({
|
|
1815
|
+
"role": "tool", "name": "done",
|
|
1816
|
+
# Session-ledger facts ride the audit bounce
|
|
1817
|
+
"content": audit
|
|
1818
|
+
+ ambient.ledger_suffix(step=step, force=True)})
|
|
1779
1819
|
continue
|
|
1780
1820
|
# Absent-path re-bounce (see the final-answer twin above): a
|
|
1781
1821
|
# still-absent task-named path at accept time gets ONE more bounce,
|
|
@@ -1802,6 +1842,7 @@ class Agent:
|
|
|
1802
1842
|
and guardrails.done_spec_recheck(
|
|
1803
1843
|
did_work, unverified_edit, done_recheck_done, read_only_intent):
|
|
1804
1844
|
done_recheck_done = True
|
|
1845
|
+
levers.fired("done_spec_recheck", step=step)
|
|
1805
1846
|
log.info("DONE deferred for deliverable recheck (step %d)", step)
|
|
1806
1847
|
self.messages.append({"role": "tool", "name": "done",
|
|
1807
1848
|
"content": guardrails.DONE_SPEC_RECHECK})
|
|
@@ -1897,6 +1938,7 @@ class Agent:
|
|
|
1897
1938
|
_sub_sig = (str(args.get("description", "")).strip(),
|
|
1898
1939
|
str(args.get("prompt", "")).strip())
|
|
1899
1940
|
if _sub_sig in subagent_sigs and levers.enabled("subagent_no_respawn"):
|
|
1941
|
+
levers.fired("subagent_no_respawn", step=step)
|
|
1900
1942
|
result = ("[you already ran this exact sub-agent this turn — do NOT "
|
|
1901
1943
|
"re-run it. Use what it returned, or do the work yourself "
|
|
1902
1944
|
"now with grep/read to locate the code and edit to change "
|
|
@@ -1940,12 +1982,27 @@ class Agent:
|
|
|
1940
1982
|
else:
|
|
1941
1983
|
_t0 = time.perf_counter()
|
|
1942
1984
|
self.tool_dispatches += 1
|
|
1985
|
+
# A snapshot happens AFTER approval, immediately before the tool
|
|
1986
|
+
# runs — a denied edit must not leave a checkpoint claiming it ran.
|
|
1987
|
+
if (name in AUTO_EDIT_TOOLS
|
|
1988
|
+
and levers.enabled("edit_checkpoint")):
|
|
1989
|
+
_ref = checkpoint.snapshot(
|
|
1990
|
+
os.getcwd(), f"before {name} {args.get('path', '')}".strip())
|
|
1991
|
+
if _ref:
|
|
1992
|
+
levers.fired("edit_checkpoint", step=step, ref=_ref)
|
|
1993
|
+
# Seatbelt context is scoped to exactly this dispatch: `active`
|
|
1994
|
+
# reflects the mode of the agent actually executing, so a
|
|
1995
|
+
# sub-agent's yolo loop is confined inside a normal-mode parent
|
|
1996
|
+
# turn, and the context can't leak to the `!cmd` passthrough.
|
|
1997
|
+
seatbelt.set_context(self.mode == "yolo", os.getcwd())
|
|
1943
1998
|
try:
|
|
1944
1999
|
result = fn(args, self._should_stop)
|
|
1945
2000
|
if plan_write and result.startswith("[wrote"):
|
|
1946
2001
|
self.last_plan_path = os.path.abspath(args["path"])
|
|
1947
2002
|
except Exception as e: # noqa: BLE001 - surface tool errors to model
|
|
1948
2003
|
result = f"[tool error: {type(e).__name__}: {e}]"
|
|
2004
|
+
finally:
|
|
2005
|
+
seatbelt.set_context(False, None)
|
|
1949
2006
|
_tool_s = time.perf_counter() - _t0
|
|
1950
2007
|
# Backstop: bound the prefill from any tool, AND from the step as a
|
|
1951
2008
|
# whole — several calls in one step stack into one prefill, so later
|
|
@@ -1960,6 +2017,16 @@ class Agent:
|
|
|
1960
2017
|
log.info("TOOL %s duplicate result elided (%d chars)",
|
|
1961
2018
|
name, len(result))
|
|
1962
2019
|
result = _elided
|
|
2020
|
+
# Ambient state: fold this result into the session
|
|
2021
|
+
# ledger and append whatever fact lines the enabled levers owe it
|
|
2022
|
+
# (session_ledger on landed mutations, bash_read_skeleton on file
|
|
2023
|
+
# content surfacing through bash/read). After the clip cap on
|
|
2024
|
+
# purpose — a clipped result must still carry its facts; the
|
|
2025
|
+
# appended text is bounded in ambient.py. Main agent only: a
|
|
2026
|
+
# sub-agent's transcript folds away and must not pollute the
|
|
2027
|
+
# parent's ledger.
|
|
2028
|
+
if not self._subagent:
|
|
2029
|
+
result = ambient.annotate(name, args, result, step=step)
|
|
1963
2030
|
step_tool_chars += len(result)
|
|
1964
2031
|
if _PREFILL_TRACE:
|
|
1965
2032
|
self._trace_tools_pending.append([name, round(_tool_s, 4)])
|
|
@@ -1977,6 +2044,7 @@ class Agent:
|
|
|
1977
2044
|
if (plan_write and result.startswith("[wrote") and not plan_reviews
|
|
1978
2045
|
and levers.enabled("plan_review")):
|
|
1979
2046
|
plan_reviews += 1
|
|
2047
|
+
levers.fired("plan_review", step=step)
|
|
1980
2048
|
log.info("PLAN REVIEW nudge for %s", self.last_plan_path)
|
|
1981
2049
|
self.messages.append({"role": "tool", "name": "read", "content": (
|
|
1982
2050
|
f"[plan written to {self.last_plan_path}. Before you call `done`, "
|
|
@@ -2087,6 +2155,9 @@ class Agent:
|
|
|
2087
2155
|
n == "bash"
|
|
2088
2156
|
and not guardrails.is_readonly_bash(str(a.get("command", "")))
|
|
2089
2157
|
for n, a in calls):
|
|
2158
|
+
if readonly_streak: # the exemption actually reset a live streak
|
|
2159
|
+
levers.fired("gate_ops_exempt", step=step,
|
|
2160
|
+
streak=readonly_streak)
|
|
2090
2161
|
readonly_streak = 0
|
|
2091
2162
|
else:
|
|
2092
2163
|
readonly_streak += 1
|