samagotchi 0.64.0 → 0.66.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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +88 -1
  3. data/README.md +2 -1
  4. data/bin/chi +51 -2
  5. data/docs/architecture.md +20 -3
  6. data/docs/cli.md +36 -2
  7. data/docs/configuration.md +177 -8
  8. data/docs/context.md +4 -1
  9. data/docs/internals/engine.md +19 -4
  10. data/docs/internals/prompt-caching.md +10 -2
  11. data/docs/memory.md +1 -1
  12. data/docs/plugins.md +163 -2
  13. data/docs/releasing.md +1 -1
  14. data/docs/sessions.md +13 -2
  15. data/docs/sub-agent.md +3 -1
  16. data/lib/samagotchi/bridge.rb +11 -1
  17. data/lib/samagotchi/bundles/claude-peer/manifest.yml +2 -2
  18. data/lib/samagotchi/bundles/claude-peer/plugin.rb +18 -1
  19. data/lib/samagotchi/bundles/codex-peer/manifest.yml +21 -0
  20. data/lib/samagotchi/bundles/codex-peer/plugin.rb +464 -0
  21. data/lib/samagotchi/bundles/codex-peer/scripts/chi-peer-SKILL.md +131 -0
  22. data/lib/samagotchi/bundles/codex-peer/scripts/pair +92 -0
  23. data/lib/samagotchi/bundles/codex-peer/skill_codex_peer.md +68 -0
  24. data/lib/samagotchi/bundles/coordinator/manifest.yml +5 -5
  25. data/lib/samagotchi/bundles/coordinator/plugin.rb +24 -2
  26. data/lib/samagotchi/bundles/dev/manifest.yml +2 -1
  27. data/lib/samagotchi/bundles/system/config_modification_protocol.md +2 -2
  28. data/lib/samagotchi/bundles/system/delegated.md +2 -1
  29. data/lib/samagotchi/bundles/system/manifest.yml +4 -4
  30. data/lib/samagotchi/bundles/system/memory_guide.md +1 -1
  31. data/lib/samagotchi/cache_confirm.rb +9 -6
  32. data/lib/samagotchi/client.rb +4 -2
  33. data/lib/samagotchi/commands/registry.rb +25 -8
  34. data/lib/samagotchi/config.rb +13 -5
  35. data/lib/samagotchi/engine.rb +151 -11
  36. data/lib/samagotchi/generation_phase.rb +17 -0
  37. data/lib/samagotchi/host_config.rb +27 -6
  38. data/lib/samagotchi/host_registry.rb +34 -11
  39. data/lib/samagotchi/idle_client.rb +62 -25
  40. data/lib/samagotchi/idle_recap.rb +5 -2
  41. data/lib/samagotchi/idle_target.rb +30 -7
  42. data/lib/samagotchi/kernel_loop.rb +9 -1
  43. data/lib/samagotchi/llm/adapters.rb +41 -0
  44. data/lib/samagotchi/llm/anthropic_assembly.rb +212 -0
  45. data/lib/samagotchi/llm/anthropic_messages.rb +416 -0
  46. data/lib/samagotchi/llm/anthropic_wire.rb +238 -0
  47. data/lib/samagotchi/llm/api_key.rb +26 -9
  48. data/lib/samagotchi/llm/chat_loop.rb +113 -14
  49. data/lib/samagotchi/llm/dialect.rb +57 -0
  50. data/lib/samagotchi/llm/errors.rb +81 -12
  51. data/lib/samagotchi/llm/model_listing.rb +111 -0
  52. data/lib/samagotchi/llm/model_result.rb +9 -2
  53. data/lib/samagotchi/llm/openai_chat.rb +46 -93
  54. data/lib/samagotchi/model_profile.rb +1 -1
  55. data/lib/samagotchi/plugin/api.rb +80 -7
  56. data/lib/samagotchi/plugin/context.rb +22 -1
  57. data/lib/samagotchi/plugin/loader.rb +3 -2
  58. data/lib/samagotchi/reasoning_history.rb +14 -4
  59. data/lib/samagotchi/recap_setup.rb +2 -1
  60. data/lib/samagotchi/self_report.rb +18 -28
  61. data/lib/samagotchi/send_command.rb +23 -7
  62. data/lib/samagotchi/session.rb +21 -3
  63. data/lib/samagotchi/session_commands.rb +16 -1
  64. data/lib/samagotchi/session_manager.rb +4 -1
  65. data/lib/samagotchi/session_metrics.rb +6 -11
  66. data/lib/samagotchi/session_setup.rb +14 -7
  67. data/lib/samagotchi/steer_cut.rb +4 -0
  68. data/lib/samagotchi/system_prompt.rb +16 -1
  69. data/lib/samagotchi/terminal_ui/attach_launcher.rb +19 -9
  70. data/lib/samagotchi/terminal_ui/attached_loop.rb +8 -2
  71. data/lib/samagotchi/terminal_ui/event_renderer.rb +2 -1
  72. data/lib/samagotchi/terminal_ui/formatting.rb +4 -3
  73. data/lib/samagotchi/terminal_ui/status_row.rb +24 -3
  74. data/lib/samagotchi/terminal_ui.rb +18 -2
  75. data/lib/samagotchi/tool_runner.rb +13 -1
  76. data/lib/samagotchi/toolkit.rb +246 -0
  77. data/lib/samagotchi/toolkit_command.rb +117 -0
  78. data/lib/samagotchi/tools/registry.rb +43 -6
  79. data/lib/samagotchi/turn_note.rb +22 -5
  80. data/lib/samagotchi/version.rb +1 -1
  81. data/lib/samagotchi/web/app.rb +14 -1
  82. data/lib/samagotchi/web/public/activity.js +3 -0
  83. data/lib/samagotchi/web/public/annotate_actions.js +109 -0
  84. data/lib/samagotchi/web/public/annotate_bar.js +84 -2
  85. data/lib/samagotchi/web/public/app.js +111 -20
  86. data/lib/samagotchi/web/public/commands.js +4 -2
  87. data/lib/samagotchi/web/public/delivery_line.js +228 -0
  88. data/lib/samagotchi/web/public/index.html +38 -0
  89. data/lib/samagotchi/web/public/info_bar.js +5 -3
  90. data/lib/samagotchi/web/public/live_turn.js +8 -0
  91. data/lib/samagotchi/web/public/session_state.js +3 -0
  92. data/lib/samagotchi/web/public/stage_model.js +27 -7
  93. data/lib/samagotchi/web/public/stage_view.js +32 -10
  94. data/lib/samagotchi/web/public/stream_handlers.js +21 -2
  95. data/lib/samagotchi/web/public/toolkit_chip.js +29 -0
  96. data/lib/samagotchi/web/public/turn_events.js +22 -8
  97. data/lib/samagotchi/web/public/turn_html.js +20 -4
  98. data/lib/samagotchi/web/public/turn_model.js +16 -9
  99. data/lib/samagotchi/web/public/turn_view.js +6 -4
  100. data/lib/samagotchi/worker.rb +2 -0
  101. metadata +17 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 61245b8998baef2bbb4c29cabc648314b4cc124cf9233099948cb7cf66dc890e
4
- data.tar.gz: ac4e8fa165b5cc59d683b1541a3758e03f60a1d14b7fd9cded087770fda162de
3
+ metadata.gz: 0f78cadf95c501357ce71b15b672dee0a4558016b995a1948532924168580f86
4
+ data.tar.gz: 583f9594eed0ed2febe41390e9c2e0dfbfbde2df72c92f6eab5d8d6a3cf8d776
5
5
  SHA512:
