samagotchi 0.61.0 → 0.63.0

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 (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +91 -1
  3. data/README.md +4 -1
  4. data/bin/chi +3 -2
  5. data/docs/architecture.md +3 -0
  6. data/docs/cli.md +26 -9
  7. data/docs/configuration.md +33 -20
  8. data/docs/context.md +21 -3
  9. data/docs/guardrails.md +2 -1
  10. data/docs/hooks.md +14 -3
  11. data/docs/internals/engine.md +24 -11
  12. data/docs/internals/prompt-caching.md +6 -1
  13. data/docs/memory.md +1 -1
  14. data/docs/plugins.md +131 -5
  15. data/docs/releasing.md +1 -1
  16. data/docs/sessions.md +4 -4
  17. data/docs/sub-agent.md +6 -0
  18. data/docs/testing.md +10 -4
  19. data/lib/samagotchi/answer_refs.rb +18 -26
  20. data/lib/samagotchi/bridge.rb +23 -1
  21. data/lib/samagotchi/bundles/claude-peer/manifest.yml +22 -0
  22. data/lib/samagotchi/bundles/claude-peer/plugin.rb +406 -0
  23. data/lib/samagotchi/bundles/claude-peer/scripts/chi-peer-SKILL.md +104 -0
  24. data/lib/samagotchi/bundles/claude-peer/scripts/pair +66 -0
  25. data/lib/samagotchi/bundles/claude-peer/skill_claude_peer.md +60 -0
  26. data/lib/samagotchi/bundles/dev/manifest.yml +2 -1
  27. data/lib/samagotchi/bundles/github-pr/manifest.yml +3 -3
  28. data/lib/samagotchi/bundles/github-pr/plugin.rb +70 -14
  29. data/lib/samagotchi/bundles/loop-guard/manifest.yml +2 -2
  30. data/lib/samagotchi/bundles/loop-guard/plugin.rb +17 -6
  31. data/lib/samagotchi/bundles/slow-commands/manifest.yml +2 -2
  32. data/lib/samagotchi/bundles/slow-commands/plugin.rb +5 -1
  33. data/lib/samagotchi/bundles/source-links/hooks/source_links.rb +58 -3
  34. data/lib/samagotchi/bundles/source-links/manifest.yml +4 -4
  35. data/lib/samagotchi/bundles/source-links/source_links.md +1 -1
  36. data/lib/samagotchi/bundles/system/manifest.yml +2 -2
  37. data/lib/samagotchi/bundles/system/memory_guide.md +1 -1
  38. data/lib/samagotchi/context_absorber.rb +25 -6
  39. data/lib/samagotchi/engine.rb +9 -0
  40. data/lib/samagotchi/engine_peers.rb +1 -0
  41. data/lib/samagotchi/idle_recap.rb +2 -3
  42. data/lib/samagotchi/image_store.rb +8 -0
  43. data/lib/samagotchi/kernel_loop.rb +7 -16
  44. data/lib/samagotchi/llm/chat_loop.rb +23 -15
  45. data/lib/samagotchi/plugin/api.rb +36 -18
  46. data/lib/samagotchi/plugin/attached_context.rb +7 -4
  47. data/lib/samagotchi/reasoning_history.rb +22 -4
  48. data/lib/samagotchi/recap_setup.rb +3 -3
  49. data/lib/samagotchi/row_refs.rb +125 -0
  50. data/lib/samagotchi/session.rb +12 -0
  51. data/lib/samagotchi/session_metrics.rb +24 -2
  52. data/lib/samagotchi/terminal_ui/attach_launcher.rb +6 -3
  53. data/lib/samagotchi/terminal_ui/attached_loop.rb +26 -9
  54. data/lib/samagotchi/terminal_ui/formatting.rb +1 -1
  55. data/lib/samagotchi/terminal_ui.rb +57 -14
  56. data/lib/samagotchi/tool_activity.rb +8 -4
  57. data/lib/samagotchi/tool_response.rb +5 -4
  58. data/lib/samagotchi/tool_row_fields.rb +4 -2
  59. data/lib/samagotchi/tool_runner.rb +16 -3
  60. data/lib/samagotchi/tools/builtins.rb +9 -2
  61. data/lib/samagotchi/tools/execute.rb +37 -7
  62. data/lib/samagotchi/tools/peers.rb +3 -2
  63. data/lib/samagotchi/tools/registry.rb +5 -3
  64. data/lib/samagotchi/version.rb +1 -1
  65. data/lib/samagotchi/web/app.rb +20 -5
  66. data/lib/samagotchi/web/message_parts.rb +17 -9
  67. data/lib/samagotchi/web/public/activity.js +6 -3
  68. data/lib/samagotchi/web/public/app.js +106 -106
  69. data/lib/samagotchi/web/public/card.js +73 -23
  70. data/lib/samagotchi/web/public/chains.js +24 -24
  71. data/lib/samagotchi/web/public/command_view.js +39 -35
  72. data/lib/samagotchi/web/public/context_chips.js +92 -64
  73. data/lib/samagotchi/web/public/copy.js +7 -15
  74. data/lib/samagotchi/web/public/diff_view.js +20 -23
  75. data/lib/samagotchi/web/public/families_view.js +51 -39
  76. data/lib/samagotchi/web/public/format.js +0 -48
  77. data/lib/samagotchi/web/public/images.js +12 -16
  78. data/lib/samagotchi/web/public/index.html +61 -10
  79. data/lib/samagotchi/web/public/keep_focus.js +24 -0
  80. data/lib/samagotchi/web/public/lists_view.js +13 -13
  81. data/lib/samagotchi/web/public/llm_context_chip.js +55 -34
  82. data/lib/samagotchi/web/public/question_card.js +11 -0
  83. data/lib/samagotchi/web/public/question_cards.js +63 -7
  84. data/lib/samagotchi/web/public/read_view.js +10 -9
  85. data/lib/samagotchi/web/public/ref_menu.js +18 -5
  86. data/lib/samagotchi/web/public/refs.js +113 -15
  87. data/lib/samagotchi/web/public/scope.js +23 -0
  88. data/lib/samagotchi/web/public/scroll.js +42 -0
  89. data/lib/samagotchi/web/public/stage_model.js +22 -3
  90. data/lib/samagotchi/web/public/stage_view.js +299 -70
  91. data/lib/samagotchi/web/public/timing.js +1 -8
  92. data/lib/samagotchi/web/public/turn_html.js +122 -61
  93. data/lib/samagotchi/web/public/turn_model.js +49 -0
  94. data/lib/samagotchi/web/public/turn_view.js +167 -116
  95. data/lib/samagotchi/web/public/vendor/directive.js +8 -0
  96. data/lib/samagotchi/web/public/vendor/directives/unsafe-html.js +8 -0
  97. data/lib/samagotchi/worker.rb +49 -32
  98. data/lib/samagotchi/worker_inbound.rb +17 -14
  99. metadata +10 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a3f828171b95ef34cef1b89706f21a34822ea73dae63e55b2219767961241f7c
4
- data.tar.gz: b12760887f0613c4e087f824f653e8ccc20e0602de55ef69fd8aed69a897a33c
3
+ metadata.gz: 20b05cd0da6dfdd621eba3b2add37f00b2abe96c2e3950d8ddd066bdf3078424
4
+ data.tar.gz: ecf5784f9f82c8a8c9e813185570d326a3f4d9ad3a404033f53abf1b62a20902
5
5
  SHA512:
6
- metadata.gz: e68486688dfe56739e1d05a68549d050f3cb73c369536c5fa097d832baabcfb42ba41917580ad9d0a4707ab3933550247177e92b0184dee2493b467be5773bad
7
- data.tar.gz: 630eca4f31585d00938ad89b77275d25969384301b43c71415e20fac95be9fb22dfa60695f59afd1fe612e4cad8de94ead8ab949464c6ab7f21eb18fb8667357
6
+ metadata.gz: 302c8ccf54cc947c9165fe8e30f4d32b4054ee02a1e2d62cbb7d7942a30574af58af84a7eafc5b2daf24a8f167e98024e2798fcacccf597b919435019d498613
7
+ data.tar.gz: b6ac96ff05fb4e52a5705b55de3fa505054860b154cd6622c4badc232c6284bc5e80d57ffc94b13a7eae1384557acd5b697b630a0ed21be1a8e85d99610b3d04
data/CHANGELOG.md CHANGED
@@ -8,6 +8,94 @@ and commands may change between minor versions. How releases are made:
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.63.0] - 2026-10-13
12
+
13
+ ### Added
14
+
15
+ - The claude-peer bundle (in the dev profile): pair a chi session with a Claude Code session you work with, and
16
+ the `claude_send` tool sends Claude a part of the work (a plan step, a diff, a finding) to validate, as context or
17
+ with a question; Claude's answer comes back as the `claude-review` context. `/claude validate|context <text>` and
18
+ `/claude last` send without the model, `/claude` shows whether a Claude is paired, and `/claude install-skill`
19
+ installs Claude's `chi-peer` skill, which pairs ("pair with chi <id>"). See docs/plugins.md, "The claude-peer
20
+ bundle".
21
+ - Plugins: `ctx.context.list` gives each source's last `summary:` too. See docs/plugins.md, "Attached context".
22
+
23
+ ### Fixed
24
+
25
+ - Attached context: a source added with `chi context add NAME --push` now wakes an idle session when its first
26
+ push asks to (`"wake": true`); before, only later pushes could.
27
+ - Plugins: `ctx.context.list` returns `[]` for a new session whose file isn't saved yet (a plugin command before
28
+ the first turn) instead of raising "Session not found".
29
+ - An after_turn hook's file refs on the answer (`event[:refs]`) must be under the session's folder, as a plugin
30
+ tool row's are: a file elsewhere is refused (and logged), and a stored one is dropped when the answer is shown.
31
+ - loop-guard (0.3.8): the card for a turn it stopped prints under the blocked call's row in the terminal, not
32
+ before it.
33
+ - Attached TUI: joining a session whose last turn was images alone (sent from the web) names each image once, not
34
+ twice.
35
+ - Attached TUI: text you had typed when the step-limit question opened comes back at the prompt once you answer
36
+ it, instead of being lost.
37
+ - `chi -p … --non-interactive` says so on stderr and exits 1 when the iteration limit cuts the turn short, instead
38
+ of exiting 0 with the partial text as if it were the answer.
39
+ - `chi --no-shared --resume` drops a question the session's last worker saved and died with, as an attached resume
40
+ does, instead of keeping it in the session file.
41
+
42
+ ## [0.62.0] - 2026-10-13
43
+
44
+ ### Added
45
+
46
+ - Plugins: `chi.tool(…, refs: ->(args) { [...] })` gives a tool call's web row its refs (a ticket, a PR, a file
47
+ under the session's folder) with their URL actions: the row's title opens the first, its `▾` menu offers the
48
+ rest, on a phone too. Saved with the result, so a reload shows them. See docs/plugins.md, "Refs on a tool's
49
+ row".
50
+
51
+ ### Changed
52
+
53
+ - Web: the stage view no longer jumps: it keeps its height while a turn runs, its chip stays put when the answer
54
+ arrives, and the finished turn slides away instead of collapsing. The stage floats above the composer (its rising at
55
+ a turn's start moves nothing, and the history behind it glides instead of jumping); content changes inside it (the
56
+ first words of a reply, an answered card, a short answer), so a card you answer stays where it is; it grows with a
57
+ short ease when a card or the answer needs room; its chip is its footer (the steps open right above it); and when
58
+ the turn moves into the history the stage slides away under the composer, then the turn appears there.
59
+ - Web: on a phone the top bar no longer gains a row mid-turn when the hidden strip's pill grows ("· 1 waiting"), which
60
+ pushed the whole session down; the pill drops its session count while it says live or waiting.
61
+ - Web: while a question or approval waits in the stage view, the stage shows the whole text of the reply that asked
62
+ it, not only its last sentence.
63
+ - Web: the model's question or its own approval, once answered, keeps the reply that asked it (a delegate's relayed
64
+ approval, a hook's question and the step limit don't): two muted lines under its `question → answer` line, the
65
+ whole text when the card is open, live and after a reload.
66
+ - Web: a finished turn's steps block shows its outline when its steps said something: open, one line per step that
67
+ wrote text (a mid-turn note reads without a click), and each run of tool-only steps folded into one `· N steps`
68
+ line that shows them. Live (both views) and after a reload; a turn whose steps said nothing collapses as before.
69
+ - Web: question and approval cards answer from the keyboard. Enter in the response box or on an option submits
70
+ (never Deny or Dismiss), and a new card takes the focus when the message box is empty.
71
+ - github-pr 0.5.0: a `path:line` it links in an answer is also a file ref, so the web's ref menu offers the file in
72
+ this checkout (editor, show in turn, copy) and a "PR diff" (or "File at head") action. Needs chi 0.61.0; run
73
+ `chi update`.
74
+ - source-links 0.5.0: each ref it links in an answer gets an "Open in <source>" action in the web's ref menu; a
75
+ source's new `kind:` key names its refs' kind (default `issue`). Needs chi 0.61.0; run `chi update`.
76
+ - Chat hosts (`api: openai`) keep the running turn's reasoning across a mid-turn steer under `reasoning_history:
77
+ turn`, as native hosts do: a person's line, a plugin's nudge (check-in, agents-md) or a step-limit Continue's text no
78
+ longer drops the reasoning of the replies before it, so the request after it extends the one before and the
79
+ server's prompt cache holds. A `!cmd` run while the step-limit question stands still starts past itself. On a stock
80
+ Qwen template (Splash) the benefit needs `chat_template_kwargs.preserve_thinking: true`; see
81
+ [docs/configuration.md](docs/configuration.md#reasoning-history).
82
+
83
+ ### Fixed
84
+
85
+ - A turn run in the REPL (`--no-shared`) keeps the answer's display and refs (source-links' and github-pr's links,
86
+ a hook's `present`), so the web shows them when it opens the session; before, the save dropped them.
87
+ - A command a signal to chi stopped (a worker's TERM, HUP or INT mid-`execute`) says "stopped by a signal to chi",
88
+ not "stopped by the user"; one it kept from starting says "not run, a signal to chi stopped the turn".
89
+ slow-commands 0.2.1 doesn't count either as slow.
90
+ - A turn whose worker was killed (SIGKILL, a crash) keeps its tokens, speed and timings up to its last checkpoint:
91
+ the session's stats count it as a canceled turn (`worker_stopped`); before, they lost it.
92
+ - Web: hovering a ref with a ▾ at the very end of a line no longer pushes it onto the next line.
93
+ - Web: a failed `+ URL` attach keeps the "why" you typed (it was emptied), and a context chip with the keyboard
94
+ focus keeps it when the chips refresh.
95
+
96
+ Update with `chi update`: the system bundle 0.62.0, github-pr 0.5.0, source-links 0.5.0 and slow-commands 0.2.1.
97
+ Then restart `chi web`.
98
+
11
99
  ## [0.61.0] - 2026-10-12
12
100
 
13
101
  ### Added
@@ -2662,7 +2750,9 @@ and long-lived sessions.
2662
2750
  - A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
2663
2751
  hotkey panel that send selected text or the clipboard to your sessions.
2664
2752
 
2665
- [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.61.0...HEAD
2753
+ [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.63.0...HEAD
2754
+ [0.63.0]: https://github.com/dm1try/samagotchi/compare/v0.62.0...v0.63.0
2755
+ [0.62.0]: https://github.com/dm1try/samagotchi/compare/v0.61.0...v0.62.0
2666
2756
  [0.61.0]: https://github.com/dm1try/samagotchi/compare/v0.60.0...v0.61.0
2667
2757
  [0.60.0]: https://github.com/dm1try/samagotchi/compare/v0.59.0...v0.60.0
2668
2758
  [0.59.0]: https://github.com/dm1try/samagotchi/compare/v0.58.0...v0.59.0
data/README.md CHANGED
@@ -150,7 +150,10 @@ task to a child session that runs in parallel and reports back only its final
150
150
  reply (a normal session: `chi --attach <id>` steers it). With the `coordinator`
151
151
  bundle (in `dev`), `/coordinate <goal>` has chi split the work into tasks, each
152
152
  in a child session in its own git worktree, check each report and ask you
153
- before each merge; `/children` shows the children and their state.
153
+ before each merge; `/children` shows the children and their state. With the
154
+ `claude-peer` bundle (in `dev`), chi sends a part of its work to a Claude Code
155
+ session you pair it with, to validate or as context, and Claude answers back
156
+ into chi (`/claude`).
154
157
 
155
158
  `chi send` is the other half: the text goes in as your message, the same as
156
159
  typing it in the attached terminal or the web composer, and a turn runs. Piped
data/bin/chi CHANGED
@@ -386,8 +386,9 @@ begin
386
386
  scratch: scratch
387
387
  ).run
388
388
  # `chi -p X </dev/null`: the turn failed (nothing was sent again); -p
389
- # --non-interactive: the turn ended with no answer, or Ctrl-C cancelled it.
390
- exit 1 if %i[turn_failed empty_answer].include?(ended)
389
+ # --non-interactive: the turn ended with no answer or at the iteration
390
+ # limit, or Ctrl-C cancelled it.
391
+ exit 1 if %i[turn_failed empty_answer iteration_limit].include?(ended)
391
392
  exit Samagotchi::CLI::Exit::INTERRUPTED if ended == :interrupted
392
393
  rescue Samagotchi::TerminalUI::SessionBusy, Samagotchi::TerminalUI::SessionNotFound, Samagotchi::ModelProfile::MissingModel => e
393
394
  warn "Error: #{e.message}"
data/docs/architecture.md CHANGED
@@ -326,6 +326,9 @@ The page's frequent reads of `GET /api/sessions/:id` stay light too:
326
326
  turn's answer). A plain `?tail=1` (a tab opened before this) gets the whole timing.
327
327
  The page binds an answer's `display_refs` to its bubble (`answer_refs.js`: on a reload's render and on this
328
328
  read's re-render) and pairs them with the bubble's links and text before the `path:line` scan.
329
+ A plugin tool's row refs (`refs`, `RowRefs`) ride its tool events and, on a reload, its part (saved as
330
+ `tool_refs`); `turn_view.js` binds them to the row's title (`refs.js bindRowRefs`: the first on the title,
331
+ the others in its menu).
329
332
  - `?timing=1`: the whole timing (`analytics.json` merged with the worker's `/state`) and
330
333
  `turn_count`, when the page's merge came up short.
331
334
  - `?cards=1`: the cards, from `/tail`; the session file is only stat'ed for the 404.
data/docs/cli.md CHANGED
@@ -250,7 +250,10 @@ Notes:
250
250
  retry lines and hooks' notices as they come, the after_turn hooks' ones
251
251
  (source-links' `sources:`) after the answer; and the error of a failed turn,
252
252
  then `chi: the session is kept with your prompt; continue it with: chi
