samagotchi 0.65.0 → 0.67.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 (155) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +107 -1
  3. data/README.md +2 -1
  4. data/bin/chi +51 -2
  5. data/docs/architecture.md +50 -28
  6. data/docs/cli.md +14 -2
  7. data/docs/configuration.md +101 -16
  8. data/docs/context.md +39 -2
  9. data/docs/desktop.md +90 -22
  10. data/docs/guardrails.md +8 -5
  11. data/docs/hooks.md +2 -2
  12. data/docs/internals/anthropic.md +156 -0
  13. data/docs/internals/background-tasks.md +1 -1
  14. data/docs/internals/bundles.md +2 -0
  15. data/docs/internals/engine.md +75 -30
  16. data/docs/internals/gemma4-contract.md +1 -1
  17. data/docs/internals/llm-context-bench.md +5 -4
  18. data/docs/internals/prompt-caching.md +36 -13
  19. data/docs/memory.md +1 -1
  20. data/docs/plugins.md +289 -25
  21. data/docs/releasing.md +1 -1
  22. data/docs/sessions.md +16 -6
  23. data/docs/sub-agent.md +6 -1
  24. data/docs/testing.md +17 -3
  25. data/lib/samagotchi/bootstrap/probe.rb +4 -3
  26. data/lib/samagotchi/bootstrap_command.rb +13 -3
  27. data/lib/samagotchi/bridge/ring_buffer.rb +0 -5
  28. data/lib/samagotchi/bridge.rb +6 -3
  29. data/lib/samagotchi/bundles/claude-peer/manifest.yml +7 -6
  30. data/lib/samagotchi/bundles/claude-peer/plugin.rb +40 -302
  31. data/lib/samagotchi/bundles/claude-peer/scripts/chi-peer-SKILL.md +8 -3
  32. data/lib/samagotchi/bundles/claude-peer/skill_claude_peer.md +14 -6
  33. data/lib/samagotchi/bundles/codex-peer/manifest.yml +22 -0
  34. data/lib/samagotchi/bundles/codex-peer/plugin.rb +189 -0
  35. data/lib/samagotchi/bundles/codex-peer/scripts/chi-peer-SKILL.md +136 -0
  36. data/lib/samagotchi/bundles/codex-peer/scripts/pair +92 -0
  37. data/lib/samagotchi/bundles/codex-peer/skill_codex_peer.md +76 -0
  38. data/lib/samagotchi/bundles/coordinator/manifest.yml +5 -5
  39. data/lib/samagotchi/bundles/coordinator/plugin.rb +24 -2
  40. data/lib/samagotchi/bundles/dev/manifest.yml +2 -1
  41. data/lib/samagotchi/bundles/mcp/manifest.yml +5 -5
  42. data/lib/samagotchi/bundles/mcp/plugin.rb +82 -294
  43. data/lib/samagotchi/bundles/system/config_modification_protocol.md +7 -3
  44. data/lib/samagotchi/bundles/system/delegated.md +2 -1
  45. data/lib/samagotchi/bundles/system/manifest.yml +5 -5
  46. data/lib/samagotchi/bundles/system/memory_guide.md +1 -1
  47. data/lib/samagotchi/bundles/system/self_map.md +1 -1
  48. data/lib/samagotchi/cache_confirm.rb +9 -6
  49. data/lib/samagotchi/commands/registry.rb +25 -8
  50. data/lib/samagotchi/config.rb +23 -8
  51. data/lib/samagotchi/context_absorber.rb +66 -13
  52. data/lib/samagotchi/desktop/macos/AgentTerminal.swift +260 -0
  53. data/lib/samagotchi/desktop/macos/App.swift +3 -3
  54. data/lib/samagotchi/desktop/macos/ChiRunner.swift +5 -2
  55. data/lib/samagotchi/desktop/macos/Images.swift +2 -2
  56. data/lib/samagotchi/desktop/macos/Kitty.swift +33 -197
  57. data/lib/samagotchi/desktop/macos/Panel.swift +118 -82
  58. data/lib/samagotchi/desktop/macos/ProcessRunner.swift +1 -1
  59. data/lib/samagotchi/desktop/macos/Tmux.swift +239 -0
  60. data/lib/samagotchi/desktop/macos.rb +84 -13
  61. data/lib/samagotchi/desktop_command.rb +14 -3
  62. data/lib/samagotchi/engine.rb +120 -107
  63. data/lib/samagotchi/engine_peers.rb +3 -0
  64. data/lib/samagotchi/guardrail_wiring.rb +1 -1
  65. data/lib/samagotchi/guardrails/approval.rb +1 -1
  66. data/lib/samagotchi/guardrails/approvals.rb +1 -1
  67. data/lib/samagotchi/guardrails/rules.rb +1 -1
  68. data/lib/samagotchi/guardrails/targets.rb +1 -1
  69. data/lib/samagotchi/hook_routes.rb +151 -0
  70. data/lib/samagotchi/hooks/registry.rb +2 -2
  71. data/lib/samagotchi/host_keys.rb +147 -0
  72. data/lib/samagotchi/kernel_loop.rb +9 -1
  73. data/lib/samagotchi/llm/adapter_common.rb +138 -0
  74. data/lib/samagotchi/llm/anthropic_assembly.rb +4 -2
  75. data/lib/samagotchi/llm/anthropic_messages.rb +28 -105
  76. data/lib/samagotchi/llm/api_key.rb +23 -7
  77. data/lib/samagotchi/llm/chat_loop.rb +10 -6
  78. data/lib/samagotchi/llm/openai_chat.rb +9 -92
  79. data/lib/samagotchi/llm/refusal_memo.rb +29 -0
  80. data/lib/samagotchi/llm_context_override.rb +1 -0
  81. data/lib/samagotchi/peer_sources.rb +58 -0
  82. data/lib/samagotchi/plugin/api.rb +138 -156
  83. data/lib/samagotchi/plugin/context.rb +22 -1
  84. data/lib/samagotchi/plugin/loader.rb +5 -2
  85. data/lib/samagotchi/plugin/peer.rb +394 -0
  86. data/lib/samagotchi/plugin/tool_spec.rb +166 -0
  87. data/lib/samagotchi/plugin_tasks.rb +2 -2
  88. data/lib/samagotchi/row_refs.rb +1 -1
  89. data/lib/samagotchi/self_report.rb +5 -3
  90. data/lib/samagotchi/send_command.rb +41 -15
  91. data/lib/samagotchi/session.rb +18 -1
  92. data/lib/samagotchi/session_commands.rb +16 -1
  93. data/lib/samagotchi/session_continue.rb +58 -15
  94. data/lib/samagotchi/session_inbox.rb +0 -13
  95. data/lib/samagotchi/session_manager.rb +4 -1
  96. data/lib/samagotchi/session_setup.rb +14 -7
  97. data/lib/samagotchi/system_prompt.rb +16 -1
  98. data/lib/samagotchi/terminal_ui/attach_launcher.rb +19 -9
  99. data/lib/samagotchi/terminal_ui.rb +18 -2
  100. data/lib/samagotchi/tool_call_parser.rb +1 -1
  101. data/lib/samagotchi/tool_runner.rb +13 -1
  102. data/lib/samagotchi/toolkit.rb +246 -0
  103. data/lib/samagotchi/toolkit_binding.rb +138 -0
  104. data/lib/samagotchi/toolkit_command.rb +117 -0
  105. data/lib/samagotchi/tools/context_read.rb +14 -0
  106. data/lib/samagotchi/tools/execute.rb +12 -12
  107. data/lib/samagotchi/tools/registry.rb +43 -6
  108. data/lib/samagotchi/tools/task_create.rb +6 -6
  109. data/lib/samagotchi/tools/task_get.rb +13 -13
  110. data/lib/samagotchi/tools/task_list.rb +6 -6
  111. data/lib/samagotchi/tools/task_record.rb +52 -0
  112. data/lib/samagotchi/tools/task_runtime.rb +55 -91
  113. data/lib/samagotchi/tools/task_stop.rb +6 -6
  114. data/lib/samagotchi/tools/task_wait.rb +12 -13
  115. data/lib/samagotchi/tools/tool_index.rb +454 -0
  116. data/lib/samagotchi/update_command.rb +9 -6
  117. data/lib/samagotchi/version.rb +1 -1
  118. data/lib/samagotchi/web/app.rb +28 -9
  119. data/lib/samagotchi/web/capabilities.rb +1 -1
  120. data/lib/samagotchi/web/public/activity.js +2 -2
  121. data/lib/samagotchi/web/public/annotate_actions.js +109 -0
  122. data/lib/samagotchi/web/public/annotate_bar.js +84 -2
  123. data/lib/samagotchi/web/public/app.js +52 -43
  124. data/lib/samagotchi/web/public/chains.js +1 -1
  125. data/lib/samagotchi/web/public/commands.js +6 -4
  126. data/lib/samagotchi/web/public/composer_size.js +2 -2
  127. data/lib/samagotchi/web/public/copy.js +1 -1
  128. data/lib/samagotchi/web/public/ctx.js +1 -1
  129. data/lib/samagotchi/web/public/delivery_line.js +4 -4
  130. data/lib/samagotchi/web/public/families_view.js +1 -1
  131. data/lib/samagotchi/web/public/format.js +11 -7
  132. data/lib/samagotchi/web/public/images.js +25 -2
  133. data/lib/samagotchi/web/public/index.html +9 -0
  134. data/lib/samagotchi/web/public/info_bar.js +5 -3
  135. data/lib/samagotchi/web/public/llm_context_chip.js +2 -2
  136. data/lib/samagotchi/web/public/notify.js +6 -4
  137. data/lib/samagotchi/web/public/question_card.js +1 -1
  138. data/lib/samagotchi/web/public/ref_scan.js +1 -1
  139. data/lib/samagotchi/web/public/session_state.js +17 -0
  140. data/lib/samagotchi/web/public/session_title.js +2 -3
  141. data/lib/samagotchi/web/public/sessions_list.js +0 -8
  142. data/lib/samagotchi/web/public/stage_model.js +2 -2
  143. data/lib/samagotchi/web/public/stream_handlers.js +13 -0
  144. data/lib/samagotchi/web/public/strip.js +3 -3
  145. data/lib/samagotchi/web/public/task_stop.js +3 -2
  146. data/lib/samagotchi/web/public/toolkit_chip.js +29 -0
  147. data/lib/samagotchi/web/public/turn_events.js +1 -1
  148. data/lib/samagotchi/web/public/turn_html.js +2 -2
  149. data/lib/samagotchi/web/public/turn_model.js +2 -2
  150. data/lib/samagotchi/web/public/turn_view.js +1 -1
  151. data/lib/samagotchi/web/server.rb +0 -4
  152. data/lib/samagotchi/worker.rb +13 -8
  153. data/lib/samagotchi/worker_inbound.rb +36 -4
  154. data/lib/samagotchi/worker_wakes.rb +67 -7
  155. metadata +23 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 43045b7872d6b50ba24cc2ef1678ac6708346aac9236a5764593660e4e81fc6f