6
- metadata.gz: 717ca2421c97d4f93493891e4f695528a0b8495ab11a2b47c348b29c726004abb32e638ab21b3e6af62676acfc4cc31c45527bdb9dfe72a9a270b3f7bfda3e02
7
- data.tar.gz: 4916e876cf70f3b1b03e8c7554a127019e08e3b6f2e991305c8fe63ce1f6cf399155c43167ff36382c09eb2774792a4783ccbe39109702df6bd4efbe67a1ff53
6
+ metadata.gz: 312f72d24bbfc6543d0899f164e69798d068916cef8728242d54893a783c3da7efceb2cbbfeac0337790596984af221e78f5c98541a910945266a2a1122e6ef7
7
+ data.tar.gz: 347d52314752545c6fbc37e7b1a7186faadb6fa2170f91e00b35b8101c76ed9b0ee80b8b954ea927382583c97511dae2fd0a6eca28b694778657f1b654c81226
data/CHANGELOG.md CHANGED
@@ -8,6 +8,91 @@ and commands may change between minor versions. How releases are made:
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.66.0] - 2026-10-14
12
+
13
+ ### Added
14
+
15
+ - **Plugin annotation actions** (web): `chi.annotation(label, command:, template:, send:)` adds a pill to the
16
+ selection bar that prepares a command line of the plugin from the selected text (`{quote}`, `{text}`, `{source}`):
17
+ it fills the composer (an earlier draft stays under the quote), or with `send: true` sends at once. Up to 3 inline,
18
+ the rest behind `⋯`. See docs/plugins.md, `chi.annotation`.
19
+ - claude-peer 0.2.0: **Confirm with Claude** in the web's selection bar fills `/claude validate` with the quote;
20
+ `/claude validate|context` titles a quote by its first quoted line, not its `From …:` label.
21
+ - The codex-peer bundle (in the dev profile): claude-peer for an OpenAI Codex CLI session. Codex pairs (its
22
+ `chi-peer` skill, installed by `/codex install-skill`), the `codex_send` tool and `/codex validate|context|last`
23
+ send it a part of the work with `codex queue` (killed after 10 s, never a hang), and Codex's answer comes back as
24
+ the `codex-review` context; **Confirm with Codex** in the web's selection bar. Codex's sandbox needs chi's
25
+ context folder in `writable_roots` (the install card shows the line). See docs/plugins.md, "The codex-peer bundle".
26
+ - **Toolkits**: a session's toolkit names the tools its prompts declare. chi ships `full` (every built-in, the
27
+ default), `lean` (the file, shell, memory, task, question, `web_fetch` and `context_read` tools: ~1.4k tokens
28
+ fewer) and `child` (`lean` with `send_note`, without `memory_write`); config's `toolkit.kits` and bundles
29
+ (`chi.toolkit`) add their own. Pick one with `toolkit.default` / `toolkit.child`, `chi --toolkit NAME` (with
30
+ `--tool` / `--no-tool`, also on `chi send --new`) or `/toolkit NAME | add TOOL | remove TOOL`. It is saved in the
31
+ session when it is created and kept by continues and forks; a change applies at the next turn's start. A left-out
32
+ tool is named in one prompt line, and a call to it is refused before the guardrails. The web shows a session not
33
+ on `full` as a `tools` chip. See docs/configuration.md, "Toolkits".
34
+ - Plugins: `ctx.toolkit` and `ctx.use_toolkit(name)` (before the session's first reply). The coordinator bundle
35
+ (0.4.0) declares the `coordinator` toolkit (`lean` + `delegate`, `delegate_result`, `list_sessions`), and
36
+ `/coordinate` puts a fresh session on it.
37
+
38
+ ### Changed
39
+
40
+ - A command's name may be followed by any whitespace, a newline too: `/foo` on its own line with text under it
41
+ runs `/foo` with that text, in the terminals and the web (before, only a space did).
42
+ - Web: a command refused as busy mid-turn comes back into the composer (it was cleared when sent).
43
+ - A delegate child is told to report what should change (a wrong memory or skill, instructions that led it astray)
44
+ in its reply instead of writing memories (`delegated.md`).
45
+
46
+ ### Fixed
47
+
48
+ - `dialect: fireworks` hosts: the web's ctx meter no longer reads 0% while an answer streams (Fireworks' first stream
49
+ event carries all-zero usage; its counts come at the end).
50
+
51
+ **Still open:** the Claude 5.x "bound to a different conversation" 400 on Anthropic Messages hosts (see 0.65.0); its
52
+ recovery comes in a later release.
53
+
54
+ Update with `chi update` (the system bundle moves with the gem; claude-peer 0.2.0, coordinator 0.4.0, and the dev
55
+ profile 0.5.0 with the new codex-peer 0.1.0).
56
+
57
+ ## [0.65.0] - 2026-10-13
58
+
59
+ ### Added
60
+
61
+ - **Anthropic Messages hosts** (`api: anthropic`): streamed text and thinking, native tool use, images, usage with
62
+ cache reads and writes, thinking blocks replayed verbatim (also after `--resume`; `reasoning_history` defaults to
63
+ `all` there). A new host key `auth: bearer|x-api-key` (any host) picks the header the key goes in. Thinking levels
64
+ map to Messages' thinking (`off` disabled, `low`/`medium`/`high` adaptive at that effort, `default` nothing), with
65
+ fallbacks when a host refuses a field. Recaps, `ctx.ask_model` and broadcast triage work there too. See
66
+ docs/configuration.md, "Anthropic Messages hosts". **Known limit until the next release:** Claude 5.x models
67
+ (Opus, Sonnet and Haiku 5.5, Fable 5.1) may stop a turn with a 400 "… bound to a different conversation" after the
68
+ history changed under their thinking blocks; the next release adds the recovery.
69
+ - `dialect: fireworks` on an `api: anthropic` host, for Fireworks directly or behind a gateway: Bearer auth, no
70
+ thinking `display`, no `cache_control`, and `x-session-affinity` for cache routing. `chi self` names it.
71
+ - A provider's refusal (Messages `stop_reason: refusal`, or an empty `content_filter` stop) ends the turn with
72
+ `no answer: refused by the provider (category)` in every UI and isn't retried. A `content_filter` stop after some
73
+ text keeps the text, marked `[cut off by the provider's content filter]`.
74
+ - Web composer: a row above the text field shows the keys while idle and, while a turn runs, where a message sent now
75
+ goes in (after this thinking step, tool call or answer, with its clock), with `cut in now` and `queue for after the
76
+ turn` links. Alt+Enter (Option+Enter) queues. See docs/cli.md, "Typing during a turn".
77
+ - Terminal: while a turn runs, the status row ends in `· typing goes in at the next step (/cut, /queue)`.
78
+
79
+ ### Changed
80
+
81
+ - Web: a message queued for after the running turn sits below the running turn's card, not among its rows, also
82
+ after a reload.
83
+ - A chat host that refuses a sampling field (`temperature`, `top_p`, `top_k`) gets the step again without it instead
84
+ of the turn failing; on `api: openai` hosts such a 400 no longer turns reasoning off.
85
+ - A Splash or Fireworks model's vision comes from its model list instead of reading "unknown".
86
+
87
+ ### Fixed
88
+
89
+ - A server error without a numeric code is classified by its type: an invalid request fails at once instead of being
90
+ retried, an unknown model (404, `NOT_FOUND`) is no longer retried, a `timeout_error` is a 504.
91
+ - Anthropic's context-overflow 400s ("prompt is too long", "input length and `max_tokens` exceed context limit")
92
+ roll the turn back and report it as too long for the context window, also through a gateway on the OpenAI path.
93
+
94
+ Update with `chi update` (the system bundle moves with the gem).
95
+
11
96
  ## [0.64.0] - 2026-10-13