253
- --resume ID` (exit 1). Ctrl-C saves the session with the prompt and a
253
+ --resume ID` (exit 1). A turn the iteration limit (`turn.max_iterations`)
254
+ cut short prints what the model wrote so far, then `chi: the iteration limit
255
+ (N) was reached before the turn finished; continue it with: chi --resume ID`
256
+ on stderr (exit 1). Ctrl-C saves the session with the prompt and a
254
257
  cancel note and exits 130.
255
258
  Nobody can answer there: an approval is denied, and a question the model
256
259
  asks (`ask_user_question`) is dismissed at once (one line on stderr), never
@@ -689,14 +692,23 @@ the step's reasoning), the running tool with what it does, and the last
689
692
  three calls. A plugin's or hook's notice and a plugin's nudge ("check-in
690
693
  nudged the model") show ahead of those calls for about 4 s, as their rows
691
694
  wait in the closed block. A card that needs you (a question, an approval, check-in)
692
- sits in the stage too. The chip under it, `N steps · M tool calls` with one
693
- tick per call, opens the turn view's block in place (newest step first
695
+ sits in the stage too. While a question or approval waits, the stage shows
696
+ the whole text of the reply that asked it (in place of its newest
697
+ sentence) above the card, until you answer. Once answered, the model's
698
+ question or its own approval keeps that reply (a delegate's relayed
699
+ approval, a hook's question and the step limit don't): two muted lines
700
+ under its `question → answer` line, the whole text when you open the card,
701
+ in both views and after a reload. The chip at the stage's bottom edge, `N steps · M tool
702
+ calls` with one tick per call, opens the turn view's block right above it (newest step first
694
703
  while it runs; a tick opens its step). `▾` folds the stage to its status
695
- row, remembered in this browser. When the turn ends the answer shows in
704
+ row, remembered in this browser. The stage keeps its height while a turn
705
+ runs: it grows, with a short ease, when a card or the answer needs room and gives the room back
706
+ only when the turn moves into the history. When the turn ends the answer shows in
696
707
  the stage, and the whole turn moves up into the history once you are not
697
708
  using the stage (the pointer over it, a touch or scroll in the last 4 s,
698
- keyboard focus or a selection keep it) for 1.5 s; sending the next message
699
- moves it at once. The history is never scrolled while a turn runs, and only
709
+ keyboard focus or a selection keep it) for 1.5 s: the stage slides away
710
+ under the composer, then the turn appears in the history. Sending the next
711
+ message moves it at once. The history is never scrolled while a turn runs, and only
700
712
  follows the hand-off if you were at its end. The `/` command list opens
701
713
  over the stage's lower edge.
702
714
 
@@ -714,9 +726,14 @@ again). When a step ends its thinking closes to a plain `thinking` line
714
726
  (unless you opened it); a step that only thought shows that line alone. The
715
727
  live narration is a box of about three lines that fills sentence by
716
728
  sentence (a sentence shows once it is complete) and scrolls to the newest
717
- when full. When the turn ends the block collapses to its summary (`3 steps
718
- · 8 tool calls · execute ×7 · read ×1`) and the answer expands from the
719
- box into a normal bubble under it (Markdown, annotate). A plain answer
729
+ when full. When the turn ends the block shows its outline when any of its steps said
730
+ something: it stays open on one line per step that wrote text (that text's
731
+ first line, so a mid-turn note reads without a click), and each run of steps
732
+ that only called tools or thought sits behind one `· N steps` line that
733
+ shows them; old turns open the same way after a reload. A turn whose steps
734
+ said nothing collapses the block to its summary (`3 steps
735
+ · 8 tool calls · execute ×7 · read ×1`). Either way the answer expands from
736
+ the box into a normal bubble under it (Markdown, annotate). A plain answer
720
737
  without tools ends exactly as it does today. Rows the code collapses stay
721
738
  as you toggled them. A reloaded session shows the same block
722
739
  from the saved messages (each step's thinking, narration, tool parameters
@@ -521,9 +521,10 @@ What each backend gets:
521
521
 
522
522
  On chat hosts: llama.cpp honours both off switches but ignores the effort (when its `/props` says
523
523
  `chat_template_caps.supports_reasoning_effort: false`, chi says so once per session and host, from the `/props`
524
- answer the turn already fetched for the window); Splash takes `reasoning_effort` (off only
525
- through `none`) and scales with it; OpenRouter translates `reasoning_effort` per model (some can't turn thinking off:
526
- Qwen3-30B-A3B thinks anyway, gpt-oss refuses).
524
+ answer the turn already fetched for the window); Splash honours both off switches and passes the effort to the
525
+ model's template, which decides: Qwen3.6's ignores it (`low`, `medium` and `high` think the same), and the server's
526
+ `--default-reasoning-effort` decides what `default` means there; OpenRouter translates `reasoning_effort` per model
527
+ (some can't turn thinking off: Qwen3-30B-A3B thinks anyway, gpt-oss refuses).
527
528
 
528
529
  When the model thinks although the level is `off`, chi says so once per session and host
529
530
  (`thinking> warning: off wasn't honoured by … (N chars of thinking)`) and logs `thinking_not_honoured` each time.