4
- data.tar.gz: dad96a9922329ea3294d7bf04159b3c829796bdfda03d42eef724bbee4a3e4e7
3
+ metadata.gz: 58f42be965113cdd1f5736f77b9a5be8e9fbd77088b4a2013f9b7e86ae7ceb12
4
+ data.tar.gz: a093f0921d62e84c9e6c34ac10482e57e9e041a5a9d21eda41a88052e819cda1
5
5
  SHA512:
6
- metadata.gz: 4a35a44e9905f8c3411a656383c8a7f2493318732b959d526cdcbdc5dba90e71e874fe678057d3b060878dc5e4560398683790dade299258bd43cf541e0b62cf
7
- data.tar.gz: 9dc56d9cd85b5c5f323315f99bc7c2f4e7580927966f41723a833db561dd5d6c0b3a8720c92ea32e1fafbb3b75e9eb0e93e57e9c4784e1a9983f95e7ac6f17c6
6
+ metadata.gz: d02e8644b724765182cabb9a74eafcfaec94eb13781680e29cc33bfb1ee2bc850f55a7df4117b61d8a2889c7d48bf4792b88ba316676a1fb6be5632cc98d1a3f
7
+ data.tar.gz: a4397b9a00729a4eb230415e265c834f85f42b8b6ded242dc3c981ad6f3e543f367d7a9df00ed6d516aef12c2e2266a765b0f66c7025f575975c83fd4b556b86
data/CHANGELOG.md CHANGED
@@ -8,6 +8,110 @@ and commands may change between minor versions. How releases are made:
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.67.0] - 2026-10-14
12
+
13
+ ### Added
14
+
15
+ - **Desktop helper: agents in tmux panes**: with `tmux.enabled: true` (then `chi update`) the panel lists agent CLIs
16
+ running in tmux panes, in any terminal, beside kitty's, and pastes into them with or without Enter (a pane in copy
17
+ mode is taken out of it first). `tmux.binary` defaults to the tmux on chi's PATH when the launch file is written;
18
+ `chi update` warns when there is none. `ChiHelper --kitty` is now `--agents` (the helper's internal seam). See
19
+ docs/desktop.md, "Agents in kitty and tmux".
20
+ - **Continue on another model**: `chi send --new --continues ID --model NAME` and the web API's `POST /api/sessions`
21
+ with `continues` and `model` start the next link of a chain on that model (its host checked first, nothing
22
+ archived on a refusal). See docs/sessions.md, "Session chains".
23
+ - **Host API keys from the macOS Keychain**: with the variable a host's `api_key_env:` names unset, chi reads the key
24
+ from the login Keychain (service `chi`, account the variable's name), so the desktop helper's sessions and model
25
+ list, launchd and cron reach keyed hosts. Store a key with `security add-generic-password -s chi -a VAR -w`.
26
+ `chi self` says `VAR (keychain)`, `chi desktop status` has a `host keys` row, and install/upgrade warn about keys
27
+ the helper won't get. `SAMAGOTCHI_KEYCHAIN=off` turns it off. See docs/configuration.md, "API keys from the Keychain".
28
+ - **The peer loop**: chi and a paired Claude Code or Codex session can go back and forth on a part. A peer's answer
29
+ wakes chi up to `context.peer_turns` (default 3) turns in a row, within `session.max_wakes`; chi fixes what it
30
+ flagged or replies with `claude_send`/`codex_send`; an answer whose verdict is `VERDICT OK` arrives as a note and
31
+ ends the round; only a message typed in the web or a TUI starts the count again (not `chi send`). The wake note
32
+ frames the answer as the peer's advice, within your task; every other source keeps its guard line. Plugins
33
+ declare a peer's source with `chi.peer_source`. See docs/context.md, "Peer sources: the peer loop".
34
+
35
+ ### Changed
36
+
37
+ - **claude-peer 0.3.0 and codex-peer 0.2.0** join the peer loop and need chi 0.67.0 (`chi update`, then `chi bundle
38
+ upgrade claude-peer` / `codex-peer`, and `/claude install-skill` / `/codex install-skill` again for the peer's
39
+ side). Both are now built on `Samagotchi::Plugin::Peer`, one shared base; their texts are unchanged but for the
40
+ peer loop's.
41
+ - `session.max_wakes` caps every wake turn in a row (delegate reports, attached context, peers); the docs said
42
+ delegate reports only.
43
+ - **MCP: `find_mcp_tools` and `mcp_call` are now `find_tools` and `call_tool`**, chi's own tool index's (the mcp
44
+ bundle publishes its servers' tools into it: `chi.tool_source`, docs/plugins.md). Run `chi bundle upgrade mcp`
45
+ (mcp 0.7.0) to get it; an older mcp bundle keeps its own two tools on this chi. Search, calls, lazy starts and
46
+ `/mcp` work as before. Rename `tool: mcp_call` in guardrail rules and hooks, and `mcp_call` in loop-guard's or
47
+ check-in's `ignore_tools` in config.yml, to `call_tool`; rules on `mcp_*` keep working (they match the MCP
48
+ tool). Approvals saved for MCP tools (keyed `mcp_call>…`) no longer match: each MCP tool is asked about once
49
+ more. The two tools' new names and place in the tool list re-prefill an MCP user's prompt once.
50
+ - A continue whose previous link's model names a host config.yml no longer has (or a disabled one) starts the new
51
+ link on the default model with a warning (`chi send: warning: …`, the API's `model_warning`) instead of being
52
+ refused.
53
+
54
+ ### Fixed
55
+
56
+ - Web: a session opened (or switched to) shows its live worker's features at once: the queue link on the delivery
57
+ line, the task-stop button, Alt+Enter's queue and the stale-worker badge with Restart no longer wait for the
58
+ session's next change.
59
+ - Web: a switch starts the open session's page state over: a "copied ✓" on the info bar no longer carries into the
60
+ next session, a Restart under way in one session no longer blocks Restart in another, and a worker woken after a
61
+ failed load streams from its start, not from the previous session's cursor.
62
+ - Web: a send stays with the session it was sent from: switching sessions while its images upload no longer sends the
63
+ prompt into the other session, and an image whose send failed in one session is uploaded again when it is sent to
64
+ another (it was refused there as an unknown image).
65
+
66
+ Update with `chi update` (it upgrades the bundles too): mcp 0.7.0, claude-peer 0.3.0, codex-peer 0.2.0 and the
67
+ system bundle 0.67.0. Then restart `chi web` and run `chi desktop upgrade` for the tmux support.
68
+
69
+ ## [0.66.0] - 2026-10-14
70
+
71
+ ### Added
72
+
73
+ - **Plugin annotation actions** (web): `chi.annotation(label, command:, template:, send:)` adds a pill to the
74
+ selection bar that prepares a command line of the plugin from the selected text (`{quote}`, `{text}`, `{source}`):
75
+ it fills the composer (an earlier draft stays under the quote), or with `send: true` sends at once. Up to 3 inline,
76
+ the rest behind `⋯`. See docs/plugins.md, `chi.annotation`.
77
+ - claude-peer 0.2.0: **Confirm with Claude** in the web's selection bar fills `/claude validate` with the quote;
78
+ `/claude validate|context` titles a quote by its first quoted line, not its `From …:` label.
79
+ - The codex-peer bundle (in the dev profile): claude-peer for an OpenAI Codex CLI session. Codex pairs (its
80
+ `chi-peer` skill, installed by `/codex install-skill`), the `codex_send` tool and `/codex validate|context|last`
81
+ send it a part of the work with `codex queue` (killed after 10 s, never a hang), and Codex's answer comes back as
82
+ the `codex-review` context; **Confirm with Codex** in the web's selection bar. Codex's sandbox needs chi's
83
+ context folder in `writable_roots` (the install card shows the line). See docs/plugins.md, "The codex-peer bundle".
84
+ - **Toolkits**: a session's toolkit names the tools its prompts declare. chi ships `full` (every built-in, the
85
+ default), `lean` (the file, shell, memory, task, question, `web_fetch` and `context_read` tools: ~1.4k tokens
86
+ fewer) and `child` (`lean` with `send_note`, without `memory_write`); config's `toolkit.kits` and bundles
87
+ (`chi.toolkit`) add their own. Pick one with `toolkit.default` / `toolkit.child`, `chi --toolkit NAME` (with
88
+ `--tool` / `--no-tool`, also on `chi send --new`) or `/toolkit NAME | add TOOL | remove TOOL`. It is saved in the
89
+ session when it is created and kept by continues and forks; a change applies at the next turn's start. A left-out
90
+ tool is named in one prompt line, and a call to it is refused before the guardrails. The web shows a session not
91
+ on `full` as a `tools` chip. See docs/configuration.md, "Toolkits".
92
+ - Plugins: `ctx.toolkit` and `ctx.use_toolkit(name)` (before the session's first reply). The coordinator bundle
93
+ (0.4.0) declares the `coordinator` toolkit (`lean` + `delegate`, `delegate_result`, `list_sessions`), and
94
+ `/coordinate` puts a fresh session on it.
95
+
96
+ ### Changed
97
+
98
+ - A command's name may be followed by any whitespace, a newline too: `/foo` on its own line with text under it
99
+ runs `/foo` with that text, in the terminals and the web (before, only a space did).
100
+ - Web: a command refused as busy mid-turn comes back into the composer (it was cleared when sent).
101
+ - A delegate child is told to report what should change (a wrong memory or skill, instructions that led it astray)
102
+ in its reply instead of writing memories (`delegated.md`).
103
+
104
+ ### Fixed
105
+
106
+ - `dialect: fireworks` hosts: the web's ctx meter no longer reads 0% while an answer streams (Fireworks' first stream
107
+ event carries all-zero usage; its counts come at the end).
108
+
109
+ **Still open:** the Claude 5.x "bound to a different conversation" 400 on Anthropic Messages hosts (see 0.65.0); its
110
+ recovery comes in a later release.
111
+
112
+ Update with `chi update` (the system bundle moves with the gem; claude-peer 0.2.0, coordinator 0.4.0, and the dev
113
+ profile 0.5.0 with the new codex-peer 0.1.0).
114
+
11
115
  ## [0.65.0] - 2026-10-13
12
116
 
13
117
  ### Added
@@ -2833,7 +2937,9 @@ and long-lived sessions.
2833
2937
  - A macOS desktop helper (`chi desktop install`): a "Send to chi" Service and a
2834
2938
  hotkey panel that send selected text or the clipboard to your sessions.
2835
2939
 
2836
- [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.65.0...HEAD
2940
+ [Unreleased]: https://github.com/dm1try/samagotchi/compare/v0.67.0...HEAD
2941
+ [0.67.0]: https://github.com/dm1try/samagotchi/compare/v0.66.0...v0.67.0
2942
+ [0.66.0]: https://github.com/dm1try/samagotchi/compare/v0.65.0...v0.66.0
2837
2943
  [0.65.0]: https://github.com/dm1try/samagotchi/compare/v0.64.0...v0.65.0
2838
2944
  [0.64.0]: https://github.com/dm1try/samagotchi/compare/v0.63.0...v0.64.0
2839
2945
  [0.63.0]: https://github.com/dm1try/samagotchi/compare/v0.62.0...v0.63.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
@@ -45,7 +45,7 @@ A compact visual overview of the current architecture, then the core API in pros
45
45
  │ task_create/get/list/stop/wait · delegate · delegate_result · ask_user_question │
46
46
  │ register_reminder/cancel_reminder/list_reminders · send_note · list_sessions · │
47
47
  │ context_read · forget_outputs (+ bundle plugin tools) │
48
- │ declared via tool_declarations.rb │
48
+ │ offered by Tools::Registry (Builtins + bundles), filtered by the toolkit │
49
49
  └──────────────────────────────────────────────────────────────────────────────────┘
50
50
  │
51
51
  ▼
@@ -83,10 +83,10 @@ TerminalUI ── delegates ──▶ Engine
83
83
  ## Request / turn flow
84
84
 
85
85
  ```
86
- bin/chi ─▶ TerminalUI ─▶ Engine#run_turn ─▶ KernelLoop ──┬─▶ Model API (LLM)
87
- (builds) │ └─▶ Tool call(s) ─▶ Tool
88
- │ │
89
- └── on_event: ─▶ render update ────┘
86
+ bin/chi ─▶ TerminalUI ─▶ Engine#run_turn ─▶ KernelLoop / ChatLoop ──┬─▶ Model API (LLM)
87
+ (builds) │ └─▶ Tool call(s) ─▶ Tool
88
+ │ │
89
+ └── on_event: ─▶ render update ────────────────┘
90
90
  (turn started / completed / canceled)
91
91
  ```
92
92
 
@@ -99,12 +99,12 @@ terminal renders the events it gets over the Bridge.
99
99
  |-------|-----------|----------------|
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
- | Model loops | `KernelLoop` (via `LLM::NativeBackend`), `LLM::ChatLoop` | The model↔tool loop: raw prompt or OpenAI chat API, chosen per host (see below). |
102
+ | Model loops | `KernelLoop` (via `LLM::NativeBackend`), `LLM::ChatLoop` | The model↔tool loop: raw prompt, or a chat API (OpenAI chat, Anthropic Messages), chosen per host (see below). |
103
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. |
107
- | Web | `Samagotchi::Web::App`, `Samagotchi::Web::Server`, `Samagotchi::Web::SessionHub` | Rack+WEBrick single-port `127.0.0.1:4567` (index.html + `/api/*` + SSE). The hub is chi web's projection of the session list, pushed to every tab over `GET /api/events`. |
107
+ | Web | `Samagotchi::Web::App`, `Samagotchi::Web::Server`, `Samagotchi::Web::SessionHub` | Rack+WEBrick on one port, `127.0.0.1:4567` (index.html + `/api/*` + SSE); with `web.host: lan` also on this machine's LAN address, behind an access token (`Web::Lan`, `Web::Token`). The hub is chi web's projection of the session list, pushed to every tab over `GET /api/events`. |
108
108
  | Sessions | `Samagotchi::Session`, `SessionManager` | File `sessions/<uuid>.json` + sidecar `input/`/`output/`; retention 14d/500, `updated_at desc`, lazy sweep. |
109
109
 
110
110
  ## Entry points
@@ -122,7 +122,8 @@ terminal renders the events it gets over the Bridge.
122
122
  as a client of its Bridge (`BridgeClient#follow`, `post_turn`, `post_command`, `cancel`, `answer`,
123
123
  `dismiss_question`). The worker runs in the session's `working_directory`, so `!cmd` and
124
124
  the tools don't depend on where the terminal attached from.
125
- - `bin/chi web` → builds `Web::Server` (Rack+WEBrick on `127.0.0.1:4567`, `--port`/`SAMAGOTCHI_WEB_PORT`, `--open`).
125
+ - `bin/chi web` → builds `Web::Server` (Rack+WEBrick on `127.0.0.1:4567`, `--port`/`SAMAGOTCHI_WEB_PORT`, `--open`;
126
+ `web.host: lan` adds a LAN address, `--new-token` replaces its access token).
126
127
  - `bin/chi sessions {list,stop,restart,archive,unarchive,delete,prune,clean,stats}` (`SessionsCommand`; not the REPL's `SessionCommands`) → retention & ordering (`SessionRetention`, `updated_at desc`, dry-run, test-only); `stop` is `SessionManager.stop_session(wait:)`, which waits for the worker to release `owner.lock`; `delete` (`SessionDeleteCommand`) is `SessionManager.delete_session(stop:)`, which the TUI's `/exit --delete` and the web's `DELETE /api/sessions/:id` use too.
127
128
  - In REPL mode `TerminalUI` builds the `Engine` in process (`-p` and `--non-interactive` too);
128
129
  otherwise the session's `Worker` (`lib/samagotchi/worker.rb`, spawned by `SessionManager`) builds it.
@@ -132,7 +133,7 @@ terminal renders the events it gets over the Bridge.
132
133
  - **Files:** `~/.local/state/samagotchi/sessions/<uuid>.json` + `<uuid>/input|output|owner.lock|bridge.json` (XDG-aware), and the `<uuid>/stopped` and `<uuid>/archived` marker files.
133
134
  - **Single owner:** the process running a session's Engine (worker or in-process TUI) holds a flock on `owner.lock` (`OwnerLock`); a second owner backs off, and the web answers 409 for a TUI-owned session.
134
135
  - **Status:** `status` is turn state (`idle`/`running`); liveness is the lock. A stop is the `<uuid>/stopped` marker (`Session.mark_stopped`; a resume removes it), which `Session.load` and `.summary_from_file` read as status `stopped`, so no other process rewrites a worker's session file.
135
- - **Retention:** 14 days / 500 cap (env `SAMAGOTCHI_SESSION_RETENTION_DAYS`/`MAX_COUNT`, optional `KEEP_STATUS`), live-owner guard, only when `*.json` present; lazy sweep ≤1/24h from the session hub's full-probe tick (and on `GET /api/sessions`, which the page no longer calls), manual via `bin/chi sessions prune --dry-run`.
136
+ - **Retention:** 14 days / 500 cap (env `SAMAGOTCHI_SESSION_RETENTION_DAYS`/`MAX_COUNT`, optional `KEEP_STATUS`), live-owner guard, only when `*.json` present; lazy sweep at most once per `session.sweep_interval_hours` (default 24) from the session hub's full-probe tick (and on `GET /api/sessions`, which the page no longer calls), manual via `bin/chi sessions prune --dry-run`.
136
137
  - **Ordering:** `Session.list(sort:,order:,limit:,offset:)` and `GET /api/sessions?sort=&order=&limit=&offset=` default `updated_at desc`; Web UI sort/filter/pagination.
137
138
  - **Test hygiene:** `test_run` flag when `SAMAGOTCHI_ENV=test`/`RACK_ENV=test`/`CI`, targetable via `prune --test-only` / `clean`.
138
139
 
@@ -145,9 +146,9 @@ agent logic and can be used without any terminal rendering; the UI is a thin lay
145
146
  |-------|-------|----------------|
146
147
  | Core | `Samagotchi::Engine` | System prompt, memory injection, tool declarations, session lifecycle, the model↔tool loop (`run_turn`). No terminal coupling. |
147
148
  | UI | `Samagotchi::TerminalUI` | Interactive REPL (Reline), rendering (ANSI, spinner, status line), REPL commands. Delegates all core work to an `Engine`. |
148
- | Model loops and adapters | `KernelLoop`, `LLM::ChatLoop`, `Samagotchi::Client`, `LLM::OpenAIChat`, `LLM::HTTP` | The model↔tool loops and the HTTP adapters they talk through (see "Model loops and adapters"). |
149
+ | Model loops and adapters | `KernelLoop`, `LLM::ChatLoop`, `Samagotchi::Client`, `LLM::OpenAIChat`, `LLM::AnthropicMessages`, `LLM::HTTP` | The model↔tool loops and the HTTP adapters they talk through (see "Model loops and adapters"). |
149
150
  | Bridge (SSE/HTTP) | `Samagotchi::Bridge`, `SessionManager` | The **single live client transport**: an SSE read stream + HTTP POST turn/cancel/answer surface that attaches to a worker's existing `Engine` via `Engine#subscribe`. Every session worker starts it (bound `127.0.0.1`, no auth, localhost-only). |
150
- | Web (Rack) | `Samagotchi::Web::App`, `SessionManager` | Single-port `127.0.0.1:4567` control plane via `rack`+`webrick` (serve `index.html` + `/api/*`). It talks to each worker's Bridge (`App#relay`, `bridge_get`; `/stream` proxies it) and reads the session files; no `Engine` of its own. `bin/chi web` entrypoint. |
151
+ | Web (Rack) | `Samagotchi::Web::App`, `SessionManager` | Single-port `127.0.0.1:4567` control plane via `rack`+`webrick` (serve `index.html` + `/api/*`). With `web.host: lan` (or one of this machine's IPv4 addresses) the server also listens on that address, where every request but this machine's needs the access token (`App#token_gate`: the `chi_token` cookie or `Authorization: Bearer`; [CLI](cli.md#chi-web-on-your-phone)). It talks to each worker's Bridge (`App#relay`, `bridge_get`; `/stream` proxies it) and reads the session files; no `Engine` of its own. `bin/chi web` entrypoint. |
151
152
  | Sessions | `Samagotchi::Session`, `SessionManager` | File-based `~/.local/state/samagotchi/sessions/<uuid>.json` + sidecar `input/`/`output/`; retention (14d/500) + ordering (`updated_at desc`). |
152
153
 
153
154
  - `bin/chi` in REPL mode (see `LaunchMode` above) builds `TerminalUI`. `TerminalUI#run` is the single
@@ -199,21 +200,24 @@ renders whatever it needs from the stream + final `Result`. The public Engine AP
199
200
  ```ruby
200
201
  engine.run_turn(session, prompt, on_event: nil, max_iterations: nil, cancel_controller: nil, # nil: turn.max_iterations
201
202
  max_tool_output_chars: nil, pending_input: nil, continue: false, origin: nil,
202
- images: [], id: nil)
203
+ images: [], id: nil, resumed: false, resume_from: nil) # resumed/resume_from: a step-limit Continue
203
204
  engine.system_prompt # fully built system prompt string
204
205
  engine.session # current session (Engine owns create/resume)
205
206
  ```
206
207
 
207
- The system prompt (`SystemPrompt#build`) is the base prompt, then rg guidance, the
208
- identity and the preloaded memories, AGENTS.md, the memory indexes, and last **the
209
- model** (`Model: this session runs on <ref> (host …; model key …)` and a line telling
208
+ The system prompt (`SystemPrompt#build`, assembled in `#system_prompt_with_index`) is the
209
+ base prompt, then the toolkit's "Not loaded in this session" line (only when the toolkit
210
+ leaves tools out), rg guidance, the identity, the model notes, the preloaded memories,
211
+ AGENTS.md (root → cwd), the appended files (`--append-system-prompt`), the memory
212
+ indexes, and last **the model** (`Model: this session runs on <ref> (host …; model key …)` and a line telling
210
213
  the model to answer "which model are you" from it, not from training), the working
211
214
  directory and the session id and log (Gemma's native tool declarations still follow).
212
215
  The order is for the servers' prompt caches, which reuse only an exact token prefix:
213
216
  what every session shares first, what changes least before what changes more, the
214
217
  per-session lines last, so a new session reuses everything above them. It is built
215
- once per loop and rebuilt only by a model switch (or changed tools), so the model
216
- line costs no KV churn.
218
+ once per loop and thinking level and rebuilt only by a model switch, changed tools or a
219
+ toolkit change (`Engine#apply_toolkit!`, at a turn's start), so the model line costs no
220
+ KV churn.
217
221
  The full request layout, what breaks the cache and the rules for changes:
218
222
  [internals/prompt-caching.md](internals/prompt-caching.md).
219
223
 
@@ -272,7 +276,8 @@ process that already owns the `Engine`); every worker starts it, and it exposes:
272
276
  - `POST /session/:id/tasks/stop` — `{task_id}`: stop a background task as the user
273
277
  (`TaskRuntime.stop_task by: "user"`, the web's stop-task button on a running `task_wait`;
274
278
  web route `POST /api/sessions/:id/tasks/:task_id/stop`). Only a task this conversation's
275
- `task_create` started (the saved messages, or the running turn's accumulator); answers once the
279
+ `task_create` started, or an `execute` handed over as a task at its limit (`TaskRuntime.created_ids_in` on the
280
+ saved messages, or the running turn's accumulator); answers once the
276
281
  task is gone: `200 {status, stop_reason, task_id}`, `404 task_not_found`, `409 not_running`.
277
282
  The worker names it in its sidecar features (`task_stop`).
278
283
  - `POST /session/:id/relay` — the approval relay, on a delegated child's worker
@@ -307,7 +312,10 @@ process that already owns the `Engine`); every worker starts it, and it exposes:
307
312
  actions: its turn no longer runs), `event_seq`, `event_id`, all taken in one event-log hold; no message
308
313
  list, so it costs the same however long the session is. `?turn_id=` answers that turn's answer
309
314
  (an unknown or missing id: the newest); the only route that reads its query.
310
- - `OPTIONS *` — CORS preflight (`Access-Control-Allow-Origin: *`).
315
+ - No browser: only chi's own clients talk to a Bridge. A request with an `Origin` header, a `Sec-Fetch-Site`
316
+ other than `none`, or a `Host` that isn't loopback (a DNS-rebound page) is `403 cross_origin`
317
+ (`Bridge#browser_request?`, before any route); there is no CORS and no `OPTIONS` route. The browser reaches a
318
+ worker only through `chi web`.
311
319
 
312
320
  The metrics in `/state`, `/snapshot`, `/stats` and the SSE `snapshot`/`reset` frames
313
321
  (`SessionMetrics#snapshot`) carry the session's totals and only the recent timing records: the
@@ -438,7 +446,7 @@ attached TUI get a fresh number each time), and on a rise past the second
438
446
  threshold puts the model's own `[CONTEXT: …]` line on the tail as a system
439
447
  message (docs/internals/context-telemetry.md).
440
448
 
441
- **Adapters.** `Client` (raw-prompt servers) and `LLM::OpenAIChat` (one per host,
449
+ **Adapters.** `Client` (raw-prompt servers), `LLM::OpenAIChat` and `LLM::AnthropicMessages` (one per host,
442
450
  `HostRegistry#adapter_for`, built by `LLM::Adapters` from the host's api) share
443
451
  `LLM::HTTP`: timeouts, TLS for https, a line reader for streamed bodies, the retry loop (`retry.*`; network errors, 429 and
444
452
  500/502/503/504/529, honouring `Retry-After`; never after a stream has produced
@@ -459,7 +467,10 @@ of chi reads (the prompt is input + cache reads + cache writes), stop reasons to
459
467
  (`max_tokens` → `length`, `tool_use` → `tool_calls`, `end_turn` → `stop`). A stream is read by content-block index
460
468
  from the `data:` lines alone (`event:` lines are not needed); `message_start` / `message_delta` events reach
461
469
  `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`).
470
+ first-token limit. Both adapters share their model-list cache (`LLM::ModelListing`) and their HTTP plumbing
471
+ (`LLM::AdapterCommon`: timeouts, the paged model list, the SSE line loop, JSON errors). A host's `dialect:`
472
+ (`LLM::Dialect`, `fireworks`) changes the auth header, the thinking `display`, the cache breakpoints and a session
473
+ header; [internals/anthropic.md](internals/anthropic.md) covers the adapter.
463
474
 
464
475
  **Errors.** A failed request raises an `LLM::ProviderError` of one kind:
465
476
  `ConnectionError` (`RetryExhausted`), `RateLimited` (`CreditsHeld`: a 402 about
@@ -474,9 +485,10 @@ stream events (server counts, else a chars/4 estimate). Model lists are `LLM::Mo
474
485
  (`HostRegistry#list_all_models`); the context window comes from the running
475
486
  server (`/props`), then the host's model list, then config.
476
487
 
477
- **Keys.** A host's API key comes only from the environment variable its
478
- `api_key_env:` names; it never reaches config.yml, `SAMAGOTCHI_HOSTS_JSON`, `chi self`,
479
- logs or events.
488
+ **Keys.** A host's API key comes from the environment variable its
489
+ `api_key_env:` names, else, on macOS, the login Keychain's item for that name (`HostKeys`, read through
490
+ `/usr/bin/security` with a deadline; hits cached per process; `SAMAGOTCHI_KEYCHAIN=off` skips it); it never
491
+ reaches config.yml, `SAMAGOTCHI_HOSTS_JSON`, `chi self`, logs or events.
480
492
 
481
493
  ## Other components
482
494
 
@@ -487,16 +499,26 @@ A map of the parts the overview above doesn't name (paths under `lib/samagotchi/
487
499
  | Worker | `worker.rb` (`worker_idle_exit.rb`, `worker_inbound.rb`, `worker_signals.rb`) | A session's background process: builds the `Engine` and `Bridge`, runs queued turns, exits when idle. A spawned one traps TERM, HUP and INT (`WorkerSignals`, installed by `run_session_loop` when `SAMAGOTCHI_WORKER_PROCESS=1`): the running turn stops as a Stop with reason `signal`, is saved, and the worker leaves (`:signal`, exit 0; a new worker only for input already queued). A running turn is checkpointed at its iteration boundaries (`TurnCheckpoint`, the `interrupted_turn` mark), and the next worker settles a turn its predecessor died in (`TurnCheckpoint.settle`). |
488
500
  | Config | `config.rb` | Every setting as an `Entry`: env, config.yml and CLI layers ([configuration.md](configuration.md)). |
489
501
  | MemoryBundle | `memory_bundle.rb`, `memory_bundle/{installer,builder,installed_bundle,plugin_ref,bundle_hook}.rb` (`Installer`, `Builder`, `InstalledBundle`, `PluginRef`, `BundleHook`) | Installing, building and reading bundles of memories, hooks and plugins. |
490
- | Plugin API | `plugin/api.rb` (`Plugin::Api`), `plugin/loader.rb` | What a bundle's plugin registers: tools, commands, hooks, services ([plugins.md](plugins.md)). |
502
+ | Plugin API | `plugin/api.rb` (`Plugin::Api`), `plugin/tool_spec.rb` (`Plugin::ToolSpec`: a checked tool declaration, how it is registered), `plugin/loader.rb` | What a bundle's plugin registers: tools, commands, annotation actions, hooks, services, toolkits, peer sources ([plugins.md](plugins.md)). |
503
+ | Peer bundles' base | `plugin/peer.rb` (`Plugin::Peer`) | What claude-peer and codex-peer share (the send tool, the command, the cards, payloads); each bundle's subclass sets its strings and transport ([plugins.md](plugins.md#the-claude-peer-bundle)). |
491
504
  | Shipped bundles | `bundles/` (`mcp`, `github-pr`, `loop-guard`, `skills`, `source-links`, …) | Bundles that ship with chi. |
492
- | Hooks | `hooks/registry.rb` (`Hooks::Registry`), `hooks/loader.rb` | Hook registration and dispatch ([hooks.md](hooks.md)). |
505
+ | Hooks | `hooks/registry.rb` (`Hooks::Registry`), `hooks/loader.rb`, `hook_routes.rb` (`HookRoutes`) | Hook registration and dispatch; what a hook or plugin does beyond reading its event (notice, question, stop, steer) routed to the UIs ([hooks.md](hooks.md)). |
493
506
  | Guardrails | `guardrails.rb`, `guardrails/shell_lex.rb`, `guardrails/rules.rb`, `guardrail_wiring.rb` | The gate before every tool call: shell lexing, YAML rules, approvals ([guardrails.md](guardrails.md)). |
494
507
  | Delegation | `tools/delegate.rb`, `tools/delegate_result.rb`, `tools/delegate_wait.rb`, `child_reports.rb`, `relay_desk.rb` | Child sessions a session starts and waits on, and their approval relay ([sessions.md](sessions.md#delegating)). |
495
508
  | send / broadcast | `send_command.rb` (`SendCommand`), `broadcast_command.rb` (`BroadcastCommand`), `broadcast/` | `chi send` and `chi broadcast` ([broadcast.md](broadcast.md), [sub-agent.md](sub-agent.md)). |
496
509
  | Idle work | `idle_scheduler.rb` (`IdleScheduler`), `idle_recap.rb` (`IdleRecap`), `idle_reminders.rb` | One poller for an idle session's recap and reminders. |
497
- | Attached context | `context_sources.rb` (`ContextSources`), `context_poller.rb`, `context_absorber.rb` | External sources a session keeps fresh ([context.md](context.md)). |
510
+ | Attached context | `context_sources.rb` (`ContextSources`), `context_poller.rb`, `context_absorber.rb`, `peer_sources.rb` (`PeerSources`: the sources peer bundles declare) | External sources a session keeps fresh, and the peer loop's wakes ([context.md](context.md)). |
498
511
  | LLM context strategies | `llm_context_strategy.rb`, `llm_context_view.rb`, `llm_context_stale.rb`, `llm_context_forget.rb` | What a request sends of the history (none, stale, forget) ([internals/llm-context-forget.md](internals/llm-context-forget.md)). |
499
512
  | Session chains | `session_continue.rb` (`SessionContinue`), `session_chain.rb` (`SessionChain`) | A session that continues an earlier one. |
500
513
  | Analytics | `analytics_file.rb` (`AnalyticsFile`) | A session's `analytics.json` (`SessionMetrics#persist`). |
501
514
  | Images / vision | `image_store.rb` (`ImageStore`), `vision_support.rb`, `vision_context.rb` | Stored prompt images and whether a model takes them. |
502
- | Desktop | `desktop.rb`, `desktop/macos.rb`, `desktop_command.rb` | `chi desktop`, the macOS "Send to chi" helper ([desktop.md](desktop.md)). |
515
+ | Toolkits | `toolkit.rb` (`Toolkit`), `toolkit_binding.rb` (`ToolkitBinding`, the Engine's seam), `toolkit_command.rb` (`/toolkit`), `tools/registry.rb` (`Registry#toolkit`, `#left_out_names`), `web/public/toolkit_chip.js` | Which registered tools a session's prompts declare: saved in the session, resolved at a turn's start (`Engine#apply_toolkit!`), named in the prompt's not-loaded line; `chi.toolkit` and `ctx.use_toolkit` for plugins ([configuration.md](configuration.md#toolkits)). |
516
+ | Steer delivery | `steer.rb` (`Steer`), `delivery.rb` (`Delivery`), `steer_cut.rb` (`SteerCut`), `generation_phase.rb`, `waiting_steer.rb`, `steer_cut_after.rb` (`SteerCutAfter`), `cut_policy.rb` (`CutPolicy`) | A message for a running turn: at the next boundary, cutting a thinking-only generation, or queued for the turn's end ([internals/engine.md](internals/engine.md)). |
517
+ | Prompt files | `prompt_files.rb` (`PromptFiles`) | The AGENTS.md chain (root → cwd) and the appended files the system prompt carries. |
518
+ | Model settings | `model_profile.rb` (`ModelProfile`), `model_settings.rb`, `sampling_settings.rb`, `model_catalog.rb` (`ModelCatalog`), `model_notes.rb` (`ModelNotes`) | A model's prompt format, its `models:` entry, its request parameters, the pickable model rows (`chi models`, `/api/models`), the notes its prompt loads. |
519
+ | Dialects | `llm/dialect.rb` (`LLM::Dialect`) | What an `api: anthropic` provider's `/v1/messages` does differently (`dialect: fireworks`) ([internals/anthropic.md](internals/anthropic.md)). |
520
+ | Logging | `log.rb` (`Log`), `log_subscriber.rb` (`LogSubscriber`), `log_path.rb` (`LogPath`), `log_line.rb` | The debug log: tagged records, a session's event trail, where the file goes ([configuration.md](configuration.md#debug-log-file)). |
521
+ | Stores | `reminder_store.rb` (`ReminderStore`), `recap_store.rb` (`RecapStore`) | A session's reminders, and its idle recap (`recap.json`). |
522
+ | Setup and update | `bootstrap_command.rb`, `bootstrap/`, `update_command.rb`, `gem_update.rb` | `chi bootstrap` (first config.yml, system bundle, core profile) and `chi update` (the gem, shipped bundles, the macOS helper). |
523
+ | Process groups | `process_group.rb` (`ProcessGroup`) | A command in a process group of its own, stopped as a group (TERM, grace, KILL): execute, tasks, context fetches, the mcp bundle's servers. |
524
+ | Desktop | `desktop.rb`, `desktop/macos.rb`, `desktop_command.rb`, `desktop/macos/*.swift` | `chi desktop`, the macOS "Send to chi" helper ([desktop.md](desktop.md)). The helper's Swift side: the panel (`Panel.swift`), the chi CLI runner (`ChiRunner.swift`), agent terminals behind `AgentTerminal` (`AgentTerminal.swift`, `Kitty.swift`, `Tmux.swift`; `ChiHelper --agents` is their CLI seam). |
data/docs/cli.md CHANGED
@@ -44,7 +44,9 @@ chi bootstrap # try localhost 8080, 11434, 1234, 8000
44
44
  at once.
45
45
  - **The key.** A server that answers 401/403 wants an API key: `--key-env VAR`
46
46
  names the environment variable holding it (on a terminal chi asks for the
47
- name). Only the variable's name is written, never the key.
47
+ name); on macOS a key only the login Keychain holds under that name counts
48
+ (docs/configuration.md, "API keys from the Keychain"). Only the variable's
49
+ name is written, never the key.
48
50
  - **The model.** One model is taken; with several, `--model ID` picks one
49
51
  (a terminal gets a numbered list, a script the ids and exit 2). A llama.cpp
50
52
  server also shows its context size and the prompt profile its chat template
@@ -187,6 +189,7 @@ controls exit behavior (`--non-interactive`); `--resume` composes with both.
187
189
  | `--attach SESSION_ID` | Attach to a session's worker, waking one if it has exited. |
188
190
  | `--model NAME` | Use this model for the run (overrides the configured default and a resumed session's model). |
189
191
  | `--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. |
192
+ | `--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
193
  | `--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
194
  | `--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
195
  | `--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 +516,16 @@ reasoning of the turn it continues. `/stats` shows it on its `reasoning history:
513
516
  line, the web as the `reasoning` chip. See "Reasoning history" in
514
517
  [configuration.md](configuration.md).
515
518
 
516
- A `/thinking` or `/reasoning-history` change that restarts the prompt cache
519
+ `/toolkit` lists the tools the next turn declares (each with its estimated
520
+ tokens), the session's toolkit and the tools it leaves out. `/toolkit NAME` puts
521
+ the session on another toolkit (dropping its own adds and removes); `/toolkit
522
+ add TOOL` and `/toolkit remove TOOL` change one tool (a tool's name keeps its
523
+ case). The change is saved at once and applies from the next turn's start; a
524
+ running turn keeps its tools. `/toolkit` alone answers mid-turn too; a change
525
+ sent while a turn runs waits for the turn's end. The web shows a session that
526
+ isn't on `full` as the `tools` chip. See [Toolkits](configuration.md#toolkits).
527
+
528
+ A `/thinking`, `/reasoning-history` or `/toolkit` change that restarts the prompt cache
517
529
  asks first: the reply says what it costs, changes nothing, and names the line
518
530
  that applies it (`/thinking low --yes`). `--yes` (or `-y`) applies it at once.
519
531
  The terminal puts that line in the next input (Enter applies it); the web's