12
97
 
13
98
  ### Added
@@ -2794,7 +2879,9 @@ and long-lived sessions.
2794
2879
  - A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
2795
2880
  hotkey panel that send selected text or the clipboard to your sessions.
2796
2881
 
2797
- [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.64.0...HEAD
2882
+ [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.66.0...HEAD
2883
+ [0.66.0]: https://github.com/dm1try/samagotchi/compare/v0.65.0...v0.66.0
2884
+ [0.65.0]: https://github.com/dm1try/samagotchi/compare/v0.64.0...v0.65.0
2798
2885
  [0.64.0]: https://github.com/dm1try/samagotchi/compare/v0.63.0...v0.64.0
2799
2886
  [0.63.0]: https://github.com/dm1try/samagotchi/compare/v0.62.0...v0.63.0
2800
2887
  [0.62.0]: https://github.com/dm1try/samagotchi/compare/v0.61.0...v0.62.0
data/README.md CHANGED
@@ -153,7 +153,8 @@ in a child session in its own git worktree, check each report and ask you
153
153
  before each merge; `/children` shows the children and their state. With the
154
154
  `claude-peer` bundle (in `dev`), chi sends a part of its work to a Claude Code
155
155
  session you pair it with, to validate or as context, and Claude answers back
156
- into chi (`/claude`).
156
+ into chi (`/claude`); the `codex-peer` bundle (in `dev`) does the same with an
157
+ OpenAI Codex CLI session (`/codex`).
157
158
 
158
159
  `chi send` is the other half: the text goes in as your message, the same as
159
160
  typing it in the attached terminal or the web composer, and a turn runs. Piped
data/bin/chi CHANGED
@@ -82,7 +82,7 @@ ARGV.each do |arg|
82
82
  base = arg.split("=", 2).first
83
83
  # If dashed version is a known flag (registry or hardcoded), reject underscore variant
84
84
  dashed = base.tr("_", "-")
85
- known = %w[--prompt --resume --attach --shared --no-shared --model --thinking --memory --mute --append-system-prompt --llm-context --llm-context-apply --llm-context-budget --no-interrupt --non-interactive --verbose --no-default-input --port --open --new-token] + Samagotchi::Config.cli_entries.map(&:cli_flag)
85
+ known = %w[--prompt --resume --attach --shared --no-shared --model --thinking --toolkit --tool --no-tool --memory --mute --append-system-prompt --llm-context --llm-context-apply --llm-context-budget --no-interrupt --non-interactive --verbose --no-default-input --port --open --new-token] + Samagotchi::Config.cli_entries.map(&:cli_flag)
86
86
  if known.include?(dashed) || known.include?(base)
87
87
  warn "Unknown option: #{arg} (did you mean #{dashed}?)"
88
88
  exit 1
@@ -119,6 +119,12 @@ parser = OptionParser.new do |opts|
119
119
  opts.on("--thinking LEVEL", Samagotchi::Thinking::LEVELS.map(&:to_s), "This session's own thinking level: off|low|medium|high|default (saved in the session, " \
120
120
  "before the model's; default unsets it; /thinking changes it). With chi web: the default for the sessions " \
121
121
  "its workers run, below a session's own [env SAMAGOTCHI_THINKING_LEVEL]") { |l| options[:thinking] = l }
122
+ opts.on("--toolkit NAME", "This session's toolkit, the tools its prompts declare: full|lean|child|a config or bundle one " \
123
+ "(saved in the session; config toolkit.default; /toolkit changes it)") { |n| options[:toolkit] = n }
124
+ opts.on("--tool NAME", "Also declare this tool on top of the toolkit (repeatable; a comma list too)") { |t| (options[:tools] ||= []) << t }
125
+ # --no-tool is taken out of ARGV before parse! (OptionParser would read it
126
+ # as --tool's negation); this is its help line.
127
+ opts.separator " --no-tool NAME Leave this tool out of the toolkit (repeatable; a comma list too)"
122
128
  opts.on("--memory NAME", "Preload a memory entry into the system prompt (repeatable; a comma list too)") { |m| (options[:memories] ||= []) << m }
123
129
  opts.on("--append-system-prompt FILE", "Put FILE's text into the system prompt, after AGENTS.md (repeatable; saved in the session; " \
124
130
  "config append_system_prompt: is the baseline)") { |f| (options[:append_system_prompt] ||= []) << f }
@@ -171,6 +177,34 @@ parser = OptionParser.new do |opts|
171
177
  opts.on("--version", "Print chi's version") { puts "chi #{Samagotchi::VERSION}"; exit }
172
178
  opts.on("-h", "--help", "Show help") { puts opts; exit }
173
179
  end
180
+ # --no-tool NAME (or --no-tool=NAME), up to a "--". A flag that takes a
181
+ # value (-p, --model …: the parser's own list) keeps its next word, so
182
+ # `chi -p "--no-tool=x is broken"` is a prompt. A tool name never starts
183
+ # with "-": `--no-tool --` or `--no-tool --model` is a missing argument.
184
+ takes_value = parser.instance_variable_get(:@stack).flat_map do |list|
185
+ list.list.select { |switch| switch.is_a?(OptionParser::Switch::RequiredArgument) }.flat_map { |switch| switch.long + switch.short }
186
+ end
187
+ missing_no_tool = lambda do
188
+ warn "Error: missing argument: --no-tool (see chi --help)"
189
+ exit 1
190
+ end
191
+ index = 0
192
+ while index < ARGV.size && ARGV[index] != "--"
193
+ arg = ARGV[index]
194
+ if arg == "--no-tool"
195
+ value = ARGV[index + 1]
196
+ missing_no_tool.call if value.nil? || value.start_with?("-")
197
+ (options[:no_tools] ||= []) << ARGV.delete_at(index + 1)
198
+ ARGV.delete_at(index)
199
+ elsif arg.start_with?("--no-tool=")
200
+ value = arg.delete_prefix("--no-tool=")
201
+ missing_no_tool.call if value.empty? || value.start_with?("-")
202
+ options[:no_tools] = (options[:no_tools] || []) << value
203
+ ARGV.delete_at(index)
204
+ else
205
+ index += takes_value.include?(arg) ? 2 : 1
206
+ end
207
+ end
174
208
  begin
175
209
  parser.parse!
176
210
  rescue OptionParser::ParseError => e
@@ -327,6 +361,17 @@ if options[:append_system_prompt]
327
361
  end.uniq
328
362
  end
329
363
 
364
+ # --toolkit/--tool/--no-tool: a new session's toolkit, as named (the
365
+ # worker resolves it; a bundle's toolkit isn't known here).
366
+ if options[:toolkit] || options[:tools] || options[:no_tools]
367
+ require "samagotchi/toolkit"
368
+ # The flags as given, for the line an existing session's run prints.
369
+ options[:toolkit_flags] = [("--toolkit" if options[:toolkit]), ("--tool" if options[:tools]),
370
+ ("--no-tool" if options[:no_tools])].compact
371
+ options[:toolkit_value] = Samagotchi::Toolkit.from_flags(name: options[:toolkit], add: options[:tools],
372
+ remove: options[:no_tools])
373
+ end
374
+
330
375
  chi_update_hint.call
331
376
  require "samagotchi/launch_mode"
332
377
  launch, launch_note = if scratch
@@ -361,7 +406,9 @@ if launch == :attached
361
406
  muted_memories: options[:muted] || [],
362
407
  llm_context: options[:llm_context] || {},
363
408
  thinking: options[:thinking],
364
- appended_prompt_files: options[:appended_prompt_files] || [])
409
+ appended_prompt_files: options[:appended_prompt_files] || [],
410
+ toolkit: options[:toolkit_value],
411
+ toolkit_flags: options[:toolkit_flags] || [])
365
412
  rescue Samagotchi::TerminalUI::AttachLauncher::Error => e