@@ -558,26 +559,39 @@ hosts:
558
559
  url: https://api.deepseek.com/v1
559
560
  api: openai
560
561
  api_key_env: DEEPSEEK_API_KEY
561
- reasoning_history: all # its API wants every earlier reply's reasoning when tools are sent
562
+ reasoning_history: all # with tools its API wants every earlier reply's reasoning: under turn, turn 2 fails
562
563
  models:
563
564
  some-model:
564
565
  reasoning_history: drop
565
566
  ```
566
567
 
567
- - `turn` (the default) sends the running turn's reasoning: each reply since the last user message carries its
568
- reasoning as `reasoning_content`, which is what Qwen's, Gemma 4's and DeepSeek's chat templates and the hosted APIs
569
- (OpenRouter takes it for DeepSeek, Qwen and other text-reasoning models) expect. The running turn starts at the
570
- last message you sent (a prompt, or a steer: as the chat template counts it) and never before the turn's own start,
571
- so a turn that goes on by itself (a reminder, a context wake, a delegate report's wake) sends nothing of the one
572
- before it, an interrupted reply's partial thought included. A **step-limit Continue** is the same turn going on: it
573
- keeps the reasoning of the run it continues (back to the first run of a chain of Continues), so its first request
574
- matches the last one before the limit and the server's prompt cache stays warm. Continue's text, or a `!cmd` run
575
- while the step-limit question stands, is a user message: the reasoning before it goes, as for any steer, and that
576
- Continue reads the conversation again once (a re-prefill on a local server). After a `/model` change while the
577
- question stands the Continue starts afresh (no other model's reasoning is sent).
568
+ - `turn` (the default) sends the running turn's reasoning: each reply since the turn's start carries its reasoning
569
+ as `reasoning_content` (OpenRouter takes it for DeepSeek, Qwen and other text-reasoning models). A steer, a plugin's
570
+ nudge (check-in, agents-md) or a Continue's text merged into the turn doesn't reset it, as on a native host: the
571
+ request after it is the one before plus the steer, so the server's prompt cache holds. A turn that goes on by itself
572
+ (a reminder, a context wake, a delegate report's wake) sends nothing of the one before it, an interrupted reply's
573
+ partial thought included. A **step-limit Continue** is the same turn going on: it keeps the reasoning of the run it
574
+ continues (back to the first run of a chain of Continues), so its first request matches the last one before the
575
+ limit and the server's prompt cache stays warm; Continue's text keeps it too. A `!cmd` run while the step-limit
576
+ question stands is a user message added after the run: the reasoning before it goes, and that Continue reads the
577
+ conversation again once (a re-prefill on a local server). After a `/model` change while the question stands the
578
+ Continue starts afresh (no other model's reasoning is sent).
579
+ - **A stock Qwen template (Splash, vLLM, SGLang; llama.cpp with Qwen's own template)** renders an earlier reply's
580
+ reasoning only after the last user message: a steer hides the reasoning before it whatever chi sends, and the
581
+ prompt changes there. `sampling: {chat_template_kwargs: {preserve_thinking: true}}` on the host renders every
582
+ reasoning chi sends (a reply sent without any renders an empty think block; adding it restarts the prompt cache
583
+ once). What it costs by level, with it on: under `turn` a steer no longer re-reads the turn, but each new turn still
584
+ reads the conversation again from the previous turn's first reply (that turn's reasoning leaves the request); under
585
+ `all` every request extends the one before (append-only), at the cost of a prompt that keeps the reasoning of every
586
+ turn. Without it (the template's default) both re-read from the reply before each user message. A strict server
587
+ that rejects unknown fields ("Unrecognized request argument supplied: chat_template_kwargs": OpenAI proper, some
588
+ gateways) fails every request with it, so set it only on a host you know takes it.
589
+ - A **direct DeepSeek API** host (`api.deepseek.com`) with tools wants the reasoning of every earlier reply, earlier
590
+ turns included, and answers HTTP 400 without it: under `turn` every turn after one that thought fails. Set
591
+ `reasoning_history: all` there (the example above). DeepSeek through OpenRouter takes `turn`.
578
592
  - `drop` sends none (what chi did before this setting).
579
593
  - `all` sends every reply's reasoning, earlier turns too. It sends only the field: a local template that leaves out
580
- earlier turns' reasoning (Qwen's) needs `sampling: {chat_template_kwargs: {preserve_thinking: true}}` as well.
594
+ earlier turns' reasoning (Qwen's) needs `preserve_thinking` as well (above).
581
595
  - Order, first set wins: the session's own level, `SAMAGOTCHI_REASONING_HISTORY` (the whole process; for A/B runs),
582
596
  the `models:` entry (found the way `profile:` is), the `hosts:` entry, then `turn`. Anything else than a level warns
583
597
  once and counts as unset. A host's level reaches a worker when it starts, as its `sampling:` does.
@@ -597,9 +611,8 @@ models:
597
611
  | to or from `all`, once an earlier reply has thinking | every earlier reply gains or loses its reasoning: a server that renders it (hosted APIs, llama.cpp's default template) reads the conversation again from the first earlier thought |
598
612
  | nothing thought yet, the same level, or a native host | none |
599
613
 
600
- - The native loop (`/completion`) ignores it: it always keeps the running turn's reasoning, a mid-turn steer included
601
- (the chat loop starts again after a steer), and a step-limit Continue keeps the thoughts of the run it continues,
602
- a mid-turn steer's earlier ones included.
614
+ - The native loop (`/completion`) ignores it: it always keeps the running turn's reasoning, a mid-turn steer included,
615
+ and a step-limit Continue keeps the thoughts of the run it continues, a mid-turn steer's earlier ones included.
603
616
  - When a host answers a request that sent reasoning back with an HTTP 400 about reasoning, chi asks again without it,
604
617
  sends none to that model from then on (until the worker restarts) and says so once. If the request without it is
605
618
  refused too, the thinking fields get the same fallback ("Thinking") and the reasoning goes back on the next step.
@@ -1337,7 +1350,7 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
1337
1350
  | `memory.index_warn_tokens` | `2500` | yes | The tokens one scope's memory index (project or system; sent with every prompt) may hold before a `memory_write`, `write` or `edit` that takes it over gets a note asking the model to tighten long index descriptions; `0`: no note. Estimated as characters / `context.chars_per_token`. `memory:` is its own section, not the `memories:` preload list. See [Memory](memory.md). |
1338
1351
  | `context.status_thresholds` | `20,40,60,80` | yes | Percentages that put the estimate into a bucket; the rise into a guidance bucket (from the second up) leaves the model its own `[CONTEXT: …]` line, and the bucket decides the guidance. |
1339
1352
  | `context.every_seconds` | `300` | | How often an attached context source's command runs when it has no `--every` (seconds, at least 30). See [Attached context](context.md). |
1340
- | `context.wake` | `true` | yes | A source whose update says `wake: true` may start a turn in a live, idle session (one per source per 10 minutes, within `session.max_wakes`); `false`: updates wait as notes for the next turn. See [Waking](context.md#waking). |
1353
+ | `context.wake` | `true` | yes | A source whose update says `wake: true` may start a turn in a live, idle session (one per source per 10 minutes, within `session.max_wakes`); so may a session's pushed source's first push. `false`: updates wait as notes for the next turn. See [Waking](context.md#waking). |
1341
1354
  | `thinking.turn_preamble` | `true` | yes | Ask a `qwen36` model to open its thinking with a short `TURN:` line (the step label). |
1342
1355
  | `reasoning_history` | `turn` | | Which of its reasoning a chat host's model gets back: `drop`, `turn`, `all`; environment only (`SAMAGOTCHI_REASONING_HISTORY`), below a session's own level (`/reasoning-history`), above the `models:`/`hosts:` entries. See "Reasoning history". |
1343
1356
  | `thinking.level` | `default` | `--thinking` | `off`, `low`, `medium`, `high` or `default` for every model; the flag and env outrank the `models:`/`hosts:` entries, the file's value doesn't; a session's own level outranks them all, and `--thinking` sets it on a session run (on `chi web` it sets this). See "Thinking". |
data/docs/context.md CHANGED
@@ -123,6 +123,11 @@ chi context add clip --push --project
123
123
  pbpaste | chi context push clip --project
124
124
  ```
