chad-code 2.0.1__tar.gz → 2.0.2__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 (106) hide show
  1. {chad_code-2.0.1 → chad_code-2.0.2}/PKG-INFO +3 -3
  2. {chad_code-2.0.1 → chad_code-2.0.2}/README.md +2 -2
  3. {chad_code-2.0.1 → chad_code-2.0.2}/pyproject.toml +1 -1
  4. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/__init__.py +1 -1
  5. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/agent.py +19 -3
  6. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad_code.egg-info/PKG-INFO +3 -3
  7. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_agent.py +67 -3
  8. {chad_code-2.0.1 → chad_code-2.0.2}/LICENSE +0 -0
  9. {chad_code-2.0.1 → chad_code-2.0.2}/setup.cfg +0 -0
  10. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/ambient.py +0 -0
  11. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/atif.py +0 -0
  12. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/base_engine.py +0 -0
  13. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/bench.py +0 -0
  14. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/checkpoint.py +0 -0
  15. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/cli.py +0 -0
  16. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/compaction.py +0 -0
  17. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/completion_engine.py +0 -0
  18. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/config.py +0 -0
  19. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/diag.py +0 -0
  20. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/engine.py +0 -0
  21. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/guardrails.py +0 -0
  22. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/ignore.py +0 -0
  23. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/levers.py +0 -0
  24. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/mcp.py +0 -0
  25. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/mcp_oauth.py +0 -0
  26. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/mlx_dflash.py +0 -0
  27. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/mlx_fastpath.py +0 -0
  28. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/mlx_qmm_mma.py +0 -0
  29. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/mlx_qsdpa.py +0 -0
  30. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/parakeet/LICENSE +0 -0
  31. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/parakeet/__init__.py +0 -0
  32. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/parakeet/alignment.py +0 -0
  33. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/parakeet/attention.py +0 -0
  34. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/parakeet/audio.py +0 -0
  35. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/parakeet/cache.py +0 -0
  36. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/parakeet/conformer.py +0 -0
  37. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/parakeet/ctc.py +0 -0
  38. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/parakeet/parakeet.py +0 -0
  39. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/parakeet/rnnt.py +0 -0
  40. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/parakeet/tokenizer.py +0 -0
  41. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/parakeet/utils.py +0 -0
  42. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/prompt.py +0 -0
  43. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/prove.py +0 -0
  44. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/render.py +0 -0
  45. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/repomap.py +0 -0
  46. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/seatbelt.py +0 -0
  47. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/serve.py +0 -0
  48. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/session.py +0 -0
  49. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/skills.py +0 -0
  50. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/speech.py +0 -0
  51. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/spill.py +0 -0
  52. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/syntaxgate.py +0 -0
  53. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/toolcall_parse.py +0 -0
  54. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/tools.py +0 -0
  55. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/tui.py +0 -0
  56. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad/validate.py +0 -0
  57. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad_code.egg-info/SOURCES.txt +0 -0
  58. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad_code.egg-info/dependency_links.txt +0 -0
  59. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad_code.egg-info/entry_points.txt +0 -0
  60. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad_code.egg-info/requires.txt +0 -0
  61. {chad_code-2.0.1 → chad_code-2.0.2}/src/chad_code.egg-info/top_level.txt +0 -0
  62. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_agent_e2e.py +0 -0
  63. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_agent_guards.py +0 -0
  64. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_ambient.py +0 -0
  65. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_atif.py +0 -0
  66. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_bench.py +0 -0
  67. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_checkpoint.py +0 -0
  68. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_cli.py +0 -0
  69. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_compaction.py +0 -0
  70. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_completion_engine.py +0 -0
  71. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_config.py +0 -0
  72. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_confirm_preview.py +0 -0
  73. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_edit.py +0 -0
  74. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_engine.py +0 -0
  75. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_engine_dflash.py +0 -0
  76. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_engine_kvquant.py +0 -0
  77. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_engine_pld_hybrid.py +0 -0
  78. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_engine_pld_wide.py +0 -0
  79. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_feel_pack.py +0 -0
  80. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_gate.py +0 -0
  81. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_ignore.py +0 -0
  82. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_intent.py +0 -0
  83. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_levers.py +0 -0
  84. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_log_redaction.py +0 -0
  85. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_mcp.py +0 -0
  86. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_mcp_oauth.py +0 -0
  87. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_mlx_fastpath.py +0 -0
  88. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_mlx_qmm_mma.py +0 -0
  89. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_mlx_qsdpa.py +0 -0
  90. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_prove.py +0 -0
  91. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_render.py +0 -0
  92. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_repomap.py +0 -0
  93. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_repomap_polyglot.py +0 -0
  94. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_seatbelt.py +0 -0
  95. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_serve.py +0 -0
  96. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_session.py +0 -0
  97. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_skills.py +0 -0
  98. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_speech.py +0 -0
  99. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_speech_tui.py +0 -0
  100. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_spill.py +0 -0
  101. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_syntaxgate.py +0 -0
  102. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_toolcall_dialect.py +0 -0
  103. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_toolcall_parse.py +0 -0
  104. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_tools.py +0 -0
  105. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_tui.py +0 -0
  106. {chad_code-2.0.1 → chad_code-2.0.2}/tests/test_validate.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chad-code
