kronk-cli 0.1.3 → 0.2.1

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/README.md CHANGED
@@ -23,6 +23,9 @@ entirely on your machine. No network, no API key, no per-token cost.
23
23
  2104→812 tok · 61.3 tok/s · ttft 240ms · 1980 cached
24
24
  ```
25
25
 
26
+ *A real session transcript, kept here to show the UI format. For numbers you can reproduce
27
+ yourself on your own hardware, see [Performance](#performance).*
28
+
26
29
  [![ci](https://github.com/BardiaN/kronk-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/BardiaN/kronk-cli/actions/workflows/ci.yml)
27
30
  [![security](https://github.com/BardiaN/kronk-cli/actions/workflows/security.yml/badge.svg)](https://github.com/BardiaN/kronk-cli/actions/workflows/security.yml)
28
31
  [![codeql](https://github.com/BardiaN/kronk-cli/actions/workflows/codeql.yml/badge.svg)](https://github.com/BardiaN/kronk-cli/actions/workflows/codeql.yml)
@@ -321,8 +324,99 @@ kronk-cli "explain src/agent.js" # one shot, prints and exits
321
324
  git diff | kronk-cli "review this diff" # stdin as extra context
322
325
  git log --oneline -20 | kronk-cli # stdin as the whole prompt
323
326
  kronk-cli --auto "make the tests pass" # unattended, runs the whole task
327
+ kronk-cli setup # first run: model, profile, restart
328
+ ```
329
+
330
+ ---
331
+
332
+ ## Subcommands
333
+
334
+ ### `kronk-cli setup`
335
+
336
+ The three things a fresh install needs — the model on disk, an `/AGENT` profile for it, and a
337
+ server restart so Kronk reads that profile — walked in order, announcing each step. `--dry-run`
338
+ walks the whole thing and prints what it *would* do, which is the safest way to see it:
339
+
340
+ ```console
341
+ $ kronk-cli setup --dry-run --model unsloth/Qwen3-0.6B-Q8_0/AGENT
342
+
343
+ kronk-cli setup · dry run, nothing will change
344
+
345
+ 1) Checking the Kronk server
346
+ http://localhost:11435/v1 · serving 2 models
347
+
348
+ 2) Resolving the target
349
+ profile unsloth/Qwen3-0.6B-Q8_0/AGENT
350
+ catalog unsloth/Qwen3-0.6B-Q8_0
351
+ binary /opt/homebrew/bin/kronk
352
+
353
+ 3) Checking whether the model is downloaded
354
+ would run: kronk catalog show unsloth/Qwen3-0.6B-Q8_0 --local
355
+
356
+ 4) Downloading the model
357
+ would run: kronk model pull unsloth/Qwen3-0.6B-Q8_0
358
+
359
+ 5) Writing the /AGENT profile
360
+ file ~/.kronk/models/model_config.yaml
361
+ This block will be added:
362
+
363
+ unsloth/Qwen3-0.6B-Q8_0/AGENT:
364
+ context-window: 131072
365
+ nseq-max: 2
366
+ chat-template-kwargs:
367
+ preserve_thinking: true
368
+ sampling-parameters:
369
+ max_tokens: 16384
370
+
371
+ would update: ~/.kronk/models/model_config.yaml
372
+ would back up first: ~/.kronk/models/model_config.yaml.bak…
373
+
374
+ 6) Restarting Kronk
375
+ model_config.yaml is read only when the server starts, so the new
376
+ profile does nothing until Kronk is restarted.
377
+ would run: kronk server stop
378
+ would run: kronk server start --detach
379
+
380
+ Dry run complete — nothing was written and nothing was started.
324
381
  ```
325
382
 