366
413
  warn "Error: #{e.message}"
367
414
  exit 1
@@ -381,6 +428,8 @@ begin
381
428
  llm_context: options[:llm_context] || {},
382
429
  thinking: options[:thinking],
383
430
  appended_prompt_files: options[:appended_prompt_files] || [],
431
+ toolkit: options[:toolkit_value],
432
+ toolkit_flags: options[:toolkit_flags] || [],
384
433
  non_interactive: options[:non_interactive],
385
434
  model_name: options[:model],
386
435
  scratch: scratch
data/docs/architecture.md CHANGED
@@ -100,7 +100,7 @@ terminal renders the events it gets over the Bridge.
100
100
  | Core | `Samagotchi::Engine` | System prompt, memory injection, tool declarations, session lifecycle, model↔tool loop (`run_turn`). No terminal coupling. |
101
101
  | UI | `Samagotchi::TerminalUI` | REPL (Reline), rendering, REPL commands. Delegates all core work to an `Engine`. |
102
102
  | Model loops | `KernelLoop` (via `LLM::NativeBackend`), `LLM::ChatLoop` | The model↔tool loop: raw prompt or OpenAI chat API, chosen per host (see below). |
103
- | Adapters | `Samagotchi::Client`, `LLM::OpenAIChat`, `LLM::HTTP` | Raw-prompt servers, the OpenAI chat API, and the HTTP both share. |
103
+ | Adapters | `Samagotchi::Client`, `LLM::OpenAIChat`, `LLM::AnthropicMessages`, `LLM::HTTP` | Raw-prompt servers, the OpenAI chat API, Anthropic's Messages API, and the HTTP they share. |
104
104
  | Tools | `lib/samagotchi/tools/*` | Execute, read, edit, write, memory, task_*, web_fetch, delegate/delegate_result, ask_user_question, reminders, send_note, list_sessions, context_read, forget_outputs, plus runtime/output-guardrails. |
105
105
  | Bundles | `lib/samagotchi/memory_bundle/*`, `BundleCommand` | `chi bundle`: install/upgrade (3-way merge), build, uninstall; the install record under `.bundles/<name>/` that the hook, guardrail and plugin loaders read ([internals/bundles.md](internals/bundles.md)). |