3
- Version: 2.0.1
3
+ Version: 2.0.2
4
4
  Summary: Claude-Code-style coding agent that runs entirely on a 24 GB Apple Silicon Mac (MLX, Qwen3.8-27B)
5
5
  License-Expression: MIT
6
6
  Project-URL: Repository, https://github.com/nathansutton/chad
@@ -33,11 +33,11 @@ Provides-Extra: speech
33
33
  Requires-Dist: sounddevice>=0.5; extra == "speech"
34
34
  Dynamic: license-file
35
35
 
36
- # chad: a Claude-Code-style coding agent that fits on a 24 GB MacBook
36
+ # chad: a local Claude-Code-style coding agent for your laptop
37
37
 
38
38
  [![tests](https://github.com/nathansutton/chad/actions/workflows/tests.yml/badge.svg)](https://github.com/nathansutton/chad/actions/workflows/tests.yml)
39
39
 
40
- <img src="docs/claude-vs-chad.jpg" width="33%" alt="Two staircase newel posts side by side: Claude is a hand-carved wooden horse head, chad is a scuffed plastic toy horse tied on with twine">
40
+ <img src="docs/claude-vs-chad.jpg" width="66%" alt="Two staircase newel posts side by side: Claude is a hand-carved wooden horse head, chad is a scuffed plastic toy horse tied on with twine">
41
41
 
42
42
  > Claude can do anything, for anyone, anywhere. chad does one thing. 🗿
43
43
  > *Coding under supervision.*
@@ -1,8 +1,8 @@
1
- # chad: a Claude-Code-style coding agent that fits on a 24 GB MacBook
1
+ # chad: a local Claude-Code-style coding agent for your laptop
2
2
 
3
3
  [![tests](https://github.com/nathansutton/chad/actions/workflows/tests.yml/badge.svg)](https://github.com/nathansutton/chad/actions/workflows/tests.yml)
4
4
 
5
- <img src="docs/claude-vs-chad.jpg" width="33%" alt="Two staircase newel posts side by side: Claude is a hand-carved wooden horse head, chad is a scuffed plastic toy horse tied on with twine">
5
+ <img src="docs/claude-vs-chad.jpg" width="66%" alt="Two staircase newel posts side by side: Claude is a hand-carved wooden horse head, chad is a scuffed plastic toy horse tied on with twine">
6
6
 
7
7
  > Claude can do anything, for anyone, anywhere. chad does one thing. 🗿
8
8
  > *Coding under supervision.*
@@ -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 = "2.0.1"
7
+ version = "2.0.2"
8
8
  description = "Claude-Code-style coding agent that runs entirely on a 24 GB Apple Silicon Mac (MLX, Qwen3.8-27B)"
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__ = "2.0.1"
7
+ __version__ = "2.0.2"
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
@@ -206,8 +206,15 @@ def close_unclosed_think(text: str, thinking: bool) -> str:
206
206
  re-prefill of the whole transcript next step (measured: tens of thousands of tokens
207
207
  at large context). Appending the missing `</think>` keeps the cached tokens a strict
208
208
  prefix of the re-render, so only a couple of tokens prefill instead. No-op when
209
- thinking is off or the block is already closed."""
210
- if thinking and "<think>" not in text and "</think>" not in text and text:
209
+ thinking is off or the block is already closed.
210
+
211
+ Only the CLOSE decides this. An earlier guard also required no `<think>` in the
212
+ text, which read a stray opening tag — one the model wrote inside its own reasoning,
213
+ not the template's — as "already a block" and skipped the append. That turn then
214
+ reached `split_inline_reasoning` with no `</think>` to split on, so it was stored raw
215
+ and re-rendered with an injected empty think block: the exact full re-prefill this
216
+ function exists to prevent, on the longest turns, where it costs the most."""
217
+ if thinking and "</think>" not in text and text:
211
218
  return text + "\n</think>"
212
219
  return text
213
220
 
@@ -237,8 +244,17 @@ def split_inline_reasoning(m: dict) -> dict:
237
244
  if m.get("role") != "assistant" or "</think>" not in content:
238
245
  return m
239
246
  head, _, tail = content.partition("</think>")
247
+ head = head.rstrip("\n")
248
+ # Strip a LEADING `<think>` only. The generation prompt already opened the block, so
249
+ # any later tag is reasoning the model wrote, not a delimiter. Taking the LAST one
250
+ # (`split("<think>")[-1]`) silently dropped every reasoning token before a stray tag:
251
+ # transcript content lost, and the shortened re-render diverges from the cache right
252
+ # where the turn began.
253
+ unwrapped = head.lstrip()
254
+ if unwrapped.startswith("<think>"):
255
+ head = unwrapped[len("<think>"):]
240
256
  return {**m,
241
- "reasoning_content": head.rstrip("\n").split("<think>")[-1].lstrip("\n"),
257
+ "reasoning_content": head.lstrip("\n"),
242
258
  "content": tail.lstrip("\n")}
243
259
 
244
260
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chad-code
3
- Version: 2.0.1
3
+ Version: 2.0.2
4
4
  Summary: Claude-Code-style coding agent that runs entirely on a 24 GB Apple Silicon Mac (MLX, Qwen3.8-27B)
5
5
  License-Expression: MIT
6
6
  Project-URL: Repository, https://github.com/nathansutton/chad
@@ -33,11 +33,11 @@ Provides-Extra: speech
33
33
  Requires-Dist: sounddevice>=0.5; extra == "speech"
34
34
  Dynamic: license-file
35
35
 
36
- # chad: a Claude-Code-style coding agent that fits on a 24 GB MacBook
36
+ # chad: a local Claude-Code-style coding agent for your laptop
37
37
 
38
38
  [![tests](https://github.com/nathansutton/chad/actions/workflows/tests.yml/badge.svg)](https://github.com/nathansutton/chad/actions/workflows/tests.yml)
39
39
 
40
- <img src="docs/claude-vs-chad.jpg" width="33%" alt="Two staircase newel posts side by side: Claude is a hand-carved wooden horse head, chad is a scuffed plastic toy horse tied on with twine">
40
+ <img src="docs/claude-vs-chad.jpg" width="66%" alt="Two staircase newel posts side by side: Claude is a hand-carved wooden horse head, chad is a scuffed plastic toy horse tied on with twine">
41
41
 
42
42
  > Claude can do anything, for anyone, anywhere. chad does one thing. 🗿
43
43
  > *Coding under supervision.*
@@ -99,9 +99,15 @@ def test_close_unclosed_think():
99
99
  check("closed think untouched", close_unclosed_think(closed, True) == closed)
100
100
  # Thinking disabled (--no-think) -> never inject a tag.
101
101
  check("no-think untouched", close_unclosed_think("plain answer", False) == "plain answer")
102
- # An explicit opening <think> in the text -> leave it (conservative; don't double-handle).
103
- check("explicit <think> untouched",
104
- close_unclosed_think("<think>partial", True) == "<think>partial")
102
+ # A stray opening <think> the model wrote INSIDE its own reasoning does not close
103
+ # anything. Only `</think>` decides. Reading the stray tag as "already handled" left
104
+ # the turn unclosed, which is the full re-prefill this helper exists to avoid.
105
+ check("stray <think>, no close -> closed",
106
+ close_unclosed_think("thinking <think> wait", True) == "thinking <think> wait\n</think>")
107
+ # ...and one that IS closed stays untouched, stray tag or not.
108
+ stray_closed = "thinking <think> wait</think>answer"
109
+ check("stray <think>, already closed -> untouched",
110
+ close_unclosed_think(stray_closed, True) == stray_closed)
105
111
  # Empty text -> no spurious tag.
106
112
  check("empty untouched", close_unclosed_think("", True) == "")
107
113
 
@@ -128,6 +134,14 @@ def test_split_inline_reasoning():
128
134
  check("tool turn untouched", split_inline_reasoning(tool) is tool)
129
135
  check("user turn untouched",
130
136
  split_inline_reasoning({"role": "user", "content": "a</think>b"})["content"] == "a</think>b")
137
+ # A stray <think> mid-reasoning is reasoning text, not a delimiter. Splitting on the
138
+ # LAST tag discarded everything before it — reasoning the model really emitted, gone
139
+ # from the transcript, and the shortened re-render no longer extends the KV cache.
140
+ out = split_inline_reasoning(
141
+ {"role": "assistant", "content": "first half <think> second half\n</think>\n\nans"})
142
+ check("stray tag keeps whole reasoning",
143
+ out["reasoning_content"] == "first half <think> second half", out)
144
+ check("stray tag keeps action", out["content"] == "ans", out)
131
145
 
132
146
 
133
147
  def test_reasoning_split_probe_classifies_templates():
@@ -181,6 +195,55 @@ def test_reasoning_split_probe_classifies_templates():
181
195
  check("unprobeable template -> off", a._reasoning_split_supported() is False)
182
196
 
183
197
 
198
+ def test_stored_turn_extends_the_kv_cache():
199
+ """The invariant the two helpers exist to hold: whatever the model generated, the
200
+ NEXT render must begin with the bytes already in the KV cache.
201
+
202
+ Break it and the server re-evaluates the whole turn it just produced — the longer
203
+ the reasoning, the more it costs. Testing the property rather than each helper is
204
+ what catches a turn shape neither of them was written for; the shapes below are all
205
+ ones a real 27B emits. Model-free: `render` is a stand-in for Qwen3.8's template,
206
+ which reads `reasoning_content` and does NOT recover inline `</think>` itself.
207
+ """
208
+ def render(msgs):
209
+ out = []
210
+ for m in msgs:
211
+ role = m["role"]
212
+ if role == "assistant":
213
+ out.append("<|im_start|>assistant\n<think>\n"
214
+ + (m.get("reasoning_content") or "")
215
+ + "\n</think>\n\n" + (m.get("content") or "") + "<|im_end|>\n")
216
+ else:
217
+ out.append(f"<|im_start|>{role}\n{m.get('content') or ''}<|im_end|>\n")
218
+ # The generation prompt auto-opens the think block, so the turn continues inside it.
219
+ return "".join(out) + "<|im_start|>assistant\n<think>\n"
220
+
221
+ prefix = [{"role": "system", "content": "SYS"}, {"role": "user", "content": "task"}]
222
+ reasoning = "weighing the ragged-row rule " * 8
223
+ call = "<tool_call>\n<function=bash>\n</function>\n</tool_call>"
224
+
225
+ shapes = {
226
+ "normal turn": reasoning + "\n</think>\n\nrunning it." + call,
227
+ "cut off mid-think": reasoning,
228
+ "stray <think>, never closed": reasoning + "<think> wait " + reasoning,
229
+ "stray <think>, then closed": (reasoning + "<think> wait " + reasoning
230
+ + "\n</think>\n\nrunning it." + call),
231
+ }
232
+ for name, generated in shapes.items():
233
+ # What the server holds after decoding this turn: the prompt it was given, then
234
+ # the raw bytes it emitted.
235
+ cached = render(prefix) + generated
236
+ # What chad sends next, once the turn is stored and a tool result appended.
237
+ stored = {"role": "assistant", "content": close_unclosed_think(generated, True)}
238
+ nxt = render([split_inline_reasoning(m) for m in
239
+ prefix + [stored, {"role": "tool", "name": "bash", "content": "ok"}]])
240
+ keep = len(os.path.commonprefix([cached, nxt]))
241
+ check(f"{name}: render extends the cache",
242
+ keep >= len(cached) - len("\n"), f"diverged after {keep} of {len(cached)} chars")
243
+ # Reasoning the model emitted must survive into the transcript, all of it.
244
+ check(f"{name}: reasoning kept",
245
+ reasoning.strip() in split_inline_reasoning(stored).get("reasoning_content", ""))
246
+
184
247
  if __name__ == "__main__":
185
248
  with pytest.MonkeyPatch.context() as mp:
186
249
  with tempfile.TemporaryDirectory() as d:
@@ -198,6 +261,7 @@ if __name__ == "__main__":
198
261
  test_close_unclosed_think()
199
262
  test_split_inline_reasoning()
200
263
  test_reasoning_split_probe_classifies_templates()
264
+ test_stored_turn_extends_the_kv_cache()
201
265
  print(f"\n{PASS} passed, {FAIL} failed")
202
266
  raise SystemExit(1 if FAIL else 0)
203
267
 
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