125
125
 
126
+ A Claude Code session can be a pushed source too: the
127
+ [claude-peer bundle](plugins.md#the-claude-peer-bundle)'s `pair` script attaches
128
+ `claude-inbox` (Claude's inbox socket as the hint) and `claude-review`, where
129
+ Claude pushes its answers as JSON with `wake: true`.
130
+
126
131
  ### A stream: chat, a log, a feed
127
132
 
128
133
  A source's text is a snapshot, not a log: each run replaces it. For
@@ -233,6 +238,10 @@ you're away from the terminal. All of these must hold:
233
238
  - the source asked (`"wake": true`; a plain-text source never does), in
234
239
  any change since the session last heard of it: a later change that doesn't
235
240
  ask doesn't take the wake back;
241
+ - it's a change, not the source's first text, except for a session's pushed
242
+ source (`chi context add NAME --push`, attached empty): its first push can
243
+ wake too. A command or URL source's first fetch runs as you attach it, and a
244
+ project source's first text may predate the session, so neither wakes;
236
245
  - `context.wake` is on (default `true`);
237
246
  - the session's worker is up and idle: no turn running, nothing queued, no
238
247
  question or continue offer waiting, not just started;
@@ -242,15 +251,16 @@ you're away from the terminal. All of these must hold:
242
251
  - the source hasn't woken the session in the last 10 minutes.
243
252
 
244
253
  Otherwise the change arrives as a note, as above. The turn starts with the
245
- update note, whose last line becomes:
254
+ update note, or, for a session's pushed source, its attached note, whose last
255
+ line becomes:
246
256
 
247
257
  > chi started this turn because the source changed; your user didn't write anything. Tell your user in a sentence or two what changed and what it may mean for their work. Don't run commands, change files or reply on the source for it: wait for your user to ask.
248
258
 
249
259
  Every UI labels the turn `context <name> changed`, and the web's
250
260
  notifications count it as a finished turn however short.
251
261
 
252
- A wake turn that fails goes back to before it and keeps the update as a
253
- plain note; the model reads `[SYSTEM: the previous turn failed before any
262
+ A wake turn that fails goes back to before it and keeps its note as a
263
+ plain one; the model reads `[SYSTEM: the previous turn failed before any
254
264
  answer: … The wake turn for the change in attached context <name> was not
255
265
  answered; chi starts no other wake turn until the user writes.]`.
256
266
 
@@ -346,6 +356,14 @@ show the answer as it is.
346
356
  at the PR's head commit (a removed one: at its base), which has every
347
357
  line.
348
358
  - Code blocks, markdown links and URLs are left alone.
359
+ - **The ref menu**: each linked reference is also a
360
+ [file ref](hooks.md#refs-on-the-answer-display-only) on the answer: the
361
+ file in this checkout (the repository's root + the PR's path), so its
362
+ menu has the editor, show-in-turn and copy targets, plus **PR diff** (or
363
+ **File at head**, **File at base** for a removed file), the link's URL.
364
+ Only for a PR of this checkout's repository (one of its git remotes
365
+ names it on github.com): another project's PR keeps its links only.
366
+ Bundle 0.5.0, chi 0.61.0.
349
367
  - The PR's files are read with `gh api` in the background when a turn
350
368
  starts (and when a PR is attached while it runs), at most once a minute,
351
369
  and read again only when the PR's head moved; the answer is linked from
data/docs/guardrails.md CHANGED
@@ -35,7 +35,8 @@ Approve tool call?
35
35
 
36
36
  In a terminal, answer with a number, the exact label, `y` (Allow once) or `n`
37
37
  (Deny); add `; reason` to tell the model why (`n; open a PR instead`). An empty
38
- answer, Ctrl-C, the web's Deny button or a cancelled turn deny it. The prompt stays open during turns: when the question comes up it turns
38
+ answer, Ctrl-C, the web's Deny button or a cancelled turn deny it (on the web card, Enter in the reason box or
39
+ on an option submits, never denies). The prompt stays open during turns: when the question comes up it turns
39
40
  into a yellow `? `, with the call and the options listed under it; only a line
40
41
  submitted there answers it (never one typed before), and what you had typed comes back
41
42
  once it closes. On a short terminal the list shrinks (the hint row, then the `in`/`why`
data/docs/hooks.md CHANGED
@@ -301,9 +301,12 @@ end
301
301
  keywords only (String or Symbol keys inside `target` and `actions`).
302
302
  - `match`: the text in the answer the ref stands for (at most 500 characters).
303
303
  - `kind`: a short lowercase word. `"file"` takes `target: {path:, line:, end_line:}`
304
- (the lines optional); a relative path is resolved against the session's
305
- folder, an absolute one is kept as given. Any other kind (`"issue"`,
306
- `"commit"`, …) takes `target: {id:}`.
304
+ (the lines optional); the path is absolute or relative to the session's
305
+ folder, and must be **under** it (its own spelling or its real path:
306
+ `/tmp/x` or `/private/tmp/x`), as a plugin tool row's file refs
307
+ ([plugins.md](plugins.md)): a file elsewhere is refused. It is kept under
308
+ the folder's real path. Any other kind (`"issue"`, `"commit"`, …) takes
309
+ `target: {id:}`.
307
310
  - `label`: what a UI shows for it (optional, at most 200 characters).
308
311
  - `actions`: links a UI offers on top of the kind's own, each
309
312
  `{id:, label:, url:}`; http(s) URLs only, without userinfo (`user:pass@`),
@@ -636,6 +639,7 @@ bundles:
636
639
  pattern: '\bGH-(\d+)\b' # full form: a regex
637
640
  url: 'https://github.com/org/repo/issues/{match}'
638
641
  case_insensitive: false # optional, default false
642
+ kind: issue # optional: the refs' kind, default issue
639
643
  max: 10 # optional: refs per line, default 10
640
644
  note: false # optional: no sources line, default true
641
645
  ```
@@ -654,6 +658,13 @@ text ([`fallback_for: :display`](#what-a-hook-can-do-the-runtime)); it shows
654
658
  when it names one the answer doesn't link (a ref only in code, two sources on
655
659
  one ref) and whenever markdown is off. Bundle 0.4.0 needs chi 0.35.0 for that.
656
660
 
661
+ Each ref the answer links is also a [ref on the
662
+ answer](#refs-on-the-answer-display-only): kind `issue` (or the source's
663
+ `kind:`, a short lowercase word other than `file`), target `{id: "JIRA-123"}`,
664
+ label `JIRA JIRA-123`, and one action, **Open in JIRA** (the source's name),
665
+ its URL. The web's ref menu on the link shows it. A ref whose URL isn't
666
+ http(s) stays a markdown link only. Bundle 0.5.0 needs chi 0.61.0 for that.
667
+
657
668
  The `prefix:` form compiles to `\b<prefix>-(\d+)\b` and the URL is
658
669
  `base_url` + the full ref text (`JIRA-123`). The `pattern:` form takes a
659
670
  regex and a `url:` template with placeholders (below). `case_insensitive:
@@ -196,14 +196,15 @@ The differences from the native loop:
196
196
  - A model turn's saved reasoning (`thinking:`) goes back as `reasoning_content` per the turn's
197
197
  `TurnSettings#reasoning_history` (`ReasoningHistory.resolve`, the session's own level first:
198
198
  `Session#reasoning_history`, `/reasoning-history`): `all` on every model message, `turn` past
199
- `ChatLoop#reasoning_boundary`: the later of the last query (a `role: "user"` entry: a prompt, a steer, merged input,
200
- the chat template's `last_query_index`; or a `turn_start` entry) and the Run's start (`Run`'s `@start`, the
201
- conversation length at `#complete`), so a continue turn (reminder, context wake, delegate report wake) replays
202
- nothing of the run before. A step-limit Continue's Run starts where the run it resumes did: `#complete`'s
203
- `reasoning_since:` (the Engine's `resume_from`, checked against the messages' length in `#resumed_run_start`) is
204
- the Run's `@start`. `Run#prompt_chars` counts it (`#reasoning_chars`). The native loop differs on a steer:
205
- `KernelLoop#inject_pending_input!` doesn't re-sanitize, so native keeps the turn's reasoning across a mid-turn
206
- steer, where the chat loop starts again (as the template does).
199
+ `ChatLoop#reasoning_boundary`: the Run's `@reasoning_start`, fixed once per Run by `ReasoningHistory.run_start`
200
+ (the rule `KernelLoop#start_turn` uses too) from the Run's start (the conversation length at `#complete`), so a
201
+ continue turn (reminder, context wake, delegate report wake) replays nothing of the run before. Entries the Run
202
+ merges (a person's line, a plugin's steer, a Continue's text) don't move it, as natively (nothing re-sanitizes
203
+ within a run): the request after a steer extends the one before. A step-limit Continue's Run starts where the run
204
+ it resumes did: `#complete`'s `reasoning_since:` (the Engine's `resume_from`, checked against the messages' length
205
+ in `#resumed_run_start`), past a user entry added after that run's last reply (a `!cmd`'s output). Without
206
+ `since:` (`#reasoning_chars` from outside a Run) the boundary is the last query (a `role: "user"` or `turn_start`
207
+ entry). `Run#prompt_chars` counts it (`#reasoning_chars`).
207
208
  - A 400 about reasoning (`BadRequest#reasoning_refused?`) for a request that sent reasoning back asks the step again
208
209
  without it; when that goes through, `#reasoning_history_refused!` marks the model (worker lifetime) and
209
210
  `:reasoning_history_refused` goes out. When it is refused too, the thinking-fields fallback (`#thinking_refused!`,
@@ -240,13 +241,22 @@ One iteration with tool calls:
240
241
  - Runs the gate first, so `:tool_call_started` shows the call that will run (a before_tool_call hook may
241
242
  replace it; known-names corrects a misspelled name this way, and the model gets one line saying what ran).
242
243
  - Emits `:tool_call_started` {iteration, call_count, call_index, tool, call, params, label (plugin tools),
243
- title, called_as, view, ref}. The row fields (title, called_as, view, ref) come from `ToolRowFields.for`, which the
244
+ title, called_as, view, ref, refs}. The row fields (title, called_as, view, ref) come from `ToolRowFields.for`, which the
244
245
  reload (`Web::MessageParts#tool_part`) calls too. `ToolRowFields::ACTIVITY_KEYS` (title, called_as) travel
245
- inside `activity` on the completed event; `EVENT_KEYS` (view, ref) go top-level on both events.
246
+ inside `activity` on the completed event; `EVENT_KEYS` (view, ref, refs) go top-level on both events.
246
247
  `Bridge::TurnAccumulator` (snapshot and replay) and the web (`activity.js` `ACTIVITY_FIELDS` /
247
248
  `EVENT_FIELDS`) copy them by these lists, so a new row field is added there. `ref` is `FileRef.for` (a
248
249
  read/write/edit's file against the cwd, `{kind: "file", path, line?, end_line?}`); the web opens it in the editor
249
250
  that `Web::Editor` (web.editor) names, for a local viewer only (`App#viewer_capabilities`).
251
+ `refs` is a plugin tool's row refs: its registry entry's `refs` (chi.tool's `refs:` lambda, `Api.per_call`), run
252
+ once per call by `RowRefs.for` (never raises: a raise or a non-list gives none, `tool_refs_failed`; each entry a
253
+ `Ref`, a bad one dropped, `tool_refs_rejected`; a relative file resolved against the session's folder
254
+ (`KernelLoop#session_cwd`, else the process's), one outside it (its spelling or its real path) dropped, a kept one
255
+ stored under the real path; at most `RowRefs::MAX_REFS` and `MAX_BYTES`). `ToolRowFields.for` can't build it (no plugins on a reload), so the run carries it
256
+ as `refs:`, saved with the result as `tool_refs` (`ToolResponse::SAVED_KEYS`, never sent), and
257
+ `MessageParts#tool_part` reads it back through `RowRefs.for_display` (checked again). The page binds them to the
258
+ row's title (`refs.js bindRowRefs`, from `turn_view.js` live and on a reload): the first ref on it, the rest in its
259
+ menu.
250
260
  - Settles an ask verdict after `:tool_call_started`, so a UI shows the tool line and then the approval.
251
261
  - Dispatches through `KernelLoop#dispatch_tool_call`, or writes the denial text. Unknown tools are answered in
252
262
  `KernelLoop#dispatch` ("Error: no such tool ...", never repeating the wrong name).
@@ -319,7 +329,10 @@ end
319
329
  boundary) copies it, with the lines that boundary merges, into `Session#turn_checkpoint` (`TurnCheckpoint`):
320
330
  every save while the turn runs writes that copy with an `interrupted_turn` mark instead of the session's
321
331
  messages, written at the first boundary and then at most every 10 s, so a killed worker loses at most that much
322
- of its turn. `session.messages` itself is untouched until the turn ends.
332
+ of its turn. `session.messages` itself is untouched until the turn ends. When the checkpoint writes, the worker
333
+ also calls `Engine#checkpoint_metrics` (`SessionMetrics#persist(running_turn: true)`): `analytics.json` gets the
334
+ running turn's record so far as a killed turn's (canceled, `worker_stopped`), which the next worker loads; the
335
+ turn's end writes over it.
323
336
 
324
337
  ### GenerationPhase
325
338
 
@@ -127,7 +127,12 @@ is prefilled once. The native and the chat loop use the same plan.
127
127
  a per-session value in the stable part. A guard spec checks that model, location and session are the last
128
128
  sections (`spec/system_prompt_model_spec.rb`).
129
129
  2. **History is append-only.** Never rewrite an earlier message. Add a note at the tail instead. If a model's
130
- spec forces a rewrite (e.g. stripping previous-turn thinking), accept the re-prefill and note it. A worker's
130
+ spec forces a rewrite (e.g. stripping previous-turn thinking), accept the re-prefill and note it. Within a
131
+ turn both loops keep the turn's replies as sent across a mid-turn steer, a plugin's nudge and a Continue's text
132
+ (`ReasoningHistory.run_start` fixes where the run's reasoning starts), so the request after a steer is the one
133
+ before plus the steer; a new turn drops the previous turn's reasoning under `turn` (one re-prefill from its
134
+ first reply, on a server that renders it; a stock Qwen template without `preserve_thinking` re-renders from the
135
+ reply before every user message whatever chi sends). A worker's
131
136
  mid-turn checkpoint (`TurnCheckpoint`) is append-only too (the history plus what the loop appended) and
132
137
  never changes the request bytes.
133
138
  3. **Deterministic order.** Tools, memory lists and anything else that's rendered from a hash or a set needs a
data/docs/memory.md CHANGED
@@ -270,7 +270,7 @@ profiles, meta bundles that install a set of them:
270
270
  | profile | bundles |
271
271
  |---|---|
272
272
  | `core` | loop-guard, check-in, guardrails, slow-commands, agents-md: the recommended safety set |
273
- | `dev` | known-names, mcp, btw, skills, source-links, github-pr, coordinator |
273
+ | `dev` | known-names, mcp, btw, skills, source-links, github-pr, coordinator, claude-peer |
274
274
 
275
275
  `chi bootstrap` installs `core` and, on a terminal, offers `dev`. An existing
276
276
  install gets neither by itself: `chi bundle install core` (or `dev`).