samagotchi 0.2.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 (243) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +43 -0
  3. data/LICENSE +21 -0
  4. data/README.md +126 -0
  5. data/bin/chi +1140 -0
  6. data/docs/architecture.md +299 -0
  7. data/docs/cli.md +490 -0
  8. data/docs/configuration.md +494 -0
  9. data/docs/desktop.md +97 -0
  10. data/docs/guardrails.md +218 -0
  11. data/docs/hooks.md +309 -0
  12. data/docs/internals/background-tasks.md +26 -0
  13. data/docs/internals/context-telemetry.md +36 -0
  14. data/docs/internals/gemma4-contract.md +23 -0
  15. data/docs/internals/tool-guardrails.md +45 -0
  16. data/docs/memory.md +85 -0
  17. data/docs/plugins.md +819 -0
  18. data/docs/releasing.md +135 -0
  19. data/docs/sessions.md +155 -0
  20. data/lib/samagotchi/bridge/bounded_queue.rb +70 -0
  21. data/lib/samagotchi/bridge/card_store.rb +126 -0
  22. data/lib/samagotchi/bridge/event_id.rb +25 -0
  23. data/lib/samagotchi/bridge/ring_buffer.rb +63 -0
  24. data/lib/samagotchi/bridge/sse_writer.rb +248 -0
  25. data/lib/samagotchi/bridge/turn_accumulator.rb +189 -0
  26. data/lib/samagotchi/bridge.rb +993 -0
  27. data/lib/samagotchi/bridge_client/event_stream.rb +158 -0
  28. data/lib/samagotchi/bridge_client/sse_parser.rb +51 -0
  29. data/lib/samagotchi/bridge_client.rb +330 -0
  30. data/lib/samagotchi/bundle_needs.rb +97 -0
  31. data/lib/samagotchi/bundles/btw/manifest.yml +10 -0
  32. data/lib/samagotchi/bundles/btw/plugin.rb +100 -0
  33. data/lib/samagotchi/bundles/guardrails/guardrails/rules.yml +82 -0
  34. data/lib/samagotchi/bundles/guardrails/guardrails.md +14 -0
  35. data/lib/samagotchi/bundles/guardrails/manifest.yml +8 -0
  36. data/lib/samagotchi/bundles/known-names/hooks/known_names.rb +210 -0
  37. data/lib/samagotchi/bundles/known-names/known_names.md +3 -0
  38. data/lib/samagotchi/bundles/known-names/manifest.yml +14 -0
  39. data/lib/samagotchi/bundles/loop-guard/manifest.yml +10 -0
  40. data/lib/samagotchi/bundles/loop-guard/plugin.rb +158 -0
  41. data/lib/samagotchi/bundles/mcp/manifest.yml +11 -0
  42. data/lib/samagotchi/bundles/mcp/plugin.rb +631 -0
  43. data/lib/samagotchi/bundles/system/config_modification_protocol.md +149 -0
  44. data/lib/samagotchi/bundles/system/delegated.md +10 -0
  45. data/lib/samagotchi/bundles/system/identity.md +7 -0
  46. data/lib/samagotchi/bundles/system/manifest.yml +11 -0
  47. data/lib/samagotchi/bundles/system/memory_guide.md +107 -0
  48. data/lib/samagotchi/bundles/system/self_map.md +55 -0
  49. data/lib/samagotchi/cancellation_controller.rb +78 -0
  50. data/lib/samagotchi/client.rb +429 -0
  51. data/lib/samagotchi/commands/registry.rb +112 -0
  52. data/lib/samagotchi/config.rb +910 -0
  53. data/lib/samagotchi/context_note.rb +77 -0
  54. data/lib/samagotchi/context_quote.rb +21 -0
  55. data/lib/samagotchi/context_usage.rb +66 -0
  56. data/lib/samagotchi/context_window.rb +76 -0
  57. data/lib/samagotchi/debug_log.rb +110 -0
  58. data/lib/samagotchi/desktop/macos/App.swift +102 -0
  59. data/lib/samagotchi/desktop/macos/ChiRunner.swift +201 -0
  60. data/lib/samagotchi/desktop/macos/Hotkey.swift +42 -0
  61. data/lib/samagotchi/desktop/macos/Info.plist.erb +42 -0
  62. data/lib/samagotchi/desktop/macos/Panel.swift +383 -0
  63. data/lib/samagotchi/desktop/macos.rb +255 -0
  64. data/lib/samagotchi/desktop.rb +21 -0
  65. data/lib/samagotchi/desktop_command.rb +143 -0
  66. data/lib/samagotchi/engine.rb +2807 -0
  67. data/lib/samagotchi/guardrails/approval.rb +125 -0
  68. data/lib/samagotchi/guardrails/approvals.rb +177 -0
  69. data/lib/samagotchi/guardrails/context.rb +71 -0
  70. data/lib/samagotchi/guardrails/gate.rb +125 -0
  71. data/lib/samagotchi/guardrails/load_failures.rb +46 -0
  72. data/lib/samagotchi/guardrails/protected_paths.rb +77 -0
  73. data/lib/samagotchi/guardrails/rules.rb +199 -0
  74. data/lib/samagotchi/guardrails/targets.rb +119 -0
  75. data/lib/samagotchi/guardrails/verdict.rb +134 -0
  76. data/lib/samagotchi/guardrails.rb +18 -0
  77. data/lib/samagotchi/hooks/bundle_loader.rb +158 -0
  78. data/lib/samagotchi/hooks/loader.rb +162 -0
  79. data/lib/samagotchi/hooks/registry.rb +261 -0
  80. data/lib/samagotchi/hooks.rb +30 -0
  81. data/lib/samagotchi/host_registry.rb +315 -0
  82. data/lib/samagotchi/idle_client.rb +147 -0
  83. data/lib/samagotchi/idle_recap.rb +549 -0
  84. data/lib/samagotchi/idle_reminders.rb +101 -0
  85. data/lib/samagotchi/idle_scheduler.rb +76 -0
  86. data/lib/samagotchi/image_store.rb +393 -0
  87. data/lib/samagotchi/installed_gem.rb +38 -0
  88. data/lib/samagotchi/kernel_loop.rb +1017 -0
  89. data/lib/samagotchi/launch_mode.rb +34 -0
  90. data/lib/samagotchi/llm/backend.rb +28 -0
  91. data/lib/samagotchi/llm/chat_loop.rb +450 -0
  92. data/lib/samagotchi/llm/errors.rb +329 -0
  93. data/lib/samagotchi/llm/http.rb +412 -0
  94. data/lib/samagotchi/llm/model_result.rb +72 -0
  95. data/lib/samagotchi/llm/native_backend.rb +50 -0
  96. data/lib/samagotchi/llm/native_tool_normalizer.rb +277 -0
  97. data/lib/samagotchi/llm/openai_chat.rb +403 -0
  98. data/lib/samagotchi/llm/usage.rb +79 -0
  99. data/lib/samagotchi/log.rb +200 -0
  100. data/lib/samagotchi/log_line.rb +127 -0
  101. data/lib/samagotchi/log_path.rb +31 -0
  102. data/lib/samagotchi/log_subscriber.rb +163 -0
  103. data/lib/samagotchi/memory_bundle/builder.rb +364 -0
  104. data/lib/samagotchi/memory_bundle/index_updater.rb +123 -0
  105. data/lib/samagotchi/memory_bundle/installer.rb +528 -0
  106. data/lib/samagotchi/memory_bundle/listing.rb +72 -0
  107. data/lib/samagotchi/memory_bundle/manifest.rb +225 -0
  108. data/lib/samagotchi/memory_bundle/merger.rb +52 -0
  109. data/lib/samagotchi/memory_bundle/placeholder.rb +37 -0
  110. data/lib/samagotchi/memory_bundle/provenance.rb +257 -0
  111. data/lib/samagotchi/memory_bundle/source.rb +153 -0
  112. data/lib/samagotchi/memory_bundle/status.rb +107 -0
  113. data/lib/samagotchi/memory_bundle/system_bundle.rb +161 -0
  114. data/lib/samagotchi/memory_bundle/uninstaller.rb +128 -0
  115. data/lib/samagotchi/memory_bundle.rb +17 -0
  116. data/lib/samagotchi/memory_paths.rb +101 -0
  117. data/lib/samagotchi/model_overlay.rb +53 -0
  118. data/lib/samagotchi/model_profile.rb +309 -0
  119. data/lib/samagotchi/muted_memories.rb +66 -0
  120. data/lib/samagotchi/note_command.rb +163 -0
  121. data/lib/samagotchi/output_formatter.rb +100 -0
  122. data/lib/samagotchi/owner_lock.rb +110 -0
  123. data/lib/samagotchi/pending_input_queue.rb +48 -0
  124. data/lib/samagotchi/plugin/api.rb +362 -0
  125. data/lib/samagotchi/plugin/context.rb +193 -0
  126. data/lib/samagotchi/plugin/loader.rb +126 -0
  127. data/lib/samagotchi/plugin/service.rb +117 -0
  128. data/lib/samagotchi/plugin/sessions.rb +150 -0
  129. data/lib/samagotchi/plugin/side_question.rb +60 -0
  130. data/lib/samagotchi/plugin/tool_result.rb +24 -0
  131. data/lib/samagotchi/project_scope.rb +25 -0
  132. data/lib/samagotchi/prompt.rb +119 -0
  133. data/lib/samagotchi/prompt_literal_guard.rb +70 -0
  134. data/lib/samagotchi/recap_store.rb +92 -0
  135. data/lib/samagotchi/reminder_store.rb +165 -0
  136. data/lib/samagotchi/self_report.rb +195 -0
  137. data/lib/samagotchi/send_command.rb +170 -0
  138. data/lib/samagotchi/served_model.rb +32 -0
  139. data/lib/samagotchi/session.rb +508 -0
  140. data/lib/samagotchi/session_commands.rb +527 -0
  141. data/lib/samagotchi/session_delete_command.rb +105 -0
  142. data/lib/samagotchi/session_manager.rb +1049 -0
  143. data/lib/samagotchi/session_metrics.rb +466 -0
  144. data/lib/samagotchi/session_observer.rb +117 -0
  145. data/lib/samagotchi/terminal_ui/attach_launcher.rb +118 -0
  146. data/lib/samagotchi/terminal_ui/attached_loop.rb +1037 -0
  147. data/lib/samagotchi/terminal_ui/attached_view.rb +264 -0
  148. data/lib/samagotchi/terminal_ui/event_renderer.rb +192 -0
  149. data/lib/samagotchi/terminal_ui/formatting.rb +291 -0
  150. data/lib/samagotchi/terminal_ui/image_input.rb +36 -0
  151. data/lib/samagotchi/terminal_ui/input_support.rb +324 -0
  152. data/lib/samagotchi/terminal_ui/legacy_surface.rb +111 -0
  153. data/lib/samagotchi/terminal_ui/line_reader.rb +113 -0
  154. data/lib/samagotchi/terminal_ui/live_region.rb +36 -0
  155. data/lib/samagotchi/terminal_ui/plain_surface.rb +51 -0
  156. data/lib/samagotchi/terminal_ui/question_prompt.rb +153 -0
  157. data/lib/samagotchi/terminal_ui/question_slot.rb +131 -0
  158. data/lib/samagotchi/terminal_ui/reline_seam.rb +216 -0
  159. data/lib/samagotchi/terminal_ui/repl_input.rb +138 -0
  160. data/lib/samagotchi/terminal_ui/screen.rb +316 -0
  161. data/lib/samagotchi/terminal_ui/surface.rb +47 -0
  162. data/lib/samagotchi/terminal_ui/thinking_line.rb +101 -0
  163. data/lib/samagotchi/terminal_ui.rb +1992 -0
  164. data/lib/samagotchi/thinking_ticker.rb +110 -0
  165. data/lib/samagotchi/thought_stream_splitter.rb +149 -0
  166. data/lib/samagotchi/token_usage.rb +88 -0
  167. data/lib/samagotchi/tool_activity.rb +216 -0
  168. data/lib/samagotchi/tool_call_parser.rb +637 -0
  169. data/lib/samagotchi/tool_declarations.rb +561 -0
  170. data/lib/samagotchi/tool_runner.rb +211 -0
  171. data/lib/samagotchi/tools/args.rb +259 -0
  172. data/lib/samagotchi/tools/ask_user_question.rb +152 -0
  173. data/lib/samagotchi/tools/builtins.rb +122 -0
  174. data/lib/samagotchi/tools/cancel_reminder.rb +21 -0
  175. data/lib/samagotchi/tools/delegate.rb +167 -0
  176. data/lib/samagotchi/tools/delegate_result.rb +53 -0
  177. data/lib/samagotchi/tools/delegate_wait.rb +153 -0
  178. data/lib/samagotchi/tools/edit.rb +155 -0
  179. data/lib/samagotchi/tools/execute.rb +214 -0
  180. data/lib/samagotchi/tools/list_reminders.rb +20 -0
  181. data/lib/samagotchi/tools/list_sessions.rb +74 -0
  182. data/lib/samagotchi/tools/memory.rb +256 -0
  183. data/lib/samagotchi/tools/output_guardrails.rb +93 -0
  184. data/lib/samagotchi/tools/peers.rb +18 -0
  185. data/lib/samagotchi/tools/read.rb +182 -0
  186. data/lib/samagotchi/tools/register_reminder.rb +53 -0
  187. data/lib/samagotchi/tools/registry.rb +60 -0
  188. data/lib/samagotchi/tools/send_note.rb +49 -0
  189. data/lib/samagotchi/tools/task_create.rb +29 -0
  190. data/lib/samagotchi/tools/task_get.rb +39 -0
  191. data/lib/samagotchi/tools/task_list.rb +43 -0
  192. data/lib/samagotchi/tools/task_runtime.rb +311 -0
  193. data/lib/samagotchi/tools/task_stop.rb +29 -0
  194. data/lib/samagotchi/tools/task_wait.rb +104 -0
  195. data/lib/samagotchi/tools/tool_path.rb +18 -0
  196. data/lib/samagotchi/tools/web_fetch.rb +163 -0
  197. data/lib/samagotchi/tools/write.rb +26 -0
  198. data/lib/samagotchi/turn_flow.rb +242 -0
  199. data/lib/samagotchi/turn_note.rb +76 -0
  200. data/lib/samagotchi/turn_tally.rb +101 -0
  201. data/lib/samagotchi/version.rb +7 -0
  202. data/lib/samagotchi/vision_context.rb +132 -0
  203. data/lib/samagotchi/vision_support.rb +109 -0
  204. data/lib/samagotchi/web/app.rb +1349 -0
  205. data/lib/samagotchi/web/markdown_renderer.rb +107 -0
  206. data/lib/samagotchi/web/message_parts.rb +169 -0
  207. data/lib/samagotchi/web/public/activity.js +100 -0
  208. data/lib/samagotchi/web/public/annotations.js +67 -0
  209. data/lib/samagotchi/web/public/app.js +2382 -0
  210. data/lib/samagotchi/web/public/card.js +74 -0
  211. data/lib/samagotchi/web/public/chat_view.js +360 -0
  212. data/lib/samagotchi/web/public/chunk_router.js +25 -0
  213. data/lib/samagotchi/web/public/command_complete.js +39 -0
  214. data/lib/samagotchi/web/public/composer_size.js +19 -0
  215. data/lib/samagotchi/web/public/copy.js +142 -0
  216. data/lib/samagotchi/web/public/ctx.js +35 -0
  217. data/lib/samagotchi/web/public/data.js +256 -0
  218. data/lib/samagotchi/web/public/format.js +232 -0
  219. data/lib/samagotchi/web/public/hold.js +78 -0
  220. data/lib/samagotchi/web/public/images.js +77 -0
  221. data/lib/samagotchi/web/public/index.html +568 -0
  222. data/lib/samagotchi/web/public/init_row.js +60 -0
  223. data/lib/samagotchi/web/public/model_pick.js +23 -0
  224. data/lib/samagotchi/web/public/question_card.js +100 -0
  225. data/lib/samagotchi/web/public/route.js +17 -0
  226. data/lib/samagotchi/web/public/scope.js +36 -0
  227. data/lib/samagotchi/web/public/scroll.js +24 -0
  228. data/lib/samagotchi/web/public/sentences.js +88 -0
  229. data/lib/samagotchi/web/public/sessions_list.js +60 -0
  230. data/lib/samagotchi/web/public/strip.js +25 -0
  231. data/lib/samagotchi/web/public/tally.js +37 -0
  232. data/lib/samagotchi/web/public/thinking_ticker.js +79 -0
  233. data/lib/samagotchi/web/public/timing.js +185 -0
  234. data/lib/samagotchi/web/public/turn_events.js +209 -0
  235. data/lib/samagotchi/web/public/turn_model.js +204 -0
  236. data/lib/samagotchi/web/public/turn_view.js +587 -0
  237. data/lib/samagotchi/web/server.rb +183 -0
  238. data/lib/samagotchi/web/session_hub.rb +329 -0
  239. data/lib/samagotchi/web/session_summary.rb +85 -0
  240. data/lib/samagotchi/worker.rb +635 -0
  241. data/lib/samagotchi/worker_idle_exit.rb +87 -0
  242. data/lib/samagotchi.rb +12 -0
  243. metadata +374 -0