383
+ | Flag | |
384
+ |---|---|
385
+ | `--model <id>` | Set up a model other than the default. A trailing `/AGENT` is a Kronk profile name, not a catalog id, so it is stripped before pulling |
386
+ | `--context <n>` | Override the profile's `context-window`. Default `131072`, capped at the model's native maximum when Kronk reports one |
387
+ | `-y`, `--yes` | Answer every prompt yes. Required in CI |
388
+ | `--dry-run` | Print every action, including the exact YAML and the exact commands, and change nothing |
389
+
390
+ Nothing slow or destructive happens without an answer: the pull, the file write and the restart
391
+ are each confirmed. Declining exits 0 and prints the commands you would run by hand. If the
392
+ `kronk` binary is not on `PATH`, setup prints the whole manual recipe instead of failing with a
393
+ spawn error. Run it twice and the second run does nothing.
394
+
395
+ Piped or unattended input answers the first question only — readline discards lines nobody is
396
+ waiting for — so a question with no answer left is a **no**: setup says `stdin ended, assuming
397
+ no`, prints the command, and exits 0. Scripts and CI want `-y`.
398
+
399
+ **What it writes.** Only the `models:` mapping of `~/.kronk/models/model_config.yaml` — point it
400
+ elsewhere with `KRONK_MODEL_CONFIG`. This project has no YAML parser and will not gain one, so
401
+ the writer is a structural scan with a single job — find the one `models:` key and splice the
402
+ entry beneath it — and it refuses whenever the answer is not obvious:
403
+
404
+ - The file is copied aside before any write, to `model_config.yaml.bak`, and **never over a
405
+ backup that already exists**: `.bak2`, `.bak3`, and so on until a free name is found.
406
+ - Every line it did not add survives byte for byte — comments, blank lines and key order
407
+ included. LF and CRLF files each keep their own endings.
408
+ - Missing file → created with `version: 1`, `models:` and the entry. No `models:` key → both are
409
+ appended. A `models:` key with no children → the entry becomes its first child.
410
+ - **More than one top-level `models:` key** — two concatenated documents, which does happen in
411
+ the wild — is refused outright. It prints the block, names the file, and exits non-zero
412
+ without touching anything.
413
+ - A missing `~/.kronk/models/` means Kronk has never run. Setup says so and stops, rather than
414
+ creating Kronk's data directory on its behalf.
415
+
416
+ The profile it writes is the one documented under
417
+ [Tip: use an `/AGENT` profile](#tip-use-an-agent-profile) — `context-window`, `nseq-max`,
418
+ `preserve_thinking` and `max_tokens`, and deliberately no sampling parameters.
419
+
326
420
  ---
327
421
 
328
422
  ## Startup: the model is loaded before you type
@@ -364,8 +458,98 @@ as before.
364
458
 
365
459
  ---
366
460
 
461
+ ## Sampling override warning
462
+
463
+ The same startup lookup that finds the context window also carries the model's own sampling
464
+ recommendations — `general.sampling.temp`, `top_k` and `top_p`, straight from the GGUF — next
465
+ to the effective `sampling-parameters` Kronk is actually applying once any profile has been
466
+ merged in. `kronk-cli` compares the two and, if a profile is overriding what the model ships,
467
+ says so in one grey line under the startup banner:
468
+
469
+ ```console
470
+ $ kronk-cli
471
+ ██ kronk-cli · local agent, no network
472
+ model unsloth/Qwen3.6-35B-A3B-UD-Q4_K_M/AGENT
473
+ server http://localhost:11435/v1
474
+ /help for commands · Ctrl-C to interrupt · /exit to quit
475
+
476
+ note profile overrides the model's own sampling: temperature 0.6 (model recommends 1)
477
+ ```
478
+
479
+ One line, however many of the three parameters disagree — not one per parameter. It stays
480
+ silent when the values agree, when the model ships no `general.sampling.*` metadata at all, or
481
+ when the model-info lookup fails: this is information, not a failure, and it never blocks,
482
+ prompts, or changes the exit code.
483
+
484
+ **The fix** is the one described in [Tip: use an `/AGENT` profile](#tip-use-an-agent-profile):
485
+ remove `temperature`, `top_k` and `top_p` from the profile's `sampling-parameters` block so
486
+ Kronk applies what the model itself recommends.
487
+
488
+ Since Kronk 1.32.2 this line can fire on a profile you never wrote. That release changed Kronk's
489
+ own shipped default for `unsloth/Qwen3.8-27B-UD-Q4_K_XL/AGENT` to pin `temperature: 0.7`,
490
+ `top_p: 0.80` and `top_k: 20`, where it previously carried none of the three. The warning is still
491
+ accurate — something *is* overriding the GGUF — but the something is the server's authors making a
492
+ deliberate choice for that model, not a stale hand-edit. Read it as a fact worth knowing rather
493
+ than a defect to repair, and leave it alone unless you have measured otherwise.
494
+
495
+ Like the rest of the banner, the line is part of the interactive REPL's startup — a one-shot
496
+ run (`kronk-cli "prompt"`) prints no banner and prints nothing here either.
497
+
498
+ ---
499
+
500
+ ## Performance
501
+
502
+ The transcripts above are real sessions, kept to show what the UI looks like — not a benchmark
503
+ table, and nothing here reproduced them until now. `scripts/bench.mjs` does: a fixed prompt run
504
+ twice for generation speed, a long deterministic filler prompt carried across three turns for
505
+ prompt-cache retention, and — only when the target model is not already resident — a cold-load
506
+ timing using the same request `warm()` sends at boot. It is a dev tool, excluded from the
507
+ published package; run it straight from a checkout:
508
+
509
+ ```bash
510
+ node scripts/bench.mjs # table
511
+ node scripts/bench.mjs --json # same numbers, one JSON object, diffable between runs
512
+ node scripts/bench.mjs --skip-cold-load
513
+ ```
514
+
515
+ It talks to whatever `KRONK_URL` / `KRONK_MODEL` you already have configured, so the numbers
516
+ below are specific to one machine — measure your own rather than trusting these across different
517
+ hardware.
518
+
519
+ Measured 2026-08-23 on an Apple M4 Max, 64 GB RAM (macOS), against
520
+ `unsloth/Qwen3.6-35B-A3B-UD-Q4_K_M/AGENT`, kronk 1.31.9, llama.cpp b10549 — from
521
+ `node scripts/bench.mjs`:
522
+
523
+ ```
524
+ kronk-bench · model unsloth/Qwen3.6-35B-A3B-UD-Q4_K_M/AGENT · kronk 1.31.9 · llama.cpp b10549
525
+
526
+ 1. Generation speed
527
+ run 1: 229 tok · 2.97s · 77.1 tok/s · ttft 133ms
528
+ run 2: 220 tok · 2.82s · 78 tok/s · ttft 115ms
529
+
530
+ 2. Prompt cache retention (filler prompt: 5491 tok)
531
+ turn 1: 5518 tok prompt · 5496 tok cached (100%) · 149ms
532
+ turn 2: 5544 tok prompt · 5511 tok cached (99%) · 185ms
533
+ turn 3: 5571 tok prompt · 5537 tok cached (99%) · 160ms
534
+
535
+ 3. Cold load
536
+ skipped — unsloth/Qwen3.6-35B-A3B-UD-Q4_K_M/AGENT is already resident — a cold load number
537
+ here would be misleading
538
+ ```
539
+
540
+ That generation speed and cache-retention region matches the transcripts above (60–80 tok/s,
541
+ prompt cache reused above 99% on repeat turns) — this machine's Kronk server is shared with other
542
+ work, so a run under contention lands lower in that range and a quiet run lands higher; that
543
+ variance is expected, which is why the script reports each run rather than a single averaged
544
+ number. The cold-load section only prints a number the one time a run finds the model not
545
+ resident — do not stop or unload a shared server's model just to force that path; skip it, the
546
+ same way this run did.
547
+
548
+ ---
549
+
367
550
  ## Command-line options
368
551
 
552
+
369
553
  | Flag | Default | |
370
554
  |---|---|---|
371
555
  | `-m`, `--model <id>` | `unsloth/Qwen3.6-35B-A3B-UD-Q4_K_M/AGENT` | Model to use. A substring is enough; `/AGENT` profiles win ties |
@@ -380,9 +564,15 @@ as before.
380
564
  | `--no-think` | off | Disable the model's reasoning pass server-side. Much faster |
381
565
  | `--steps <n>` | unlimited | Cap tool calls per task. `0`, `off`, `none`, `inf`, `unlimited` all mean no cap |
382
566
  | `-h`, `--help` | — | Print all options and exit |
567
+ | `--` | — | End option parsing; everything after it is prompt text, dashes and all |
383
568
 
384
- Anything not consumed as a flag becomes the prompt. With both an inline prompt and piped stdin,
385
- the two are concatenated.
569
+ Anything not consumed as an option becomes the prompt, except a token that looks like an
570
+ option and is not one: `kronk-cli -auto "…"` exits 2 with `unknown option: -auto` and a
571
+ suggestion, rather than folding the typo into the prompt and running with the mode off.
572
+ Prose is untouched — a dash followed by a space is not option-shaped, so
573
+ `kronk-cli "- fix the dashes bug"` still asks the question — and `--` ends option parsing,
574
+ so `kronk-cli -- --explain this` sends `--explain this`. With both an inline prompt and
575
+ piped stdin, the two are concatenated.
386
576
 
387
577
  `Ctrl-C` aborts the in-flight response and the tool loop without killing the session.
388
578
 
@@ -431,10 +621,13 @@ The per-turn usage line still prints after each response; this one is the runnin
431
621
  | `KRONK_URL` | `http://localhost:11435/v1` | Kronk API base |
432
622
  | `KRONK_TOKEN` | `kronk` | Any non-empty value while Kronk runs open; a real JWT when protected |
433
623
  | `KRONK_MODEL` | `unsloth/Qwen3.6-35B-A3B-UD-Q4_K_M/AGENT` | Model id |
624
+ | `KRONK_MODEL_CONFIG` | `~/.kronk/models/model_config.yaml` | Kronk's per-model config, the file `kronk-cli setup` writes |
434
625
  | `KRONK_MAX_TOKENS` | `8192` | Output cap per response |
435
626
  | `KRONK_MAX_STEPS` | unlimited | Cap on tool calls per task |
436
627
  | `KRONK_THINKING` | `true` | `false` hides reasoning but still generates it |
437
628
  | `KRONK_NO_THINK` | — | `1` disables reasoning server-side |
629
+ | `KRONK_PRESERVE_THINKING` | `true` | `false` stops pinning earlier think blocks in the prompt |
630
+ | `KRONK_REPLAY_REASONING` | autonomous-only | `true`/`false` overrides the default in either direction — see [What reasoning gets sent back](#what-reasoning-gets-sent-back) |
438
631
  | `KRONK_TOOL_TIMEOUT` | `900` | Seconds before a shell command is killed |
439
632
  | `KRONK_SANDBOX` | `auto` | `auto` confines `bash` when the OS can, `strict` refuses to run it when it cannot, `off` disables it |
440
633
  | `KRONK_SANDBOX_ALLOW` | — | Paths to make fully available inside the sandbox, comma or colon separated |
@@ -460,7 +653,9 @@ The per-turn usage line still prints after each response; this one is the runnin
460
653
  "showThinking": false,
461
654
  "autoCompact": true,
462
655
  "compactAt": 0.85,
463
- "noThink": true
656
+ "noThink": true,
657
+ "preserveThinking": true,
658
+ "replayReasoning": true
464
659
  }
465
660
  ```
466
661
 
@@ -504,7 +699,9 @@ memory it holds.
504
699
 
505
700
  ### Tip: use an `/AGENT` profile
506
701
 
507
- Kronk lets one GGUF serve several runtime configurations. Add this to
702
+ Kronk lets one GGUF serve several runtime configurations. `kronk-cli setup` writes this profile
703
+ for you, backs the file up first, and offers the restart — see
704
+ [Subcommands](#kronk-cli-setup). To do it by hand, add this to
508
705
  `~/.kronk/models/model_config.yaml` and restart the server:
509
706
 
510
707
  ```yaml
@@ -513,12 +710,101 @@ models:
513
710
  unsloth/Qwen3.6-35B-A3B-UD-Q4_K_M/AGENT:
514
711
  context-window: 131072
515
712
  nseq-max: 2
713
+ chat-template-kwargs:
714
+ preserve_thinking: true
516
715
  sampling-parameters:
517
- temperature: 0.6
518
- top_k: 20
519
- top_p: 0.95
716
+ max_tokens: 16384
520
717
  ```
521
718
 
719
+ **Do not set `temperature`, `top_k` or `top_p` here.** Most current GGUFs carry the values
720
+ their authors recommend, Kronk reads them, and an explicit block only overrides the model's own
721
+ advice. This model ships `general.sampling.temp: 1`, `top_k: 20`, `top_p: 0.95`; an earlier
722
+ version of this page recommended `temperature: 0.6`, which quietly fought that. Check what your
723
+ model carries with `kronk model show <id> --local`. If a profile does pin one of these,
724
+ `kronk-cli` says so at startup — see [Sampling override warning](#sampling-override-warning).
725
+
726
+ `preserve_thinking` earns its place in an agent profile. The chat template decides per assistant
727
+ message whether to render its `<think>` block, and the decision depends on which user message is
728
+ the most recent real query. During a tool loop that boundary moves, so the same earlier turn
729
+ renders one way now and another way after your next prompt — the prefix changes and the cached
730
+ prompt is thrown away. `preserve_thinking: true` renders those blocks the same way every time, so
731
+ the prefix stays stable and the server keeps the cache.
732
+
733
+ Measured on Kronk 1.31.8, on a 13.4k-token conversation with the profile above: the first turn prefilled in
734
+ 10.9 s with nothing cached, and every following turn reported **13,4xx of 13,4xx tokens cached**
735
+ and answered in 1.6 s. *A one-off session, not reproducible from this repo as run — see
736
+ [Performance](#performance) for the current, scripted measurement of the same effect.*
737
+
738
+ **You no longer have to set it.** kronk-cli sends `chat_template_kwargs: {preserve_thinking: true}`
739
+ on every chat request, regardless of what the profile says, so an existing profile that predates
740
+ this line gets the stable prefix anyway. It is sent only when the selected model's chat template
741
+ actually declares the parameter — read from `tokenizer.chat_template` in
742
+ `GET /v1/kronk/models/<id>` at startup — and never guessed at when that lookup fails.
743
+
744
+ It is a trade, not a free win: the retained `<think>` blocks stay in the prompt and cost tokens.
745
+ On a small window you may prefer to pay the prefill instead, so `KRONK_PRESERVE_THINKING=false`
746
+ (or `"preserveThinking": false` in `~/.kronk-cli.json`) turns it off, and the status line says
747
+ `no-preserve` when it is off. With `--no-think` there is no reasoning to preserve and the field is
748
+ not sent at all.
749
+
750
+ ### What reasoning gets sent back
751
+
752
+ `preserve_thinking` decides how the template *renders* a think block. What is in that block is a
753
+ separate question, and kronk-cli answers it like this: **the model's reasoning is replayed for the
754
+ current task and dropped for everything before it — by default in `--auto`, and never by default
755
+ in the interactive REPL.**
756
+
757
+ Concretely, an assistant message keeps its `reasoning_content` on the wire while it sits after the
758
+ most recent real user prompt. A tool result is `role: tool`, so it does not end the task — one
759
+ prompt and the whole tool loop it started share the boundary. The moment a new user prompt arrives,
760
+ the previous task's blocks are stripped and render empty. That is the same boundary the chat
761
+ template computes as `ns.last_query_index`.
762
+
763
+ Within a task the model reasons about tool result N before it picks tool N+1, and dropping that
764
+ makes it re-derive its plan from tool output alone at every step. Those tokens are also
765
+ append-only — new on every step, never part of the cached prefix — so replaying them costs nothing
766
+ in cache terms. Reasoning from *earlier* turns is the opposite: it sits in the prefix for the rest
767
+ of the session, grows without bound, and brings automatic compaction forward.
768
+
769
+ It is a trade, and the bill arrives at your *next* prompt: stripping the previous task's blocks at
770
+ the boundary rewrites the prefix that prompt sits after, so the request resets to the cached system
771
+ prompt instead of the cached conversation and re-prefills the rest. Measured on a live server, first
772
+ turn after a second prompt, prompt tokens cached vs. re-prefilled:
773
+
774
+ | | cached | re-prefilled |
775
+ |---|---|---|
776
+ | never replay | 906 | 60 |
777
+ | replay current task | 757 | 516 |
778
+ | replay everything | 1,827 | 367 |
779
+
780
+ `--auto` runs exactly one user prompt through a whole tool loop, so that boundary is never crossed
781
+ in a run: the within-task benefit above is free there, and the reset never happens. The REPL is a
782
+ user typing repeatedly, so every prompt after the first pays it. Measured over five paired sessions
783
+ on Kronk 1.31.9 with `unsloth/Qwen3.6-35B-A3B-UD-Q4_K_M/AGENT` (llama.cpp `b10549`,
784
+ darwin/arm64/metal) with replay forced on for the whole session — the same multi-step task, nine to
785
+ twenty-four tool calls depending on the run, followed by the same follow-up question:
786
+
787
+ | | replay on | replay off |
788
+ |---|---|---|
789
+ | Prompt tokens at end of session | 10,179 / 11,238 / 7,960 / 10,018 / 9,470 | 20,640 / 8,889 / 10,648 / 10,259 / 13,209 |
790
+ | Model turns to finish the first task | 6 / 7 / 2 / 7 / 8 | 9 / 9 / 7 / 7 / 5 |
791
+ | Cached tokens on the first turn after the second prompt | 1,222 every run | the whole prefix every run |
792
+ | Time to first token on that turn | 5.9 / 7.4 / 4.4 / 5.9 / 6.5 s | 1.2 / 0.8 / 0.9 / 0.7 / 1.1 s |
793
+ | Wall clock, three timed pairs | 51 / 63 / 50 s | 68 / 49 / 55 s |
794
+
795
+ So: fewer turns and fewer prompt tokens over a session, paid for with one re-prefill per prompt —
796
+ 4.4-7.4 s to first token instead of 0.7-1.2 s once cached. That cost is only worth paying when there
797
+ is exactly one prompt to begin with, which is why the default is autonomous-only rather than on
798
+ everywhere. Sampling is at the model's own `temperature: 1`, so the turn counts above are indicative
799
+ rather than reproducible.
800
+
801
+ `KRONK_REPLAY_REASONING`, or `"replayReasoning"` in `~/.kronk-cli.json`, overrides the default in
802
+ either direction rather than just turning it off: `true` replays reasoning in the REPL too, `false`
803
+ turns it off even in `--auto`. Leaving it unset is what gives you the autonomous-only default. It is
804
+ off regardless of `--auto` when `--no-think` is set — there is no reasoning to replay — and when the
805
+ selected model's chat template does not declare `preserve_thinking`, because such a template does
806
+ not read `reasoning_content` either and would discard the blocks.
807
+
522
808
  ---
523
809
 
524
810
  ## Context window
@@ -540,7 +826,8 @@ Three places surface it:
540
826
  18471→57 tok · 69.0 tok/s · ttft 397ms · 18260 cached 18k/131k 14% ▓░░░░░░░░░
541
827
  ```
542
828
 
543
- Grey under 70%, yellow past 70%, red past 90%.
829
+ Grey under 70%, yellow past 70%, red past 90%. *Also a real transcript — see
830
+ [Performance](#performance) for how to reproduce numbers like these.*
544
831
 
545
832
  **`/context`**, on demand:
546
833
 
@@ -635,6 +922,10 @@ Disable with `--no-compact` or `KRONK_AUTO_COMPACT=false` if you would rather se
635
922
  | `search` | — | Regex search via ripgrep, falling back to grep |
636
923
  | `write_file` | ✋ | Create or overwrite; shows a diff preview first |
637
924
  | `bash` | ✋ | Run a command; shows it first |
925
+ | `set_plan` | — | Record the task checklist; replaces the stored one |
926
+
927
+ `set_plan` writes nothing and runs nothing — it hands the harness a list, which is why it is
928
+ never gated. See [Autonomous mode](#autonomous-mode) for what the harness then does with it.
638
929
 
639
930
  `--yes` and `--auto` skip the prompts. Paths resolve against the session directory and cannot
640
931
  escape the launch root — including through a symlink. `bash` additionally runs under an OS
@@ -857,6 +1148,35 @@ $ kronk-cli --auto "write a CSV stats script, add a node:test, run it, fix what
857
1148
 
858
1149
  `Ctrl-C` stops it. Add `--steps N` for unattended runs where nobody is watching.
859
1150
 
1151
+ **The task checklist.** Long tickets used to end early: the model satisfied one criterion of a
1152
+ dozen, wrote a confident summary, and stopped. The autonomous prompt now tells it to call
1153
+ `set_plan` before its first edit, with one item per acceptance criterion, and to update the list
1154
+ as it goes. The harness holds that list and does two things with it:
1155
+
1156
+ - **Re-states it every round.** The open items, verbatim, and how many of how many are done, are
1157
+ appended to the last tool result of the round that has just run — so the original request is
1158
+ never thousands of tokens behind. Appended, never moved: the conversation only ever grows at
1159
+ the end, which is what keeps Kronk's prompt cache alive across a long run. Earlier rounds keep
1160
+ the snapshot they were given, and each one says it is a point in time; the last is the current
1161
+ one. The list is held outside the message list as well, so [compaction](#compaction) cannot
1162
+ summarise it away.
1163
+ - **Declines the first premature "done".** A reply with no tool calls, while items are still
1164
+ open, gets the open list handed back instead of ending the run. Twice at most: after that the
1165
+ turn ends and the unfinished items are printed in yellow. `--steps` still wins, and `Ctrl-C`
1166
+ still stops everything.
1167
+
1168
+ ```console
1169
+ 1 ⚙ plan: 4 items
1170
+ · keep the push trigger on master
1171
+ ▸ add the concurrency guard
1172
+ · document the change in README.md
1173
+ · run the linter
1174
+ ```
1175
+
1176
+ None of this fires without a plan: if the model never calls `set_plan`, a run behaves exactly as
1177
+ it did before, and interactive (non-`--auto`) conversation is never held to a checklist. The
1178
+ model writes the plan; the harness only holds it to it.
1179
+
860
1180
  > ⚠️ `--auto` runs shell commands without asking. Use it where `git checkout` can save you.
861
1181
 
862
1182
  ---
@@ -885,6 +1205,7 @@ previous prompt prefix — watch `cached` climb in the usage line.
885
1205
  | `src/tools.js` | tool definitions, sandbox, shell session |
886
1206
  | `src/context.js` | startup scan: git, layout, `AGENTS.md` |
887
1207
  | `src/compact.js` | summarizing the conversation when the window fills |
1208
+ | `src/plan.js` | the task checklist and the snapshot the model sees |
888
1209
  | `src/mcp.js` | MCP client: stdio + HTTP transports, tool routing |
889
1210
  | `src/distill.js` | summarizing large tool output in a throwaway context |
890
1211
  | `src/config.js` | precedence of flags, env, config file |
@@ -915,6 +1236,15 @@ previous prompt prefix — watch `cached` climb in the usage line.
915
1236
 
916
1237
  ---
917
1238
 
1239
+ ## Contributing
1240
+
1241
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for setup and what gets merged quickly. Commits and PR
1242
+ titles follow [Conventional Commits](https://www.conventionalcommits.org/), and releases follow
1243
+ npm [semver](https://semver.org/) — a feature or fix PR leaves `package.json` alone, and a
1244
+ separate release commit bumps the version. Details are in CONTRIBUTING.md.
1245
+
1246
+ ---
1247
+
918
1248
  ## License
919
1249
 
920
- MIT
1250
+ Apache-2.0 — see [LICENSE](LICENSE) and [NOTICE](NOTICE).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kronk-cli",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "A Claude-Code-style terminal agent for local models served by Kronk.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -34,7 +34,7 @@
34
34
  },
35
35
  "devDependencies": {
36
36
  "@eslint/js": "^10.0.1",
37
- "eslint": "^10.8.1"
37
+ "eslint": "^10.9.1"
38
38
  },
39
39
  "repository": {
40
40
  "type": "git",