106
106
  | Background | `Samagotchi::Worker`, `Samagotchi::SessionManager` | `SessionManager` forks/spawns one worker process per session; its `Worker` builds the `Engine` and `Bridge` (no terminal rendering) and runs queued turns. The spawned process (own process group) forks the worker, which `setsid`s and is reparented to init once its starter exits (it waits until the worker holds `owner.lock`, so the spawner's `Process.detach` waiter tells a starting worker): nothing that walks the spawner's process tree reaches it (`SAMAGOTCHI_WORKER_DETACH=1`, `detach_from_spawner`). The lock's pid is the worker's; `spawn`'s `child_pid` is its starter's. |
@@ -398,6 +398,7 @@ Engine picks the loop from the effective model's host (`HostRegistry#resolve`):
398
398
  |---|---|---|---|
399
399
  | Raw prompt | `KernelLoop`, wrapped by `LLM::NativeBackend` | no `api:`, or `llama_cpp`/`mlx`/`omlx` | `Client` (`/completion` or `/v1/completions`), chi's own Gemma/Qwen prompt and tool-call parsing |
400
400
  | Chat | `LLM::ChatLoop` | `api: openai` | `LLM::OpenAIChat` (`/v1/chat/completions`, streamed, native tool calls) |
401
+ | Chat | `LLM::ChatLoop` | `api: anthropic` | `LLM::AnthropicMessages` (`/v1/messages`, streamed SSE, native `tool_use`) |
401
402
 
402
403
  Both return an `LLM::ModelResult` and emit the same stream events; tool calls in
403
404
  both go through `ToolRunner` (events, hooks, veto, output cap) and
@@ -438,12 +439,28 @@ threshold puts the model's own `[CONTEXT: …]` line on the tail as a system
438
439
  message (docs/internals/context-telemetry.md).
439
440
 
440
441
  **Adapters.** `Client` (raw-prompt servers) and `LLM::OpenAIChat` (one per host,
441
- `HostRegistry#adapter_for`) share `LLM::HTTP`: timeouts, TLS for https, a line
442
- reader for streamed bodies, the retry loop (`retry.*`; network errors, 429 and
442
+ `HostRegistry#adapter_for`, built by `LLM::Adapters` from the host's api) share
443
+ `LLM::HTTP`: timeouts, TLS for https, a line reader for streamed bodies, the retry loop (`retry.*`; network errors, 429 and
443
444
  500/502/503/504/529, honouring `Retry-After`; never after a stream has produced
444
445
  output) and cancel. Cancel closes the in-flight socket from the
445
446
  `CancellationController` listener, so it works on any thread.
446
447
 
448
+ `LLM::AnthropicMessages` talks Anthropic's Messages API (`POST {root}/v1/messages`) for the same chat loop:
449
+ ChatLoop builds the same OpenAI-shaped wire messages for every chat host, and the adapter translates them
450
+ (`LLM::AnthropicWire`: the head system message becomes the top-level `system` blocks, tool calls `tool_use` blocks,
451
+ tool messages `tool_result` blocks in one user message, image parts `image` blocks, later system messages user
452
+ text; empty text blocks are never sent). Its answer (`LLM::AnthropicAssembly`) is a `ChatResponse` whose
453
+ `provider_content` holds the content blocks as received, which ChatLoop saves on the model entry as
454
+ `anthropic_content` (string keys, so a resumed session sends the same bytes) and sends back verbatim when the
455
+ adapter `replays_content?` (only for entries whose calls are all paired, never for an interrupted one; earlier
456
+ entries' thinking blocks left out under `reasoning_history: turn`) (thinking blocks
457
+ need their signature and their order among text and `tool_use`). Usage is normalized to the OpenAI shape the rest
458
+ of chi reads (the prompt is input + cache reads + cache writes), stop reasons to the loop's finish reasons
459
+ (`max_tokens` → `length`, `tool_use` → `tool_calls`, `end_turn` → `stop`). A stream is read by content-block index
460
+ from the `data:` lines alone (`event:` lines are not needed); `message_start` / `message_delta` events reach
461
+ `on_delta` with that normalized `usage`, pings not at all, and a block's start or delta counts as shown for the
462
+ first-token limit. Both adapters share their model-list cache (`LLM::ModelListing`).
463
+
447
464
  **Errors.** A failed request raises an `LLM::ProviderError` of one kind:
448
465
  `ConnectionError` (`RetryExhausted`), `RateLimited` (`CreditsHeld`: a 402 about
449
466
  credit held by in-flight requests, retried after 20 s), `OutOfCredits` (any other
data/docs/cli.md CHANGED
@@ -187,6 +187,7 @@ controls exit behavior (`--non-interactive`); `--resume` composes with both.
187
187
  | `--attach SESSION_ID` | Attach to a session's worker, waking one if it has exited. |
188
188
  | `--model NAME` | Use this model for the run (overrides the configured default and a resumed session's model). |
189
189
  | `--thinking LEVEL` | This session's own thinking level: `off`, `low`, `medium` or `high`, saved in the session and first in the order (`default` unsets it). On `--resume` or `--attach` it is set at once, through a `/thinking` on an attached worker. With `chi web` it is instead the default for the sessions its workers run, below a session's own (as env `SAMAGOTCHI_THINKING_LEVEL` is). See "Thinking" in configuration.md. |
190
+ | `--toolkit NAME`, `--tool NAME`, `--no-tool NAME` | A new session's toolkit, the tools its prompts declare: `full` (every built-in), `lean`, `child`, one of config's `toolkit.kits` or a bundle's (without `--toolkit`: config's `toolkit.default`), with tools declared on top of it (`--tool`) or left out (`--no-tool`); both repeatable, a comma list too. Saved in the session as given; a respawned worker, a fork and a continue keep it. Ignored with `--attach`/`--resume` (one line, as `--memory`); `/toolkit` changes it. Not on `chi web` (config's `toolkit.default` covers its sessions). See [Toolkits](configuration.md#toolkits). |
190
191
  | `--profile NAME` | Prompt profile (`qwen36` or `gemma4`) for every model in this run, over config and the server's template (same as `--model-profile`, env `SAMAGOTCHI_MODEL_PROFILE`). See "Prompt profile" in configuration.md. |
191
192
  | `--memory NAME` | Preload a memory entry into the system prompt (repeatable; a comma list too). Merged under the config.yml `memories:` baseline. Works attached: the list is stored on the session, so its worker builds the same prompt on every respawn. |
192
193
  | `--append-system-prompt FILE` | Put `FILE`'s text into the system prompt, after AGENTS.md and before the memory indexes (repeatable; relative to the shell's directory). Added after the config.yml `append_system_prompt:` list. A missing or unreadable file is an error, exit 1. Saved in the session, so a respawned worker, a fork and a continue keep it; ignored with `--attach`/`--resume` (one line, as `--memory`). See [Appended prompt files](configuration.md#appended-prompt-files). |
@@ -513,7 +514,16 @@ reasoning of the turn it continues. `/stats` shows it on its `reasoning history:
513
514
  line, the web as the `reasoning` chip. See "Reasoning history" in
514
515
  [configuration.md](configuration.md).
515
516
 
516
- A `/thinking` or `/reasoning-history` change that restarts the prompt cache
517
+ `/toolkit` lists the tools the next turn declares (each with its estimated
518
+ tokens), the session's toolkit and the tools it leaves out. `/toolkit NAME` puts
519
+ the session on another toolkit (dropping its own adds and removes); `/toolkit
520
+ add TOOL` and `/toolkit remove TOOL` change one tool (a tool's name keeps its
521
+ case). The change is saved at once and applies from the next turn's start; a
522
+ running turn keeps its tools. `/toolkit` alone answers mid-turn too; a change
523
+ sent while a turn runs waits for the turn's end. The web shows a session that
524
+ isn't on `full` as the `tools` chip. See [Toolkits](configuration.md#toolkits).
525
+
526
+ A `/thinking`, `/reasoning-history` or `/toolkit` change that restarts the prompt cache
517
527
  asks first: the reply says what it costs, changes nothing, and names the line
518
528
  that applies it (`/thinking low --yes`). `--yes` (or `-y`) applies it at once.
519
529
  The terminal puts that line in the next input (Enter applies it); the web's
@@ -555,6 +565,26 @@ REPL alike:
555
565
  sent after it still go in at the next step.
556
566
  At the open prompt (no turn running) `/cut TEXT` and `/queue TEXT` run TEXT
557
567
  as a normal prompt.
568
+ - **The web composer** has a row above the text field in a session (always
569
+ there, so the composer never changes height): the keys while idle, and while
570
+ the turn runs the delivery line, where a message sent now goes in (`↳ goes in after this
571
+ thinking step · 0:42 · 3k chars`, `… after this tool call (execute · 0:12)`,
572
+ `… after this answer; the turn may go on`, `… once the card above is
573
+ answered`), then two links. **Enter** (or Send) is the next step;
574
+ **Alt+Enter** (Option+Enter) or `queue for after the turn` queues it, like
575
+ `/queue TEXT`, and the bubble is dashed (`after this turn`), below the
576
+ running turn's card with the commands queued for its end, until its turn
577
+ starts; `cut in now` cuts, like `/cut TEXT` (by click only: it throws the
578
+ thinking away). Cut is enabled only while the step has streamed nothing but
579
+ thinking and cutting is on for the model (the line says `cuts at about 0:25
580
+ if you choose cut in now` until the thinking passes `steer_cut_after`, and
581
+ `cutting is off for <model>` with no cut link when it is 0); the queue link
582
+ is gone for a worker too old to queue (Alt+Enter then sends nothing, keeps the
583
+ text and says to restart the worker). A link clicked with an empty composer
584
+ arms that choice (`cut in now ✓ · Enter sends`); a second click or Esc lets
585
+ go of it. The echo says what it waits for: `waits for the next step` or
586
+ `cutting in…`, then `steered`. A phone shows the short form (`↳ after this
587
+ thinking · 0:42`) with `cut in` and `queue` chips.
558
588
  - Commands answer, wait or are refused, by what they do:
559
589
  - At once, beside the turn: `/stats`, `/recap`, `/help`, `/context`, a
560
590
  plugin's anytime command (`/btw`), and the commands that only show
@@ -708,7 +738,9 @@ approval, a hook's question and the step limit don't): two muted lines
708
738
  under its `question → answer` line, the whole text when you open the card,
709
739
  in both views and after a reload. The chip at the stage's bottom edge, `N steps · M tool
710
740
  calls` with one tick per call, opens the turn view's block right above it (newest step first
711
- while it runs; a tick opens its step). `▾` folds the stage to its status
741
+ while it runs; a tick opens its step). Below the chip, past a dashed rule, wait the
742
+ messages queued for after the turn and the commands queued for its end; they go into
743
+ the history after the turn's answer. `▾` folds the stage to its status
712
744
  row, remembered in this browser. The stage keeps its height while a turn
713
745
  runs: it grows, with a short ease, when a card or the answer needs room and gives the room back
714
746
  only when the turn moves into the history. When the turn ends the answer shows in
@@ -953,6 +985,8 @@ status> model=Qwen3.6-35B | ⑂ 2 running · 1 waiting | ctx=8.1% (under20) | me
953
985
  report none, `api: openai`, at the turn's end).
954
986
  - `mem:`: the memories the session read, with its `--memory` list; `muted:` its `--mute` list.
955
987
  Up to 8 names each, then `+N`.
988
+ - While a turn runs it ends in `· typing goes in at the next step (/cut, /queue)`; the hint is the first thing dropped
989
+ on a narrow terminal.
956
990
  - The row is cut to the terminal's width. Without a live region (output or input not a terminal,
957
991
  `TERM=dumb`) it prints as a line when it changes.
958
992
  - `SAMAGOTCHI_STATUS_LINE` / `status.line` (default `on`): `off`, `false` or `0` hides it.
@@ -231,7 +231,8 @@ hosts:
231
231
 
232
232
  `chi self` shows the variable and whether it is set (`api key FIREWORKS_API_KEY (set)`).
233
233
 
234
- Every host with `api_key_env:` sends `Authorization: Bearer <key>` on each request,
234
+ Every host with `api_key_env:` sends `Authorization: Bearer <key>` on each request (`api: anthropic`:
235
+ `x-api-key: <key>`, see [Anthropic Messages hosts](#anthropic-messages-hosts)),
235
236
  chat or raw-prompt alike, so a llama.cpp started with `--api-key` works as a native
236
237
  host too (`chi bootstrap --key-env VAR` writes such an entry). An unset variable
237
238
  fails the turn before any request (`set VAR`); a 401/403 names the variable to
@@ -292,6 +293,90 @@ workers, and resumed sessions all use the loop of the model's host. Hosts withou
292
293
  `api: openai` keep using the native `/completion`, `/v1/completions`, or oMLX
293
294
  transport path.
294
295
 
296
+ ### Anthropic Messages hosts
297
+
298
+ `api: anthropic` makes a host talk Anthropic's Messages API (`POST {url}/v1/messages`) through the same chat loop:
299
+ Anthropic's own API, a gateway that serves `/v1/messages`, or a local server that does (Splash). The url is the API
300
+ root **without** `/v1`, as `ANTHROPIC_BASE_URL` takes it (a url that ends in `/v1` is accepted: no second `/v1` is
301
+ added); an entry with neither `url:` nor `host:` talks to `https://api.anthropic.com`. The key goes in the
302
+ `x-api-key` header; `auth: bearer` sends it as `Authorization: Bearer` instead (a gateway or provider that wants
303
+ that), `auth: x-api-key` is the default spelled out. Exactly one of the two headers is sent.
304
+
305
+ ```yaml
306
+ hosts:
307
+ claude:
308
+ api: anthropic
309
+ api_key_env: ANTHROPIC_API_KEY
310
+ gateway:
311
+ url: https://gateway.example.com
312
+ api: anthropic
313
+ api_key_env: GATEWAY_KEY
314
+ auth: bearer
315
+ splash-msg:
316
+ url: http://192.0.2.10:8000
317
+ api: anthropic
318
+ ```
319
+
320
+ `auth:` works on any host (`bearer` is the default everywhere but `api: anthropic`).
321
+
322
+ **Known limit (until a later release):** Claude 5.x models (Opus, Sonnet and Haiku 5.5, Fable 5.1) bind each
323
+ thinking block to the request it came from. When the history before a block changes (the system prompt rebuilt after a
324
+ worker restart or a `/model` switch, a changed tool set, the forget layer's edits), such a model may answer HTTP 400
325
+ "… bound to a different conversation" and the turn fails with that error; `/reasoning-history turn` makes it rarer.
326
+ A later release sends Anthropic's binding controls and recovers from that 400. Fireworks, Splash and other
327
+ non-Claude models are not affected.
328
+
329
+ The loop, tools, events, hooks and UIs are the chat loop's, as for `api: openai`; only the wire differs: the
330
+ system prompt goes as the request's `system` (later system notes as user text), tool calls as `tool_use` blocks,
331
+ results as `tool_result` blocks, images as `image` blocks; thinking streams into the thinking lane; usage counts
332
+ cache reads and writes into the prompt (so `/stats` shows them). Without `default.max_tokens`, `max_tokens` is the
333
+ model list's `max_tokens` capped at 64000, else 64000 (Messages requires one). chi's OpenAI-only request fields are
334
+ never sent there; `sampling:` passes as written.
335
+
336
+ Its model list is `GET {url}/v1/models` (every page): Anthropic's shape (the window from `max_input_tokens`, images
337
+ from `capabilities.image_input`) or the OpenAI shape a gateway, Splash or Fireworks serves there (images from
338
+ `input_modalities`, `vision` or `supports_image_input`). A gateway whose list lacks an
339
+ id (a high-availability alias) takes it from the host's declared `models:`, as on any host.
340
+
341
+ **Dialects.** A provider whose `/v1/messages` departs from Anthropic's gets `dialect:` on its host (only on
342
+ `api: anthropic`; an unknown value, or a dialect on another api, warns once and is ignored). `chi self` names it on
343
+ its `loop` line. `dialect: standard` is the default written out. There is one other dialect today, `fireworks` (Fireworks' Anthropic-compatible API, directly or behind a gateway):
344
+
345
+ - the key goes as `Authorization: Bearer` (an explicit `auth:` still wins);
346
+ - thinking at `low`/`medium`/`high` is `{type: "adaptive"}` with `output_config.effort`, without `display` (Fireworks'
347
+ schema has none and refuses unknown fields; it returns thinking by default); a model switched to the budget form gets
348
+ none either;
349
+ - no `cache_control` breakpoints (Fireworks caches prompt prefixes by itself);
350
+ - `x-session-affinity: <session id>` beside `Session-Id`, so a session stays on one replica, where its cache is.
351
+
352
+ A host without `dialect:` is standard Anthropic (Claude, Splash). Give each upstream kind its own host entry: when a
353
+ gateway serves both Fireworks models and Claude ids on one url, the Claude ids go on a second entry with the same url
354
+ and no `dialect:` (Claude wants `display: summarized` and the breakpoints).
355
+
356
+ ```yaml
357
+ hosts:
358
+ work-msg: # the work gateway's Messages route, Fireworks behind it
359
+ url: https://gateway.example.com # the API root, no /v1 (chi adds /v1/messages)
360
+ api: anthropic
361
+ dialect: fireworks # Bearer auth, no thinking display, no cache_control, x-session-affinity
362
+ api_key_env: WORK_API_KEY
363
+ # auth: x-api-key # only if the gateway itself wants that header
364
+ thinking: medium # low|medium|high → output_config.effort; off → thinking disabled
365
+ models:
366
+ accounts/fireworks/models/kimi-k2p5:
367
+ price: {input: 0.6, cache_read: 0.1, output: 2.5} # example rates: put the gateway's (no cache writes on Fireworks)
368
+ work-claude: # the same gateway's Claude ids: standard Messages
369
+ url: https://gateway.example.com
370
+ api: anthropic
371
+ api_key_env: WORK_API_KEY
372
+ auth: bearer # if the gateway wants Bearer for Claude ids too
373
+ fireworks: # Fireworks directly
374
+ url: https://api.fireworks.ai/inference
375
+ api: anthropic
376
+ dialect: fireworks
377
+ api_key_env: FIREWORKS_API_KEY
378
+ ```
379
+
295
380
  To manually verify a live chat-loop tool round trip, run the gated integration
296
381
  spec. It requires the model to call `execute` and return the current UTC date:
297
382
 
@@ -512,7 +597,13 @@ models:
512
597
  - `/model` shows the level and where it came from (`thinking: off (models: qwen3.6-35b-a3b)`, `thinking: low
513
598
  (session)`), `/stats` on its `thinking:` line, `chi self` too (a session's own level first when run from its
514
599
  commands).
515
- - The idle recap and plugins' side questions always ask with thinking off, whatever the level.
600
+ - The idle recap, plugins' side questions and a broadcast's triage always ask with thinking off, whatever the
601
+ level. A model that refuses that gets no thinking fields on an `api: openai` host (its default); on an
602
+ `api: anthropic` host, where such a model always thinks, it gets adaptive thinking at `low` effort and a limit of
603
+ at least 4096 tokens, so the thinking leaves room for the answer; a model that refuses that too (one that takes no
604
+ thinking fields at all) gets none, at the normal limit. What a host refused is kept for the next recap, the next
605
+ `ctx.ask_model` on the same model, and the rest of a broadcast. A recap and `ctx.ask_model` send the session's id
606
+ as the turns do (`Session-Id`, and `x-session-affinity` under `dialect: fireworks`); a triage sends none.
516
607
 
517
608
  What each backend gets:
518
609
 
@@ -521,6 +612,20 @@ What each backend gets:
521
612
  | native (`/completion`), `qwen36` | an empty thought after the assistant cue, and no turn preamble | no knob: thinking stays as the model has it, one notice |
522
613
  | native, `gemma4` | no `<\|think\|>` token at the start of the system prompt | no knob, one notice |
523
614
  | chat host (`api: openai`) | `chat_template_kwargs: {enable_thinking: false}` and `reasoning_effort: "none"` | `reasoning_effort: <level>` |
615
+ | Messages host (`api: anthropic`) | `thinking: {type: "disabled"}` | `thinking: {type: "adaptive", display: "summarized"}` and `output_config: {effort: <level>}` |
616
+ | Messages host, `dialect: fireworks` | `thinking: {type: "disabled"}` | `thinking: {type: "adaptive"}` and `output_config: {effort: <level>}` (no `display`) |
617
+
618
+ On a Messages host `default` sends nothing: the provider's own default, which differs by model (some Claude models
619
+ think adaptively but show no thinking text, their blocks still going back; others, and Splash, don't think at all). A model that refuses `disabled` (current
620
+ Claude models) gets the request again without it, as on any chat host. A model that refuses adaptive thinking
621
+ (older Claude: Haiku 4.5) is switched, once and for the worker's life, to the budget form: `thinking: {type:
622
+ "enabled", budget_tokens: B}` with B 2048 / 8192 / 24576 for low / medium / high and no effort; `max_tokens` always
623
+ stays above the budget (the budget is lowered to leave 1024 for the answer, or `max_tokens` raised to 2048). A
624
+ host that refuses the thinking `display` field gets thinking without it from then on (the level still applies); one
625
+ that refuses `cache_control` gets no cache breakpoints from then on, and a `max_tokens` over the model's output
626
+ limit is sent again with the limit the host names. On any chat host, a model that refuses a sampling field
627
+ (`temperature`, `top_p`, `top_k`; Claude refuses the empty-answer retry's temperature while thinking) gets the
628
+ request again without them, and none of them after.
524
629
 
525
630
  On chat hosts: llama.cpp honours both off switches but ignores the effort (when its `/props` says
526
631
  `chat_template_caps.supports_reasoning_effort: false`, chi says so once per session and host, from the `/props`
@@ -593,10 +698,17 @@ models:
593
698
  turns included, and answers HTTP 400 without it: under `turn` every turn after one that thought fails. Set
594
699
  `reasoning_history: all` there (the example above). DeepSeek through OpenRouter takes `turn`.
595
700
  - `drop` sends none (what chi did before this setting).
701
+ - **On an `api: anthropic` host** the reasoning goes back as the answer's own content blocks, verbatim (thinking
702
+ blocks with their signatures, in their order among text and `tool_use`), not as `reasoning_content`. The default
703
+ there is `all`: Claude keeps earlier turns' thinking in context, and leaving it out would rewrite the cached prefix
704
+ each turn (a Qwen template on Splash drops earlier turns' reasoning itself, so `all` costs nothing there). `turn`
705
+ leaves earlier turns' thinking blocks out (their text and calls stay), and `drop` counts as `turn`: the running
706
+ turn's thinking blocks always go back (a tool loop with thinking needs the last reply's). An interrupted reply, or
707
+ one whose tool calls never got their results, goes back as plain text without its blocks.
596
708
  - `all` sends every reply's reasoning, earlier turns too. It sends only the field: a local template that leaves out
597
709
  earlier turns' reasoning (Qwen's) needs `preserve_thinking` as well (above).
598
710
  - Order, first set wins: the session's own level, `SAMAGOTCHI_REASONING_HISTORY` (the whole process; for A/B runs),
599
- the `models:` entry (found the way `profile:` is), the `hosts:` entry, then `turn`. Anything else than a level warns
711
+ the `models:` entry (found the way `profile:` is), the `hosts:` entry, then `turn` (`all` on `api: anthropic`). Anything else than a level warns
600
712
  once and counts as unset. A host's level reaches a worker when it starts, as its `sampling:` does.
601
713
  - **A session's own level** (`drop`, `turn` or `all`; an explicit `turn` counts as its own) is saved in the session
602
714
  (its file's `"reasoning_history"`), so a `--resume`, a respawned worker, a continue and `/model` keep it; a plugin's
@@ -627,10 +739,10 @@ models:
627
739
 
628
740
  ## Changes that restart the prompt cache
629
741
 
630
- `/thinking` and `/reasoning-history` ask before a change that makes the server read the conversation again: Gemma's
631
- thinking switch, a remote chat host's thinking fields ("Thinking"), and a change to or from `all` once an earlier reply
632
- has thinking ("Reasoning history"). They ask only when the level the next turn runs under really changes and the
633
- session has a model reply.
742
+ `/thinking`, `/reasoning-history` and `/toolkit` ask before a change that makes the server read the conversation again:
743
+ Gemma's thinking switch, a remote chat host's thinking fields ("Thinking"), a change to or from `all` once an earlier
744
+ reply has thinking ("Reasoning history"), and any change to the declared tools, which start every request
745
+ ("Toolkits"). They ask only when what the next turn runs under really changes and the session has a model reply.
634
746
 
635
747
  - The reply says what it costs and changes nothing yet: `Nothing changed yet: send /thinking low --yes to apply.`
636
748
  - `--yes` (or `-y`, anywhere after the command) applies it at once.
@@ -891,6 +1003,12 @@ many times per turn; `0` ends the turn at the empty answer as before. An answer
891
1003
  (90 % or more) is not retried. When the retries run out the turn ends with no answer: every UI shows one muted line,
892
1004
  `no answer: the model returned nothing (after 1 retry)`, and the model is told on its next turn with a hidden note;
893
1005
  no made-up answer is saved.
1006
+ A provider that refuses the answer (a Messages host's `stop_reason: refusal`, or an OpenAI-compatible host's
1007
+ `finish_reason: content_filter` with no text) is not asked again: the turn ends with no answer (text streamed before
1008
+ a Messages refusal is not kept), every UI shows `no answer: refused by the provider (cyber)` (the category from
1009
+ `stop_details`, when there is one), and the hidden note says the answer was refused. A `content_filter` stop after
1010
+ some text keeps that text as the answer, followed by `[cut off by the provider's content filter]`, and is not asked
1011
+ again either. Tool calls that come with either are not run.
894
1012
  A generation a plugin cuts while it streams (loop-guard's thinking watch, or any `stop_generation`,
895
1013
  [hooks.md](hooks.md#watching-the-stream)) uses the same budget: `↻ cut by loop-guard, asking again (1/1)`, and with
896
1014
  none left the turn ends as cancelled (hook).
@@ -959,6 +1077,13 @@ message (before, a failed llama.cpp `/completion` ended the turn as
959
1077
  | bad request | other 4xx; a prompt larger than the context window, whatever the status | no |
960
1078
  | protocol | a body the API doesn't promise | no |
961
1079
 
1080
+ An error event (or an error body inside a 200) has no HTTP status of its own: its numeric `error.code` stands for
1081
+ one, or a Google-style code name (Fireworks: `INVALID_ARGUMENT` 400, `UNAUTHENTICATED` 401, `PERMISSION_DENIED` 403,
1082
+ `NOT_FOUND` 404, `RESOURCE_EXHAUSTED` 429), else its `error.type` (Anthropic's names; some OpenAI-compatible servers,
1083
+ OpenAI and Ollama, use some of them: `invalid_request_error` 400, `authentication_error` 401, `billing_error` 402,
1084
+ `permission_error` 403, `not_found_error` 404, `request_too_large` 413, `rate_limit_error` 429, `api_error` 500,
1085
+ `timeout_error` 504, `overloaded_error` 529); with neither it counts as a 500.
1086
+
962
1087
  The turn's prompt and its completed tool calls stay in the session.
963
1088
 
964
1089
  ## Debug Log File
@@ -1151,6 +1276,48 @@ work tree top when inside it, else with `~`.
1151
1276
  picked up by a new process or a rebuild (`/model`). `/stats` lists the files
1152
1277
  in the prompt on its `prompt files:` line.
1153
1278
 
1279
+ ## Toolkits
1280
+
1281
+ A session's **toolkit** names the tools its prompts declare. The declared tools are the front of every request (the
1282
+ `tools` field, or the native template's declarations), so a session that declares fewer sends a shorter prompt, and
1283
+ sessions on the same toolkit share that prefix in a prompt cache.
1284
+
1285
+ ```yaml
1286
+ toolkit:
1287
+ default: lean # the sessions you start (unset: full)
1288
+ child: child # delegate children (unset: full)
1289
+ kits: # your own, on a base
1290
+ review: { base: lean, add: [web_fetch, delegate], remove: [memory_write] }
1291
+ ```
1292
+
1293
+ | toolkit | declares |
1294
+ |---|---|
1295
+ | `full` | every built-in; the default |
1296
+ | `lean` | `execute`, `read`, `write`, `edit`, `memory_read`, `memory_write`, `task_create`, `task_wait`, `task_get`, `task_list`, `task_stop`, `ask_user_question`, `web_fetch`, `context_read` |
1297
+ | `child` | `lean` plus `send_note`, without `memory_write` |
1298
+ | `coordinator` | `lean` plus `delegate`, `delegate_result`, `list_sessions` (declared by the coordinator bundle) |
1299
+
1300
+ - **Plugin tools** are declared in every toolkit unless its `remove:` names them, and so is `forget_outputs` under
1301
+ the `forget` layer. A `kits:` entry takes `base:` (default `full`), `add:` and `remove:`; it can't reuse a
1302
+ built-in's name. A bundle declares its own with `chi.toolkit` ([plugins](plugins.md#chitoolkitname-base-add--remove-)).
1303
+ - **Saved at creation, never re-resolved from config.** A session stores its toolkit as named
1304
+ (`"toolkit": {"name": "lean", "add": [], "remove": []}`) when it is created: `toolkit.default`, `toolkit.child`
1305
+ for a delegate child, or `chi --toolkit NAME` / `--tool` / `--no-tool` ([cli](cli.md)). Changing `toolkit.default`
1306
+ doesn't move existing sessions, not even when their worker is woken again; a session from before toolkits is on
1307
+ `full`. A continue and a fork keep the toolkit. `/toolkit` shows and changes it.
1308
+ - **Not loaded.** When a toolkit leaves tools out, one line after the base prompt names them (`Not loaded in this
1309
+ session (toolkit lean): …. Only the user can load one (/toolkit add <name>).`). A call to one is refused before
1310
+ the guardrails (no approval card): `Error: delegate isn't loaded in this session (toolkit lean) …`.
1311
+ - **Applied at a turn's start.** A change (`/toolkit`, a plugin's `ctx.use_toolkit`) is saved at once and the next
1312
+ turn declares it; once the session has a model reply, `/toolkit` asks first ("Changes that restart the prompt
1313
+ cache").
1314
+ - **A name that doesn't resolve** (a typo, a bundle that was uninstalled) declares every tool for that process,
1315
+ with one `toolkit>` warning at the first turn; the saved name stays, so the bundle's return brings it back.
1316
+ - **Versions.** A toolkit's definition can change with chi or a bundle, which changes what saved sessions on it
1317
+ declare after an upgrade: the next request reads the prompt again once, as a tool's schema change does. So can
1318
+ an edit to `toolkit.kits`: a session's worker reads the kits once when it starts, so a running session keeps its
1319
+ tools and the edit reaches it at its next worker start (after an idle exit, `chi sessions restart`).
1320
+
1154
1321
  ## LLM context: the forget layer
1155
1322
 
1156
1323
  Experimental, off by default. With `forget` in a model's `llm_context.strategy` (next to `stale`), chi offers that
@@ -1290,7 +1457,7 @@ Every setting below takes the three forms described in
1290
1457
  marked config.yml only) and, where the CLI
1291
1458
  column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
1292
1459
  `model_aliases:`, `hooks:`, `guardrails:` rules, `bundles:`, `memories:`,
1293
- `append_system_prompt:`) are described in their own sections.
1460
+ `append_system_prompt:`, `toolkit.kits`) are described in their own sections.
1294
1461
 
1295
1462
  | Setting | Default | CLI | What it does |
1296
1463
  |---|---|---|---|
@@ -1358,6 +1525,8 @@ column says so, a `--kebab-name` flag. The maps (`hosts:`, `models:`,
1358
1525
  | `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. |
1359
1526
  | `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). |
1360
1527
  | `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). |
1528
+ | `toolkit.default` | `full` | | The toolkit a new session is stored on: the tools its prompts declare (`full`, `lean`, `child`, one of `toolkit.kits` or a bundle's). `chi --toolkit` picks one per session. See "Toolkits". |
1529
+ | `toolkit.child` | `full` | | A delegate child's toolkit. See "Toolkits". |
1361
1530
  | `thinking.turn_preamble` | `true` | yes | Ask a `qwen36` model to open its thinking with a short `TURN:` line (the step label). |
1362
1531
  | `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". |
1363
1532
  | `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
@@ -126,7 +126,10 @@ pbpaste | chi context push clip --project
126
126
  A Claude Code session can be a pushed source too: the
127
127
  [claude-peer bundle](plugins.md#the-claude-peer-bundle)'s `pair` script attaches
128
128
  `claude-inbox` (Claude's inbox socket as the hint) and `claude-review`, where
129
- Claude pushes its answers as JSON with `wake: true`.
129
+ Claude pushes its answers as JSON with `wake: true`. The
130
+ [codex-peer bundle](plugins.md#the-codex-peer-bundle)'s does the same for a
131
+ Codex session: `codex-inbox` (Codex's thread id as the hint) and
132
+ `codex-review`.
130
133
 
131
134
  ### A stream: chat, a log, a feed
132
135