@@ -0,0 +1,494 @@
1
+ # Configuration
2
+
3
+ ## Global Config File
4
+
5
+ Chi can preload a global config file and expose those entries as environment
6
+ variables before the app boots.
7
+
8
+ Default path:
9
+
10
+ - `$XDG_CONFIG_HOME/samagotchi/config.yml`
11
+ - Fallback when `XDG_CONFIG_HOME` is unset: `~/.config/samagotchi/config.yml`
12
+
13
+ Example:
14
+
15
+ ```yaml
16
+ SAMAGOTCHI_DEFAULT_MODEL: Qwen3-14B-Instruct
17
+ server:
18
+ host: 192.0.2.10
19
+ port: 8081
20
+ SAMAGOTCHI_THINKING_UI: spinner
21
+
22
+ # Multi-host (optional): aggregated /models and per-model routing.
23
+ # Bare SAMAGOTCHI_DEFAULT_MODEL uses the default host; host:model pins to a host.
24
+ # Transport per host overrides SAMAGOTCHI_SERVER_TRANSPORT; api: openai makes a
25
+ # host use the OpenAI chat API instead of chi's raw prompt.
26
+ hosts:
27
+ main:
28
+ host: localhost
29
+ port: 8080
30
+ transport: llama_cpp
31
+ small-box:
32
+ host: 192.0.2.20
33
+ port: 8080
34
+
35
+ # Idle recap: on by default, written with the session's own model and host
36
+ # (on a paid remote host that is one small request per idle window).
37
+ # host_ref + model pin another model: host_ref asks that host's OpenAI API (its
38
+ # url:, else http://host:port/v1) with its api_key_env; base_url is an OpenAI API
39
+ # base as given (e.g. http://h:8081/v1). recap: false turns it off.
40
+ recap:
41
+ # host_ref: small-box
42
+ # model: your-small-model-id
43
+ # inactivity: 180
44
+ # timeout: 30
45
+ # min_user_turns: 2
46
+ # sentences: 2-4 # or 3, 5-7; 1-10 (from the next recap written)
47
+
48
+ model_aliases:
49
+ small: your-small-model-id
50
+ tiny: small-box:your-small-model-id # alias may be bare or host:model (hybrid)
51
+
52
+ # Plain chi runs its session in a background worker and attaches to it, so the
53
+ # web UI can share it (default true; env SAMAGOTCHI_SESSION_SHARED). false keeps
54
+ # the in-process REPL; --no-shared does for one run.
55
+ session:
56
+ shared: true
57
+ # idle_exit_minutes: 30 # an unused worker exits after this (0 = never)
58
+ # keep_empty: false # true keeps sessions nothing happened in (default: deleted when left)
59
+ # max_children: 4 # running sessions one session may have delegated at a time (the delegate tool)
60
+
61
+ # Baseline memories preloaded into the system prompt (same shape as --memory).
62
+ # CLI --memory entries are appended after these, deduped.
63
+ memories:
64
+ - system/user_preferences
65
+ - project/feature-env-template
66
+ ```
67
+
68
+ Behavior:
69
+
70
+ - The file is optional.
71
+ - Top level is a YAML mapping of scalar env overrides plus nested sections.
72
+ - Real environment variables still win over config-file values.
73
+ - Workers inherit hosts via `SAMAGOTCHI_HOSTS_JSON` propagated through `SessionManager.spawn_options`.
74
+
75
+ This lets you run `chi` without repeating common defaults such as model
76
+ and llama host/port on every invocation.
77
+
78
+ Note: The global config file supports both flat scalar entries (for env vars)
79
+ and nested sections like `hosts:`, `recap:`, `hooks:`, `guardrails:` (see
80
+ [Guardrails](guardrails.md)), `bundles:` (a bundle's settings for its hooks,
81
+ see [Hooks: Settings](hooks.md#settings)), `model_aliases:`,
82
+ `memories:`. Scalar entries are loaded as environment
83
+ variables; non-scalar sections are skipped by the env-loader and parsed by
84
+ their respective subsystems (e.g. the hooks system, `HostRegistry`). The
85
+ `memories:` list is the persistent baseline for preloaded memory entries —
86
+ the same name shape as `--memory` (bare name or `scope/name`), merged under
87
+ any per-run `--memory` values (config baseline first, deduped). A per-run
88
+ `--mute NAME` removes an entry from the merged list for that session (see
89
+ "Muting a memory" in cli.md).
90
+
91
+ ## Model Server Transport
92
+
93
+ Chi talks to a model server over HTTP and supports three transports:
94
+
95
+ - `llama_cpp` (default): llama.cpp's native `/completion` and `/models` endpoints.
96
+ - `mlx`: [mlx-lm](https://github.com/ml-explore/mlx-lm)'s OpenAI-compatible
97
+ `/v1/completions` and `/v1/models` endpoints (Apple Silicon-native models).
98
+ - `omlx`: [oMLX](https://github.com/jundot/omlx) (the mlx-lm successor —
99
+ continuous batching + tiered SSD KV cache) using the same `/v1/completions`
100
+ and `/v1/models` endpoints as `mlx`.
101
+
102
+ Select the transport with `SAMAGOTCHI_SERVER_TRANSPORT` (`llama_cpp`, `mlx`, or
103
+ `omlx`). `SAMAGOTCHI_SERVER_HOST`/`SAMAGOTCHI_SERVER_PORT` are reused for all three —
104
+ only the request/response shape differs. oMLX's default server port is `8000` (not
105
+ `8080`), so point `SAMAGOTCHI_SERVER_PORT` at it, e.g. `SAMAGOTCHI_SERVER_PORT=8000`.
106
+ With `hosts:` each entry may set `transport: llama_cpp|mlx|omlx` to override the
107
+ global transport per host (`lib/samagotchi/host_registry.rb`).
108
+
109
+ Each host may also set `api:`, which says how chi talks to it:
110
+
111
+ - `llama_cpp`, `mlx` or `omlx`: chi's own raw-prompt loop (the value is also the
112
+ host's transport, so don't set a different `transport:` next to it);
113
+ - `openai`: the OpenAI chat API at `http://HOST:PORT/v1`, or at `url:` (see below).
114
+
115
+ Without `api:` a host uses the raw-prompt loop, as before. The loop follows the
116
+ model's host, so `/model other-host:model` can move a session between the two.
117
+ Workers started by plain `chi`, `chi web` or `--attach` get the same hosts, `api:` included.
118
+
119
+ Example for mlx-lm:
120
+
121
+ ```yaml
122
+ SAMAGOTCHI_SERVER_TRANSPORT: mlx
123
+ server:
124
+ host: 127.0.0.1
125
+ port: 8080
126
+ ```
127
+
128
+ ```shell
129
+ mlx_lm.server --model mlx-community/Qwen3-14B-Instruct-4bit
130
+ ```
131
+
132
+ Example for oMLX:
133
+
134
+ ```yaml
135
+ SAMAGOTCHI_SERVER_TRANSPORT: omlx
136
+ server:
137
+ host: 192.0.2.10
138
+ port: 8000
139
+ ```
140
+
141
+ Both the `mlx` and `omlx` transports still send chi's own raw formatted prompt
142
+ (via `/v1/completions`) rather than a `messages` array, so the existing
143
+ per-model prompt/tool-call formatting is unaffected — neither server reapplies its
144
+ own chat template on this endpoint. Only the Gemma4 (`<|tool_call>…`) and Qwen3.6
145
+ (`[[…]]`/`<|tool_call>`) tool-call formats are in scope; GLM/Mistral/Kimi/MiniMax
146
+ formats are not parsed.
147
+
148
+ For an OpenAI Chat Completions server such as [Splash](https://github.com/incoai/splash)
149
+ (a fast inference engine for Macs that works well for a local setup alongside
150
+ llama.cpp), give its host `api: openai`:
151
+
152
+ ```yaml
153
+ default:
154
+ model: splash:incoai/Qwen3.6-35B-A3B-Splash
155
+ hosts:
156
+ splash:
157
+ host: 192.0.2.10
158
+ port: 8000
159
+ api: openai
160
+ ```
161
+
162
+ A host can give `url:` instead of `host:`/`port:` (not both): `http` or `https`,
163
+ with an optional path. For `api: openai` the url is the API base as written (no
164
+ `/v1` is added); raw-prompt hosts use only its scheme, host and port. A remote
165
+ provider's key comes from the environment variable that `api_key_env:` names;
166
+ the key itself never goes into config.yml, `chi self`, logs or events, and
167
+ workers get it by inheriting the environment:
168
+
169
+ ```yaml
170
+ hosts:
171
+ fw:
172
+ url: https://api.fireworks.ai/inference/v1
173
+ api: openai
174
+ api_key_env: FIREWORKS_API_KEY
175
+ ```
176
+
177
+ `chi self` shows the variable and whether it is set (`api key FIREWORKS_API_KEY (set)`).
178
+
179
+ For models on that host, chi uses the chat loop (its own OpenAI chat adapter): it
180
+ takes the OpenAI base (`url:`, else `http://HOST:PORT/v1`) and streams messages plus
181
+ function schemas from `/v1/chat/completions`; the model's reasoning (`reasoning_content`)
182
+ shows as thinking. This works with Splash and with llama.cpp servers that
183
+ expose the OpenAI-compatible chat endpoint. In verbose mode (`-v`), chi prints the loop the
184
+ starting model uses (`[verbose] backend=chat` or `backend=native`); request
185
+ bodies are not logged. The interactive REPL, `--prompt`,
186
+ workers, and resumed sessions all use the loop of the model's host. Hosts without
187
+ `api: openai` keep using the native `/completion`, `/v1/completions`, or oMLX
188
+ transport path.
189
+
190
+ To manually verify a live chat-loop tool round trip, run the gated integration
191
+ spec. It requires the model to call `execute` and return the current UTC date:
192
+
193
+ ```shell
194
+ SAMAGOTCHI_INTEGRATION=1 \
195
+ SAMAGOTCHI_SERVER_HOST=192.0.2.10 SAMAGOTCHI_SERVER_PORT=8000 \
196
+ SAMAGOTCHI_DEFAULT_MODEL=incoai/Qwen3.8-27B-Splash \
197
+ bundle exec rspec spec/integration/chat_loop_spec.rb -fd < /dev/null
198
+ ```
199
+
200
+ The same test works against a llama.cpp OpenAI-compatible server by changing
201
+ the host, port, and model values. The test is skipped unless
202
+ `SAMAGOTCHI_INTEGRATION=1` is set.
203
+
204
+ oMLX's known tool-call limitation (a stream filter that strips markup) only
205
+ affects its `/v1/chat/completions` endpoint, not the `/v1/completions` endpoint
206
+ chi uses, so raw `[[…]]`/`<|tool_call>` markers stream through untouched.
207
+
208
+ `SAMAGOTCHI_DEFAULT_MODEL` (config default) and `/model` (runtime effective) pick the model; the status line and `/model`
209
+ output always render the runtime effective model (showing default when diverged). Which prompt format it gets is the
210
+ prompt profile (see "Prompt profile" below). How the selector reaches the request differs by transport:
211
+
212
+ - **mlx** (`mlx_lm.server`): the `model` field is omitted entirely — the server
213
+ uses whatever was loaded via its own `--model` CLI flag.
214
+ - **omlx**: the server *requires* a `model` field and returns `HTTP 400`
215
+ (`model: Field required`) without it, so samagotchi forwards the selector
216
+ resolved to the exact id listed in the server's `/v1/models` — matched by exact
217
+ (case-insensitive) first, then substring, then passed through unchanged. That
218
+ resolved id is usually prefixed (e.g. `mlx-community--gemma-3-4b-it-4bit`), so a
219
+ short selector such as `gemma-3-4b-it-4bit` is what you set in
220
+ `SAMAGOTCHI_DEFAULT_MODEL`. An unknown selector passes through raw and oMLX 404s,
221
+ listing its available models; if `/v1/models` is unreachable, samagotchi falls
222
+ back to the raw selector and lets the server decide (its own 400/404). Either
223
+ error fails the turn with the server's message (see "Server errors" below). Runtime
224
+ model switch re-resolves each completion (the `/v1/models` id list is cached per
225
+ client; the selector itself is re-resolved every time).
226
+
227
+ ## Prompt profile
228
+
229
+ A native host (`llama_cpp`, `mlx`, `omlx`) gets a raw prompt in one model family's format: its turn markers, tool-call
230
+ syntax, thought tags and stop sequences. That is the prompt profile, `qwen36` or `gemma4`. A wrong one is not just
231
+ worse output: a ChatML model under `gemma4` never hits a stop sequence, generates until its limit and then runs the
232
+ tool calls it made up on the way. The first of these that says something wins:
233
+
234
+ 1. `--profile NAME` (or `--model-profile NAME`), then `SAMAGOTCHI_MODEL_PROFILE`: for every model in the process,
235
+ including one picked later with `/model`.
236
+ 2. `models:` in `config.yml`, keyed by model id or alias (case-insensitive; the name as typed, alias-resolved or
237
+ without its host prefix):
238
+
239
+ ```yaml
240
+ models:
241
+ ornith-ai/Ornith-1.5-35B-A3B-GGUF:Q4_K_M:
242
+ profile: qwen36
243
+ my-alias:
244
+ profile: qwen36
245
+ ```
246
+
247
+ 3. `profile:` on a `hosts:` entry, for anything that host serves:
248
+
249
+ ```yaml
250
+ hosts:
251
+ mlx:
252
+ host: 192.0.2.10
253
+ port: 8081
254
+ transport: mlx
255
+ profile: qwen36
256
+ ```
257
+
258
+ 4. The server's chat template, on `llama_cpp` hosts: `/props` (asked with `?model=`, which a router needs) with
259
+ `<|im_start|>` and `<function=` is `qwen36`, any other ChatML template too; `<|turn>` and `<|tool_call>` is `gemma4`.
260
+ mlx_lm.server and oMLX publish no template, so config or the name decides there.
261
+ 5. The name: `qwen` → `qwen36`, `gemma` → `gemma4`.
262
+ 6. `qwen36`.
263
+
264
+ An unknown value in `models:` or `hosts:` warns and is skipped; an unknown `--profile` or `SAMAGOTCHI_MODEL_PROFILE`
265
+ warns too (the CLI refuses it). The profile is resolved at start and on `/model`, then kept for the session, so the
266
+ system prompt stays the same; a server that swaps models between turns goes unnoticed until `/model` or a new chi. If
267
+ `/props` could not be read at start (server down, or 503 while loading), chi asks again before the next turn.
268
+
269
+ `/stats` and `/model` show the profile and its source (`cli`, `env`, `config (models: …)`, `config (hosts.<name>)`,
270
+ `server (chat_template)`, `name`, `default`); `chi self` shows what config says without asking the server. A chat
271
+ host (`api: openai`) formats nothing itself: its profile comes from the name and only strips thought tags.
272
+
273
+ Workers get `hosts:` (with `profile:`) through `SAMAGOTCHI_HOSTS_JSON` and read `models:` from the same config file.
274
+ `--profile` reaches the worker a chi starts, but a worker that another process wakes later (`chi web`, `--attach`
275
+ after an idle exit) gets that process's environment, so put a lasting choice in config.
276
+
277
+ ## Llama HTTP Timeouts
278
+
279
+ Long-running llama.cpp completions can exceed Ruby's default HTTP read timeout.
280
+ Configure these environment variables to avoid premature request failures:
281
+
282
+ - `SAMAGOTCHI_SERVER_OPEN_TIMEOUT` (default: `10`) connection timeout in seconds.
283
+ - `SAMAGOTCHI_SERVER_READ_TIMEOUT` (default: `600`) response read timeout in seconds.
284
+
285
+ A streamed answer also has a **first-token limit**: the seconds it may take to show its first text, reasoning or
286
+ tool call. A remote provider can keep a queued request open for minutes with SSE keep-alive comments
287
+ (OpenRouter's `: OPENROUTER PROCESSING`), which reset the read timeout, so only this limit ends the wait. The
288
+ turn then fails with `no answer from host <name> within 120s (first_token_timeout); …` and is not retried.
289
+
290
+ ```yaml
291
+ hosts:
292
+ openrouter:
293
+ url: https://openrouter.ai/api/v1
294
+ api: openai
295
+ api_key_env: OPENROUTER_API_KEY
296
+ first_token_timeout: 180 # seconds; 0 = off
297
+ ```
298
+
299
+ `hosts.<name>.first_token_timeout` wins over `server.first_token_timeout` (`SAMAGOTCHI_SERVER_FIRST_TOKEN_TIMEOUT`),
300
+ which applies to every host. With neither set, remote hosts (an API key or an https url) get 120 seconds and local
301
+ servers no limit: a long prompt evaluation is normal there, and the read timeout catches a dead server.
302
+
303
+ Every chat request carries the session's id as a `Session-Id` header (next to `User-Agent: chi/<version>`). A
304
+ gateway that spreads requests over several providers can key on it to keep one conversation on one provider, so
305
+ prompt caches hit and every turn is answered by the same model. Servers that don't know the header ignore it.
306
+
307
+ ## Llama Model Routing
308
+
309
+ To explicitly route requests to a named model in llama.cpp, set:
310
+
311
+ - `SAMAGOTCHI_DEFAULT_MODEL` (required): model name/id sent as the `model` field on `/completion` requests.
312
+
313
+ When `SAMAGOTCHI_DEFAULT_MODEL` is unset or blank, Samagotchi fails fast with a clear startup/configuration error.
314
+
315
+ With several `hosts:`, an unqualified model name goes to the host whose `/models`
316
+ list has it (after `/models` ran), by exact id first, then by substring. A
317
+ **remote** host (one with `api_key_env:` or an `https` url) is only chosen by exact
318
+ id, `host:model` or an alias, never by a substring, and its model list is kept for
319
+ 10 minutes (60s for local hosts). For a chat host the context window comes from
320
+ the running server (llama.cpp's `/props`), else the window the host's model list
321
+ gives (`context_length`, `context_window`, `max_model_len` or llama.cpp's
322
+ `meta.n_ctx`), else `context.window_tokens`.
323
+
324
+ ## Llama Network Retry Behavior
325
+
326
+ Transient network failures are retried automatically with exponential backoff.
327
+
328
+ - Default retries: `5` (up to `6` total attempts including the first call).
329
+ - Default backoff: `0.5s`, `1s`, `2s`, `4s`, `8s`.
330
+ - Retry scope: transient network errors (timeouts, refused/reset connections, EOF/socket reachability failures),
331
+ HTTP 429 and HTTP 500/502/503/504/529. A `Retry-After` header replaces the backoff delay; one longer than
332
+ 60s is not waited out and the error is reported instead.
333
+ - A stream that has already produced output is never retried (the retry would repeat it); it fails the turn.
334
+ - Cancellation (`Ctrl-C`) is never retried.
335
+
336
+ Configuration:
337
+
338
+ - `SAMAGOTCHI_RETRY_MAX` (default `5`): number of retries after the first failed attempt.
339
+ - `SAMAGOTCHI_RETRY_BASE_DELAY` (default `0.5`): backoff base delay in seconds.
340
+ - `SAMAGOTCHI_RETRY_MAX_DELAY` (default `8.0`): cap for backoff delay in seconds.
341
+
342
+ Assist-mode UX:
343
+
344
+ - While waiting, retry notices are rendered in the existing thinking spinner area as a red `network error: retrying ...` status.
345
+ - If retry attempts are exhausted, the submitted prompt is restored into the input editor so you can edit and resubmit.
346
+
347
+ ## Server errors
348
+
349
+ An error status or a server's error event fails the turn with the server's
350
+ message (before, a failed llama.cpp `/completion` ended the turn as
351
+ `[No response]`). The error names its kind:
352
+
353
+ | Kind | When | Retried |
354
+ |---|---|---|
355
+ | connection | refused, reset, timed out, dropped mid-stream | yes (network retry), not mid-stream |
356
+ | rate limited | HTTP 429 | yes, honouring `Retry-After` |
357
+ | server | HTTP 5xx, llama.cpp's mid-stream `error:` event | 500/502/503/504/529 only |
358
+ | auth | HTTP 401/403 | no |
359
+ | bad request | other 4xx; a prompt larger than the context window, whatever the status | no |
360
+ | protocol | a body the API doesn't promise | no |
361
+
362
+ The turn's prompt and its completed tool calls stay in the session.
363
+
364
+ ## Debug Log File
365
+
366
+ Every `chi` process (the REPL, the attached terminal, the background
367
+ workers, `chi web`) appends tagged records to one log file, so you can see
368
+ what happened in a session, what went over the wire and why something
369
+ failed, without `--verbose`.
370
+
371
+ Default path:
372
+
373
+ - `$XDG_STATE_HOME/samagotchi/samagotchi.log`, i.e.
374
+ `~/.local/state/samagotchi/samagotchi.log` when `XDG_STATE_HOME` is unset
375
+ (next to the sessions and prompt history, never inside the gem)
376
+
377
+ Configuration (CLI > env > config file, like every other entry):
378
+
379
+ - `log.file` / `SAMAGOTCHI_LOG_FILE` / `--log-file PATH`: another path. `~`
380
+ and relative paths are expanded against the directory `chi` runs in.
381
+ - `log.disable` / `SAMAGOTCHI_LOG_DISABLE=true` / `--log-disable`: no file logging.
382
+ - `log.level` / `SAMAGOTCHI_LOG_LEVEL` / `--log-level LEVEL`: `debug`, `info`
383
+ (default), `warn` or `error`.
384
+
385
+ A worker takes the log settings (file and level) of the `chi` or `chi web`
386
+ that started it.
387
+
388
+ ### Format
389
+
390
+ One record is one line, plus indented payload lines at debug level:
391
+
392
+ ```
393
+ 2026-09-25T10:11:12.345Z INFO turn pid=4242 sid=6f1c2a9b tool_call_completed iteration=1 tool=read ms=12 output_chars=5120
394
+ 2026-09-25T10:11:13.001Z WARN http pid=4242 sid=6f1c2a9b retry host=openrouter method=POST url=https://openrouter.ai/api/v1/chat/completions model=qwen/qwen3.6 purpose=chat attempt=1 max_retries=5 delay_s=2.0 status=429 error=Samagotchi::LLM::RateLimited msg="…"
395
+ 2026-09-25T10:11:14.500Z DEBUG model pid=4242 sid=6f1c2a9b response model=qwen3.6 iteration=2
396
+ the model's answer, every line indented by four spaces
397
+ ```
398
+
399
+ - time (UTC, milliseconds), level, tag, the process id, the session's first
400
+ 8 characters (`sid=`, when the record is about one; `turn_started` has the
401
+ full id as `session=`), the event, then `key=value` fields. A value with a
402
+ space, quote or `=` is a JSON string, so a record never spans lines;
403
+ control characters (terminal colours in tool output) are escaped.
404
+ - Tags: `turn` (a session's event trail), `http` (model requests),
405
+ `worker`, `bridge`, `web`, `attached`, `repl`, `idle`, `recap`, `hooks`,
406
+ `plugins`, `guardrails`, `config`, `memory`, `model` (debug dumps).
407
+ - The format is parsed by `Samagotchi::LogLine` (`parse`, `each_record`);
408
+ keep tools that read it on that parser.
409
+
410
+ What each level adds:
411
+
412
+ - `error`: crashes (a worker, a bridge connection, the idle scheduler, a
413
+ recap) with the first 20 backtrace frames; failed model requests.
414
+ - `warn`: retries (429, 5xx, network), failed turns, hook and guardrail
415
+ problems, config warnings. Warnings `chi` prints on stderr are logged too,
416
+ with the same text as `msg=`; a worker's (its stderr goes nowhere) now
417
+ only reach the file.
418
+ - `info`: turns, generations and tool calls with sizes and times (never the
419
+ text of a prompt, answer or tool output), one line per model request
420
+ (status, time to first token, total), worker start/spawn/stop/idle exit,
421
+ `chi web` start.
422
+ - `debug`: payload dumps (each model answer with its thinking, tool calls
423
+ and results, context status), probes and model lists, every web API and
424
+ bridge request.
425
+
426
+ `-v`/`--verbose` (the plain REPL only) logs at `debug` and prints every
427
+ record to stderr as well.
428
+
429
+ ### Rotation and secrets
430
+
431
+ At 5 MB the file moves to `samagotchi.log.1` (one kept) and a new one
432
+ starts; every process follows. Request headers and bodies are never
433
+ logged; fields named like a credential (`api_key`, `token`, `secret`,
434
+ `authorization`, `password`) show `[redacted]`, and URLs lose their user
435
+ info and query. Debug dumps can still hold secrets a tool read or was
436
+ given (a file's contents, a command line): treat a debug log as sensitive.
437
+ `tools/web_fetch` requests are not logged (their own HTTP client).
438
+
439
+ ### Recipes
440
+
441
+ ```sh
442
+ tail -f ~/.local/state/samagotchi/samagotchi.log
443
+ # one session
444
+ grep 'sid=6f1c2a9b' ~/.local/state/samagotchi/samagotchi.log
445
+ # model requests only, or warnings and errors
446
+ awk '$3 == "http"' ~/.local/state/samagotchi/samagotchi.log
447
+ awk '$2 == "WARN" || $2 == "ERROR"' ~/.local/state/samagotchi/samagotchi.log
448
+ # how long each tool call took
449
+ grep ' tool_call_completed ' ~/.local/state/samagotchi/samagotchi.log | grep -o 'tool=[^ ]* ms=[0-9]*'
450
+ ```
451
+
452
+ ## Images
453
+
454
+ Images a model gets (see [CLI: Images](cli.md#images)) are converted and
455
+ downscaled first; three settings bound them (env `SAMAGOTCHI_IMAGE_*` or
456
+ `config.yml`):
457
+
458
+ ```yaml
459
+ image:
460
+ max_side: 1568 # long side in px (Claude's standard; ~1.3k tokens for 1280×800)
461
+ max_bytes: 3750000 # larger after downscaling → re-encoded as JPEG
462
+ max_per_request: 20 # older images in the conversation become placeholder lines
463
+ ```
464
+
465
+ Whether a model can see images is found out before a turn with images is sent:
466
+
467
+ - a native llama.cpp host: `/props` must report `modalities.vision` (the server
468
+ runs with `--mmproj`) and a media marker, and the prompt profile must know the
469
+ chat template's image wrapping (qwen36 does; gemma4 not yet);
470
+ - mlx and oMLX hosts: no;
471
+ - an OpenAI-API host: a local llama.cpp's `/props`, else the host's model list
472
+ (OpenRouter's `architecture.input_modalities`); when it doesn't say, the image
473
+ is sent and a refusal is reported.
474
+
475
+ `vision: true|false` overrides that per model or per host:
476
+
477
+ ```yaml
478
+ models:
479
+ ornith: { vision: true }
480
+ hosts:
481
+ gateway: { url: https://…, api: openai, vision: false }
482
+ ```
483
+
484
+ `models:` wins over `hosts:`. On a native host, `vision: true` skips only the
485
+ modalities check: without a media marker the prompt can't carry an image.
486
+
487
+ ## Project specific description
488
+
489
+ If an AGENT.md file is present in the project root, samagotchi injects its
490
+ contents into the system prompt under a "Project specific description:" section.
491
+
492
+ To skip loading AGENT.md, set:
493
+
494
+ `SAMAGOTCHI_SKIP_AGENT_MD=true`
data/docs/desktop.md ADDED
@@ -0,0 +1,97 @@
1
+ # Desktop helper (macOS)
2
+
3
+ `chi desktop` installs **Chi Helper**, a small native app. It sends text you selected in any app to a chi session,
4
+ either with a question as [your message](sessions.md#sending-a-message) (a turn runs, and the answer shows in the
5
+ attached terminal or web page), or as a [context note](sessions.md#context-notes) (the model sees it on its next
6
+ turn, and no turn starts).
7
+
8
+ - **Services menu:** select text → right-click → Services → **Send to chi**.
9
+ - **Hotkey ⌃⌥⌘N:** opens the panel with the **clipboard** (not the selection), for apps whose Services menu lacks
10
+ the item.
11
+ - **A shortcut for the selection:** give "Send to chi" its own shortcut in System Settings → Keyboard → Keyboard
12
+ Shortcuts… → Services → Text (pick one other than ⌃⌥⌘N). It goes through the Services menu route, so the panel
13
+ opens with the selected text. If it doesn't fire at once, see Troubleshooting below.
14
+
15
+ The panel has a one-line message field on top ("Ask chi…", focused), the text below it (editable, shown as the
16
+ quote it becomes), where it came from (the app's name, editable; notes only), the size against the 16 KB cap, and the
17
+ sessions. Keys:
18
+
19
+ - **⏎** sends a message: `chi send -m <the line>` with the text as quoted context above it. With the line empty,
20
+ the text is the message.
21
+ - **⌘⏎** sends a note: `chi note`, the line (if any) then the text.
22
+ - **⇧⏎** is a newline, in either field. Esc closes.
23
+
24
+ The list shows the sessions a worker runs now, then, under a "recent" divider, up to 3 stopped ones (dimmed, with
25
+ their age: "2h ago", "yesterday"). Click a session or press ⌘1…⌘9 to tick it. The last choice is preselected while
26
+ it's still live; if there's only one live session, that one is (a recent one never is). A message to a recent
27
+ session starts its worker; a note to one waits for its next start, and the panel shows chi's line saying so. After a
28
+ send the panel shows chi's line and closes. On an error it stays open and shows the error.
29
+
30
+ A session open in a `chi --no-shared` REPL isn't listed: it takes no notes or messages.
31
+
32
+ ## Install
33
+
34
+ ```sh
35
+ chi desktop install # build it into ~/Applications and start it
36
+ chi desktop install --login # … and start it at login (macOS shows a "Login Item Added" notice)
37
+ ```
38
+
39
+ It needs the Command Line Tools (`xcode-select --install`): the app is compiled on your Mac with `swiftc` and
40
+ signed ad hoc, with no notarization and no Xcode project. A build takes a few seconds warm, but can take a few
41
+ minutes cold. The app has no Dock icon and keeps running once started. Without `--login` it runs until you log out,
42
+ and opening it from `~/Applications` starts it again.
43
+
44
+ Install from the checkout or gem you keep. From a gem install the helper runs the gem's `chi` wrapper (the one on
45
+ your PATH, e.g. `$GEM_HOME/bin/chi`), which picks the newest installed version, so gem upgrades and `gem cleanup`
46
+ don't break it. From a checkout it runs **that** checkout's `bin/chi`; installing from a linked git worktree prints a
47
+ warning, because the helper stops working once that worktree is removed. After switching between a checkout and a
48
+ gem install, run `chi desktop upgrade` from the one you now use.
49
+
50
+ ## Commands
51
+
52
+ | Command | Does |
53
+ |---|---|
54
+ | `chi desktop install [--force] [--login]` | builds, installs and starts it; `--force` replaces an existing copy |
55
+ | `chi desktop upgrade` | rebuilds it for this chi and restarts it, keeping the login setting |
56
+ | `chi desktop uninstall` | quits it and removes the app, its login item, launch file and settings |
57
+ | `chi desktop status` | version against chi's, how it runs chi, state dirs, Service, hotkey, process, login item |
58
+
59
+ `chi self` has a `desktop` line: `0.1.x (matches)`, `0.1.w (chi is 0.1.x: chi desktop upgrade)` or `not installed`.
60
+
61
+ ## How it runs chi
62
+
63
+ Apps started by macOS get a bare environment: no shell rc files, so no rbenv/chruby/mise/asdf and none of your
64
+ exports. So `install` writes `~/Library/Application Support/Chi Helper/launch.json` with:
65
+
66
+ - the absolute path of the Ruby running chi and of chi itself (the gem's wrapper, or a checkout's `bin/chi`);
67
+ - `LANG=en_US.UTF-8`;
68
+ - only these variables, and only when they are set: `XDG_CONFIG_HOME`, `XDG_STATE_HOME`, `GEM_HOME`, `GEM_PATH`,
69
+ `RUBYLIB`. No tokens and no `SAMAGOTCHI_*` settings go in.
70
+
71
+ The file freezes the install shell's values. If you install with a temporary `XDG_STATE_HOME`, the helper keeps
72
+ using it. `status` prints the baked dirs.
73
+
74
+ ### The contract with chi
75
+
76
+ The helper uses only these commands, so it could ship on its own later:
77
+
78
+ - `chi sessions list --live --scope=all --format json` and `chi sessions list --limit 20 --scope=all --format json` →
79
+ `[{id, short_id, desc, cwd, project, updated_at, live, busy, owner, recap}]` (it uses `id`, `desc`, `cwd`, `busy`, `updated_at`;
80
+ "recent" = rows of the second call that aren't live and have `owner: null`; only UUID-shaped ids go on to chi).
81
+ - `chi send [-m LINE] ID...` with the text on stdin (or none) and `chi note --source NAME ID...` with the text on
82
+ stdin → one line per session on stdout; exit 0 means all sent or queued, 1 means some were refused or failed.
83
+
84
+ Each call is stopped after 10 s. A stopped `chi note` says the note may be partly delivered.
85
+
86
+ ## Troubleshooting
87
+
88
+ - **"chi not found at …, run `chi desktop upgrade`"**: the Ruby or checkout in `launch.json` moved (a Ruby upgrade,
89
+ a removed worktree). Run `chi desktop upgrade` from the chi you use now.
90
+ - **No "Send to chi" in the Services menu:** check `chi desktop status` (service). Try
91
+ `/System/Library/CoreServices/pbs -update`, start the app again, or log out and back in. It must be ticked in
92
+ System Settings → Keyboard → Keyboard Shortcuts… → Services → Text.
93
+ - **A keyboard shortcut for the Service** (in the same settings pane) may keep firing the old one until the app
94
+ restarts or you log out: macOS caches Services.
95
+ - **⌃⌥⌘N does nothing:** `status` says whether another app holds it. macOS doesn't report clashes with its own
96
+ shortcuts.
97
+ - **"No live sessions":** start one with `chi` in a terminal; `chi sessions list --live --scope=all` shows the same list.